regina-4.95/admin/cmakelint.pl000755 000765 000024 00000072574 12234011536 016222 0ustar00babstaff000000 000000 #!/usr/bin/perl -w ############################################################################### # Sanity checks CMakeLists.txt files. # # Copyright (C) 2006-2009 by Allen Winter # # Copyright (C) 2008-2010 by Laurent Montel # # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation; either version 2 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License along # # with this program; if not, write to the Free Software Foundation, Inc., # # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # # ############################################################################### # # A program to check KDE CMakeLists.txt files for common errors. # # This program needs a rewrite.. it assumes that each CMake command is # all on 1 line. So stuff can easily fall through the cracks -- Allen # Program options: # --help: display help message and exit # --version: display version information and exit # use strict; use Getopt::Long; use Cwd 'abs_path'; my($Prog) = 'cmakelint.pl'; my($Version) = '1.10'; my($help) = ''; my($version) = ''; my($verbose) = ''; exit 1 if (!GetOptions('help' => \$help, 'version' => \$version, 'verbose' => \$verbose)); &Help() if ($help); if ($#ARGV < 0){ &Help(); exit 0; } &Version() if ($version); my($f,$tot_issues); $tot_issues=0; for $f (@ARGV) { $tot_issues += &processFile($f); } exit $tot_issues; sub processFile() { my($in) = @_; print "Processing $in:\n"; open(IN,"$in") || die "Couldn't open $in"; my($apath) = abs_path($in); my($in_kdelibs)=0; $in_kdelibs=1 if ($apath =~ m+/kdelibs/+); my($in_kdepimlibs)=0; $in_kdepimlibs=1 if ($apath =~ m+/kdepimlibs/+); my($in_kdebase)=0; $in_kdebase=1 if ($apath =~ m+/kdebase/+); my($in_kdegames)=0; $in_kdegames=1 if ($apath =~ m+/kdegames/+); my($top_of_module)=0; $top_of_module=1 if ($apath =~ m+/koffice/CMakeLists.txt+); $top_of_module=1 if ($apath =~ m+/kdereview/CMakeLists.txt+); $top_of_module=1 if ($apath =~ m+/playground/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_module=1 if ($apath =~ m+/extragear/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_module=1 if ($apath =~ m+/kdebase/(apps|runtime|workspace)/CMakeLists.txt+); $top_of_module=1 if ($apath =~ m+/kde(libs|pimlibs|base|base-apps|base-runtime|base-workspace|accessibility|addons|admin|artwork|bindings|edu|games|graphics|multimedia|network|pim|sdk|toys|utils|develop|devplatform|webdev|plasma-addons)/CMakeLists.txt+); my($top_of_project)=0; $top_of_project=1 if ($apath =~ m+/koffice/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_project=1 if ($apath =~ m+/kdereview/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_project=1 if ($apath =~ m+/playground/[a-zA-Z_1-9]*/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_project=1 if ($apath =~ m+/extragear/[a-zA-Z_1-9]*/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_project=1 if ($apath =~ m+/kde(libs|pimlibs|base|accessibility|addons|admin|artwork|bindings|edu|games|graphics|multimedia|network|pim|sdk|toys|utils|develop|devplatform|webdev|plasma-addons)/[a-zA-Z_1-9]*/CMakeLists.txt+); $top_of_project=0 if ($apath =~ m+/(cmake|pics)/+); my(@lines) = ; my($line,$pline); my($linecnt)=0; my($issues)=0; my(@ch,$c); my($nob,$ncb)=(0,0); my($nop,$ncp)=(0,0); my($pack,%optpacks); #look for "bad" stuff my($prevline)=""; foreach $line (@lines) { $linecnt++; chomp($line); #pline is used for paren/brace matching only $pline = $line; $pline =~ s/".*"//g; $pline =~ s/#.*$//; $line =~ s/#.*$//; #remove comments next if (! $line); #skip empty lines next if ($line =~ m/^[[:space:]]$/); #skip blank lines @ch = split(//,$pline); $nob = $ncb = 0; foreach $c (@ch) { $nop++ if ($c eq '('); $ncp++ if ($c eq ')'); $nob++ if ($c eq '{'); $ncb++ if ($c eq '}'); } if ($in !~ m+kjsembed/qtonly/CMakeLists.txt+) { if ($line =~ m/macro_optional_find_package\s*\(\s*([a-zA-Z0-9]*).*\)/i || $line =~ m/find_package\s*\(\s*([a-zA-Z0-9]*).*\)/i || $line =~ m/find_program\s*\(\s*([a-zA-Z0-9_]*).*\)/i) { $pack = lc($1); $pack = "libusb" if ($pack eq "usb"); $pack = "mysql_embedded" if ($pack eq "mysql"); if ($pack !~ m/^(carbon|iokit|qt4|kde4internal|kde4|kdewin32|kdepimlibs|kdevplatform|gpgme|kdcraw|kexiv2)/) { $optpacks{$pack}{'name'} = $pack; $optpacks{$pack}{'log'} = 0; } } if ($line =~ m/macro_log_feature\(\s*([A-Z0-9_]*).*\)/i) { $pack = lc($1); if ($pack !~ m/^(compositing|strigiqtdbusclient|xsltproc|qt_qtopengl|ggzconfig)_/ && $pack !~ m/x11_.*_found/ && $pack !~ m/true/i && $pack !~ m/false/i) { $pack =~ s/_xcb//; $pack =~ s/have_//; $pack =~ s/_found//; $pack =~ s/_video//; $pack =~ s/shared_mime_info/sharedmimeinfo/; if (!defined($optpacks{$pack}{'name'})) { # if ($pack !~ m/^(kwin_compositing|current_alsa)/) { $issues++; &printIssue($line,$linecnt,"macro_log_feature($pack) used without a corresponding find_package"); } # } $optpacks{$pack}{'log'} = 1; } } } $issues += &checkLine($line,$linecnt, '[[:space:]]{\$', 'replace "{$" with "${", or garbage detected'); $issues += &checkLine($line,$linecnt, '[[^:print:]]{\$', 'non-printable characters detected'); $issues += &checkLine($line,$linecnt, '[Kk][Dd][Ee]4_[Aa][Uu][Tt][Oo][Mm][Oo][Cc]', 'KDE4_AUTOMOC() is obsolete. Remove it.'); $issues += &checkLine($line,$linecnt, '^[[:space:]]*[Qq][Tt]4_[Aa][Uu][Tt][Oo][Mm][Oo][Cc]', 'No need for QT4_AUTOMOC(). Remove it.'); #$issues += &checkLine($line,$linecnt, # '[Kk][Dd][Ee]3_[Aa][Dd][Dd]_[Kk][Pp][Aa][Rr][Tt]', # 'Use KDE4_ADD_PLUGIN() instead of KDE3_ADD_KPART()'); $issues += &checkLine($line,$linecnt, '^[[:space:]]*[Aa][Dd][Dd]_[Ll][Ii][Bb][Rr][Aa][Rr][Yy]', 'Use KDE4_ADD_LIBRARY() instead of ADD_LIBRARY()'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${APPLNK_INSTALL_DIR}', 'APPLNK_INSTALL_DIR is dead with kde4 replace "${APPLNK_INSTALL_DIR}" with "${XDG_APPS_INSTALL_DIR}" and convert desktop file to xdg format (add Categories)'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${MIME_INSTALL_DIR}', 'Files installed into MIME_INSTALL_DIR will not read. Port them on freedesktop xdg mimetypes.'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/lib/kde[[:digit:]]', 'replace /lib/kde" with "${PLUGIN_INSTALL_DIR}"'); if ($line !~ m/kdeinit/ && $prevline !~ m/kdeinit/) { $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\$\{LIB_INSTALL_DIR\}\s*\)', 'replace "DESTINATION ${LIB_INSTALL_DIR}" with "${INSTALL_TARGETS_DEFAULT_ARGS}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]lib', 'replace "DESTINATION lib" with "${INSTALL_TARGETS_DEFAULT_ARGS}"'); } $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${LIB_INSTALL_DIR}/\$', 'replace "${LIB_INSTALL_DIR}/${...}" with "${LIB_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*include/*', 'replace "include" or "/include" with "${INCLUDE_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${INCLUDE_INSTALL_DIR}/\$', 'replace "${INCLUDE_INSTALL_DIR}/${...}" with "${INCLUDE_INSTALL_DIR}/realname"'); if ($line !~ m/PROGRAMS/ && $line !~ m/FILES/ && $line !~ m/PERMISSIONS/ && $line !~ m/OWNER/ && $line !~ m/GROUP/ && $line !~ m/WORLD/) { $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\$\{BIN_INSTALL_DIR\}\s*\)', 'replace "DESTINATION ${BIN_INSTALL_DIR}" with "${INSTALL_TARGETS_DEFAULT_ARGS}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*bin/*', 'replace "DESTINATION bin" or "/bin" with "${INSTALL_TARGETS_DEFAULT_ARGS}"'); } $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${BIN_INSTALL_DIR}/\$', 'replace "${BIN_INSTALL_DIR}/${...}" with "${BIN_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/doc/HTML/*', 'replace "share/doc/HTML/" or "/share/doc/HTML/" with "${HTML_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/apps/*', 'replace "share/apps" or "/share/apps" with "${DATA_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${DATA_INSTALL_DIR}/\$', 'replace "${DATA_INSTALL_DIR}/${...}" with "${DATA_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/applications/*', 'replace "share/applications" or "/share/applications" with "${XDG_APPS_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/autostart/*', 'replace "share/autostart" or "/share/autostart" with "${AUTOSTART_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${AUTOSTART_INSTALL_DIR}/\$', 'replace "${AUTOSTART_INSTALL_DIR}/${...}" with "${AUTOSTART_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/icons/*', 'replace "share/icons" or "/share/icons" with "${ICON_INSTALL_DIR}"'); if ($in !~ m/IconThemes/) { $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${ICON_INSTALL_DIR}/\$', 'replace "${ICON_INSTALL_DIR}/${...}" with "${ICON_INSTALL_DIR}/realname"'); } $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/locale/*', 'replace "share/locale" or "/share/locale" with "${LOCALE_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${LOCALE_INSTALL_DIR}/\$', 'replace "${LOCALE_INSTALL_DIR}/${...}" with "${LOCALE_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/services/*', 'replace "share/services" or "/share/services" with "${SERVICES_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${SERVICES_INSTALL_DIR}/\$', 'replace "${SERVICES_INSTALL_DIR}/${...}" with "${SERVICES_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]/*share/sounds/*', 'replace "share/sounds" or "/share/sounds" with "${SOUND_INSTALL_DIR}"'); $issues += &checkLine($line,$linecnt, 'DESTINATION[[:space:]]\${SOUND_INSTALL_DIR}/\$', 'replace "${SOUND_INSTALL_DIR}/${...}" with "${SOUND_INSTALL_DIR}/realname"'); $issues += &checkLine($line,$linecnt, 'install_targets[[:space:]]*\(', 'replace "install_targets" with "install(TARGETS...)'); $issues += &checkLine($line,$linecnt, 'INSTALL_TARGETS[[:space:]]*\(', 'replace "install_targets" with "install(TARGETS...)'); $issues += &checkLine($line,$linecnt, 'install_files[[:space:]]*\(', 'replace "install_files" with "install(FILES...)'); $issues += &checkLine($line,$linecnt, 'INSTALL_FILES[[:space:]]*\(', 'replace "install_files" with "install(FILES...)'); $issues += &checkLine($line,$linecnt, '\sFILES\sDESTINATION', 'missing list of files between FILES and DESTINATION'); $issues += &checkLine($line,$linecnt, '\sTARGETS\sDESTINATION', 'missing list of files between TARGETS and DESTINATION'); $issues += &checkLine($line,$linecnt, 'DESTINATION\s\$\{INSTALL_TARGETS_DEFAULT_ARGS\}', 'remove DESTINATION keyword before ${INSTALL_TARGETS_DEFAULT_ARGS}'); $issues += &checkLine($line,$linecnt, 'macro_bool_to_01[[:space:]]*\(.*[[:space:]][[:digit:]][[:space:]]*\)', 'do not use a digit as a variable'); $issues += &checkLine($line,$linecnt, 'MACRO_BOOL_TO_01[[:space:]]*\(.*[[:space:]][[:digit:]][[:space:]]*\)', 'do not use a digit as a variable'); $issues += &checkLine($line,$linecnt, '-fexceptions', 'replace "-fexceptions" with "${KDE4_ENABLE_EXCEPTIONS}"'); if ($in !~ m+/(phonon|okular|kopete|kdevelop|libkdegames)/+) { $issues += &checkLine($line,$linecnt, 'set_target_properties.*PROPERTIES.*[[:space:]]VERSION[[:space:]][[:digit:]]', 'replace a hard-coded VERSION with "${GENERIC_LIB_VERSION}"'); $issues += &checkLine($line,$linecnt, 'set_target_properties.*PROPERTIES.*[[:space:]]SOVERSION[[:space:]][[:digit:]]', 'replace a hard-coded SOVERSION with "${GENERIC_LIB_SOVERSION}"'); } #Qt variable $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtDBus[\s/)]', 'replace "QtDBus" with "${QT_QTDBUS_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]Qt3Support[\s/)]', 'replace "Qt3Support" with "${QT_QT3SUPPORT_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtGui[\s/)]', 'replace "QtGui" with "${QT_QTGUI_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtNetwork[\s/)]', 'replace "QtNetwork" with "${QT_QTNETWORK_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtCore[\s/)]', 'replace "QtCore" with "${QT_QTCORE_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtSql[\s/)]', 'replace "QtSql" with "${QT_QTSQL_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtSvg[\s/)]', 'replace "QtSvg" with "${QT_QTSVG_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtSvg[\s/)]', 'replace "QtSvg" with "${QT_QTSVG_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtTest[\s/)]', 'replace "QtTest" with "${QT_QTTEST_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtXml[\s/)]', 'replace "QtXml" with "${QT_QTXML_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtScript[\s/)]', 'replace "QtScript" with "${QT_QTSCRIPT_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtAssistantClient[\s/)]', 'replace "QtAssistantClient" with "${QT_QTASSISTANTCLIENT_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtHelp[\s/)]', 'replace "QtHelp" with "${QT_QTHELP_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtWebKit[\s/)]', 'replace "QtWebKit" with "${QT_QTWEBKIT_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtXmlPatterns[\s/)]', 'replace "QtXmlPatterns" with "${QT_QTXMLPATTERNS_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]QtMultimedia[\s/)]', 'replace "QtMultimedia" with "${QT_QTMULTIMEDIA_LIBRARY}"'); # kdegames variables if (! $in_kdegames) { $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kdegames[\s/)]', 'replace "kdegames" with "${KDEGAMES_LIBRARY}"'); } # kdelibs variables if (! $in_kdelibs) { $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kdeui[\s/)]', 'replace "kdeui" with "${KDE4_KDEUI_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kio[\s/)]', 'replace "kio" with "${KDE4_KIO_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kdesu[\s/)]', 'replace "kdesu" with "${KDE4_KDESU_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]khtml[\s/)]', 'replace "khtml" with "${KDE4_KHTML_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kparts[\s/)]', 'replace "kparts" with "${KDE4_KPARTS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kde3support[\s/)]', 'replace "kde3support" with "${KDE4_KDE3SUPPORT_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kutils[\s/)]', 'replace "kutils" with "${KDE4_KUTILS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kdnssd[\s/)]', 'replace "kdnssd" with "${KDE4_KDNSSD_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]knewstuff2[\s/)]', 'replace "knewstuff2" with "${KDE4_KNEWSTUFF2_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]knewstuff3[\s/)]', 'replace "knewstuff3" with "${KDE4_KNEWSTUFF3_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]knotifyconfig[\s/)]', 'replace "knotifyconfig" with "${KDE4_KNOTIFYCONFIG_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]threadweaver[\s/)]', 'replace "threadweaver" with "${KDE4_THREADWEAVER_LIBRARIES}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]krosscore[\s/)]', 'replace "krosscore" with "${KDE4_KROSSCORE_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]krossui[\s/)]', 'replace "krossui" with "${KDE4_KROSSUI_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]phonon[\s/)]', 'replace "phonon" with "${PHONON_LIBRARY}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kaudiodevicelist[\s/)]', 'replace "kaudiodevicelist" with "${KDE4_KAUDIODEVICELIST_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]solidifaces[\s/)]', 'replace "solidifaces" with "${KDE4_SOLIDIFACES_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]solid[\s/)]', 'replace "solid" with "${KDE4_SOLID_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]ktexteditor[\s/)]', 'replace "ktexteditor" with "${KDE4_KTEXTEDITOR_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kfile[\s/)]', 'replace "kfile" with "${KDE4_KFILE_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]knepomuk[\s/)]', 'replace "knepomuk" with "${KDE4_KNEPOMUK_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kmetadata[\s/)]', 'replace "kmetadata" with "${KDE4_KMETADATA_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kjs[\s/)]', 'replace "kjs" with "${KDE4_KJS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kjsapi[\s/)]', 'replace "kjsapi" with "${KDE4_KJSAPI_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kformula[\s/)]', 'replace "kformula" with "${KDE4_KFORMULA_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]plasma[\s/)]', 'replace "plasma" with "${KDE4_PLASMA_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kunitconversion[\s/)]', 'replace "kunitconversion" with "${KDE4_KUNITCONVERSION_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kdewebkit[\s/)]', 'replace "kdewebkit" with "${KDE4_KDEWEBKIT_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kprintutils[\s/)]', 'replace "kprintutils" with "${KDE4_KPRINTUTILS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kcmutils[\s/)]', 'replace "kcmutils" with "${KDE4_KCMUTILS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kemoticons[\s/)]', 'replace "kemoticons" with "${KDE4_KEMOTICONS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kidletime[\s/)]', 'replace "kidletime" with "${KDE4_KIDLETIME_LIBS}"'); } # kdepimlibs variables if (! $in_kdelibs && ! $in_kdepimlibs) { $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]akonadi-kde[\s/)]', 'replace "akonadi-kde" with "${KDEPIMLIBS_AKONADI_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]akonadi-kmime[\s/)]', 'replace "akonadi-kmime" with "${KDEPIMLIBS_AKONADI_KMIME_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]gpgmepp[\s/)]', 'replace "gpgmepp" with "${KDEPIMLIBS_GPGMEPP_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kabc[\s/)]', 'replace "kabc" with "${KDEPIMLIBS_KABC_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kblog[\s/)]', 'replace "kblog" with "${KDEPIMLIBS_KBLOG_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kcal[\s/)]', 'replace "kcal" with "${KDEPIMLIBS_KCAL_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kholidays[\s/)]', 'replace "kholidays" with "${KDEPIMLIBS_KHOLIDAYS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kimap[\s/)]', 'replace "kimap" with "${KDEPIMLIBS_KIMAP_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kldap[\s/)]', 'replace "kldap" with "${KDEPIMLIBS_KLDAP_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kmime[\s/)]', 'replace "kmime" with "${KDEPIMLIBS_KMIME_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kontactinterface[\s/)]', 'replace "kontactinterface" with "${KDEPIMLIBS_KONTACTINTERFACE_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kpimidentities[\s/)]', 'replace "kpimidentities" with "${KDEPIMLIBS_KPIMIDENTITIES_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kpimtextedit[\s/)]', 'replace "kpimtextedit" with "${KDEPIMLIBS_KPIMTEXTEDIT_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kpimutils[\s/)]', 'replace "kpimutils" with "${KDEPIMLIBS_KPIMUTILS_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kresources[\s/)]', 'replace "kresources" with "${KDEPIMLIBS_KRESOURCES_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]ktnef[\s/)]', 'replace "ktnef" with "${KDEPIMLIBS_KTNEF_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]kxmlrpcclient[\s/)]', 'replace "kxmlrpcclient" with "${KDEPIMLIBS_KXMLRPCCLIENT_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]mailtransport[\s/)]', 'replace "mailtransport" with "${KDEPIMLIBS_MAILTRANSPORT_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]microblog[\s/)]', 'replace "microblog" with "${KDEPIMLIBS_MICROBLOG_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]qgpgme[\s/)]', 'replace "qgpgme" with "${KDEPIMLIBS_QGPGME_LIBS}"'); $issues += &checkLine($line,$linecnt, 'target_link_libraries.*[[:space:]]syndication[\s/)]', 'replace "syndication" with "${KDEPIMLIBS_SYNDICATION_LIBS}"'); } if ($line !~ m+(Qt4|IOKit|KdeSubversion|KDE4|KDE4Internal|KDEWIN32|KdepimLibs|kdevplatform|Carbon|Gpgme)+ && $in !~ m+/(examples|qtonly)/+) { $issues += &checkLine($line,$linecnt, '^\s*[Ff][Ii][Nn][Dd]_[Pp][Aa][Cc][Kk][Aa][Gg][Ee]\s*\(\s*[A-Za-z0-9_]*\sREQUIRED\s*\)', 'Do not use the REQUIRED keyword with find_package()'); } my($subdir); if ($line =~ m+macro_optional_add_subdirectory\s*\(\s*(\S*)\s*\)+) { $subdir = $1; if (!&canBeOptional($subdir) || ($top_of_module && ($in_kdelibs || $in_kdepimlibs))) { if (!&mustBeOptional($subdir)) { $issues++; &printIssue($line,$linecnt,"Replace macro_optional_add_subdirectory($subdir) with add_subdirectory($subdir)"); } } } if ($line =~ m+^\s*add_subdirectory\s*\(\s*(\S*)\s*\)+) { $subdir = $1; if (&mustBeOptional($subdir)) { $issues++; &printIssue($line,$linecnt,"Replace add_subdirectory($subdir) with macro_optional_add_subdirectory($subdir)"); } } $prevline = $line; } #look for "missing" stuff my($in_exec)=0; my($has_project)=0; my($has_display_log)=0; foreach $line (@lines) { chomp($line); $line =~ s/#.*$//; #remove comments next if ($line =~ m/^[[:space:]]$/); #skip blank lines $in_exec = 1 if ($line =~ m/add_(|kdeinit_)executable[[:space:]]*\(/i); if ($line =~ m/[Pp][Rr][Oo][Jj][Ee][Cc][Tt]/) { $has_project=1; } if ($line =~ m/macro_display_feature_log/i) { $has_display_log=1; } } if (! $has_project && $top_of_project && $in_exec) { $issues++; &printIssue($line,$linecnt,"Missing a PROJECT() command"); } if ($top_of_module && $has_display_log == 0) { $issues++; &printIssue($line,$linecnt,"Missing macro_display_feature_log() command"); } #if (!$top_of_module && $has_display_log == 1 && $in !~ m+/qtonly/+) { # $issues++; # &printIssue($line,$linecnt,"Do not put macro_display_feature_log() in a subdir CMakeLists.txt"); #} if ($nop != $ncp) { $issues++; &printIssue($line,$linecnt,"Mismatched parens"); } if ($nob != $ncb) { $issues++; &printIssue($line,$linecnt,"Mismatched braces"); } #missing macro_log_feature() foreach $pack ( keys %optpacks ) { if ($optpacks{$pack}{'log'} == 0) { $issues++; &printIssue("","","Missing macro_log_feature($pack)"); } } close(IN); return $issues; } sub checkLine { my($line,$cnt,$regex,$explain) = @_; if ($line =~ m/$regex/i) { &printIssue($line,$cnt,$explain); return 1; } return 0; } sub printIssue { my($line,$cnt,$explain) = @_; if ($line) { print "\tline#$cnt: $explain\n"; print "\t=>$line\n" if ($verbose); } else { print "\t$explain\n"; } } sub canBeOptional { my($guy) = @_; my($ret) = 1; $ret = 0 if ($guy =~ m/^lib/ || $guy =~ m/lib$/ || $guy =~ m/^cmake$/ ); return $ret; } sub mustBeOptional { my($guy) = @_; my($ret) = 0; $ret = 1 if ($guy =~ m/^doc$/); return $ret; } #============================================================================== # Help function: print help message and exit. sub Help { &Version(); print "Check KDE CMakeLists.txt files for common errors.\n\n"; print "Usage: $Prog [OPTIONS] FILES\n"; print " --help display help message and exit\n"; print " --version display version information and exit\n"; print "\n"; exit 0 if $help; } # Version function: print the version number and exit. sub Version { print "$Prog, version $Version\n"; exit 0 if $version; } __END__ regina-4.95/admin/distcheck000755 000765 000024 00000010435 12240064644 015573 0ustar00babstaff000000 000000 #!/bin/bash # # Regina - A Normal Surface Theory Calculator # Source Distribution Verification # # Copyright (c) 2003-2013, Ben Burton # For further details contact Ben Burton (bab@debian.org). # # Usage: distcheck # # Verifies that a tarball formed using "make dist" contains all the # files it should. This script outputs a list of files contained in # the git source tree that are missing from the tarball. # # Files that are not necessary for inclusion in the tarball (such as # auto-generated files or the Regina website) are not included in this # output. # # This script must be run from either the top-level source directory within # the git source tree, or from the admin/ directory beneath it. # # Requires: diff, find, grep, mktemp, sed, sort, tar # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # set -e # Command-line sanity check. if [ "$#" != 1 ]; then echo "Usage: distcheck " exit 1 fi # Locate the top of the git source tree. if [ -d admin -a -f HIGHLIGHTS.txt ]; then gittree=. elif [ -d ../admin -a -f ../HIGHLIGHTS.txt ]; then gittree=.. else echo "This script must be run from either the top-level source directory" echo "within the git source tree, or from the admin/ directory beneath it." exit 1 fi # Find any builddirs that should be ignored. builddirs=`find "$gittree" -mindepth 2 -name CMakeCache.txt | \ sed -e 's%^[^/]*/%%' -e 's%/CMakeCache.txt$%%'` display_builddirs= grep_builddirs= for i in $builddirs; do if [ -z "$grep_builddirs" ]; then display_builddirs="$i" grep_builddirs="$i" else display_builddirs="$display_builddirs $i" grep_builddirs="$grep_builddirs\\|$i" fi done if [ -z "$grep_builddirs" ]; then grep_builddirs="^$" else echo "Ignoring build directories: $display_builddirs" grep_builddirs="^\\($grep_builddirs\\)\\(/\\|$\\)" fi # Prepare the two temporary file lists. gitlist="`mktemp -t gitlist.XXXXXXXXXX`" || gitlist= distlist="`mktemp -t distlist.XXXXXXXXXX`" || distlist= if [ -z "$gitlist" -o -z "$distlist" ]; then echo "Error creating temporary files." exit 1 fi # Make a list of files in the distribution tarball. echo "Analysing distribution tarball..." if [ ! -f "$1" ]; then echo "The distribution tarball $1 does not exist or is not a regular file." exit 1 fi case "$1" in *.tar.gz ) taropts=-ztf ;; *.tgz ) taropts=-ztf ;; *.tar.bz2 ) taropts=-jtf ;; *.tar ) taropts=-tf ;; * ) echo "The distribution tarball $1 is not of the correct type." exit 1 ;; esac if ! tar "$taropts" "$1" | sed -e 's%^[^/]*/%%' -e 's%/$%%' | \ sort > "$distlist"; then echo "The contents of the distribution tarball $1 could not be listed." exit 1 fi # Make a list of files in the git source tree. echo "Analysing git source tree..." if ! find "$gittree" -type f | sed -e 's%^[^/]*/%%' | \ grep -v "$grep_builddirs" | \ grep -v '\.git/' | \ grep -v '\.DS_Store$' | \ grep -v '^engine/snappea/kernel/unused\(/\|$\)' | \ grep -v '^regina-[0-9.]\+\.tar\.gz$' | \ grep -v '^sfdist\(/\|$\)' | \ grep -v '^utils/local\(/\|$\)' | \ grep -v '^utils/snappea\(/\|$\)' | \ sort > "$gitlist" ; then echo "The contents of the git source tree could not be listed." exit 1 fi # Output the differences. diff -B "$gitlist" "$distlist" | grep -v '^[0-9]' # Clean up. rm "$gitlist" "$distlist" regina-4.95/admin/index2gloss000755 000765 000024 00000006103 12234011536 016063 0ustar00babstaff000000 000000 #!/usr/bin/perl -w # # Regina - A Normal Surface Theory Calculator # KDE DocBook Reformatting Utility # # Copyright (c) 2007-2013, Ben Burton # For further details contact Ben Burton (bab@debian.org). # # Usage: index2gloss < input.docbook > output.docbook # # Reads a docbook file containing an index using ... tags, # and reformats it using ... tags. # # This is necessary because recent KDE versions render the usual index # tags in a way that is virtually unreadable (e.g., lines joined together, # secondary entries appearing as primary entries, etc.). Using glossary # tags may be incorrect from docbook's point of view, but it at least # makes the document usable. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # use strict; my $data; sub switchTag { my $tagOld = shift; my $tagNew = shift; my $found = 0; if ($data =~ /^<$tagOld[ >]/) { $data =~ s/^<$tagOld/<$tagNew/; $found = 1; } if ($data =~ /<\/$tagOld>$/) { $data =~ s/<\/$tagOld>$/<\/$tagNew>/; $found = 1; } return $found; } sub transform { switchTag('index', 'glossary'); switchTag('indexentry', 'glossentry'); switchTag('primaryie', 'glossterm'); if (switchTag('secondaryie', 'glossdef')) { $data =~ s/^()/$1/; $data =~ s/(<\/glossdef>)$/<\/para>$1/; } if (switchTag('seeie', 'glosssee')) { $data =~ s/^()\(see /$1/; $data =~ s/\)(<\/glosssee>)$/$1/; } } my $sHead; my $sTail; my $emptyEntry = 0; while (<>) { chomp; if (/^(\s*)(\S(.*\S)?)(\s*)$/) { $sHead = $1; $data = $2; $sTail = $4; transform(); $data =~ /^]/ and $emptyEntry = 1; $data =~ /^]/ and $emptyEntry = 0; $data =~ /^]/ and $emptyEntry = 0; if ($data =~ /<\/glossentry>$/ and $emptyEntry) { print "$sHead  \n"; } print $sHead . $data . $sTail . "\n"; } else { # Blank line or whitespace only. print "$_\n"; } } exit 0; regina-4.95/admin/Makefile000644 000765 000024 00000001130 12235500316 015327 0ustar00babstaff000000 000000 ## ## Regina - A Normal Surface Theory Calculator ## Auxiliary Makefile ## ## These tools should be used only by the core software maintainers. ## In fact, there are good arguments that they should not be used at all. ## Keeping them around though just in case. ## srcdir = .. all : tabs : cd $(srcdir); ./replacetabs `find .. -name "*.cpp" -o -name "*.h" -o -name "*.tcc"` cd $(srcdir); ./replacetabs `find .. -name "*.txt"` cd $(srcdir); ./replacetabs ../qtui/doc/regina/*.docbook cd $(srcdir); ./replacetabs ../qtui/doc/regina-xml/*.docbook cd $(srcdir); ./replacetabs ../pylib/*.py regina-4.95/admin/mksums000755 000765 000024 00000005763 12234011536 015154 0ustar00babstaff000000 000000 #!/bin/bash # # Regina - A Normal Surface Theory Calculator # Signed Md5sum Generation Utility # # Copyright (c) 2002-2013, Ben Burton # For further details contact Ben Burton (bab@debian.org). # # Usage: mksums [ ... ] # # Creates a GnuPG-signed set of md5sums for the given set of files. # # This script should only need to be used by the software author (and is # specifically tailored for said author's machine and personal details). # # Requires: gpg, md5sum, mktemp, sed # Example: mksums "Regina distribution files" *.tgz # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # set -e # Details for person doing the GnuPG signing. signerid="Ben Burton " # Command-line sanity check. if [ -z "$1" -o -z "$2" ]; then echo "Usage: mksums [ ... ]" exit 1 fi # Extract the description string. desc="$1" shift # Check that the given files exist as regular files. for i; do if [ ! -e "$i" ]; then echo "File $i does not exist." exit 1 elif [ ! -f "$i" ]; then echo "File $i is not a regular file." exit 1 fi done # Create a temporary file to contain the entire md5sum message. plaintext="`mktemp -t md5sums.XXXXXXXXXX`" || plaintext= if [ -z "$plaintext" ]; then echo "Error creating temporary file." exit 1 fi # Build the md5sum message. echo > "$plaintext" echo "Below are md5sums for the $desc available from this site." >> "$plaintext" echo >> "$plaintext" md5sum "$@" | sed -e "s/^/ /" >> "$plaintext" cat >> "$plaintext" < and comparing the output with the md5sums listed above. These md5sums have been signed with my private GnuPG key; the matching public key is available from any of the pgp.net public keyservers (e.g., wwwkeys.au.pgp.net). END echo >> "$plaintext" echo " -- $signerid, `822-date`" >> "$plaintext" echo >> "$plaintext" # Sign the md5sum message. cat "$plaintext" | gpg --clearsign # Clean up. rm "$plaintext" regina-4.95/admin/README.txt000644 000765 000024 00000000572 12234011536 015376 0ustar00babstaff000000 000000 Regina Administrative Utilities ------------------------------- This directory contains utilities for housekeeping and making new releases. In general only the software author(s) should need to worry about the contents of this directory. For general information on building or running Regina, see README.txt in the top-level source directory (the directory above this one). regina-4.95/admin/RELEASE.txt000644 000765 000024 00000004615 12240064622 015524 0ustar00babstaff000000 000000 Below are locations of strings that may need changing for a new release. All locations are relative to the primary source directory. Program version: CHANGES.txt, HIGHLIGHTS.txt (version, date) CMakeLists.txt (PACKAGE_VERSION) qtui/doc/regina/index.docbook (®version;, ®date;, date, releaseinfo, copyrightyears) qtui/doc/regina/manonly.docbook (®version;) qtui/doc/regina-xml/index.docbook (same as for .../regina/index.docbook) qtui/src/reginaabout.cpp (regReleased) preconfig/*/regina-config.h (PACKAGE_* macros) www/index.html (last updated, current version, contents, news, filenames, what's new) www/source.html (unpacking instructions: filename, directory) Full license details: LICENSE.txt engine/docs.h qtui/doc/regina/credits.docbook qtui/src/reginaabout.cpp (ReginaAbout constructor) www/index.html SnapPea / SnapPy copyright (in addition to full license locations above): engine/snappea/kernel/README.txt engine/snappea/snappy/README.txt engine/snappea/nsnappeatriangulation.h Acknowledgements (in addition to primary authors listed in the license): qtui/doc/regina/credits.docbook qtui/src/reginaabout.cpp (ReginaAbout constructor) Don't forget: changes & highlights what's this & tooltips documentation in handbook, feature set, index & file format any new tips of the day? journal references (www, KDE docs, example files, headers) check that all headers are installed correctly check for doxygen warnings check for docbook warnings/errors update \test... notes in API docs python bindings for new routines rebuild manpages website, including updated news, what's new and deprecation guide debian-based packages (debian/ubuntu) rpm-based packages (fedora/mandriva/suse) windows installer macos DMG (purge libiconv-dev, libxml2 from fink first) update tips for building on different platforms update debian/ubuntu/fink READMEs as required (both text and html) copy fink info file(s) beneath www/ update census download file sizes if there have been changes tag the git repository upload new data files, both from examples/ and the large data repository update docs/ and engine-docs/ on the website update the citation years if necessary in private BibTeX databases mail regina-announce regina-4.95/admin/replacetabs000755 000765 000024 00000004017 12234011536 016111 0ustar00babstaff000000 000000 #!/bin/sh # # Regina - A Normal Surface Theory Calculator # Tab Conversion Utility # # Copyright (c) 1999-2013, Ben Burton # For further details contact Ben Burton (bab@debian.org). # # Usage: replacetabs [ ... ] # # Replaces each tab with four spaces in the given files. # # Requires: awk, diff # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # set -e # Command-line sanity check. if [ -z "$1" ]; then echo "Usage: replacetabs [ ... ]" exit 1 fi for srcfile do tmpfile=$srcfile.tabs.tmp if (awk '{gsub("\t", " "); print }' $srcfile > $tmpfile); then if ! diff $srcfile $tmpfile > /dev/null; then if (test -s $tmpfile); then mv $tmpfile $srcfile echo "$srcfile: Tabs replaced" else rm $tmpfile echo "$srcfile: *** FAILURE ***" fi else rm $tmpfile # echo "$srcfile: Unchanged" fi else rm $tmpfile echo "$srcfile: *** FAILURE ***" fi done regina-4.95/admin/updateyear000755 000765 000024 00000006666 12234011536 016003 0ustar00babstaff000000 000000 #!/usr/bin/perl -w # # Regina - A Normal Surface Theory Calculator # Source Stub Year Update Utility # # Copyright (c) 1999-2013, Ben Burton # For further details contact Ben Burton (bab@debian.org). # # Usage: updateyear [ ... ] # # Replaces the old year with the new year in the copyright header at # the beginning of each given file. You will be asked to approve or # reject each change. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (at your option) any later version. # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public # License along with this program; if not, write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # use strict; # --- Command-line sanity check. --- my $nArgs = @ARGV; if ($nArgs < 3) { print STDERR "Usage: updateyear [ ... ]\n"; exit(1); } my $oldYear; my $newYear; my @argFiles; ($oldYear, $newYear, @argFiles) = @ARGV; # --- Run through each file to process. --- foreach my $file (@argFiles) { # Read the file contents. if (! open(DATA, $file)) { print STDERR "ERROR: Could not open $file for reading.\n"; next; } my @lines = ; close(DATA); # Hunt for a sentry. my @newLines = (); my $edit; my $changed = 0; foreach my $line (@lines) { $edit = undef; if ($line =~ /Copyright \(c\) $oldYear,/) { $edit = $line; $edit =~ s/(Copyright \(c\) )$oldYear,/$1$newYear,/g; } elsif ($line =~ /Copyright \(c\) \d+-$oldYear,/) { $edit = $line; $edit =~ s/(Copyright \(c\) \d+-)$oldYear,/$1$newYear,/g; } if (not defined $edit) { push(@newLines, $line); next; } print "CHANGE:\n-$line+$edit"; print "Accept? [Y/n] "; my $response = ; $response =~ s/\s//g; $response =~ tr/A-Z/a-z/; $response eq '' and $response = 'y'; while (not ($response eq 'y' or $response eq 'n')) { print "Accept? [Y/n] "; $response = ; $response =~ s/\s//g; $response =~ tr/A-Z/a-z/; $response eq '' and $response = 'y'; } if ($response eq 'y') { push(@newLines, $edit); $changed = 1; } else { push(@newLines, $line); } } if ($changed) { if (! open(DATA, '>'.$file)) { print STDERR "ERROR: Could not open $file for writing.\n"; next; } print DATA $_ foreach (@newLines); close(DATA); print "REPLACED: $file.\n\n"; } else { # print "Unchanged: $file.\n"; } } regina-4.95/CHANGES.txt000644 000765 000024 00000510475 12237710474 014443 0ustar00babstaff000000 000000 Regina - A Normal Surface Theory Calculator This file gives a detailed list of changes between releases, including all changes to the C++/Python API. For just the key highlights, see the (far more readable) file HIGHLIGHTS.txt. Version 4.95 [ 12 November, 2013 ] GENERAL: - Updated the bundled copy of Normaliz to version 2.10.1 (27 June 2013). - All 2-faces of a triangulation are now called "triangles" (not just "faces" as before). This is to avoid ambiguity in dimension. - Regina and SnapPy can now talk directly via python: on many systems, 'import snappy' from either a GUI python console or the command-line regina-python tool should work out of the box. - Packets in a data file are no longer required to have unique labels. ENGINE: Class Dim2BoundaryComponent: - Detailed string output (via detail() / toStringLong()) now outputs details of the constituent edges. Class Dim2Component: - New routines getNumberOfSimplices() and getSimplex(), which are dimension-agnostic aliases for getNumberOfTriangles() and getTriangle(). - New template routine getNumberOfFaces(), which is a dimension-agnostic routine for counting faces of any dimension. - Detailed string output (via detail() / toStringLong()) now outputs details of the constituent triangles. Class Dim2Edge: - Detailed string output (via detail() / toStringLong()) now outputs details of where the edge appears in each triangle. Class Dim2Triangulation: - Now inherits from the new helper class NGenericTriangulation<2>. - New routine isoSigComponentSize() for extracting the number of triangles from a signature (inherited from NGenericTriangulation). - New "magic constructor" that attempts to interpret a given string as a 2-manifold triangulation under several possible encodings (currently only isomorphism signatures are supported for 2-manifolds, but this list may grow in the future). - Routine isoSig() now takes an optional argument that returns the isomorphism with the reconstruction from fromIsoSig(). Class Dim2Vertex: - Detailed string output (via detail() / toStringLong()) now outputs details of where the vertex appears in each triangle. Class NBoundaryComponent: - Renamed getFace() and getNumberOfFaces() to getTriangle() and getNumberOfTriangles(). The old names are deprecated but have been kept for backward compatibility. - Detailed string output (via detail() / toStringLong()) now outputs details of the constituent triangles or vertex. Class NComponent: - Renamed getFace() and getNumberOfFaces() to getTriangle() and getNumberOfTriangles(). The old names are deprecated but have been kept for backward compatibility. - Detailed string output (via detail() / toStringLong()) now outputs details of the constituent tetrahedra. Class NEdge: - Detailed string output (via detail() / toStringLong()) now outputs details of where the edge appears in each tetrahedron. Class NEulerSearcher: - A new subclass of NGluingPermSearcher that can enforce an arbitrary fixed Euler characteristic on the vertex links. Struct NewNormalSurfaceVector: - Moved into registryutils.h and redesigned as the more general template struct NewFunction1<>. The old type is equivalent to NewFunction1, and a deprecated typedef has been kept for backward compatibility. Class NExampleTriangulation: - Routines weberSeifert() / seifertWeber(), bingsHouse(), weeks() and poincareHomologySphere() all now build oriented triangulations. Class NFace: - Renamed to NTriangle. The old name is deprecated but has been kept as a typedef for backward compatibility. Class NFaceEmbedding: - Renamed the class to NTriangleEmbedding, and renamed getFace() to getTriangle(). The old names are deprecated but have been kept for backward compatibility. Class NGenericFacetPairing: - Renamed toString() to the simpler-to-type str(). The old name has been kept as a deprecated alias. Class NGenericTriangulation: - New template helper class that allows different triangulation classes to share the same implementations of member functions. Currently this class implements isoSig() and fromIsoSig(). Class NGlobalDirs: - Routine pythonModule() now returns an empty string if the module is installed in the standard python site-packages directory (now the default for an XDG install). Class NGroupPresentation: - Renamed toStringCompact() to compact(). The old name has been kept as a deprecated alias. Class NInteger, NLargeInteger: - Fixed a (thankfully hard-to-trigger) error in the mod operators. The error was triggered on (x % m) or (x %= m) when: (i) x was negative and stored as a native integer; (ii) m could fit into a native integer but was unnecessarily stored as a large GMP integer; (iii) x <= m < |x|. The result was then reported as x (i.e., the mod operation did nothing). - Fixed a (thankfully even-harder-to-trigger) error in the GCD operation. This error was triggered only when computing gcd(LONG_MIN, 0), for either order of the operands. Class NNormalSurface, NNormalSurfaceVector: - Renamed getFaceArcs() to getTriangleArcs(). The old name is deprecated but has been kept for backward compatibility. - Incorporated the new fast branch-and-bound machinery into NNormalSurface::isIncompressible(). - All normal surface vector classes have now gained the enum constant coordType (representing the corresponding NormalCoords constant), and the typedef Info (representing the template specialisation NormalInfo). - The requirements for new vector subclasses have changed, since the old macro-based registry was replaced with the new template-based registry. See the NNormalSurfaceVector class notes for details. - Python users can now create a normal surface by hand, e.g.,: NNormalSurface(tri, NS_STANDARD, [ 1, 2, 0, ... ]). Class NNormalSurfaceList: - Re-enabled enumeration using NS_HILBERT_FULLCONE, which previously returned an empty list. Users will not be affected unless they were explicitly asking for this much slower algorithm. - Routine flavour() has been renamed to coords(). The old name remains for now as a deprecated alias. Class NNormalSurfaceSubset: - Routine getFlavour() has been renamed to coords(). The old name remains for now as a deprecated alias. Enum NormalCoords: - Renamed NS_FACE_ARCS to NS_TRIANGLE_ARCS. The old name is deprecated but has been kept for backward compatibility. Class NormalFlavour<...>, NormalInfo<...>: - This recently-added registry helper template has changed since the last release. Since this is a very new class whose primary role is in the redesign of the coordinate system registry, no backward compatibility aliases are provided. - The template itself has been renamed from NormalFlavour<...> to NormalInfo<...>. - The declaration has moved from flavourregistry.h to normalsurface.h, and the specialisations have moved to the corresponding coordinate system headers (nsstandard.h and so on). - The typedefs Vector, StandardFlavour and ReducedFlavour have been renamed to Class, Standard and Reduced. Class NPacket: - The requirements for new subclasses have changed, since the old macro-based registery was replaced with the new template-based registry. See the NPacket class notes for details. - The constant packetType is now a compile-time enum constant, not a static const int. - The new routine getHumanLabel() returns the packet label, but adjusted for human-readable output. In particular, an empty label will be replaced by "(no label)". - Routine getFullName() now makes labels more suitable for human-readable output as described above. - New routine internalID() to produce a string that uniquely identifies the packet (which nowadays the packet label does not). Class NPerm3, NPerm4, NPerm5: - New dimension-agnostic aliases orderedSn, SnIndex() and orderedSnIndex(). These all reference existing arrays/routines involving S3, S4 or S5 in NPerm3, NPerm4 and NPerm5 respectively. - Renamed toString() to the simper-to-type str(). The old name has been kept as a deprecated alias. Class NPillowTwoSphere: - Renamed getFace() and getFaceMapping() to getTriangle() and getTriangleMapping(). The old names are deprecated but have been kept for backward compatibility. Class NScript: - Variables are now stored as pointers to packets, not string-based packet labels. This affects the API (in a non-backward- compatible way), the behaviour (e.g., renaming a packet will not affect any script variable that references it), and the data file format (packets now have IDs that scripts can use to reference them). Class NSnapPeaTriangulation: - Updated the SnapPea kernel to the one shipped with SnapPy 2.0.3. - New routine randomize() to randomly retriangulate. Class NSurfaceFilter: - The requirements for new subclasses have changed, since the old macro-based registry was replaced with the new template-based registry. See the NSurfaceFilter class notes for details. - The constant filterID has been renamed to filterType, and is now a compile-time enum constant (not a static const int). The old name has been kept as a deprecated alias. - Routines getFilterID() and getFilterName() have been renamed to getFilterType() and getFilterTypeName(). The old names have been kept as deprecated aliases. Class NTetrahedron: - Renamed getFace() and getFaceMapping() to getTriangle() and getTriangleMapping(). The old names are deprecated but have been kept for backward compatibility. Class NThread: - New routine join() to wait for threads to finish. Class NTriangle: - Detailed string output (via detail() / toStringLong()) now outputs details of where the triangle appears in each tetrahedron. Class NTriangulation: - New routines isIrreducible(), knowsIrreducible(), isHaken(), knowsHaken(), and knowsCompressingDisc(). - Routine hasCompressingDisc() has been updated to use the new fast branch-and-bound machinery. - New routines getNumberOfSimplices() and getSimplex(), which are dimension-agnostic aliases for getNumberOfTetrahedra() and getTetrahedron(). - New template routine getNumberOfFaces(), which is a dimension-agnostic routine for counting faces of any dimension. - Now inherits from the new helper class NGenericTriangulation<3>. - New routine isoSigComponentSize() for extracting the number of triangles from a signature (inherited from NGenericTriangulation). - Routine isoSig() now takes an optional argument that returns the isomorphism with the reconstruction from fromIsoSig(). - New routines snapPea() and fromSnapPea() to import and export SnapPea data using strings, without writing to the filesystem. - New "magic constructor" that attempts to interpret a given string as a 3-manifold triangulation under several possible encodings. - Routine isThreeSphere() now includes a fast check for an "obviously" trivial fundamental group. - Renamed FaceIterator, faceIndex(), getFace(), getFaces(), getNumberOfFaces() and hasBoundaryFaces(), where "face" now becomes "triangle". The old names are deprecated but have been kept for backward compatibility. - Results from hasCompressingDisc() and isHaken() are now stored in data files. - The output from getPacketTypeName() has changed from "Triangulation" to "3-Manifold Triangulation". Class NVertex: - Added buildLinkDetail(), which returns details of how triangles of the vertex link are embedded within individual tetrahedra. - Detailed string output (via detail() / toStringLong()) now outputs details of where the vertex appears in each tetrahedron. Class NXMLPacketReader, NXMLTreeResolver, NXMLTreeResolutionTask: - The API for reading packets from an XML data file has changed to incorporate the new NXMLTreeResolver class, which allows the resolution of dangling packet references after an entire data file has been read. Class PacketInfo: - Template classes that store traits of the various packet types. These are used with the new forPacket() template functions. Enum PacketType: - A new enumeration that contains constants representing different packet types. Include "packet/packettype.h". Struct Returns: - Moved into utilities/registry.h. However, its contents are still included from its previous header surfaces/filterregistry.h. Class ShareableObject: - Renamed toString() and toStringLong() to the simper-to-type str() and detail(). The old names have been kept as deprecated aliases. Class SurfaceFilterInfo: - Template classes that store traits of the various normal surface filter classes. These are used with the new forFilter() template functions. Enum SurfaceFilterType: - A new enumeration that contains constants for different types of normal surface filter. Include "surfaces/surfacefiltertype.h". Routine forFlavour(), forCoords(): - Renamed each variant of forFlavour() to forCoords(). The old names have been kept as deprecated aliases. Routine stringToToken(): - Moved from foreign/snappea.h into utilities/stringutils.h. Routine readSnapPea(), writeSnapPea(): - Added istream/ostream versions of these routines. - In writeSnapPea(), the NTriangulation& argument is now const. Headers dimtraits.h, nfacetspec.h, ngenericisomorphism.{h,tcc}: - Moved from triangulation/ to generic/. The old header locations are now deprecated. They have been kept for backward compatibility, but simply include the new headers instead. Header filterregistry.h: - The REGISTER_FILTER macros have been removed. To iterate through surface filter types, use the new forFilter() template functions. Header flavourregistry.h, coordregistry.h: - Renamed flavourregistry.h to coordregistry.h. The old header has been kept as a deprecated alias. - The REGISTER_FLAVOUR macros have been removed. To iterate through coordinate systems, use the forCoords() template functions. Header packetregistry.h: - The REGISTER_PACKET macros have been removed. To iterate through packet types, use the new forPacket() template functions. Headers *.tcc: - All *.tcc headers have been renamed as *-impl.h, to play better with development IDEs. The old header names are now deprecated but have likewise been kept for backward compatibility. USER INTERFACE: - The root container packet is now hidden from the user (it still exists internally within the packet tree). - The triangulation viewer now explicitly states "not oriented" for orientable-but-not-oriented triangulations. - The old regina-kde placeholder app (which did nothing but pop up a message directing the user to the new regina-gui) has been removed. - The boundary component viewer now displays more detailed information. PYTHON: - The python module 'regina' now has its own __init__.py, and loads 'regina.engine' as an extension submodule. All symbols from regina.engine are imported directly into 'regina', so users can continue to work directly with the 'regina' namespace as before. - For an XDG build (e.g., on GNU/Linux), the python module is now installed in the standard python site-packages directory, which means you can easily import the module from a normal python session. - Added a useful __repr__ for the core types NInteger, NLargeInteger, NRational, NPerm3, NPerm4 and NPerm5. TEST SUITE: - More NInteger / NLargeInteger tests, this time comparing operations when the same arguments are coerced from natives to GMP integers. - NBitmask tests for the bits() function. - Added python tests for the Orb import filter; thanks to Craig Hodgson for the sample files. Version 4.94 [ 24 September, 2013 ] GENERAL: - Old-style binary files are no longer supported. These have not been in use for over a decade. If you have an old-style binary file that you need to use, install Regina 4.93 and use the regconvert tool. - Widespread alignment of integer sizes throughout the code. Many integers have changed from int to long, or from unsigned to unsigned long (or size_t), throughout the API. - Fixed some minor memory leaks. ENGINE: Class NAbelianGroup, NGroupExpression, NGroupExpressionTerm, NGroupPresentation: - Removed writeToFile(NFile&) and readFromFile(NFile&, ...), which were used for old-style binary files. Class NAngleStructure: - Removed writeToFile(NFile&) and readFromFile(NFile&, ...), which were used for old-style binary files. Class NAngleStructureList: - More frequent and robust cancellation checking during enumeration of vertex angle structures. Class BanConstraintBase, BanNone, BanBoundary, BanTorusBoundary: - New constraint classes for use with the new tree traversal code for enumerating normal surfaces. See NTreeTraversal for details. Class Dim2Census, Dim2EdgePairing, Dim2GluingPerms, Dim2GluingPermSearcher: - New classes for building a census of 2-manifold triangulations. Class Dim2TriangleEdge: - New convenience typedef for NFacetSpec<2>. Class Dim2Triangulation: - New 2-manifold triangulation class. This is a new packet type that can be independently stored in data files. Class Dim2BoundaryComponent, Dim2Component, Dim2Edge, Dim2EdgeEmbedding, Dim2Isomorphism, Dim2Triangle, Dim2Vertex, Dim2VertexEmbedding, NXMLDim2TriangulationReader: - New classes to support Dim2Triangulation. Class Dim2ExampleTriangulation: - New class for building common 2-manifold triangulation. Includes support for every possible 2-manifold. Class DimTraits: - New template class to assist with dimension-agnostic code. Class Flags: - New template class for forming bitwise combinations of flags based on enumeration values. Class HashPointer, HashString: - Removed, after being deprecated for a very long time now. Class IntOfSize: - New template class for using native integer types whose sizes are not determined until compile time. Class LPConstraintBase, LPConstraintSubspace, LPConstraintNone, LPConstraintEuler, LPConstraintNonSpun: - New constraint classes for use with the new tree traversal code for enumerating normal surfaces. See NTreeTraversal for details. Class LPData, LPInitialTableaux, LPMatrix: - New classes that implement the dual simplex method, for use with the new tree traversal code for enumerating normal surfaces. See NTreeTraversal for details. Class NAbelianGroup: - New convenience routines isZ() and isZn(). Class NBlockedSFS: - Now supports Seifert fibred spaces with boundary. Class NCensus: - The NProgressManager* argument has been removed from formCensus(). If you are doing serious census generation, use the command-line utility tricensus (or tricensus-mpi) instead. Class NClosedPrimeMinSearcher, NCompactSearcher: - Improved the way in which cones and L(3,1) spines are detected and pruned during census enumeration. - Improved the way in which too many high degree edges are detected and pruned during census enumeration. Class NDoubleDescription: - More frequent polling for cancellation. - The progress tracker passed to enumerateExtremalRays() is now of class NProgresstracker, not NProgressNumber. Class NEdge: - New convenience routine getTriangulation(). Class NEnumConstraintList: - This now holds unsigned longs, not unsigned ints, for consistency with the integer types used for NVector indexing. Class NFace: - New convenience routine getTriangulation(). Class NFacePairing: - Now derives from the template base class NGenericFacetPairing<3>. - Renamed getNumberOfTetrahedra() to size(). The old name is deprecated, and will be removed in a future release. Class NFile, NFilePropertyReader: - Removed these classes, which were used for old-style binary files. Class NFileInfo: - Removed TYPE_BINARY, which was used for old-style binary files. Class NGenericFacetPairing: - New template base class that contains dimension-agnostic code for representing face/facet pairings. Class NGenericIsomorphism: - New template base class that contains dimension-agnostic code for representing isomorphisms between triangulations. Class NGroupExpression: - New routines wordLength(), erase(), cycleLeft(), cycleRight(), invert(), writeTeX(), and a version of writeText() that takes an extra argument to control the output style. Class NGroupPresentation: - Finally intelligentSimplify() really is intelligent. It now uses Dehn simplification / small cancellation theory. - New routine intelligentSimplify(NHomGroupPresentation*) for keeping track of how the group was simplified. - New routines relatorLength(), abelianlisation(), markedAbelianisation(), writeTeX(), toTeX(), writeTextCompact() and toStringCompact(). - More readable output from writeTextLong(). - New assignment operator (=). Class NHilbertCD: - The NProgressMessage* argument to enumerateHilbertBasis() has been removed, since in reality the current algorithm offers no sensible progress tracking or cancellation polling. Class NHilbertDual: - More frequent polling for cancellation. - The progress tracker passed to enumerateHilbertBasis() is now of class NProgressTracker, not NProgressNumber. Class NHilbertPrimal: - The progress tracker passed to enumerateHilbertBasis() is now of class NProgressTracker, not NProgressMessage. Class NHomGroupPresentation: - New class to represent homomorphisms between groups. Class NHomMarkedAbelianGroup: - New routine torsionSubgroup(). Class NHomologicalData: - Removed all dependencies on the old NIndexedArray. Class NIndexedArray: - Removed, after being deprecated for a very long time now. Class NInteger: - NInteger is a typedef for the new template class NIntegerBase, which offers fast arbitrary precision integers but does not support infinity. Class NIntegerBase: - This is a new template class that supercedes (and is based on) the old NLargeInteger. The boolean template argument indicates whether the class supports infinity as an allowed value. - Heavily rewritten from the old NLargeInteger: this class works with native C/C++ integers for as long as possible, and only switche to GMP when it becomes unavoidable. This makes the code enormously faster (rough testing suggests a roughly 7x speedup). - Made the destructor non-virtual to avoid the unwanted class overhead that comes with polymorphism. - Added new routines that were not in the old NLargeInteger, including isZero(), sign(), gcdWith(), lcmWith(), and nativeValue(). Class NIsomorphism: - Now a subclass of NGenericIsomorphism<3>, with inherited dimension-agnostic aliases for several routines. Class NLargeInteger: - NLargeInteger is now a typedef for the new template class NIntegerBase, and maintains backward compatibility. - Performance is much, much faster; see NIntegerBase for details. Class NLocalFileResource, NRandomAccessResource: - Removed these classes, which were used for old-style binary files. Class NMarkedAbelianGroup: - New routines torsionSubgroup() and torsionInclusion(). Class NMatrix: - New routine isZero(). Class NNativeInteger: - New template class that wraps native integer types with very little overhead and offers an interface compatible with NInteger. Class NNativeLong: - A convenience typedef for the new class NNativeInteger. Class NNormalSurface: - Routine boundarySlopes() now requires that the triangulation is oriented (otherwise SnapPea will relabel tetrahedron vertices and the matrix returned will be null). - Routines findNonTrivialSphere() and findVtxOctAlmostNormalSphere() are deprecated. Use NTriangulation::hasNonTrivialSphereOrDisc() and NTriangulation::hasOctagonalAlmostNormalSphere() instead. - Removed writeToFile(NFile&) and readFromFile(NFile&, ...), which were used for old-style binary files. Class NNormalSurfaceList: - New routines which() and algorithm() now retain details of which surfaces where enumerated and how this was done. - Now uses the new tree traversal method as a preferred enumeration algorithm where possible. This is a significant improvement upon the old double description method. See NTreeEnumeration and arXiv:1010.6200 for details. - The coordinate flavours (STANDARD, QUAD, etc.) are deprecated, and have changed from integers to NormalCoords enum types. Instead of these, use the NormalCoords enum values directly (NS_STANDARD, NS_QUAD, etc.). For both C++ and Python users, these enum values are available directly in the namespace scope. - More frequent and robust cancellation checking during enumeration of vertex surfaces. - The old enumeration routines have been deprecated. These include enumerate(NTriangulation*, int, bool, NProgressManager*), enumerateStandardDirect(), enumerateStandardANDirect(), enumerateFundPrimal(), enumerateFundDual(), enumerateFundCD() and enumerateFundFullCone(). Users should now use the "universal" enumeration routine enumerate(NTriangulation*, NormalCoords, NormalList, NormalAlg, NProgressTracker*), which offers sensible default as well as fine-grained control over the underlying algorithms. - In all enumeration routines that take progress trackers, the old-style NProgressManager* arguments have been replaced with NProgressTracker* arguments, which are more flexible and more streamlined. - The routine getFlavour() is now renamed to flavour(). The old getFlavour() is still available but deprected. - This class no longer inherits from NSurfaceSet (which has been removed). As a result, getShareableObject() is no longer necessary and has likewise been removed. Enum NormalCoords: - A new enumeration that contains constants for different normal surface coordinate systems. Include "surfaces/normalcoords.h". Class NormalFlavour: - Template classes that store traits of the various normal surface coordinate systems. These are used with the new forFlavour() template functions. Enum NormalListFlags, Class NormalList: - Flags that specify which normal surfaces a particular list represents within a given triangulation. Enum NormalAlgFlags, Class NormalAlg: - Flags that specify details and parameters of normal surface enumeration algorithms. Class NPacket: - Fix crash when sortChildren() is run on the root packet. - Removed writePacket(NFile&) and readPacket(NFile&), which were used for old-style binary files. Class NPerm3, NPerm4, NPerm5: - New dimension-agnostic alias Sn that refers to S3, S4 and S5 in NPerm3, NPerm4 and NPerm5 respectively. - New dimension-agnostic alias Sn_1 that refers to S2, S3 and S4 in NPerm3, NPerm4 and NPerm5 respectively. - New dimension-agnostic enum constants nPerms and nPerms_1 that contain the number of permutations in Sn and Sn_1 respectively. - New construtors that take an array of images. - NPerm3 and NPerm5 now have a copy of S2 (which NPerm4 already had). Likewise, NPerm5 now has a copy of S3, orderedS3, S4 and orderedS4 (which NPerm4 again already had). - NPerm4::invS2 and NPerm4::invS3 are deprecated: the former is unnecessary, and the latter is identical to NPerm3::invS3. - NPerm3 now has a transposition constructor: NPerm3(a,b). Class NProgress, NProgressFinished, NProgressManager, NProgressMessage, NProgressNumber, NProgressStateNumeric: - Deprecated in favour of the new NProgressTracker (see below). Class NProgressTracker: - A new unified and streamlined progress tracking mechanism that replaces the old suite of NProgress classes (i.e., NProgressManager and the NProgress class hierarchy). Class NQitmask1, NQitmask2: - Fast classes for working with 0/1/2/3 "qits", i.e., "base 4 bits". Class NSatBlock: - Routine nextBoundaryAnnulus() has acquired an extra boolean argument, which allows us to choose between "next" and "previous". Class NSatRegion: - Routine createSFS() no longer need to be given the number of boundary components, and now supports Klein bottle boundaries. The old two-argument version is now deprecated. Class NSnapPeaTriangulation: - Updated the SnapPea kernel to the one shipped with SnapPy 1.8.0. - New routine toRegina() to extract the underlying SnapPea triangulation, or to see exactly how SnapPea has relabelled and/or retriangulated. - New routine canonize() to produce the canonical retriangulation of the canonical cell decomposition. Class NSurfaceFilter: - Removed writeFilter(NFile&) and readFilter(NFile&, ...), which were used for old-style binary files. Class NSurfaceSet: - Removed. The corresponding functionality has been moved directly into the subclasses NNormalSurfaceList and NSurfaceSubset. Class NSurfaceSubset: - No longer inherits from NSurfaceSet (which has been removed). As a result, getShareableObject() is no longer necessary and has likewise been removed. - The constructor now takes a reference to an NNormalSurfaceList, not an NSurfaceSet. Class NTetrahedron: - New dimension-agnostic aliases adjacentSimplex() and adjacentFacet(). These are identical to adjacentTetrahedron() and adjacentFace() respectively, and can help with writing code that works with more than one dimension. Class NTreeTraversal, NTreeEnumeration, NTreeSingleSolution: - New class for enumerating vertex normal surfaces and locating a single vertex normal surface under some linear constraint (such as positive Euler characteristic). These are a significant improvement upon the old double description method. For details see arXiv:1010.6200 and arXiv:1211.1079 respectively. Class NTriangulation: - New routine drillEdge() to drill out a regular neighbourhood of an edge. - New optimised routines hasNonTrivialSphereOrDisc() and hasOctagonalAlmostNormalSphere() that use linear programming where possible to avoid a full enumeration of vertex surfaces. See arXiv:1211.1079 for details. - Some routines are now *much* faster for larger triangulations, thanks to the new linear programming code (arXiv:1211.1079). These include isZeroEfficient(), isThreeSphere(), isSolidTorus(), connectedSumDecomposition(), and makeZeroEfficient(). - Rewrote barycentricSubdivision(): the code is now clearer, but note that the precise labelling of the new tetrahedra has changed from earlier versions of Regina. - Routine insertTriangulation() now works in the case where a triangulation tries to insert itself. - New dimension-agnostic aliases getNumberOfSimplices(), getSimplices(), getSimplex(), simplexIndex(), newSimplex(), removeSimplex(), removeSimplexAt() and removeAllSimplices(). These are identical to the corresponding routines for operating on tetrahedra, and can help with writing code that works with more than one dimension. - Fixed isZeroEfficient(), which was reporting incorrect results for ideal triangulations. - Removed the old (and slow) getTetrahedronIndex(), getFaceIndex() and so on. Use tetrahedronIndex(), faceIndex(), etc. instead. - All routines that took stdhash::hash_set arguments now take std::set arguments instead. Class NTritmask1, NTritmask2: - Fast classes for working with 0/1/2 trits, i.e., "ternary bits". Class NTypeTrie: - A fast data structure for storing zero/non-zero patterns in vertex normal surfaces. This supports the new tree traversal enumeration code; see NTreeTraversal for details. Class NVertex: - New routine buildLink() to triangulate the vertex link. - New convenience routine getTriangulation(). Class ZBuffer: - Updated xsgetn() to support n larger than UINT_MAX. Routine base64Decode(), base64Encode(): - Replace the old GPL-licensed implementation with a different MIT-licensed implementation. Functionality is the same. Routine forFlavour(): - New template functions that offer a typesafe way of iterating through cases of different normal coordinate systems without abusing the C++ preprocessor. These should be used instead of the old (and now deprecated) REGISTER_FLAVOUR macros. Variants of forFlavour() are offered for different function types. Routine readFromFile(), writeToFile(): - Removed, since these were used with old-style binary files. Routine readOrb(): - Updated to correctly read the modern (and more verbose) Orb file format. Thanks to Lorenzo Losa for the patch. Typedef NFacePairingIsoList, UseFacePairing: - These typedefs have been renamed as NFacePairing::IsoList and NFacePairing::Use respectively. The old typedefs are deprecated, and will be removed in some future release. Header flavourregistry.h: - The old REGISTER_FLAVOUR macros are now deprecated, and will eventually be removed. The preferred way to iterate through coordinate flavours is the new forFlavour() template functions. Header hashmap.h, hashset.h, hashutils.h: - Removed, after being deprecated for a very long time now. Header nlargeinteger.h: - Deprecated in favour of the new ninteger.h. USER INTERFACE: In addition to incorporating the new mathematical changes above: - The preference dialog is somewhat simpler now. + Some preferences are gone, and instead Regina simply remembers your last selection (e.g., default tabs for various viewers). + Some preferences have just been removed from the dialog (e.g., the calculations thresholds), though users who really need these can still access them through the configuration file. - The elementary move dialog contains richer information about where the moves can take place, and stays open so that you can perform several moves one after another. UTILITIES: regconvert: - The option -b (for old-style binary files) is no longer supported. regina-python: - Enabled tab completion, courtesy of readline and rlcompleter. - The --quiet option no longer lists libraries as they are loaded. tricensus: - Now takes an optional argument -2/--dim2 to enumerate 2-manifold triangulations. - Now takes an optional argument -c/--subcontainers to store triangulations in subcontainers according to face pairings. TEST SUITE: - Greatly expanded the tests for NLargeInteger and NInteger, which are now extremely thorough. - New and more thorough NTriangulation tests for barycentric subdivision, drilling edges, 0-efficiency testing, 3-sphere recognition, connected sum decomposition, solid torus recognition, and comparing H1 with the abelianised fundamental group. - New NNormalSurfaceList tests for comparing the results of different vertex / fundamental surface enumeration algorithms. Also, exhaustive tests over census data now include ideal triangulations. - New enumeration tests for Dim2EdgePairing and Dim2Census. - New tests for permutation databases Sk and orderedSk. - New tests for base64 conversion. - Expanded tests for the NRational class. - Expanded enumeration tests for facet pairing classes to include pairings with boundary facets. Version 4.93 [ 30 May, 2012 ] ENGINE: Class NAngleStructure: - New routine isVeering() to test for veering structures. - Flags (taut/strict/veering) are no longer stored in data files. Class NBlockedSFSLoop, NBlockedSFSTriple, NPluggedTorusBundle: - Corrected an off-by-one error in computing the genus of certain non-orientable base orbifolds for Seifert fibred pieces. This only affects census manifolds with >= 12 tetrahedra; in particular, no existing census data is affected. Class NExampleTriangulation: - New routine weeks() to build the Weeks manifold. Class NNormalSurface: - Routines isOrientable(), isTwoSided() and isConnected() now return bool instead of NTriBool. This helps avoid unintended errors in scripts, but be warned: these properties can only be computed for compact surfaces (not spun normal surfaces). - Routines isIncompressible() and isCompressingDisc() likewise now return bool instead of NTriBool. Class NTriangulation: - New routines isSolidTorus() and knowsSolidTorus() for "one-click unknot recognition". - Routine hasCompressingDisc() now returns bool instead of NTriBool. Class NTriBool: - Deprecated. This class has been replaced with ordinary (two-way) bool throughout Regina, and will be removed in a future release. Routine writeRecogniser(): - Added to support sending triangulations to Matveev's 3-manifold recogniser software. USER INTERFACE: - Restored File -> Save and File ->Save As, which were missing from the main menu in version 4.92. - In the triangulation viewer, the old "Surfaces" tab has been renamed as the "Recognition" tab, and includes high-level algorithms such as 3-sphere recognition as well as "opportunistic" combinatorial recognition. - Replaced the python icon with a generic terminal icon. - GUI python consoles now set the variable "item", not "selected". This is easier to type, and avoids the misconception that selecting a different packet in the tree might change the variable (which it doesn't). The variable "selected" is also set for backward compability, though this will be removed in a future release. - GUI python consoles will now set item=None if there is nothing selected in the packet tree. - Simplified the cut/copy/paste behaviour in graphical Python consoles. - The knot/link census is now explicitly called the *hyperbolic* knot/link census, since non-hyperbolic knots or links are excluded. DOCUMENTATION: - Layout and navigation improvements in the user handbook. TEST SUITE: - Added several tests related to Hakenness testing. Version 4.92 [ 12 April, 2012 ] - The "hello Windows!" release. INSTALLATION: - Regina now builds and runs under Windows! - MacOS users now have a drag-and-drop app bundle, and do not need fink at runtime. - Linux (and indeed all) users no longer need KDE, since the user interface is now Qt-only. ENGINE: Class NBitmask: - Make NBitmask more suitable for use in containers. Specifically, the assignment operator can now be used to initialise an uninitialised bitmask or to reset an already-initialised bitmask to a different length, and one or both of its operands may be invalid. Class NBitmask, NBitmask1, NBitmask2: - New function lessThan() for lexicographical comparisons. Class NDoubleDescription: - Removed the inner class LexComp, and replaced it with the new global class NPosOrder (see below). A deprecated typedef NDoubleDescription::LexOrder is kept for backward compatibility. Class NFacePairing: - Function writeDot() can now optionally label graph vertices with the corresponding tetrahedron numbers. - Added graphviz export routines dot() and dotHeader(), which are like writeDot() and writeDotHeader() but return strings instead of writing to standard output. Class NFacetSpec: - New template class that generalises NTetFace to arbitrary dimensions. NTetFace is retained as a convenience typedef, but with changes; see the NTetFace notes below. Class NGlobalDirs: - New routine setDirs() to be used when an application has been moved from the cmake-configured installation directory. Class NHilbertCD: - New class that implements a modified Contejean-Devie algorithm for Hilbert basis enumeration, based on the original algorithm in "An efficient incremental algorithm for solving systems of linear Diophantine equations", Contejean and Devie, Inform. and Comput. 113 (1994), 143-172. Class NHilbertDual: - New class that implements a modified dual algorithm for Hilbert basis enumeration, based on the dual algorithm in "Normaliz: Algorithms for affine monoids and rational cones", W. Bruns and B. Ichim, J. Algebra 324 (2010), 1098-1113. Class NHilbertPrimal: - New class that enumerates Hilbert bases by decomposing into maximal admissible faces and running the primal algorithm from Normaliz on each such face. For details, see "Fundamental normal surfaces and the enumeration of Hilbert bases", B. Burton, arXiv:1111.7055, November 2011. Class NLargeInteger: - Made separate constructors and assignment operators for arguments of type int, unsigned int, long, and unsigned long. - Added ++ and -- operators. - Added (long + NLargeInteger) and (long * NLargeInteger) operators. - New routines setRaw() and rawData() for interacting directly with libgmp and libgmpxx. Class NMaxAdmissible: - New class for enumerating maximal admissible faces of the normal surface solution cone. Class NNormalSurface: - New routine boundarySlopes() that calculates boundary slopes for spun normal surfaces. - New routines getOrientedTriangleCoord() and getOrientedQuadCoord() for transversely oriented normal surfaces. Class NNormalSurfaceList: - New routines enumerateFundPrimal() and enumerateFundDual() to enumerate fundamental normal surfaces, as well as slower routines enumerateFundFullCone() and enumerateFundCD() for comparing different enumeration algorithms. - New coordinate systems ORIENTED and ORIENTED_QUAD to support transverse oriented normal surfaces. - New routines beginVectors() and endVectors() and new inner class VectorIterator for iterating through raw normal surface vectors. - New routine allowsOriented(), indicating whether the underlying coordinate system supports transverse orientations. Class NNormalSurfaceVector: - New routines getOrientedTriangleCoord() and getOrientedQuadCoord() for transversely oriented normal surfaces. - New routine allowsOriented(), indicating whether the underlying coordinate system supports transverse orientations. Class NNormalSurfaceVectorOriented, NNormalSurfaceVectorOrientedQuad: - New coordinate systems for transversely oriented normal surfaces. Class NPacket: - Fixed potential crash in fireDestructionEvent(). Class NPerm3: - New routine compareWith() for sorting permutations. Class NPerm5: - New class describing permutations of five elements. Class NPosOrder: - Class for sorting hyperplanes during vertex enumeration. This is the new name for the old NDoubleDescription::LexComp. Class NSnapPeaTriangulation: - New routine slopeEquations() that uses code from SnapPy to compute boundary slope matrices for cusps. - New routine verifyTriangulation() for ensuring that SnapPea has not retriangulated unexpectedly. Class NSurfaceSet: - New routine allowsOriented(), indicating whether the underlying coordinate system supports transverse orientations. Class NTetFace: - Now an instance of the arbitrary-dimension template class NFacetSpec. NTetFace is retained as a typedef, but the fields have been renamed from tet/face to the more general simp/facet. Class XMLPropertyDict: - Removed the optional defaultVal argument from lookup(); the default is now always the empty string. Routines perm4to5, perm5to4, perm3to4, perm4to3: - New routines for converting between permutation classes. Routine makeEmbeddedConstraints(): - Added to help generate admissibility constraints based on a flavour constant, instead of an NNormalSurfaceVector subclass. Routines writeCSVStandard, writeCSVEdgeWeight: - Included boundary slopes of spun-normal surfaces in CSV output. Macro REGISTER_FLAVOUR: - Now requires an additional argument, indicating whether the coordinate system supports transverse orientations. USER INTERFACE: - Ported from KDE to Qt-only. - Moved from a single monolithic window to lots of small windows (one for the tree, one for each open packets). The old interface can be re-enabled through Regina's preferences. - Continued the user interface overhaul, again with many more improvements that give cleaner and more sensible behaviour (too many changes to list individually). - Graphical Python consoles now execute "from regina import *" automatically. UTILITIES: regina-python: - Now executes "from regina import *" automatically. You can suppress this with the argument --noautoimport. - For MacOS users: make sure 32-bit python is called when running 32-bit fink on a 64-bit machine. tricensus: - Now outputs all face pairings as it runs, instead of periodic snapshots of where the census is up to. Version 4.91: Internal development version. Version 4.90 [ 12 September, 2011 ] - First prerelease for version 5.0. OVERALL: - The graphical user interface has been ported from KDE3 to KDE4. - The build system has been ported from autotools to cmake. - The built-in portions of the SnapPea kernel have been re-synced with the September 2009 version of SnapPea. DOCUMENTATION: - The users' handbook has undergone a thorough overhaul. It is cleaner, more streamlined, and now full of screenshots. - The data file format has been split out of the users' handbook and placed in its own separate reference manual. ENGINE: Typedef AcceptTriangulation: - This global typedef has been deprecated. Please use the class typedef NCensus::AcceptTriangulation instead. Class BitManipulator, GenericBitManipulator: - BitManipulator has new routines firstBit() and lastBit(), which return the positions of the first and last true bit respectively. This has required the addition of a new non-optimised base class GenericBitManipulator, which end users need never use directly. Class NAbelianGroup: - Routine addRank() now takes a signed integer, so that you can subtract from the rank as well as add to it. - Two new constructors to compute the homology of a chain complex with integer or mod-p coefficients. Thanks to Ryan Budney. Class NAngleStructureList: - There is now an option to enumerate only taut angle structures, which is significantly faster than enumerating all vertex angle structures. As a result, enumerate() now takes three arguments, there is a new routine isTautOnly(), and the data file format for angle structure lists contains a new element "angleparams". - Renamed allowsStrict() and allowsTaut() to spansStrict() and spansTaut(). The old routines are now deprecated but have been preserved (for now) for backward compatibility. Class NAngleStructureVector: - Removed clone(). Just use the copy constructor instead. Class NBitmask, NBitmask1, NBitmask2: - New operators ^= (XOR), -= (set difference) and = (assignment). - New routine truncate(), which truncates a bitmask to a given number of bits by setting all subsequent bits to zero. - New routines firstBit() and lastBit(), which return the positions of the first and last true bit respectively. - New convenience typedefs NBitmaskLen8, NBitmaskLen16, NBitmaskLen32 and NBitmaskLen64 for fast-and-small bitmasks of predetermined sizes. Class NCensus: - New class typedef NCensus::AcceptTriangulation to replace the old global typedef AcceptTriangulation. Class NDiscSetTetData: - Added assertions in data() to ensure that the given disc type and number are valid. These can be circumvented by compiling with -DNDEBUG. Class NDoubleDescription: - Method enumerateExtremalRays() no longer has the "base" argument (which was used for cloning vectors); instead the method takes a new template argument specifying a vector class, and new vectors are created using the corresponding class constructors. - Improved the speed of adjacency testing by using an NTrieSet instead of a linear search through all vertices. See the NNormalSurfaceList notes below for further information. - Method enumerateExtremalRays() now takes an optional argument initialRows, allowing the user to force certain hyperplanes to be processed first. Class NExampleTriangulation: - New routine bingsHouse() returning the dual triangulation to Bing's house with two rooms. - Renamed seifertWeber() to weberSeifert(), for consistency with the original paper. The old routine is now deprecated, but has being preserved (for the time being) for backward compatibility. Class NFacePairing: - Fixed the human-readable output for boundary faces in toString(). Previously boundary faces were displayed as "n:0"; now they are displayed as "bdry". Class NFastRay: - Merged into the NRay class; see the NRay and NVector changes for details. Because typedefs cannot be templated, there is no legacy typedef; instead NFastRay has been removed completely. Class NFastVector: - Merged into the NVector class; see the NVector changes for details. Because typedefs cannot be templated, there is no legacy typedef; instead NFastVector has been removed completely. Class NGlobalDirs: - New routine data() to return the internal data directory. Class NGroupPresentation: - Fixed a memory leak in intelligentSimplify(). Class NIsomorphism: - New routine applyInPlace() to directly modify a triangulation. - Routine apply() now copies tetrahedron descriptions as well as gluings (and so does the new routine applyInPlace()). Class NLayeredSurfaceBundle: - In isLayeredTorusBundle(), extend the search for possible cores to 11 and 12 tetrahedra. Class NLargeInteger: - New routines randomBoundedByThis(), randomBinary() and randomCornerBinary() for pseudo-random number generation. - Faster (GMP-native) implementation of swap(). Class NMarkedAbelianGroup, NHomMarkedAbelianGroup: - Both of these classes have much richer interfaces than before, and some old routines have now been deprecated. See the class documentation for full details. Thanks to Ryan Budney. Class NMarkedVector: - Added swap() to swap the contents of two vectors. Class NNormalSurface: - New routines isCompressingDisc() and isIncompressible() for testing incompressibility. - Fixed a memory leak in crush(). Class NNormalSurfaceList: - Sped up enumeration (again), this time by using a trie-like structure for adjacency testing. For the Weber-Seifert space this improves speed threefold (roughly). Many thanks to Jonathan Shewchuk for encouraging me to focus on the adjacency testing bottleneck. - New filtering routines filterForLocallyCompatiblePairs() and filterForDisjointPairs(). - New routine filterForPotentiallyIncompressible() to assist with bulk incompressibility testing; see arXiv:0909.4625 for details. - New routine allowsSpun() to easily identify coordinate systems that support spun normal surfaces. Class NNormalSurfaceVector: - Due to the changes in the NVector hierarchy, clone() is now introduced as a virtual function of NNormalSurfaceVector (not NVector), and it now returns an NNormalSurfaceVector*. Class NPacket: - In the python interface, makeOrphan() now returns the packet itself and the ownership becomes the responsibility of whoever takes this return value. If nobody takes this return value then the packet and its descendants are automatically destroyed. - ChangeEventBlock has been renamed to ChangeEventSpan, it fires both packetToBeChanged() and packetWasChanged() (on construction and destruction respectively), and the optional boolean argument is gone (events are now fired always). The old class name ChangeEventBlock remains as a deprecated typedef for ChangeEventSpan. - The protected routine fireChangedEvent() has been removed. The only way to fire a "packet changed" event now is to declare a local ChangeEventSpan. Class NPacketListener: - All events (except for destruction) now come with both future and past events: packetToBeChanged() and packetWasChanged(), childToBeAdded() and childrenWereAdded(), and so on. Class NPerm: - Renamed to NPerm4. The C++ header has also moved from triangulation/nperm.h to maths/nperm4.h . Both the old class name and the old header are now deprecated. - There are many other changes; see the NPerm4 notes below. Class NPerm3: - New class describing permutations of three elements. This is extremely fast, using lookup tables for all calculations. Class NPerm4: - This is the new name for the old class NPerm (see above). - New routines S4Index() and orderedS4Index() for reverse lookups into the arrays NPerm4::S4 and NPerm4::orderedS4. - The constructor that takes an internal permutation code is now private, and is replaced by a new static routine NPerm4::fromPermCode(). This new routine is easier to spot and grep for. - The internal permutation codes have changed. As a result, NPerm4 operations are significantly faster. The old codes are now referred to as "first-generation", and the new codes are referred to as "second-generation". - Routines getPermCode(), setPermCode(), isPermCode() and fromPermCode() continue to refer to first-generation codes. These old routines are not recommended, since they now incur additional overhead. To replace them, the new (and faster) routines getPermCode2(), setPermCode2(), isPermCode2() and fromPermCode2() work with second-generation codes instead. - The XML data file format continues to use first-generation permutation codes (for backward compatibility). Class NRational: - New STL-compatible routine swap(). Class NRay: - Like the vector hierarchy, NFastRay and NRay are now merged into a single NRay class. The result is that NRay is now cleaner and faster, but methods are no longer virtual. See the NVector class notes for full details of the changes (including important changes in the parent NVector class). - Removed the old intersect() function, which was based on virtual methods. Class NScript: - Fixed a bug that lost script variables when reading ancient binary files (i.e., files created before Regina 3.0, around mid-2002). This bug only showed up on some compilers. Class NSnapPeaCensusManifold, NSnapPeaCensusTri: - Routines getHomologyH1() (for both classes) and construct() (for NSnapPeaCensusManifold) are now implemented for all SnapPea census manifolds/triangulations, not just the smallest few. Class NSnapPeaTriangulation: - Kernel messages are now disabled by default. Class NSurfaceSet, NSurfaceSubset: - New routine allowsSpun() to easily identify coordinate systems that support spun normal surfaces. Class NTetrahedron: - Tetrahedra should now always belong to a triangulation, from creation until destruction. In particular: + Tetrahedra should be created by calling NTriangulation::newTetrahedron(), which will insert them into a triangulation immediately. There is no need to call NTriangulation::addTetrahedron() any more. + NTetrahedron::joinTo() now recursively adds adjacent tetrahedra to the relevant triangulation (but if you use NTriangulation::newTetrahedron() as described above then this is fast and changes nothing). - Users no longer need to call NTriangulation::gluingsHaveChanged() after gluing or ungluing tetrahedron faces; this is now handled automatically. - The NTetrahedron constructors are now deprecated in favour of NTriangulation::newTetrahedron(), as described above. - New routine getVertexMapping(), which facilitates a consistent orientation around the given vertex for the three remaining vertices in each tetrahedron. - Added assertions in joinTo() and unjoin() to ensure that the preconditions are met. These can be circumvented by compiling with -DNDEBUG. - Routines that need the triangulation skeleton (e.g., getVertex(), getEdgeMapping(), orientation(), etc.) will now compute the skeleton automatically if this has not already been done. - New routine getTriangulation() to return the enclosing triangulation. Class NTriangulation: - Add new routines newTetrahedron() and newTetrahedron(const std::string&). This is now the preferred way of creating tetrahedra (see the NTetrahedron notes above). The old addTetrahedron() is now deprecated. - Users no longer need to call gluingsHaveChanged(), which is likewise deprecated. Again, see the NTetrahedron notes above. - Routines removeTetrahedron() and removeTetrahedronAt() now destroy the tetrahedron immediately and return nothing. - New routines hasCompressingDisc() and hasSimpleCompressingDisc() to search for compressing discs; see arXiv:0909.4625 for details. - The "legality conditions" on closeBook() are now simpler, since some of the conditions were automatic consequences of others. Practically, nothing has changed (i.e., the new conditions will be satisfied if and only if the old conditions were satisfied). - The "legality conditions" on twoOneMove() were originally too conservative, and are now weaker. In particular, the endpoints of the edge may now both be boundary. Practically, this means that 2-1 moves may now be legal where they were not legal before. - New static routine rehydrate() to rehydrate a new triangulation from a Callahan-Hildebrand-Weeks dehydration string. This is a more convenient version of insertRehydration(). - New routines isoSig() and fromIsoSig() for detecting and hashing combinatorial isomorphism classes of triangulations. - New routines isOriented() and orient() to relabel tetrahedron vertices for consistent orientation. Thanks to Matthias Goerner. - New routines isOrdered() and order() to relabel tetrahedron vertices so that they are ordered consistently across adjacent faces. Thanks again to Matthias Goerner. - New routines swapContents() and moveContentsTo() for moving tetrahedra between triangulations. - Fixed a memory leak in shellBoundary(). Class NTrieSet: - A new class for storing and querying a large number of sets, where the elements of these sets are taken from a small universe. The underlying data structure is essentially a trie of bitmasks. Class NVector: - Streamlined the vector hierarchy by merging NFastVector and NVectorDense into a single NVector class, and removing NVectorUnit and NVectorMatrix entirely. The result is that NVector is now cleaner and faster, but methods are no longer virtual. See the NVector class notes for full details of the changes. - Removed the old virtual clone() and makeLinComb() methods. Class NVectorDense: - Merged into the NVector class; see the NVector changes for details. Because typedefs cannot be templated, there is no legacy typedef; instead NFastVector has been removed completely. Class NVectorMatrix, NVectorUnit: - Removed. Use NVector instead. Class NVertexEmbedding: - New routine getVertices(), which facilitates a consistent orientation for the three remaining vertices in each tetrahedron. Class XMLPropertyDict: - The interface for this class is now much more restricted. The class now derives from std::map instead of stdhash::hash_map, and it allows access to only a few inherited members of std::map. Routine metricalSmithNormalForm(): - New alternative Smith normal form routine that is better for working with extremely large matrices. Thanks to Ryan Budney. Routine readIsoSigList(): - New routine that reads a text file filled with isomorphism signatures and returns a container filled with triangulations. Routine readSnapPea(): - Allows additional text on the first line following the "% Triangulation" marker (thanks to Matthias Goerner). Routine torsionAutInverse(): - New routine for inverting automorphisms, thanks to Ryan Budney. Routines writeCSVStandard(), writeCSVEdgeWeight(): - Changed the text that is written to the "boundary" field, to be consistent with changes to the user interface. Possible values are now "spun", "real" or "none". File surfaces/flavourregistry.h: - The final "pre_test" argument (which is never used) has been removed. As a result, REGISTER_FLAVOUR now takes five arguments, not six. USER INTERFACE: - Ported from KDE3 to KDE4. Finally! - The user interface has had a thorough overhaul, and includes many usability updates to help new users find their way around. Overall, the behaviour is generally cleaner and more sensible. A full list of changes is omitted for reasons of space and sanity. - There is now an option to enable or disable diagnostic messages from the SnapPea kernel. UTILITIES: tricensus, tricensus-mpi: - New option -s to output lists of isomorphism signatures instead of the much larger Regina data files. tricensus-mpi-status: - Now finishes with a running total of all triangulations found. - Compressed logs (using gzip or bzip2) are now supported. Requires the perl module IO::Uncompress::AnyUncompress. tricensus-manager: - Removed, since tricensus-mpi is a much better alternative. TEST SUITE: - Some slower but more detailed tests are now optional, and are disabled by default. To switch these tests on, set the environment variable REGINA_DETAILED_TESTS to any non-empty value. - New tests for file I/O using both modern XML and legacy binary file formats. - More detailed tests for bitmask operations. BUILD ENVIRONMENT: - Dropped support for ancient hacks like std::ios::no_create and the boost.python make_constructor hack. - Iconv is now mandatory, not optional. Version 4.6 [ 16 May, 2009 ] OVERALL: - Deprecated everything relating to the non-standard STL/g++ classes hash_set and hash_map, for the sake of portability. - Added a "deprecation guide" to the website, with a table listing the outdated routines/classes/etc. to be removed in Regina 5.0 and the corresponding new routines/classes/etc. that replace them. ENGINE: Class HashPointer, HashString: - Deprecated, along with everything else relating to the hash_set and hash_map classes. See the "overall" notes above. Class NBoundaryComponent, NComponent, NEdge, NFace, NVertex: - All constructors for skeletal objects are now private, since only the triangulation skeletal routines should be creating them. - Cleaned up some of the more obscure parts of the documentation. Class NCensus: - Removed findAllCompletions(), which (as the documentation points out) is an empty shell of a routine that has never been implemented and quite possibly never will. Class NDiscType: - New class for specifying a normal or almost normal disc type. Class NDoubleDescription, NDoubleDescriptor: - The old NDoubleDescriptor class has been renamed, and is now called NDoubleDescription. This is merely for consistency with documentation and papers; the functionality of the class has not changed. The old name (NDoubleDescriptor) is deprecated, but for the time being a typedef is provided for backward compatibility. Class NEdge: - Added new static arrays NEdge::edgeNumber and NEdge::edgeVertex to replace the old global arrays regina::edgeNumber, regina::edgeStart end regina::edgeEnd. The old global arrays are now deprecated (see below). - Added a new static array NEdge::ordering to replace the old global routine regina::edgeOrdering(). The old routine is now deprecated (see below). Class NFace: - Added a new static array NFace::ordering to replace the old global routine regina::faceOrdering(). The old routine is now deprecated (see below). Class NFacePairing: - Added a missing precondition to isCanonical(), which requires that the face pairing be connected. - Changed the output format of toString() to make it clearer which faces are boundary faces. Class NFastRay: - New fast but inflexible ray class; this builds on NFastVector in the same way that the slower NRay builds on NVector. Class NGluingPermSearcher, NCompactSearcher, NClosedPrimeMinSearcher: - Overhauled the census code so that some of the optimisations used in the closed minimal irreducible / P^2-irreducible census can be made available to more general censuses. In particular, the modified union-find for vertex and edge links is now available to any census that insists on compact (finite) 3-manifolds. For details of these optimisations, see "Enumeration of non-orientable 3-manifolds using face-pairing graphs and union-find", B. A. Burton, Discrete Comput. Geom. 38 (2007), no. 3, 527--571. Class NIndexedArray: - Deprecated, along with everything else relating to the hash_set and hash_map classes. For a replacement, try the NMarkedVector class, which is smaller and faster but requires modification of the data type being stored. Class NIsomorphism, NIsomorphismDirect, NIsomorphismIndexed: - NIsomorphism has been enhanced to add all of the functionality of the old NIsomorphismDirect class, so NIsomorphism is now a fully-fledged isomorphism class in its own right. - NIsomorhpismDirect has been deprecated, since this is now an empty subclass of NIsomorphism with no extra functionality. Programmers can change their code to use NIsomorphism instead. - NIsomorphismIndexed has been removed entirely. Python users will not be affected, since this class was never available to them. C++ programmers can now use NIsomorphism in conjunction with the NPerm::S4 array instead. Class NLargeInteger: - The old header utilities/nmpi.h is now deprecated. Please use the new header maths/nlargeinteger.h instead. Class NMatrix2: - The old header utilities/nmatrix2.h is now deprecated. Please use the new header maths/nmatrix2.h instead. Class NNormalSurface: - Finally implemented cutAlong()! - New routine isEmpty() for identifying empty surfaces. - New routine sameSurface() for comparing two normal surfaces. - New routines locallyCompatible() and disjoint() for testing intersections between normal surfaces. - New routine getOctPosition() for locating the non-zero octagonal coordinate in an almost normal surface. - New routine rawVector() that gives direct read-only access to the underlying vector in case this is needed. - Removed canCrush() and knownCanCrush(), which are both placeholders for routines that have never been implemented. The corresponding element has been removed from the data file format (though old files that happen to contain it will load without problems). - Added an optional argument to findVtxOctAlmostNormalSphere() that lets the user choose between working in standard and quadrilateral-octagon coordinates. Class NNormalSurfaceList: - Implemented quadrilateral-octagon coordinates for almost normal surfaces (the relevant flavour constant is AN_QUAD_OCT). See arXiv:0904.3041 for details. - Added routines quadToStandard(), standardToQuad(), quadOctToStandardAN() and standardANToQuadOct() to convert between different solution sets. See arXiv:0901.2629 for details of the underlying algorithms. - Changed enumerate() in standard coordinates so that, if possible, it enumerates in quadrilateral coordinates first and then converts between solution sets (see above). This typically runs orders of magnitude faster. Similarly for standard almost normal coordinates. - New routines enumerateStandardDirect() and enumerateStandardANDirect() to allow people to circumvent the above changes to enumerate() and instead use the old direct enumeration from Regina 4.5.1. - Almost normal surface enumeration no longer strips out surfaces with more than one octagonal *disc* (though it still avoids surfaces with more than one octagonal disc *type*). This change is necessary for conversion between quad-oct and standard almost normal space, and is also necessary if we wish to enumerate *all* almost normal surfaces (as opposed to just all *vertex* almost normal surfaces). - Added a new coordinate flavour AN_LEGACY to reflect almost normal surface lists created with Regina 4.5.1 or earlier, where surfaces with more than one octagon were stripped out (see above). Class NPerm: - New routines trunc2() and trunc3() to build a truncated version of a permutation string. - New arrays NPerm::S4, NPerm::invS4, NPerm::orderedS4, NPerm::S3, NPerm::invS3, NPerm::orderedS3, NPerm::S2 and NPerm::invS2. These replace the old (and now deprecated) arrays regina::allPermsS4, regina::allPermsS4Inv, regina::orderedPermsS4, and so on. - Deprecated the setPerm() routines; these are unnecessary because NPerm objects are very small and can just be copied around using the assignment operator. Class NPermItS4: - Deprecated, since all this class does is wrap a trivial loop. Just loop through the elements of NPerm::S4 directly. Class NPillowTwoSphere, NSnappedTwoSphere: - Removed reduceTriangulation() and getReducedTriangulation(), both of which are empty shells of routines that have never been implemented and quite possibly never will. Class NRational: - Added new routines getTeX() and writeTeX() for TeX-friendly output (thanks Ryan!). - The old header utilities/nrational.h is now deprecated. Please use the new header maths/nrational.h instead. Class NTetrahedron: - Renamed getAdjacentTetrahedron(), getAdjacentTetrahedronGluing() and getAdjacentFace() to adjacentTetrahedron(), adjacentGluing() and adjacentFace(), which should be easier on the fingers. The old names are still available for backward compatibility, but they are now deprecated and will be removed in Regina 5.0. - Proofreading and clarification for some of the older and more opaque parts of the documentation. Class NTriangulation: - New routines isBall() and knowsBall() for recognising the 3-dimensional ball. - New elementary move closeBook(). - Completely overhauled collapseEdge(). The eligiblity checks for this routine are now correct and not overly conservative, which makes this routine both useful and safe. The big warning is now gone from the collapseEdge() documentation as a result. - Routine simplifyToLocalMinimum() now collapses edges, which makes a big difference for multiple-vertex triangulations. - Routine intelligentSimplify() now uses book opening moves to create new opportunities for collapsing edges (which makes a big difference for bounded triangulations), and also uses book closing moves to reduce the number of boundary faces once nothing else can be done. - Routine isThreeSphere() now works in quadrilateral-octagon coordinates instead of standard almost normal coordinates. See arXiv:0904.3041 for details. - Removed crushMaximalForest(), which (as the documentation has pointed out since the first release) has never worked properly, and which is therefore never actually used. - Fixed crashes in twoOneMove(), twoZeroMove(NVertex*, ...) and shellBoundary(). These crashes were triggered by certain types of non-minimal triangulations with boundary. - Fixed a couple of nasty bugs in shellBoundary(), which had the potential to give incorrect results when simplifying triangulations with boundary. - Fixed bugs in several simplification routines when working with invalid triangulations; these sometimes "simplified" invalid edges to become valid edges. - New routine reorderTetrahedraBFS() for renumbering tetrahedra in a more sensible fashion. - Routines isZeroEfficient() and hasSplittingSurface() now operate on a clone of the triangulation, to avoid triggering changes to the packet tree. Routine edgeOrdering(), faceOrdering(): - Deprecated these routines, in favour of the new NEdge::ordering[] and NFace::ordering[] lookup tables. Routine edgeDescription(), faceDescription(): - Deprecated these routines, in favour of the new NPerm::trunc2() and NPerm::trunc3() routines. Routine writeResUsage(): - New helper routine to assist with diagnostics and measurements of performance. Global arrays allPermsS4, allPermsS4Inv, orderedPermsS4, allPermsS3, allPermsS3Inv, orderedPermsS3, allPermsS2, allPermsS2Inv: - All of these arrays are now deprecated. Please use the new arrays NPerm::S4, NPerm::invS4, NPerm::orderedS4, and so on. Global arrays edgeNumber, edgeStart, edgeEnd: - Deprecated; users are advised to switch to the new arrays NEdge::edgeNumber and NEdge::edgeVertex instead. Namespace stdhash: - Deprecated, along with everything else relating to the hash_set and hash_map classes. See the "overall" notes above. USER INTERFACE: - The elementary moves dialog now has additional "Close book" and "Collapse edge" options. - Items in the triangulation composition list can now be copied into the clipboard via the right mouse button. Amongst other things, this makes extracting the dehydration string much simpler. - In the triangulation viewer, the Surfaces panel now has a new "3-ball?" entry beneath the current "3-sphere?" entry. - The normal surface list viewer now has two additional tabs: one to summarise all surfaces in the list (which is now the default tab), and one to list pairwise compatibilities between surfaces. - The normal surface list viewer now has an additional "Cut Along" menu item. The shortcut letter for "Crush" has changed as well. - Warn the user before enumerating immersed and/or singular normal surfaces, in case the "embedded surfaces only" box was unchecked by accident. Moreover, refuse to enumerate immersed and/or singular *almost* normal surfaces, since this feature was designed for use with normal surfaces only. - The surface coordinate viewer now lists the location of the almost normal disc(s) alongside the other high-level surface properties. - Normal surfaces are now individually numbered within surface lists, which should make it easier to keep track of which is which. - Added "Troubleshooting" and "Handbook won't open?" entries to the Help menu, to make it easier to find solutions if things break. - The "Python Reference" entry in the Help menu should now work even when konqueror is not installed. - Fixed a bug whereby small triangulations occasionally became read-only after a user visited the Surfaces panel. - Added a scrollbar to the Cellular Info pane in case the window is small. TEST SUITE: - Added very thorough tests for NPerm. - Additional tests for triangulation simplification and Euler characteristic. - New tests for the recognition of families of standard triangulations and manifolds. - New tests that enumerate automorphisms of triangulations and test for subcomplexes. - Added exhaustive testing for 3-sphere and 3-ball recognition. - Added exhaustive testing for conversions between normal surface solution sets in different coordinate systems. BUILD ENVIRONMENT: - Installing into a staging area (i.e., building packages) now works fine even if older versions of the Regina development libraries are installed (this used to cause relinking problems on some systems). Version 4.5.1 [ 28 October, 2008 ] CENSUS: - Replaced the old plain-text Notation packets with detailed PDF packets in the closed orientable / non-orientable censuses. These new Notation packets explain the manifold names and parameters precisely, and include supporting diagrams. - Renamed some census manifolds to avoid arbitrary (a), (b) suffixes. In the 11-tetrahedron closed orientable census: Hyp_2.13401634 (Z_14) (a) -> Hyp_2.13401634 (Z_14, geod = 0.4606) Hyp_2.13401634 (Z_14) (b) -> Hyp_2.13401634 (Z_14, geod = 0.3684) (These have also been reordered in the overall list of manifolds.) In the 11-tetrahedron closed non-orientable census: SFS [M] U m003 (a) -> SFS [M] U m003 (Z + Z_5) SFS [M] U m003 (b) -> SFS [M] U m003 (Z + Z_10) These changes are only stop-gaps until hyperbolic manfiolds are dealt with properly (i.e., expect the names to change again). ENGINE: Class NBitmask, NBitmask1, NBitmask2, BitManipulator: - New classes for manipulating bitmasks. NBitmask is for bitmasks of arbitrary length, whereas NBitmask1 and NBitmask2 are optimised for situations where the length is known to be small. BitManipulator makes it easier to offer optimised template specialisations. Class NCompConstraint, NCompConstraintSet: - Removed. Compatibility constraints now refer to facets of the original cone, not coordinate positions (hence the rename, since the semantics have changed in a fundamental way). Individual compatibility constraints are now straight sets of integers, and the deque-based NCompConstraintSet has been replaced with the new vector-based class NEnumConstraintList. Class NDoubleDescriptor: - Streamlined the vertex enumeration routine, which is now much, much faster. See arXiv:0808.4050 for a full list of improvements. - The vertex enumeration routine now takes its arguments in a different form, and more importantly now insists that the original cone is in fact the non-negative orthant. To ensure that python/C++ users notice that things have changed, this routine has been renamed from enumerateVertices() to enumerateExtremalRays(). - Major overhaul of the documentation, which is now (hopefully) much clearer. Class NEnumConstraintList: - Added to replace the old NCompConstraintSet class. See the notes above on NCompConstraintSet for details. Class NFastVector: - New vector class that is less flexible than NVector but more streamlined, largely because it has no virtual functions. Class NGraphTriple: - Fixed a bug in writeTeXName() that listed incorrect entries for the first matrix. This does not affect any census data that has been published or shipped with earlier versions of Regina. Class NLargeInteger: - Fixed lcm() to do the right thing in the case of lcm(0,0). Class NMatrixInt: - New integer-specific routines divRowExact(), divColExact(), gcdRow(), gcdCol(), reduceRow() and reduceCol(). Class NMatrixField: - Removed this class, since it is never used and since it does not actively address the accuracy problems raised by real numbers. Class NNormalSurfaceVector: - Removed createNonNegativeCone() from subclasses (which is no longer required after the vertex enumeration overhaul), and added makeZeroVector() (which is now required). Class NPDF: - A new packet type that allows PDF documents to be stored directly as packets inside Regina data files. Routines base64Length(), isBase64(), base64Encode(), base64Decode(): - New routines for base64 encoding and decoding, taken and modified from the gnulib library. Routines createNonNegativeCone(), makeZeroVector(): - Removed global routine createNonNegativeCone() and replaced it with the new global makeZeroVector(), in line with the changes to NNormalSurfaceVector listed above. Routine gcd(): - Now takes longs instead of unsigned longs, and guarantees that the gcd returned will be non-negative. Routine lcm(): - Added to the collection of basic number theory routines. Routines readPDF(), writePDF(): - New routines for importing and exporting PDF packets to real PDF files. Routines rowBasis(), rowBasisAndOrthComp(): - Added to find the rank of an integer matrix and bases for its row space and orthogonal complement. USER INTERFACE: - PDF documents can now be embedded within data files as PDF packets. Added import, export and creation facilities, plus a PDF packet viewer that uses either an embedded KPart (such as kpdf or kghostview's embedded viewer) or an external application (such as xpdf or evince) according to the user's preferences. TEST SUITE: - Added new tests for normal surfaces that exploit generic properties of layered loops; these tests push to 50 tetrahedra, and can easily be extended further as surface enumeration becomes faster. - Added additional tests for normal surfaces and angle strutures that compare solutions coordinate by coordinate (instead of counting solutions and looking for large-scale properties). Version 4.5 [ 17 May, 2008 ] OVERALL: - Regina finally pays attention to character encodings: + All strings in the calculation engine are assumed to be UTF-8, except for filenames which should be in whatever local encoding the operating system expects. + Regina's XML data files store their data in UTF-8, as does the python configuration file ~/.regina-libs. + The various user interfaces ensure that the correct character encodings are always used, and translate between encodings where required. + Users who pass strings directly to the calculation engine (through the python or C++ interface) must ensure that their strings are either UTF-8 or just plain ASCII. - Builds under gcc 4.3. - Supports building out-of-tree (e.g., making separate builds in debug/ and release/ subdirectories). ENGINE: Class Locale, IConvBuffer, IConvStream: - New classes in the regina::i18n namespace for working with internationalisation and character encodings. Class NExampleTriangulation: - New routine seifertWeber() to build the Seifert-Weber dodecahedral space. Class NFileInfo: - Made the XML identification routines more robust, so that they work even if extra parameters (such as encoding or standalone declarations) are present in the XML prologue. Class NGlobalDirs: - New class for easy access to system installation directories for various components of Regina. Class NMarkedVector, NMarkedElement: - New class for a vector with fast reverse lookups. This is more memory efficient than NIndexedArray, but requires modifications to the data type being stored. Class NPacket, NPacketListener: - Fires packetWasRenamed() when packet tags are added or removed. Note that packet tags still cannot be accessed through the GUI except for via the python bindings. - Made the event handling code more robust; this fixes a couple of crashes in the GUI that occured when packets were deleted from the tree while they were still in use elsewhere. Class NTriangulation: - Streamlined the implementation, cutting ~ 1/3 of the memory usage and improving speed also. As a result: + Tetrahedra and skeletal components are now stored using NMarkedVector, not NIndexedArray. As a result the return types of getTetrahedra(), getVertices(), etc. have changed. + The old reverse lookup routines getTetrahedronIndex(), getVertexIndex(), etc. are now deprecated. The new routines tetrahedronIndex(), vertexIndex(), etc. are even faster, but have new preconditions requiring the given tetrahedron, vertex, etc. to belong to the triangulation. Class NXMLElementReader: - New routine usingParser() that passes the current parser to the top-level element reader. Class XMLParserCallback: - Callback routine start_document() now takes a single argument that points to the current XMLParser. Routine readXMLFile(), readFileMagic(): - Assumes that any Regina data files created by version 4.4 or earlier use a LATIN-1 encoding (which is what the old GUI used by default), and that any data files created by version 4.5 or later use UTF-8. Routine versionUsesUTF8(): - Added to assist code that works with files on a low-level basis. Routines writeCSVStandard(), writeCSVEdgeWeight(): - New routines for exporting normal surface lists to plain-text CSV files (which can then be imported into a spreadsheet or database). USER INTERFACE: - Normal surface lists can now be exported to plain-text CSV files (which can then be imported into a spreadsheet or database). - The default SnapPea filename filter is now *.tri instead of just *. - Several imports and exports (e.g., to/from python scripts and C++ source) now allow the user to select an explicit character encoding. - Added the Seifert-Weber dodecahedral space to the list of example triangulations that can be created. - When adding a new library in Regina's python settings, the file dialog now opens in the pylib/ directory where sample libraries are installed. - Application icons are now listed under hicolor instead of crystalsvg, which should make them easier for other desktop environments to find. PYTHON: - Use docstrings for the helper routines in pylib/, so documentation for these routines can be accessed from within python at runtime. - Added loadCensus.py as a new collection of optional helper routines in pylib/. These make it easy to load census data files from within python. UTILITIES: regfiledump, trisetcmp: - Now respects the default locale; any international characters in packet labels or packet contents are displayed using the correct character set (e.g., LATIN-1 for Western European users). Version 4.4 [ 25 November, 2007 ] - The "hug a Mac today" release. OVERALL: - Builds and runs happily on MacOS! Requires Fink to be installed. - Tidied up the directory hierarchy a little. The old engine/engine/ is now just engine/, and the old engine/doc-files/ is now engine/doxygen/. - Thanks to Ryan Budney for his many contributions to this release! CENSUS: - Expanded the closed non-orientable census to 11 tetrahedra. - Expanded the closed orientable census to 11 tetrahedra, and split it into three data files. The 9-tetrahedron and 10-tetrahedron files are shipped with Regina as before (as closed-or-census.rga and closed-or-census-large.rga); the 11-tetrahedron file is extremely large and must be downloaded separately from the Regina website. - Modified the closed orientable census files to use more consistent choices of monodromy matrices for torus bundles. - Clarified the matrix notation used with graph manifolds. ENGINE: Class NClosedPrimeMinSearcher: - Census generation is much faster for larger numbers of tetrahedra, due to new tests for high-genus vertex links. - Also made census generation more efficient by pruning on high-degree edges (as well as the usual low-degree edges). Class NDoubleDescriptor: - Now a standalone class, since the base NVertexEnumerator has been removed. - Made all member functions static. Objects of this class can no longer be created. Class NFacePairing: - Modified writeDot() to behave well with ancient versions of Graphviz. Class NHomologicalData: - New class for computing all sorts of detailed homological information for a manifold; thanks to Ryan Budney for this. Class NLargeInteger: - New routine divisionAlg() for using the division algorithm. - New routine legendre() for calculating Legendre symbols. Class NMatrix: - New == and != operators for element-by-element comparison. Class NMatrixInt: - Added a set() routine to the python interface for setting matrix elements (which was previously not possible in python). - Made matrix multiplication available in the python interface. - Added a python-only variant of initialise() that fills a matrix given a complete list of elements. Class NMatrixRing: - New routine det() for fast calculation of matrix determinants. - New convenience routine isIdentity(). - Changed the return type of operator * from a raw pointer to a std::auto_ptr, to make it easier to multiply matrices inside temporary expressions. - Added a new multiplyAs() template routine that multiplies but (unlike operator *) returns a subclass of NMatrixRing. Class NMarkedAbelianGroup, NHomMarkedAbelianGroup: - New classes for working with groups defined by chain complexes; thanks to Ryan Budney for these. Class NPacket: - New routine reparent() to simplify ownership issues when using Python scripting. Class NPerm: - Micro-optimised routines that are called extremely frequently, such as sign(). Class NPrimes: - New class that provides a more sophisticated infrastructure for prime factorisation than the old factorise() and primesUpTo() routines. Class NRational: - New routine abs() for calculating absolute value. - New routine doubleApprox() for converting to a real number. Struct NSatAnnulus: - New routine attachLST() to help with Seifert fibred spaces. Class NSFSpace: - Updated reduce() to make the best possible decisions on whether to reflect all fibres in cases where this is possible (previously it made faster decisions but occasionally missed some more subtle reductions). - Enhanced construct() to support the triangulation of all Seifert fibred spaces over the 2-sphere without punctures or reflector boundaries. Class NTetrahedron: - New routine orientation() for tracking orientation. Class NTorusBundle: - Greatly improved monodromy matrix reduction, to the point where equivalent torus bundles should give equal matrices. - Fixed a bug in the matrix reduction for non-symmetric matrices in non-orientable manifolds (sometimes the transpose matrix was obtained instead of the correct matrix). Class NTriangulation: - New routine layerOn() for performing layerings. - New routine dehydrate() for extracting Callahan-Hildebrand-Weeks dehydration strings. - Optimised the skeletal calculations, which now run *much* faster. - Renamed getEulerCharacteristic() to getEulerCharTri(), since for ideal triangulations this differs from the Euler characteristic of the corresponding compact manifold. The old name is kept as an alias but is now deprecated. - Added a new routine getEulerCharManifold(), which *does* calculate the Euler characteristic of the corresponding compact manifold. - Fixed a crash in splitIntoComponents() that occurred when the triangulation skeleton had not yet been calculated. Class NVertexEnumerator: - Removed. This abstract class existed to support multiple vertex enumeration algorithms, but in reality we're only using double descriptor anyway. The virtual template member functions caused problems with g++-4.2, which was the final push. NDoubleDescriptor is now a standalone class. Routine smithNormalForm(): - New five-argument version that not only calculates the Smith normal form but also returns appropriate change of basis matrices; thanks to Ryan Budney. Routines columnEchelonForm() and preImageOfLattice(): - New routines for working with matrices and homomorphisms; thanks again to Ryan Bydney. Routines factorise(), primesUpTo(): - Deprecated in favour of routines from the new NPrimes class. Routine clonePtr(): - New routine to assist copy constructors for classes that compute data on demand. USER INTERFACE: - New Algebra -> Cellular Info tab containing a variety of new homological data for triangulations; thanks to Ryan Budney. - Graphs now look better when drawn using an old Graphviz 1.x (previously the graphs were only tested under Graphviz 2.x). - Better infrastructure for determining the status of the current Graphviz installation. For version 1.x, Regina now insists on using dot, since the old neato 1.x cannot handle multiple edges. - Removed the Crush column from normal surface lists, since it has never contained any information beyond "N/A" or "Unknown". - The Regina reference manual is now called the Regina handbook, for consistency with other KDE applications. PYTHON: - The regina-python tool has new options -i/--interactive (run a script and leave the interpreter open) and -n/--nolibs (do not load any of the normal user libraries). UTILITIES: trisetcmp: - Now outputs more appropriate messages when subcomplex testing (previously the same messages were used for both subcomplex testing and isomorphism testing). TEST SUITE: - Added a new test suite for python bindings, in addition to the usual C++ test suite that is already present. - Added tests for the NPerm class. - Added tests for vertex link calculations. - Added tests for the orientable double cover of a triangulation. - Added tests for the new NPrimes class. - Added division algorithm tests for NLargeInteger. - Added tests for the new NHomologicalData class. - Added tests for triangulation dehydrations and rehydrations. - Initial work on tests for the NRational class. BUILD ENVIRONMENT: - Updated libtool from version 1.5a to 1.5.22 with Debian patches (required for MacOS support). Version 4.3.1 [ 5 May, 2006 ] ENGINE: Class NClosedPrimeMinSearcher: - Improved speed by adding additional face pairing graph tests; see math.GT/0604584 for details. - Made a very slight improvement in speed by testing for extremely high degree edges. - Fixed memory leak (the destructor was not deallocating some internal arrays). - Minor changes to the behaviour of mergeEdgeClasses() (might return only some flags instead of all flags). Class NFacePairing: - New constructor for building the face pairing of an existing triangulation. - New routines hasOneEndedChainWithStrayBigon() and hasTripleOneEndedChain() for testing for more types of graphs that cannot appear in a closed census. - New routines hasSingleStar(), hasDoubleStar() and hasDoubleSquare() for investigating larger face pairing graphs. - New routines writeDot() and writeDotHeader() to assist with graph visualisation. Routine readOrb(): - New routine for importing Orb / Casson triangulations; thanks to Ryan Budney for contributing this import filter. Routine readSnapPea(): - Verifies that the first line of the file is "% Triangulation", instead of simply testing for the '%' and ignoring the rest. USER INTERFACE: - Now displays face pairing graphs in the triangulation viewer, using Graphviz for the rendering (see the Skeleton tab). This required splitting the main Skeleton tab into two smaller child tabs. - New configuration options for the Graphviz executable and the default Skeleton child tab. - No longer crashes when attempting to clone the root packet (it simply displays an error message instead). - Added a workaround for the icon problems that arise when using GNU/Linux distributions with buggy icon themes. - Added 48x48 and 64x64 icons for Regina and its data files. TEST SUITE: - New tests for recognising bad and otherwise interesting subgraphs within face pairing graphs. Version 4.3 [ 27 March, 2006 ] OVERALL: - Expanded the closed non-orientable census to 10 tetrahedra. - Expanded the closed orientable census to 10 tetrahedra, and split it into two data files (large and small). - Updated postal address for the Free Software Foundation. - Bibliographic updates for the reference manual. - Fixed some harmless compiler warnings, and tightened syntax to adhere to the requirements of gcc 4.1. ENGINE: Class LessDeref: - New utility class for working with pointers in the Standard Template Library. Class NGluingPerms, NGluingPermSearcher, NClosedPrimeMinSearcher: - Significant overhaul. - Moved gluing permutation search routines into new classes NGluingPermSearcher and NClosedPrimeMinSearcher. - Supports partial depth-based searching (by passing a non-negative depth parameter to the new runSearch() routine). - Tracks both vertex and edge links using a modified union find structure to prune more braches of the search tree where possible. This makes an incredible difference to the census running time. Pruning takes place on non-orientable vertex links, too many or too few vertices or edges, low degree or invalid edges, conical faces, and L(3,1) spines. Class NGraphLoop, NGraphPair, NGraphTriple: - New families of graph manifolds. Class NKnot: - Removed this unwritten placeholder class. Class NLayering: - New class to help follow through layerings of tetrahedra within a triangulation. Class NLayeredSolidTorus: - New routine formsLayeredSolidTorusTop() for finding an LST from the top end instead of the bottom. - New routine transform() for following through an isomorphism. Class NLayeredTorusBundle, NTxICore, NTxIDiagonalCore, NTxIParallelCore: - Added for recognition of layered surface bundles. Class NListOnCall: - New class for expensive and rarely used hard-coded lists. Class NManifold: - Routine writeTeXName() no longer provides wrapping dollar signs. - New operator < for ordering manifolds deterministically. Class NMatrix2: - New specialised class for working with 2-by-2 integer matrices. Class NPacket, NPacketListener: - New NPacket routine sortChildren(). - Packet listeners are now unregistered immediately *before* packetToBeDestroyed() is called. This avoids unpleasantries when a listener tries to unregister itself during this call. Class NPerm: - The assignment operator now returns a reference instead of void. Class NSatAnnulus, NSatBlock, NSatRegion, NBlockedSFS, NBlockedSFSLoop, NBlockedSFSPair, NBlockedSFSTriple, NPluggedTorusBundle, plus subclasses and other support structures: - New classes for recognising and describing Seifert fibred spaces and other graph manifolds that are built using saturated blocks. Class NSFS, NExceptionalFibre: - Removed; see below. Class NSFSpace, NSFSFibre: - Complete overhaul of the Seifert fibred space classes. - Now more general, supporting both orientable and non-orientable 3-manifolds as well as base orbifolds with reflector boundary components. - Classes have been renamed from the old NSFS / NExceptionalFibre to make it clear that large-scale changes have taken place. Class NSFSAltSet: - New class for finding alternative simple representations of the same bounded Seifert fibred space. Class NSnapPeaTriangulation: - Added a boolean argument to the constructor that permits the SnapPea kernel to work with closed triangulations if the user really wants to allow it. Class NStandardTri: - Routine writeTeXName() no longer provides wrapping dollar signs. Class NTorusBundle: - New class of 3-manifolds describing torus bundles over the circle. Class NTriangulation: - Combined isomorphism and subcomplex testing routines into a single all-in-one routine to avoid excessive code reuse. - Added new subcomplex testing routine findAllSubcomplexesIn(), in which all matches (not just the first) are returned. - Routines getTetrahedronIndex(), getComponentIndex(), getBoundaryComponentIndex(), getFaceIndex(), getEdgeIndex() and getVertexIndex() now returned signed instead of unsigned longs, so that -1 can be returned if the object could not be found. USER INTERFACE: - Added a configuration option that allows the SnapPea kernel to work with closed triangulations. - Fixed the crash when deleting a triangulation that is currently the target of an isomorphism/subcomplex test. - Fixed a crash that sometimes occurs in large files when deleting a triangulation that is currently being viewed. - Fixed extremely slow updates in the triangulation composition tab for very large data files. - Text and script packets now open with the cursor at the top instead of the bottom. PYTHON: Class NSnapPeaTriangulation: - Offers the additional zero-argument routine volumeWithPrecision() as a way of returning the precision of the volume calculation. UTILITIES: tricensus-mpi: - Significant overhaul. - Now supports finer-grained subsearches via --depth. - Better logging. - Only writes .rga data files for cases in which at least one triangulation was found. - New option --dryrun for a quick overview of the search space. tricensus-mpi-status: - New tool for parsing tricensus-mpi logs. trisetcmp: - Support subcomplex testing as well as isomorphism testing. TEST SUITE: - Don't enforce precision limits for degenerate snappea volume testing, to allow for flexibility in floating point behaviours of different chipsets. - Added tests for NIsomorphism. Version 4.2.1 [ 18 September, 2005 ] OVERALL: - Added a chapter on imports and exports to the reference manual. - Expanded the closed non-orientable census to eight tetrahedra. ENGINE: Overall structure: - Fixed "regina-engine-config --cflags", which wrote includes for Regina's dependencies but not for Regina itself (sigh). Class NIsomorphism: - New routine random() for generating random isomorphisms. - New routine apply() for permuting the tetrahedra and vertices/faces of an existing triangulation. - New routine isIdentity() for testing for identity isomorphisms. Routine writeSnapPea(): - Add a precondition that the triangulation has no boundary faces. USER INTERFACE: - Refuse to export a triangulation to SnapPea format if it has boundary faces. UTILITIES: trisetcmp: - New utility for comparing two different sets of triangulations. Version 4.2 [ 7 July, 2005 ] ENGINE: Overall structure: - Included portions of the SnapPea kernel! Thanks again to Jeff Weeks for his support. - Added a regina-engine-config script to make it easier to build Regina's calculation engine into other applications. Class NExampleTriangulation: - Added to facilitate construction of several different ready-made sample triangulations. Class NFacePairing: - New routine hasWedgedDoubleEndedChain. Class NLayeredSolidTorus: - New routine isLayeredSolidTorus for classifying an entire triangulation component. Class NPacketListener: - Added an extra boolean argument to childWasRemoved() to indicate the situation in which the parent is also being destroyed. Class NSnapPeaCensusManifold, NSnapPeaCensusTri: - Added to aid recognition of very small SnapPea census triangulations. Class NSnapPeaTriangulation: - Added to give Regina triangulations access to the SnapPea kernel. Class NTriangulation: - New routine finiteToIdeal() for extending a triangulation. - New routines insertConstruction() and dumpConstruction() to make it easier to hard-code triangulations in source code. - Fixed vertex link calculations, which were previously incorrect if a triangulation contained invalid edges. - Fixed bug in twoZeroMove() which caused a crash in some cases involving triangulations with boundary. Class NTrivialTri: - Added recognition of one-tetrahedron balls. USER INTERFACE: - Include several example triangulations in the triangulation creation dialog. - Allow exporting a triangulation to C++ source. - Renamed "Ideal to Finite" as "Truncate Ideal Vertices" in the menu. - Include a padlock in the corner of a packet icon if the packet is uneditable. - Tighter thread safety in the GUI. This is required because some calculations (such as surface enumeration) run in a separate thread. - Worked around a Qt bug that caused a crash when pressing a key in a table of normal surfaces or matching equations. - Fixed a bug in which GAP output was unparseable due to GAP inserting spaces where Regina was not expecting them. UTILITIES: tricensus-mpi: - New census manager for use on MPI-enabled clusters. TEST SUITE: - Added tests for SnapPea calculations. - Further additions to the triangulation tests, in particular involving invalid and non-standard triangulations. - Beginning of a series of tests for elementary moves. BUILD ENVIRONMENT: - Updated libtool to version 1.5a. Hopefully this will make things better for Darwin/Fink. - Requires KDE >= 3.2, so that the XDG applications directory can be used for the desktop file. - Verifies in the configure script that shared libraries are enabled where necessary (i.e., in the KDE and Python interfaces). - Better magic in the configure script for finding the correct boost.python libraries. - Fixed a bug in the configure script whereby -g stripping was too agressive, resulting in a compile failure for the python interface under some environments. Version 4.1.3 [ 25 July, 2004 ] OVERALL: - Included the closed hyperbolic census of Hodgson and Weeks. - Made explicit in the reference manual introduction where the example files can be found. PYTHON: - For most objects, == now works like C++ pointer equality instead of Python object equality. That is, it tests whether the Python wrappers point to the same C++ object, not whether the Python wrappers are in fact the same wrapper. - Fixed scripting in the GUI, which was broken with python 2.3 (indented blocks were treated as complete after just one line). - Added a sample python session illustrating progress reporting. USER INTERFACE: - Added an "Open Example" menu item for easy access to the sample files. - Allow the choice of text editor component to be configured. - Several fixes to make Regina work properly with the vimpart, including work-arounds for bugs in the vimpart itself. - Fixed the massive resource drain while editing a script's variable table in a heavily populated data file. - Fixed crashes that occured when deleting packets while a drop-down packet chooser is in use elsewhere. - Tightened up the handling of read-only mode for internal components. Also removed some loopholes that allowed editing of uneditable packets. - Improved handling of keyboard focus. - Changed "Python Reference" to point to the modules index instead of the title page. Version 4.1.2 [ 14 June, 2004 ] OVERALL: - Updated configure scripts so that the python interface builds out of the box on a larger number of platforms (specifically Red Hat and Fedora Core are now supported). Many thanks to Craig Macintyre for his patience and assistance with this. - More updates to the troubleshooting section; overhauled the README.txt and website to hopefully make everything clearer and the important information easier to find. - Added a suggested form for citing Regina. - Updated INSTALL.txt to reflect current --prefix guessing. ENGINE: Class NTriangulation: - Added simplifiedFundamentalGroup() to allow external bodies such as GAP to simplify group presentations. USER INTERFACE: - Allow users to simplify fundamental groups using GAP. - Added "Education" to the categories for the desktop file, since KDE seems adamant about having no separate maths/science menu. Anything is better than showing up in Lost & Found. :) - Fixed compile error when building against an STL-enabled Qt (thanks to Robert Myers for spotting this one). Version 4.1.1 [ 24 April, 2004 ] USER INTERFACE: - Fixed compile error when building against Python 2.3. - Added "What's This?" button to main/packet window decorations. Version 4.1 [ 7 March, 2004 ] OVERALL: - Further enhancements to the reference manual, including more detailed explanations in the main body as well as a new index. ENGINE: Class NNormalSurface: - New routine doubleSurface(). - Added findNonTrivialSphere() and findVtxOctAlmostNormalSphere() to support 0-efficiency algorithms. Class NProgress: - Added timing utilities getRealTime() and totalCPUTime(). Class NTriangulation: - New 0-efficiency / decomposition routines splitIntoComponents(), connectedSumDecomposition(), isThreeSphere(), knowsThreeSphere() and makeZeroEfficient(). - New Seifert fibred space constructions insertAugTriSolidTorus() and insertSFSOverSphere(). USER INTERFACE: - Actions specific to each packet type now appear in their own context-specific menus, i.e., a "Triangulation" menu appears when a triangulation is open, etc. - Added "Please Wait" dialogs during slow operations. - Thorough "What's This?" support and tooltips offered across the entire user interface. - New tip-of-the-day support. - More icons for triangulation actions. - Updated the .desktop file and mimetype tests to work correctly with KDE 3.2. UTILITIES: tricensus: - Fixed bug in which --genpairs created empty output files. TEST SUITE: - Added tests for connected sum decomposition. Version 4.0.1 [ 26 January, 2004 ] OVERALL: - The ./configure script now takes a guess at the correct --prefix, runs sanity tests upon it and insists upon Qt >= 3.2. - Regina now ships with pregenerated manpages to avoid the need for docbook-utils and its complicated dependencies. - The troubleshooting section of the reference manual now includes compile-time problems and discusses the test suite. USER INTERFACE: Class GridListViewItem: - Added to centralise support for list views with grids. - Fixed a compile error with Qt versions 3.1 and earlier. Version 4.0 [ 20 December, 2003 ] ENGINE: Class NAngleStructureList: - Enumeration routine now takes an optional progress manager and can run in a separate thread. Class NGroupPresentation: - Improved simplification of group presentations. Class NNormalSurface: - Using NProperty to store calculable properties. - Using NTriBool instead of 1/-1/0 for orientability, two-sidedness and connectedness. Class NNormalSurfaceList: - Enumeration routine now takes an optional progress manager and can run in a separate thread. Class NProgress: - Removed isCancellable() since this is not really necessary; an operation may simply choose not to poll for cancellation requests. - Removed isChanged() and made the changed flag protected so subclasses can modify it directly. - Requires subclasses to adjust the changed flag on all public access/update routines. - Made cancel() const so that reading threads can use it. Class NProgressNumber: - New convenience routine incCompleted(). - New lookup routine getNumericState(). Class NTriBool: - Added for representing three-way booleans. Class NVectorMatrix, NVectorUnit: - Modification routines throw exceptions if called. USER INTERFACE: Class NAngleStructureCreator: - Displays progress and allows cancellation. Class NNormalSurfaceCreator: - Displays progress and allows cancellation. Class ProgressDialogNumeric: - Added for displaying progress using regina::NProgressNumber. Class PythonConsole: - Added Help menu for displaying scripting documentation. Class ReginaMain: - Added Python reference to Help menu. Class PythonManager: - New static routine openPythonReference() for displaying calculation engine documentation. TEST SUITE: - Added tests for angle structure enumeration and analysis. - Expanded normal surface tests to include trivial triangulations. - Added tests for fundamental group calculation and recognition. Version 3.97 [ 24 November, 2003 ] - Final prerelease for version 4.0. OVERALL: - Ships with the 7-tetrahedron closed non-orientable census. - Compile-time configuration uses different tests for pthread, since the old tests were broken on some systems. - Added a Python caveats section to the reference manual. ENGINE: Class NEdge, NVertex: - Added getDegree() as an alias for getNumberOfEmbeddings(). Class NGluingPerms: - Incorporate new results that allow us to discard more face pairings in a non-orientable census (see math.GT/0307382:v2). Class NLayeredSolidTorus: - Added routine flatten() to flatten a layered solid torus to a Mobius band. Class NMutex::MutexLock: - Added reference constructor as well as a pointer constructor. Class NNormalSurface, NNormalSurfaceVector: - Added routine isCentral() to test for central surfaces. Class NSimpleSurfaceBundle, NTrivialTri: - Added for recognition of trivial non-orientable triangulations. USER INTERFACE: Overall structure: - Split out common shell/part material into the separate library libregina-kdecommon. - Integrated python scripting into the graphical user interface. This is contained within libregina-kdecommon and is accessible through the main menu/toolbar and through the script editor. - Avoid using flat buttons where possible. Class ExportDialog: - New routine validate() to detect when there are no packets suitable for export. Class ExtTabCtl, PacketTabbedUI, PacketTabbedViewerTab: - Allow changing the current tab (this required a new extension class to KTabCtl). Class ImportDialog, NewPacketDialog: - New routine validate() to detect when there are no suitable parent packets. Class NNormalSurfaceCreator: - Allow the default coordinate system to be configured. Class NScriptUI, NTextUI: - Fixed problems with word wrapping and line endings in the embedded text editor. Class NTriangulationUI, NTriAlgebraUI: - Allow the initially visible tabs to be configured. Class NTriGluingsUI: - Implemented census lookup for triangulations. - Fixed a bug in the updating of tetrahedron labels when other tetrahedra are removed from a triangulation. Class PacketChooser: - New routine hasPackets() to detect empty packet choosers. Class ReginaPart: - Make File/Save fall back to File/Save-As for new files. - Make File/Save-As respect the automatic file extension setting and also check whether the selected file already exists. Class ReginaPreferences, ReginaPrefSet: - Many new configuration options. In addition to those mentioned above, census data files and Python options can also be configured. TEST SUITE: - Added tests for normal surface enumeration and analysis. Version 3.96 [ 31 October, 2003 ] - Second prerelease for version 4.0. OVERALL: - Added surface filter documentation to the reference manual, which brings it completely up to date with the GUI. ENGINE: Overall structure: - Yet more routines made const. Class NAbelianGroup: - Added global comparisons isTrivial() and operator ==. Class NMutex: - Now uses inner class MutexLock for locking and unlocking. Class NProperty, NPropertyBase, StoreValue, StoreConstPtr, StoreManagedPtr: - New classes for management of calculable object properties. Class NPropertyHolder: - Moved most of its functionality directly into NFile and replaced what was left with the new class NFilePropertyReader. Class NTriangulation: - Turaev-Viro invariants are now cached; this includes a new routine allCalculatedTuraevViro(). Class ShareableObject: - Now derives from regina::boost::noncopyable. USER INTERFACE: Class NTriAlgebraUI: - Redesigned the algebra viewer to make it easier to read. - Incorporatd Turaev-Viro invariants into the UI. Class NTriCompositionUI: - Incorporated isomorphism / subcomplex testing into the UI. - Fixed crash when refreshing. Class PacketTabbedViewerTab: - Added to support tabbed UIs within tabbed UIs. Class ReginaPart: - Make the main window splitter remember its place when packet panes are changed. TEST SUITE: - Added tests for trivial triangulations and property handling. Version 3.95 [ 12 October, 2003 ] - Prelease for version 4.0. GRAPHICAL USER INTERFACE: - Rewrote the entire user interface in C++ using the KDE libraries. The result is much faster, cleaner and easier to maintain. The old Java user interface is gone! The user interface can be started by running "regina-kde". PYTHON: - Python scripting rewritten to use standard Python, not Jython. A python session can be started by running "regina-python". - The Python API has changed to be much more faithful to the C++ calculation engine, especially with respect to global and static routines and constants. - All classes now sit directly within the module regina. ENGINE: Overall structure: - Fixed minor memory leaks. - Made more routines const. - Beginning to incorporate std::auto_ptr. Class Engine: - Removed since this is no longer necessary with the new python bindings. Class NAngleStructureList: - Made the enumerating constructor private and added the public replacement enumerate(). Class NAugTriSolidTorus: - Changed to fit into the new NStandardTriangulation structure. Class NFacePairings: - Fixed bug in isCanonical(). Class NGluingPerms: - Further optimisations for non-orientable census generation. Class NGroupPresentation: - Slightly improved group recognition. Class NHandlebody: - Added as a new 3-manifold class. Class NIsomorphism: - Allows an isomorphism with 0 tetrahedra. - Now derives from ShareableObject. - Supports boundary incomplete isomorphisms as well as complete isomorphisms. - Changed return types of const lookup routines from const T& to just T. Class NL31Pillow: - New class for identifying particular L(3,1) triangulations. Class NLargeInteger: - Added constructor and assignment taking a const std::string&. - Routine stringValue() now returns a std::string, not a char*. Class NLensSpace: - Changed to fit into the new NManifold structure. Class NLayeredChain: - Changed to fit into the new NStandardTriangulation structure. Class NLayeredLensSpace: - Changed to fit into the new NStandardTriangulation structure. Class NLayeredLoop: - Changed to fit into the new NStandardTriangulation structure. Class NLayeredSolidTorus: - Changed to fit into the new NStandardTriangulation structure. - Renamed isLayeredSolidTorusBase() to formsLayeredSolidTorusBase(). Class NManifold: - New class to represent a 3-manifold irrespective of its triangulation. Class NNormalSurfaceList: - Made the enumerating constructor private and added the public replacement enumerate(). Class NPacket, NPacketListener: - The NPacket destructor now orphans the packet if this has not already been done. - Added event listening for packets, including new class NPacketListener, new routines NPacket::listen(), NPacket::isListening() and NPacket::unlisten() and new class NPacket::ChangeEventBlock. - New NPacket routines moveUp(), moveDown(), moveToFirst() and moveToLast(). - Reclassified member variables from protected to private. Class NPerm: - Routines edgeDescription() and faceDescription() implemented in the calculation engine. Class NPlugTriSolidTorus: - Changed to fit into the new NStandardTriangulation structure. Class NSFS: - Changed to fit into the new NManifold structure. - New overloaded routine insertFibre(long, long). - No longer allows illegal (0,k) fibres to be added. - More common names recognised. Class NSnappedBall: - Changed to fit into the new NStandardTriangulation structure. - Renamed isSnappedBall() to formsSnappedBall(). Class NSpiralSolidTorus: - Changed to fit into the new NStandardTriangulation structure. - Renamed isSpiralSolidTorus() to formsSpiralSolidTorus(). Class NStandardTriangulation: - New class to represent a standard triangulation. Class NTriSolidTorus: - Changed to fit into the new NStandardTriangulation structure. - Renamed isTriSolidTorus() to formsTriSolidTorus(). Class NTriangulation: - New routine turaevViro() to calculate Turaev-Viro invariants. - Changed isIsomorphicTo() to return an entire isomorphism, not just whether an isomorphism exists. - New routine isContainedIn() to test for boundary incomplete isomorphisms. Routine isKnownSFS(): - Removed in favour of NStandardTriangulation::isStandardTriangulation(). File dehydration.h: - New routine readDehydrationList(). File nsnappea.h: - Renamed to snappea.h. File stringutils.h: - Added routines startsWith() and stripWhitespace(). Version 3.2 [ 22 June, 2003 ] - The post-thesis release! OVERALL: - Added file format documentation to reference manual. - Calculation engine test suite is much enhanced. - Closed orientable census, closed non-orientable census and splitting surface signature census added to example files. - PhD thesis submitted on 30 May, 2003! ENGINE: Overall structure: - Using C++-style casts instead of C-style casts. - Signedness of chars is explicitly specified where it matters. Class NBoolSet: - Using unsigned chars for byte codes. Class NCensus: - New constant PRUNE_P2_REDUCIBLE. Class NFacePair: - New class for working with pairs of face numbers. Class NFacePairing: - Added convenience operator []. - Added hasTripleEdge(), hasBrokenDoubleEndedChain(), hasOneEndedChainWithDoubleHandle() and associated routines. - Renamed private routine isCanonical() to isCanonicalInternal(), added public routine isCanonical() with no preconditions. Class NGluingPerms: - Uses new NFacePairing routines to identify certain situations in which no solutions are possible. - Uses a completely redesigned algorithm in the closed prime minimal P2-irreducible case. - Added pruning during permutation generation to eliminate edges identified with themselves in reverse. - Added pruning using low-degree edges in the non-orientable P2-irreducible case. - Allows a null automorphism list in findAllPerms(). Class NLayeredLoop: - Renamed getIndex() to getLength(). Class NLayeredSolidTorus: - Fixed a bug in isLayeredSolidTorusBase() that generated false positives in ideal triangulations. Class NNormalSurface: - Added routine knownCanCrush(). Currently this routine is next to useless; it is expected to be enhanced with future releases. - Property queries are now const since internal cached properties are declared mutable. Class NPerm: - Using unsigned chars for permutation codes. Class NPrismSpec, NPrismSetSurface: - New classes for dealing with triangular prisms defined by slicing along normal quads in a tetrahedron. Currently these classes do very little. Class NSFS: - Better recognition of common names; now recognises all spaces with finite fundamental group. Class NTetFace: - Added routine setFirst(). - Added copy constructor. - Operators ++ and -- are now implemented in all forms (++x, x++, --x, x--) and all have return values. Class NTriangulation: - New routine insertLayeredLoop(). - 0-efficiency testing is done in quad space where possible. - Fixed a bug in which getHomologyH1Bdry() gave incorrect answers if the skeleton had not already been calculated. - New boundary queries hasTwoSphereBoundaryComponents() and hasNegativeIdealBoundaryComponents(). - Renamed insertLensSpace() to insertLayeredLensSpace(). - Interface-only skeletal query routines are now also implemented in the C++ calculation engine. Routine prior(), next(): - Copied from the Boost C++ libraries for easy access to prior and following iterators. JAVA USER INTERFACE: Overall structure: - Incorporated engine enhancements listed above. UTILITIES: regconcat: - New utility for combining several data files. tricensus: - New option --minprimep2 for P2-irreducibility. - Explicitly verifies that all face pairings supplied on standard input are in canonical form. Version 3.1 [ 18 October, 2002 ] OVERALL: - Added calculation engine test suite. - Environment variables now take precedence over configuration files when running the startup script. - Build process now uses standard autoconf/automake structure. ENGINE: Overall structure: - Added support for multiple vertex enumeration engines. Class NCensus: - Redesigned to do its work through classes NFacePairing and NGluingPerms. - Added support for arbitrary criterion functions. - Added support for splitting a census into pieces. Class NCompConstraint, NCompConstraintSet: - Added. Class NConeRay: - Removed in favour of new class NRay. Class NFacePairing, NGluingPerms: - Added to bear the brunt of census generation. - Massive optimisations and rewrites throughout census code. - Removed all thread yields, which were causing processes to have 0.0% CPU time on some systems. Class NIndexedArray: - Added routine validate(). Class NKnot: - New (but incomplete) knot/link class. Class NLensSpace, NSFS: - Added getCommonName(). Class NNormalSurfaceVector: - Replaced isCompatibleWith() with makeEmbeddedConstraints(). Class NPerm: - Routine isIdentity() now implemented in C++ engine. - New routine setPerm(int, int). Class NTriangulation: - Fixed idealToFinite() which was newly broken in Regina 3.0. - Routine intelligentSimplify() now tries random 4-4 moves. - New routine collapseEdge(). - Routine simplifyToLocalMinimum() now make boundary moves last and does not do book opening at all. - Fixed bug in 2-3, 3-2 and 4-4 moves that appears when faces of the old tetrahedra are glued to each other. Class NRay: - Added to replace old class NConeRay. Class NVector: - Added negate(). Class NVertexEnumerator, NDoubleDescriptor: - Added to bear the brunt of normal surface enumeration and to allow different enumeration engines to be plugged in. - Caches some frequent calculations, resulting in a startling performance increase. Routine getVersionString(), getVersionMajor(), getVersionMinor(), testEngine(): - Moved to engine.h and added to the C++ calculation engine. Routine reducedMod(): - Fixed bug in the midpoint case. File nfile.h: - New file format constants to replace the constants removed with regina.h. File nhashmap.h, nhashset.h: - Added to deal with differing STL extension installations. File nknownmanifold.h: - Added global 3-manifold recognition routines. File nperm.h: - New arrays allPermsS2Inv, allPermsS3Inv, allPermsS4Inv. File regina.h: - Removed along with the constants it defined. JAVA USER INTERFACE: normal.algorithm.Algorithm: - Fixed bug in which isPacketEditable() was enforced even for non-modifying algorithms. normal.console.JPythonPacketConsole: - Optionally creates an additional Jython variable for direct access to some preselected packet within the tree. normal.mainui.NormalFrame: - New Jython consoles create an additional variable for the packet currently selected in the visual tree. normal.packetui.surfaces.NSFPropertiesEditor: - Don't enforce Euler characteristic <= 2 (singular surfaces can give other values). UTILITIES: tricensus: - Completely redesigned interface (now command-line based). - Supports splitting a census into pieces. tricensus-manager: - New utility for distributing a census amongst several machines. Version 3.0 [ 28 June, 2002 ] - The "XML, about bloody time" release. OVERALL: - Moved from old impenetrable binary data files to new compressed XML data files. - Introduced various command-line utilities (see UTILITIES below). - Removed CORBA engine/interface. It was too much hassle to maintain, and with Regina building on more platforms it has lots much of its usefulness. - JNI engine no longer requires autogenerated JNI headers. - Reference manual much enhanced. ENGINE: Overall structure: - Moved entire calculation engine into namespace regina. - The Great STL Port: replaced hand-rolled container classes with Standard Template Library classes. - Added numerous XML-related routines and classes. - Modified to also build under gcc3. - Removed configuration macro __MUTE_WARNINGS. - Replaced configuration macros __NO_IOS_NOCREATE and __NO_RAW_CASTING with their negations __USE_IOS_NOCREATE and __USE_RAW_CASTING which are optional in all situations. - Added configuration macros __HASH_NAMESPACE and __NO_NAMESPACE_ALIASES. Class NBoolVector, NDoubleList, NDynamicArray, NHashSet, NInfiniteArray, NIntMap, NOrderedPair, NPointerSet, NQueue, NSet, NStack, NString, NStringPair and associates: - All removed in favour of Standard Template Library classes. Class NAngleStructureList, NNormalSurfaceList: - New nested classes StructureInserter / SurfaceInserter. Class NAugTriSolidTorus, NTriSolidTorus: - Supports multiple ways of attaching layered chains. Class NFileInfo: - Added. Class NGroupExpressionTerm: - Changed from simple ordered pair to its own full class. Class NIndexedArray: - Added. Class NJNIEnumeration: - Added to aid the Java-C++ link. Class NLargeInteger: - Added third error-detection parameter to constructor NLargeInteger(const char*, int). Class NLayeredChainPair, NPlugTriSolidTorus: - Added for further subcomplex recognition. Class NLayeredLoop: - Added routine getSeifertStructure(). Class NLensSpace: - Fixed bug causing reductions to be sometimes non-optimal (although still correct) - see modularInverse() notes below. Class NLocalFileResource: - Replaced static members MODE_READ, MODE_WRITE with static routines sysModeRead(), sysModeWrite(). Class NNormalSurface: - Added routines isVertexLink() and isThinEdgeLink(). Class NPacket: - Added routines to support arbitrary packet tags. - Finally changed getPacketName() to getPacketTypeName(). - Removed tidyReadPacket(). Class NPerm: - Added isPermCode(). Class NScript: - Changed list of variables to a proper map and removed routines getVariableIndex() and removeVariableAt(). - Insistance on unique variable names; in enforcing this routine addVariable() now returns bool. Class NSFS: - Added routine getHomologyH1(). Class NSignature, NSigCensus, NSigPartialIsomorphism: - Added to deal with splitting surface signatures. Class NTriangulation: - Uses NIndexedArrays for skeletal elements for fast index lookup. File boostutils.h: - Added utility classes from the Boost C++ libraries. File hashutils.h: - Added various hash functions. File memutils.h: - Added allocation/deallocation functions. File stlutils.h: - Added extension Standard Template Library utility classes. File stringutils.h: - Added miscellaneous string manipulation routines. File zstream.h: - Added compressing and decompressing I/O streams. Routine modularInverse(): - Fixed a rather nasty bug in gcdWithCoeffsInternal() that caused modularInverse() to sometimes give wrong answers. Routine readFileMagic(): - Added to provide a format-independent file reader. JAVA USER INTERFACE: Overall structure: - Incorporated engine enhancements listed above. normal.console: - Added class JPythonPacketConsole. normal.engine.implementation.jni.JNIShareableObject: - Added static method sameCppPtr() which is necessary with gcc3. normal.exports: - Now exports to three different Regina data file formats. normal.mainui: - File information dialog is somewhat more informative. normal.mainui.NormalFrame: - Now supports opening a Jython console linked to a packet tree. normal.mainui.PacketPane: - Renamed getUI() to getPacketUI() to avoid clashing with j2sdk1.4. normal.options.NormalOptionSet: - Default "Display Icon" option changed from true to false. normal.packetui: - Resizing one coordinate column in various coordinate viewers now resizes all coordinate columns. UTILITIES: regconvert, regfiledump, regfiletype, sigcensus, tricensus: - Added. Version 2.4 [ 4 April, 2002 ] OVERALL: - Much enhanced documentation. - Added SnapPea census and knot/link census to examples. - Added functionality changelog (HIGHLIGHTS.txt). - Added prepackaged Jython library directory. ENGINE: Overall structure: - Moved engine/imports to engine/foreign. Class NAngleStructure, NAngleStructureList, NAngleStructureVector: - Added. Class NConeRay: - Now a new class of its own accord, derived from NVectorDense and created to allow vertex solution routines to work in contexts outside normal surfaces. Class NNormalSurface: - Added getName() and setName(). Class NNormalSurfaceVector: - Now derives from NConeRay, to which some routines have moved. - Changed declaration of createNonNegativeCone() to return cone faces as well as extremal rays. Class NPerm: - Added toString(). Class NTriangulation: - Uses fewest possible tetrahedra in insertLensSpace(). - Added insertRehydration(), makeDoubleCover(). File nperm.h: - Added constant arrays orderedPermsS4, orderedPermsS3. Routine intersectCone(): - Works with more generic cones by requiring cone faces to be passed as well as extremal rays. Routine writeSnapPea(): - Added. JAVA USER INTERFACE: Overall structure: - Added readline/editline support using Bablok's wrapper classes. - Converted option REGINA_JNIDIR to a list of directories. - Option REGINA_OPTIONS_GLOBAL defaults to REGINA_HOME if /etc/regina does not exist. normal.ApplicationShell.CommandLineArguments: - Added. normal.Shell: - Allow filenames to be specified on the command-line. - Removed some arguments from getParameter(). - Added getFileParameters(). normal.exports: - Added class SnapPeaExporter. normal.imports: - Added class DehydrationImporter. normal.mainui: - Added more keyboard accelerators. normal.mainui.FilePane: - Added getFileType() and setFileType(). normal.mainui.NormalFrame: - Added File->Info menu item. - Made various routines public so outsiders can manipulate the primary frame. normal.packetui.surfaces.CoordinateViewer: - Inserted editable surface name as first column. normal.packetui.triangulation.TriangulationCreator: - Creates triangulations from dehydration strings. Version 2.3 [ 12 December, 2001 ] - The "Farewell Stillwater" release. OVERALL: - Makefile.options variables IDLTOJAVACLIENT and IDLTOCPPSERVER became IDLTOJAVA and IDLTOCPP with slightly more generic meanings. ENGINE: Overall structure: - Modified #includes to treat engine/engine, engine/jni and engine/corba as top-level include directories. - Removed config.h in favour of PD_MACROS in Makefile.options. - Removed configuration macros __NO_INCLUDE_PATHS and __BINARY_IO. - Introduced macro MY_ENGINE_OBJECT for CORBA wrapper classes. - Macros GET_ENGINE_OBJECT no longer crash when null is passed. Class NAugTriSolidTorus, NLayeredChain, NLayeredLoop, NLensSpace, NSFS, NSpiralSolidTorus, NTriSolidTorus: - New classes. Class NDiscSetSurface: - Modified parameters for adjacentDisc(). Class NFace: - Added getType(), getSubtype(), isMobiusBand(), isCone(). Class NHashSet, NHashSetIterator: - New classes. Class NNormalSurface: - Added crush(). - Added isConnected(), isVertexLink(), isSplitting(). - Fixed isOrientable() and added isTwoSided(). Class NPacket: - Added makeUniqueLabels(). Class NPerm: - Added operators = and != and constructor NPerm(const NPerm&). Class NTriangulation: - Added isZeroEfficient(), hasSplittingSurface(), knowsZeroEfficient() and knowsSplittingSurface(). - Added extra condition to shellBoundary() covering two boundary faces plus two identified faces. - Added extra condition to twoZeroMove(NEdge*, ...) covering two boundary faces plus two identified faces. - Added fourFourMove(). File ndisc.h: - Added routine discOrientationFollowsEdge(). File nnormalsurface.h: - Added arrays triDiscArcs, quadDiscArcs, octDiscArcs. Routine hashMap(T*), hashMap(NString): - Added to support new class NHashSet. Routine readSnapPea(): - Sets the new packet label to the SnapPea manifold name. JAVA USER INTERFACE: Overall structure: - Loading/saving local files is now possible even through CORBA! - Incorporated engine enhancements listed above. - Added Jython operator overloads to a number of classes. normal.engine.utilities: - Added NLargeInteger to replace java.math.BigInteger so Jython scripts can use native mathematical operators with arbitrary precision integers. normal.exports: - Created architecture for exporting to foreign file formats. - Added classes Exporter, ReginaExporter, ScriptExporter. normal.images: - A couple of new icons. normal.imports: - Redesigned import architecture; replaced old class ImportFilePane with new class Importer. - Imported packets now have appropriate packet labels (such as their names in the imported files). - The file dialog is now brought up before anything else is done. - Added classes ReginaImporter, ScriptImporter. normal.mainui.TopologyPane: - Fixed bug where icon was sometimes not displayed. normal.packetui: - Added a new TopologyPane argument to some routines to allow interfaces to manipulate the visual packet tree. normal.packetui.triangulation.CompositionViewer: - Displays more detailed information. normal.packetui.triangulation.SkeletonTableFrame: - Displays more details regarding face/vertex type. Version 2.2 [ 7 October, 2001 ] OVERALL: - Documentation now DocBook-based; generates HTML and man pages. - Documentation now in docs/, not docs/normal/docs/. - Builds and runs under windows! - Added support for both global and local configuration files; configuration files are now called regina.conf. - Vastly reworked runtime scripts. - Tidied up Makefiles and CVS. ENGINE: Overall structure: - Ported CORBA stuff to omniORB3. Class NDoubleList, NDynamicArray: - Added operator = to the iterator classes. Class NEdge, NFace: - Added extra skeletal query routines. Class NGroupExpression, NGroupPresentation: - New classes. Class NLayeredLensSpace, NLayeredSolidTorus, NPillowTwoSphere, NSnappedBall, NSnappedTwoSphere: - New classes. Class NPacket: - Changed meaning of second (boolean) parameter to clone(). - Added getNumberOfDescendants() and getNumberOfChildren(); renamed totalTreeSize() to getTotalTreeSize(). Class NTriangulation: - Added fundamental group as a new property. - Added a two-zero move about a vertex. File numbertheory.h: - Added modularInverse(). JAVA USER INTERFACE: Overall structure: - Reads Regina options from directory $REGINA_OPTIONS_LOCAL and reads runtime options from system properties (which should be set by startup scripts). - Ported from JPython 1.1 to Jython 2.1-alpha1. - Now supports custom Jython libraries. - Improved support for mnemonics and keyboard accelerators. - Fixed tooltips in tables. normal.Application: - Moved fileExtension member into normal.mainui.FilePane subclasses. normal.Shell: - Dynamically finds JavaHelp classes so JavaHelp is not necessary for compilation. - Cleanly handles missing runtime options file. - Added a registry of all open Jython consoles. normal.algorithm: - Added class ElementaryMove. normal.console: - Major rearrangements; new class JPythonUtils now does most of the Jython work. normal.console.JPythonConsoleFrame: - Stores the root of the associated packet tree. - Allows saving the contents of the console to a file. normal.engine.implementation.corba.CORBAEngine: - Better error handling. normal.mainui: - Renamed SystemPane to TopologyPane and moved generic file editing functionality into FilePane to allow for editing different file types. - Much all-round cleaning up. normal.mainui.LibraryPane: - New class; allows editing of Jython libraries. normal.mainui.NormalFrame: - Moved console ownership routines into normal.Shell. - More appropriate enabling/disabling of menu items and buttons. - Edit menu hooks into current working file. normal.mainui.TopologyPane: - Closing a file closes all associated consoles. - Fixed the bug where double clicking on a tree item opens it three times. - Fixed bugs in the various fire... routines. - Improved interaction with the packet rename dialog. - Fixed the packet renaming bug in the visual tree display. normal.mainui.PacketTreeNode: - Replaced insertUnwrappedDescendants() with verifyDescendants() which actually does what it should; thus the refresh button now works properly. - Added findChildNodeIndex(). normal.packetui.PacketUI: - Added subtreeWasDeleted(). normal.packetui.packet.NContainerViewer: - Displays tree size statistics. normal.packetui.surfaces: - Coordinate tables in edge weight space now flag boundary edges. normal.packetui.surfaces.Coordinates: - Some routines now require the triangulation to be passed. normal.packetui.triangulation: - Pulled SkeletonTableFrame out into its own standalone class and turned it into a dialog. normal.packetui.triangulation.NTriangulationEditor: - Added triangulation component recognition. Version 2.1.1a [ 8 March, 2001 ] OVERALL: - Set up system for creating distributions. - Final tidying up for proper release. Version 2.1.1 OVERALL: - Added a CORBA interface to the engine. - Rearranged Makefiles and directory structure for CVS and SourceForge. ENGINE: Overall structure: - Split config.h into config.h and regina.h. Class Engine: - Formalised and slightly rearranged. - Added getVersion...() routines. Class NBoolSet: - Now passed as characters to and from external interfaces. Added getByteCode(), setByteCode() and fromByteCode() for this purpose. Class NNormalSurfaceList: - Added coordinate system FACE_ARCS; renumbered EDGE_WEIGHT. Class NPerm: - Now passed as characters to and from external interfaces. Class NTriangulation: - Added routine isStandard(). JAVA USER INTERFACE: Overall structure: - Allows running as an applet as well as an application. - Class normal.Application now contains just global constants and a generic applet/application shell. Class normal.Shell contains top-level Regina runtime routines and routines specific to the shell type (applet or application). - Made miscellaneous optimisations. normal.packetui.census.NCensusCreator: - Altered the boundary combo box strings to avoid misinterpretation. normal.packetui.surfaces.NSurfaceFilterEditor: - Fixed the bug in which changes in the filter were not always being immediately reflected in the surface list. Version 2.1.0 [ 18 December, 2000 ] (Initial public release.) Ben Burton (bab@debian.org) http://regina.sourceforge.net/ regina-4.95/cmake/modules/FindGMP.cmake000644 000765 000024 00000001507 12234011536 017565 0ustar00babstaff000000 000000 # Taken from the KDE4 cmake modules directory. - Ben Burton, 22 August 2011. # Try to find the GMP librairies # GMP_FOUND - system has GMP lib # GMP_INCLUDE_DIR - the GMP include directory # GMP_LIBRARIES - Libraries needed to use GMP # Copyright (c) 2006, Laurent Montel, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) # Already in cache, be silent set(GMP_FIND_QUIETLY TRUE) endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) find_path(GMP_INCLUDE_DIR NAMES gmp.h ) find_library(GMP_LIBRARIES NAMES gmp libgmp) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMP DEFAULT_MSG GMP_INCLUDE_DIR GMP_LIBRARIES) mark_as_advanced(GMP_INCLUDE_DIR GMP_LIBRARIES) regina-4.95/cmake/modules/FindGMPXX.cmake000644 000765 000024 00000001605 12234011536 020044 0ustar00babstaff000000 000000 # Modified from FindGMP.cmake. - Ben Burton, 11 November 2011. # Try to find the GMPXX librairies # GMPXX_FOUND - system has GMPXX lib # GMPXX_INCLUDE_DIR - the GMPXX include directory # GMPXX_LIBRARIES - Libraries needed to use GMPXX # The original FindGMP.cmake is copyright (c) 2006, Laurent Montel, # # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. if (GMPXX_INCLUDE_DIR AND GMPXX_LIBRARIES) # Already in cache, be silent set(GMPXX_FIND_QUIETLY TRUE) endif (GMPXX_INCLUDE_DIR AND GMPXX_LIBRARIES) find_path(GMPXX_INCLUDE_DIR NAMES gmpxx.h ) find_library(GMPXX_LIBRARIES NAMES gmpxx libgmpxx) include(FindPackageHandleStandardArgs) FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMPXX DEFAULT_MSG GMPXX_INCLUDE_DIR GMPXX_LIBRARIES) mark_as_advanced(GMPXX_INCLUDE_DIR GMPXX_LIBRARIES) regina-4.95/cmake/modules/FindICONV.cmake000644 000765 000024 00000004514 12234011536 020021 0ustar00babstaff000000 000000 # Copyright (c) William Pettersson, 2011 # Licensed under the GNU General Public License, version 2 or later # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. include (CheckFunctionExists) include (CheckCXXSourceCompiles) IF (APPLE) # Both /usr/include/iconv.h and /sw/include/iconv.h are highly likely # to turn up on the compile-time search path if we have fink installed. # # It is very difficult to push /usr/include to the front of the search path # when compiling, which raises the risk of compiling with /sw/include/iconv.h # but linking against /usr/lib/libiconv.dylib (which is incompatible). # # Our solution: make sure we use the iconv in /sw if it exists. FIND_PATH(ICONV_INCLUDE_DIR NAMES iconv.h PATHS /sw/include NO_DEFAULT_PATH) IF (NOT ICONV_INCLUDE_DIR) FIND_PATH(ICONV_INCLUDE_DIR NAMES iconv.h) ENDIF (NOT ICONV_INCLUDE_DIR) FIND_LIBRARY(ICONV_LIBRARY NAMES iconv libiconv PATHS /sw/lib NO_DEFAULT_PATH) IF (NOT ICONV_LIBRARY) FIND_LIBRARY(ICONV_LIBRARY NAMES iconv libiconv) ENDIF (NOT ICONV_LIBRARY) ELSE (APPLE) FIND_PATH(ICONV_INCLUDE_DIR NAMES iconv.h) FIND_LIBRARY(ICONV_LIBRARY NAMES iconv libiconv) ENDIF (APPLE) IF(NOT ICONV_LIBRARY) CHECK_FUNCTION_EXISTS (iconv ICONV_FOUND) ENDIF(NOT ICONV_LIBRARY) IF(ICONV_INCLUDE_DIR AND ICONV_LIBRARY) SET(ICONV_FOUND TRUE) ENDIF(ICONV_INCLUDE_DIR AND ICONV_LIBRARY) IF(ICONV_FOUND) IF (NOT ICONV_FIND_QUIETLY) MESSAGE(STATUS "Found iconv: ${ICONV_INCLUDE_DIR}") ENDIF (NOT ICONV_FIND_QUIETLY) IF (NOT ICONV_LIBRARY) SET (ICONV_LIBRARY "") ENDIF (NOT ICONV_LIBRARY) check_cxx_source_compiles(" #include extern \"C\" size_t iconv(iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); int main(){return 0;} " ICONV_CONST) if(ICONV_CONST) message(STATUS "Setting const") set(ICONV_CONST "const") mark_as_advanced(ICONV_CONST) endif(ICONV_CONST) ELSE (ICONV_FOUND) IF (ICONV_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find iconv") ENDIF (ICONV_FIND_REQUIRED) ENDIF(ICONV_FOUND) regina-4.95/cmake/modules/FindPOPT.cmake000644 000765 000024 00000001670 12234011536 017725 0ustar00babstaff000000 000000 # Copyright (c) William Pettersson, 2011 # Licensed under the GNU General Public License, version 2 or later # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. find_path(POPT_INCLUDE_DIR popt.h PATHS /usr/include /usr/local/lib) find_library(POPT_LIBRARY popt PATHS /usr/lib /usr/local/lib) IF(POPT_INCLUDE_DIR AND POPT_LIBRARY) SET(POPT_FOUND TRUE) ENDIF(POPT_INCLUDE_DIR AND POPT_LIBRARY) IF(POPT_FOUND) IF (NOT POPT_FIND_QUIETLY) MESSAGE(STATUS "Found popt: ${POPT_LIBRARY}") ENDIF (NOT POPT_FIND_QUIETLY) ELSE (POPT_FOUND) IF (POPT_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find the popt library.") ENDIF (POPT_FIND_REQUIRED) ENDIF(POPT_FOUND) regina-4.95/cmake/modules/FindSharedMimeInfo.cmake000644 000765 000024 00000005735 12234011536 022003 0ustar00babstaff000000 000000 # Taken from the KDE4 cmake modules directory. - Ben Burton, 11 February 2012. # - Try to find the shared-mime-info package # # SHARED_MIME_INFO_MINIMUM_VERSION - Set this to the minimum version you need, default is 0.18 # # Once done this will define # # SHARED_MIME_INFO_FOUND - system has the shared-mime-info package # UPDATE_MIME_DATABASE_EXECUTABLE - the update-mime-database executable # Copyright (c) 2007, Pino Toscano, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # the minimum version of shared-mime-database we require if (NOT SHARED_MIME_INFO_MINIMUM_VERSION) set(SHARED_MIME_INFO_MINIMUM_VERSION "0.18") endif (NOT SHARED_MIME_INFO_MINIMUM_VERSION) if (UPDATE_MIME_DATABASE_EXECUTABLE) # in cache already set(SHARED_MIME_INFO_FOUND TRUE) else (UPDATE_MIME_DATABASE_EXECUTABLE) include (MacroEnsureVersion) find_program (UPDATE_MIME_DATABASE_EXECUTABLE NAMES update-mime-database) if (UPDATE_MIME_DATABASE_EXECUTABLE) exec_program (${UPDATE_MIME_DATABASE_EXECUTABLE} ARGS -v RETURN_VALUE _null OUTPUT_VARIABLE _smiVersionRaw) string(REGEX REPLACE "update-mime-database \\([a-zA-Z\\-]+\\) ([0-9]\\.[0-9]+).*" "\\1" smiVersion "${_smiVersionRaw}") set (SHARED_MIME_INFO_FOUND TRUE) endif (UPDATE_MIME_DATABASE_EXECUTABLE) if (SHARED_MIME_INFO_FOUND) if (NOT SharedMimeInfo_FIND_QUIETLY) message(STATUS "Found shared-mime-info version: ${smiVersion}") macro_ensure_version(${SHARED_MIME_INFO_MINIMUM_VERSION} ${smiVersion} _smiVersion_OK) if (NOT _smiVersion_OK) message(FATAL_ERROR "The found version of shared-mime-info (${smiVersion}) is below the minimum required (${SHARED_MIME_INFO_MINIMUM_VERSION})") endif (NOT _smiVersion_OK) endif (NOT SharedMimeInfo_FIND_QUIETLY) else (SHARED_MIME_INFO_FOUND) if (SharedMimeInfo_FIND_REQUIRED) message(FATAL_ERROR "Could NOT find shared-mime-info. See http://freedesktop.org/wiki/Software/shared-mime-info.") endif (SharedMimeInfo_FIND_REQUIRED) endif (SHARED_MIME_INFO_FOUND) endif (UPDATE_MIME_DATABASE_EXECUTABLE) mark_as_advanced(UPDATE_MIME_DATABASE_EXECUTABLE) macro(UPDATE_XDG_MIMETYPES _path) get_filename_component(_xdgmimeDir "${_path}" NAME) if("${_xdgmimeDir}" STREQUAL packages ) get_filename_component(_xdgmimeDir "${_path}" PATH) else("${_xdgmimeDir}" STREQUAL packages ) set(_xdgmimeDir "${_path}") endif("${_xdgmimeDir}" STREQUAL packages ) install(CODE " set(DESTDIR_VALUE \"\$ENV{DESTDIR}\") if (NOT DESTDIR_VALUE) # under Windows relative paths are used, that's why it runs from CMAKE_INSTALL_PREFIX execute_process(COMMAND ${UPDATE_MIME_DATABASE_EXECUTABLE} ${_xdgmimeDir} WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\") endif (NOT DESTDIR_VALUE) ") endmacro (UPDATE_XDG_MIMETYPES) regina-4.95/cmake/modules/FindSourceHighlight.cmake000644 000765 000024 00000002114 12234011536 022225 0ustar00babstaff000000 000000 # Copyright (c) William Pettersson and Ben Burton, 2011 # Licensed under the GNU General Public License, version 2 or later # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. find_path(SRCHILITE_INCLUDE_DIR srchilite/sourcehighlight.h PATHS /usr/include /usr/local/lib) find_library(SRCHILITE_LIBRARY source-highlight PATHS /usr/lib /usr/local/lib) IF(SRCHILITE_INCLUDE_DIR AND SRCHILITE_LIBRARY) SET(SRCHILITE_FOUND TRUE) ENDIF(SRCHILITE_INCLUDE_DIR AND SRCHILITE_LIBRARY) IF(SRCHILITE_FOUND) IF (NOT SRCHILITE_FIND_QUIETLY) MESSAGE(STATUS "Found source-highlight: ${SRCHILITE_LIBRARY}") ENDIF (NOT SRCHILITE_FIND_QUIETLY) ELSE (SRCHILITE_FOUND) IF (SRCHILITE_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find the source-highlight library.") ENDIF (SRCHILITE_FIND_REQUIRED) ENDIF(SRCHILITE_FOUND) regina-4.95/cmake/modules/MacroEnsureVersion.cmake000644 000765 000024 00000012055 12234011536 022132 0ustar00babstaff000000 000000 # Taken from the KDE4 cmake modules directory. - Ben Burton, 11 February 2012. # This file defines the following macros for developers to use in ensuring # that installed software is of the right version: # # MACRO_ENSURE_VERSION - test that a version number is greater than # or equal to some minimum # MACRO_ENSURE_VERSION_RANGE - test that a version number is greater than # or equal to some minimum and less than some # maximum # MACRO_ENSURE_VERSION2 - deprecated, do not use in new code # # MACRO_ENSURE_VERSION # This macro compares version numbers of the form "x.y.z" or "x.y" # MACRO_ENSURE_VERSION( FOO_MIN_VERSION FOO_VERSION_FOUND FOO_VERSION_OK) # will set FOO_VERSION_OK to true if FOO_VERSION_FOUND >= FOO_MIN_VERSION # Leading and trailing text is ok, e.g. # MACRO_ENSURE_VERSION( "2.5.31" "flex 2.5.4a" VERSION_OK) # which means 2.5.31 is required and "flex 2.5.4a" is what was found on the system # Copyright (c) 2006, David Faure, # Copyright (c) 2007, Will Stephenson # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # MACRO_ENSURE_VERSION_RANGE # This macro ensures that a version number of the form # "x.y.z" or "x.y" falls within a range defined by # min_version <= found_version < max_version. # If this expression holds, FOO_VERSION_OK will be set TRUE # # Example: MACRO_ENSURE_VERSION_RANGE3( "0.1.0" ${FOOCODE_VERSION} "0.7.0" FOO_VERSION_OK ) # # This macro will break silently if any of x,y,z are greater than 100. # # Copyright (c) 2007, Will Stephenson # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # NORMALIZE_VERSION # Helper macro to convert version numbers of the form "x.y.z" # to an integer equal to 10^4 * x + 10^2 * y + z # # This macro will break silently if any of x,y,z are greater than 100. # # Copyright (c) 2006, David Faure, # Copyright (c) 2007, Will Stephenson # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # CHECK_RANGE_INCLUSIVE_LOWER # Helper macro to check whether x <= y < z # # Copyright (c) 2007, Will Stephenson # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. MACRO(NORMALIZE_VERSION _requested_version _normalized_version) STRING(REGEX MATCH "[^0-9]*[0-9]+\\.[0-9]+\\.[0-9]+.*" _threePartMatch "${_requested_version}") if (_threePartMatch) # parse the parts of the version string STRING(REGEX REPLACE "[^0-9]*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major_vers "${_requested_version}") STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.([0-9]+)\\.[0-9]+.*" "\\1" _minor_vers "${_requested_version}") STRING(REGEX REPLACE "[^0-9]*[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" _patch_vers "${_requested_version}") else (_threePartMatch) STRING(REGEX REPLACE "([0-9]+)\\.[0-9]+" "\\1" _major_vers "${_requested_version}") STRING(REGEX REPLACE "[0-9]+\\.([0-9]+)" "\\1" _minor_vers "${_requested_version}") set(_patch_vers "0") endif (_threePartMatch) # compute an overall version number which can be compared at once MATH(EXPR ${_normalized_version} "${_major_vers}*10000 + ${_minor_vers}*100 + ${_patch_vers}") ENDMACRO(NORMALIZE_VERSION) MACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER _lower_limit _value _upper_limit _ok) if (${_value} LESS ${_lower_limit}) set( ${_ok} FALSE ) elseif (${_value} EQUAL ${_lower_limit}) set( ${_ok} TRUE ) elseif (${_value} EQUAL ${_upper_limit}) set( ${_ok} FALSE ) elseif (${_value} GREATER ${_upper_limit}) set( ${_ok} FALSE ) else (${_value} LESS ${_lower_limit}) set( ${_ok} TRUE ) endif (${_value} LESS ${_lower_limit}) ENDMACRO(MACRO_CHECK_RANGE_INCLUSIVE_LOWER) MACRO(MACRO_ENSURE_VERSION requested_version found_version var_too_old) NORMALIZE_VERSION( ${requested_version} req_vers_num ) NORMALIZE_VERSION( ${found_version} found_vers_num ) if (found_vers_num LESS req_vers_num) set( ${var_too_old} FALSE ) else (found_vers_num LESS req_vers_num) set( ${var_too_old} TRUE ) endif (found_vers_num LESS req_vers_num) ENDMACRO(MACRO_ENSURE_VERSION) MACRO(MACRO_ENSURE_VERSION2 requested_version2 found_version2 var_too_old2) MACRO_ENSURE_VERSION( ${requested_version2} ${found_version2} ${var_too_old2}) ENDMACRO(MACRO_ENSURE_VERSION2) MACRO(MACRO_ENSURE_VERSION_RANGE min_version found_version max_version var_ok) NORMALIZE_VERSION( ${min_version} req_vers_num ) NORMALIZE_VERSION( ${found_version} found_vers_num ) NORMALIZE_VERSION( ${max_version} max_vers_num ) MACRO_CHECK_RANGE_INCLUSIVE_LOWER( ${req_vers_num} ${found_vers_num} ${max_vers_num} ${var_ok}) ENDMACRO(MACRO_ENSURE_VERSION_RANGE) regina-4.95/cmake/modules/MacroLogFeature.cmake000644 000765 000024 00000013232 12234011536 021356 0ustar00babstaff000000 000000 # Taken from the KDE4 cmake modules directory. - Ben Burton, 25 August 2011. # This file defines the Feature Logging macros. # # MACRO_LOG_FEATURE(VAR FEATURE DESCRIPTION URL [REQUIRED [MIN_VERSION [COMMENTS]]]) # Logs the information so that it can be displayed at the end # of the configure run # VAR : TRUE or FALSE, indicating whether the feature is supported # FEATURE: name of the feature, e.g. "libjpeg" # DESCRIPTION: description what this feature provides # URL: home page # REQUIRED: TRUE or FALSE, indicating whether the featue is required # MIN_VERSION: minimum version number. empty string if unneeded # COMMENTS: More info you may want to provide. empty string if unnecessary # # MACRO_DISPLAY_FEATURE_LOG() # Call this to display the collected results. # Exits CMake with a FATAL error message if a required feature is missing # # Example: # # INCLUDE(MacroLogFeature) # # FIND_PACKAGE(JPEG) # MACRO_LOG_FEATURE(JPEG_FOUND "libjpeg" "Support JPEG images" "http://www.ijg.org" TRUE "3.2a" "") # ... # MACRO_DISPLAY_FEATURE_LOG() # Copyright (c) 2006, Alexander Neundorf, # Copyright (c) 2006, Allen Winter, # Copyright (c) 2009, Sebastian Trueg, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. IF (NOT _macroLogFeatureAlreadyIncluded) SET(_file ${CMAKE_BINARY_DIR}/MissingRequirements.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_file ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_file ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) IF (EXISTS ${_file}) FILE(REMOVE ${_file}) ENDIF (EXISTS ${_file}) SET(_macroLogFeatureAlreadyIncluded TRUE) ENDIF (NOT _macroLogFeatureAlreadyIncluded) MACRO(MACRO_LOG_FEATURE _var _package _description _url ) # _required _minvers _comments) STRING(TOUPPER "${ARGV4}" _required) SET(_minvers "${ARGV5}") SET(_comments "${ARGV6}") IF (${_var}) SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) ELSE (${_var}) IF ("${_required}" STREQUAL "TRUE") SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/MissingRequirements.txt) ELSE ("${_required}" STREQUAL "TRUE") SET(_LOGFILENAME ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) ENDIF ("${_required}" STREQUAL "TRUE") ENDIF (${_var}) SET(_logtext " * ${_package}") IF (NOT ${_var}) IF (${_minvers} MATCHES ".*") SET(_logtext "${_logtext} (${_minvers} or higher)") ENDIF (${_minvers} MATCHES ".*") SET(_logtext "${_logtext} <${_url}>\n ") ELSE (NOT ${_var}) SET(_logtext "${_logtext} - ") ENDIF (NOT ${_var}) SET(_logtext "${_logtext}${_description}") IF (NOT ${_var}) IF (${_comments} MATCHES ".*") SET(_logtext "${_logtext}\n ${_comments}") ENDIF (${_comments} MATCHES ".*") # SET(_logtext "${_logtext}\n") #double-space missing features? ENDIF (NOT ${_var}) FILE(APPEND "${_LOGFILENAME}" "${_logtext}\n") ENDMACRO(MACRO_LOG_FEATURE) MACRO(MACRO_DISPLAY_FEATURE_LOG) SET(_missingFile ${CMAKE_BINARY_DIR}/MissingRequirements.txt) SET(_enabledFile ${CMAKE_BINARY_DIR}/EnabledFeatures.txt) SET(_disabledFile ${CMAKE_BINARY_DIR}/DisabledFeatures.txt) IF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) SET(_printSummary TRUE) ENDIF (EXISTS ${_missingFile} OR EXISTS ${_enabledFile} OR EXISTS ${_disabledFile}) IF(_printSummary) SET(_missingDeps 0) IF (EXISTS ${_enabledFile}) FILE(READ ${_enabledFile} _enabled) FILE(REMOVE ${_enabledFile}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following external packages were located on your system.\n-- This installation will have the extra features provided by these packages.\n-----------------------------------------------------------------------------\n${_enabled}") ENDIF (EXISTS ${_enabledFile}) IF (EXISTS ${_disabledFile}) SET(_missingDeps 1) FILE(READ ${_disabledFile} _disabled) FILE(REMOVE ${_disabledFile}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following OPTIONAL packages could NOT be located on your system.\n-- Consider installing them to enable more features from this software.\n-----------------------------------------------------------------------------\n${_disabled}") ENDIF (EXISTS ${_disabledFile}) IF (EXISTS ${_missingFile}) SET(_missingDeps 1) FILE(READ ${_missingFile} _requirements) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- The following REQUIRED packages could NOT be located on your system.\n-- You must install these packages before continuing.\n-----------------------------------------------------------------------------\n${_requirements}") FILE(REMOVE ${_missingFile}) SET(_haveMissingReq 1) ENDIF (EXISTS ${_missingFile}) IF (NOT ${_missingDeps}) SET(_summary "${_summary}\n-----------------------------------------------------------------------------\n-- Congratulations! All external packages have been found.") ENDIF (NOT ${_missingDeps}) MESSAGE(${_summary}) MESSAGE("-----------------------------------------------------------------------------\n") IF(_haveMissingReq) MESSAGE(FATAL_ERROR "Exiting: Missing Requirements") ENDIF(_haveMissingReq) ENDIF(_printSummary) ENDMACRO(MACRO_DISPLAY_FEATURE_LOG) regina-4.95/cmake/modules/ReginaMacros.cmake000644 000765 000024 00000004332 12234011536 020712 0ustar00babstaff000000 000000 # CMake macros specific to Regina. # # Copyright (c) Ben Burton, 2012-2013 # Licensed under the GNU General Public License, version 2 or later # # As an exception, when this program is distributed through (i) the # App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or # (iii) Google Play by Google Inc., then that store may impose any # digital rights management, device limits and/or redistribution # restrictions that are required by its terms of service. # Macro: REGINA_CREATE_HANDBOOK(lang) # # Builds an HTML manual from XML Docbook sources. # # Includes code from KDE4_CREATE_HANDBOOK from the KDE cmake scripts. # The KDE cmake scripts are licensed as follows: # # Copyright (c) 2006-2009 Alexander Neundorf, # Copyright (c) 2006, 2007, Laurent Montel, # Copyright (c) 2007 Matthias Kretz # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # macro (REGINA_CREATE_HANDBOOK _lang) # Build the docbook documentation. set(_input ${CMAKE_CURRENT_SOURCE_DIR}/index.docbook) set(_doc ${CMAKE_CURRENT_BINARY_DIR}/index.html) set(_custom ${CMAKE_CURRENT_SOURCE_DIR}/../ksgmltools2/customization) set(_dtd ${_custom}/dtd) set(_ssheet ${_custom}/regina.xsl) get_filename_component(_handbook ${CMAKE_CURRENT_SOURCE_DIR} NAME) file(GLOB _docs *.docbook) if (REGINA_DOCS) add_custom_command(OUTPUT ${_doc} COMMAND ${UNZIP_EXECUTABLE} -o -j -d ${CMAKE_CURRENT_BINARY_DIR} ${REGINA_DOCS_FILE} "docs/${_lang}/${_handbook}/\\*") else (REGINA_DOCS) add_custom_command(OUTPUT ${_doc} COMMAND ${XSLTPROC_EXECUTABLE} --path ${_dtd} -o ${CMAKE_CURRENT_BINARY_DIR}/ ${_ssheet} ${_input} DEPENDS ${_docs} ${_ssheet} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) endif (REGINA_DOCS) add_custom_target(${_handbook}-html ALL DEPENDS ${_doc}) file(GLOB _support *.png *.css *.html) install(FILES ${_support} DESTINATION ${HTMLDIR}/${_lang}/${_handbook}) install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DESTINATION ${HTMLDIR}/${_lang} FILES_MATCHING PATTERN CMakeFiles EXCLUDE PATTERN "*.html" ) endmacro (REGINA_CREATE_HANDBOOK) regina-4.95/CMakeLists.txt000644 000765 000024 00000041105 12240017216 015343 0ustar00babstaff000000 000000 CMAKE_MINIMUM_REQUIRED (VERSION 2.8) PROJECT (regina) # Let the user override the package name. IF(NOT PACKAGE_NAME) SET (PACKAGE_NAME regina CACHE STRING "The package name used for installation directories. On some platforms this is 'regina-normal', not 'regina', to avoid conflicts with other software with the same name." FORCE) ENDIF(NOT PACKAGE_NAME) SET (PACKAGE_PRETTY_NAME Regina) # Look here for Find___.cmake modules SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") # Version SET (PACKAGE_VERSION 4.95) SET (PACKAGE_STRING "${PACKAGE_PRETTY_NAME} ${PACKAGE_VERSION}") # Extract major/minor version # Note: The PACKAGE_VERSION_MAJOR "output" is discarded, since it matches the # whole string STRING(REGEX MATCH "^([0-9]+).([0-9]+)" PACKAGE_VERSION_MAJOR "${PACKAGE_VERSION}") SET (PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1} ) SET (PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2} ) # Bug report email SET (PACKAGE_BUGREPORT "regina-user@lists.sourceforge.net") # Make a release build by default. IF(NOT CMAKE_BUILD_TYPE) SET(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build. Options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) ENDIF(NOT CMAKE_BUILD_TYPE) # Distinguish between the different Regina installation types: # XDG = standard freedesktop.org filesystem installation in a fixed location. # Bundle = self-contained app bundle for MacOS that users can drag and drop. # Windows = self-contained movable install directory for MS Windows. if (NOT REGINA_INSTALL_TYPE) if (APPLE) SET (REGINA_DEFAULT_INSTALL_TYPE Bundle) elseif (WIN32) SET (REGINA_DEFAULT_INSTALL_TYPE Windows) else () SET (REGINA_DEFAULT_INSTALL_TYPE XDG) endif () SET (REGINA_INSTALL_TYPE ${REGINA_DEFAULT_INSTALL_TYPE} CACHE STRING "Choose how Regina installs itself. Options are: XDG Bundle Windows." FORCE) ENDIF (NOT REGINA_INSTALL_TYPE) MESSAGE (STATUS "Regina install type: ${REGINA_INSTALL_TYPE}") # Sanity check the installation type: IF (${REGINA_INSTALL_TYPE} STREQUAL XDG) # No sanity checking needed here (yet). ELSEIF (${REGINA_INSTALL_TYPE} STREQUAL Bundle) IF (NOT APPLE) MESSAGE(FATAL_ERROR "REGINA_INSTALL_TYPE=Bundle is only supported on MacOSX.") ENDIF (NOT APPLE) ELSEIF (${REGINA_INSTALL_TYPE} STREQUAL Windows) IF (NOT WIN32) MESSAGE(FATAL_ERROR "REGINA_INSTALL_TYPE=Windows is only supported on MS Windows.") ENDIF (NOT WIN32) ELSE() MESSAGE(FATAL_ERROR "REGINA_INSTALL_TYPE must be one of: XDG Bundle Windows.") ENDIF() # Are we installing development files? IF (${REGINA_INSTALL_TYPE} STREQUAL Bundle) SET (REGINA_INSTALL_DEV OFF) ELSE (${REGINA_INSTALL_TYPE} STREQUAL Bundle) SET (REGINA_INSTALL_DEV ON) ENDIF (${REGINA_INSTALL_TYPE} STREQUAL Bundle) # Give packagers a way of insisting that every optional component is found. # Set PACKAGING_MODE=1 to make every component compulsory. # Set PACKAGING_MODE=1 and PACKAGING_NO_MPI=1 to make every component # compulsory except for the MPI utilities. IF(NOT PACKAGING_MODE) SET (PACKAGING_MODE OFF CACHE STRING "Makes all optional components mandatory, so that cmake will fail if any optional component is not found." FORCE) ENDIF(NOT PACKAGING_MODE) IF(NOT PACKAGING_NO_MPI) SET (PACKAGING_NO_MPI OFF CACHE STRING "Leaves MPI as an optional component, even if PACKAGING_MODE is ON." FORCE) ENDIF(NOT PACKAGING_NO_MPI) IF(PACKAGING_MODE) SET(REGINA_MANDATORY TRUE) IF(PACKAGING_NO_MPI) SET(REGINA_MANDATORY_MPI FALSE) ELSE(PACKAGING_NO_MPI) SET(REGINA_MANDATORY_MPI TRUE) ENDIF(PACKAGING_NO_MPI) ELSE(PACKAGING_MODE) SET(REGINA_MANDATORY FALSE) SET(REGINA_MANDATORY_MPI FALSE) ENDIF(PACKAGING_MODE) # For pretty logging of optional features at the end of the cmake run: INCLUDE(MacroLogFeature) # Modules needed for IOS check and function existence INCLUDE( CheckCXXSourceCompiles ) INCLUDE( CheckFunctionExists ) # All the user to exclude various pieces of third-party code that are # typically built directly into Regina's engine. IF (NOT EXCLUDE_NORMALIZ) SET (EXCLUDE_NORMALIZ OFF CACHE STRING "Exclude all Normaliz code from the build." FORCE) ENDIF (NOT EXCLUDE_NORMALIZ) IF (NOT EXCLUDE_SNAPPEA) SET (EXCLUDE_SNAPPEA OFF CACHE STRING "Exclude all SnapPea / SnapPy code from the build." FORCE) ENDIF (NOT EXCLUDE_SNAPPEA) IF (EXCLUDE_NORMALIZ) MESSAGE(STATUS "Excluding Normaliz from the build") ENDIF (EXCLUDE_NORMALIZ) IF (EXCLUDE_SNAPPEA) MESSAGE(STATUS "Excluding SnapPea / SnapPy from the build") ENDIF (EXCLUDE_SNAPPEA) # Regina's own cmake scripts: INCLUDE( ReginaMacros ) # Always include . in the header search path: set(CMAKE_INCLUDE_CURRENT_DIR ON) # Installation directories if (${REGINA_INSTALL_TYPE} STREQUAL Bundle) # MacOSX app bundle. SET (APPDIR ${CMAKE_INSTALL_PREFIX}) SET (BUNDLEDIR ${APPDIR}/Regina.app) SET (BUNDLEDIR_ESCAPED "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/Regina.app") SET (RESOURCEDIR ${BUNDLEDIR}/Contents/Resources) SET (BINDIR ${BUNDLEDIR}/Contents/MacOS) SET (LIBDIR ${BINDIR}) SET (DATADIR ${RESOURCEDIR}) SET (INCLUDEDIR ${RESOURCEDIR}/include) SET (PKGDATADIR ${DATADIR}) SET (PYLIBDIR ${LIBDIR}/python) elseif (${REGINA_INSTALL_TYPE} STREQUAL Windows) # MS Windows install. SET (APPDIR ${CMAKE_INSTALL_PREFIX}/bin) SET (BINDIR ${CMAKE_INSTALL_PREFIX}/bin) SET (LIBDIR ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) SET (DATADIR ${CMAKE_INSTALL_PREFIX}/share) SET (INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/include/${PACKAGE_NAME}) SET (PKGDATADIR ${DATADIR}/${PACKAGE_NAME}) SET (PYLIBDIR ${LIBDIR}/${PACKAGE_NAME}/python) else () # Full install. SET (APPDIR ${CMAKE_INSTALL_PREFIX}/bin) SET (BINDIR ${CMAKE_INSTALL_PREFIX}/bin) SET (LIBDIR ${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}) SET (DATADIR ${CMAKE_INSTALL_PREFIX}/share) SET (INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/include/${PACKAGE_NAME}) SET (PKGDATADIR ${DATADIR}/${PACKAGE_NAME}) UNSET (PYLIBDIR) endif () SET (HTMLDIR ${PKGDATADIR}/docs) SET (ICONDIR ${DATADIR}/icons) SET (MANDIR ${DATADIR}/man) SET (XDG_APPS_INSTALL_DIR ${DATADIR}/applications) SET (XDG_MIME_INSTALL_DIR ${DATADIR}/mime/packages) # Useful build directories SET (ENGINE_INCLUDES "${PROJECT_SOURCE_DIR}/engine" ) SET (ENGINE_LIBRARY regina-engine) # RPATH support. IF (NOT DISABLE_RPATH) SET(DISABLE_RPATH OFF CACHE BOOL "Do not build with rpath. This option may break installations in non-standard directories, since executables may be unable to find Regina's libraries. For installations in standard areas such as /usr however, this option is highly recommended." FORCE) ENDIF (NOT DISABLE_RPATH) IF (NOT DISABLE_RPATH) if (APPLE) set(CMAKE_INSTALL_NAME_DIR ${LIBDIR}) else (APPLE) set(CMAKE_INSTALL_RPATH ${LIBDIR} ) set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) endif (APPLE) ENDIF (NOT DISABLE_RPATH) # i18n checks. CHECK_CXX_SOURCE_COMPILES(" #include \"langinfo.h\" int main() { nl_langinfo(CODESET); return 0; }" LANGINFO_FOUND ) if(NOT LANGINFO_FOUND) MESSAGE(WARNING "langinfo not found: UTF-8 will be used always, and the locale-specific encoding will be ignored.") endif(NOT LANGINFO_FOUND) # Large integer checks CHECK_CXX_SOURCE_COMPILES(" long long x; signed long long y; unsigned long long z; int main() { return 0; }" LONG_LONG_FOUND ) CHECK_CXX_SOURCE_COMPILES(" #include int128_t x; uint128_t y; int main() { return 0; }" INT128_T_FOUND ) CHECK_CXX_SOURCE_COMPILES(" #include __int128_t x; __uint128_t y; int main() { return 0; }" __INT128_T_FOUND ) IF (INT128_T_FOUND OR __INT128_T_FOUND) SET(INT128_AVAILABLE TRUE) ENDIF (INT128_T_FOUND OR __INT128_T_FOUND) IF(LONG_LONG_FOUND) CHECK_CXX_SOURCE_COMPILES(" unsigned long long x = 0xFFFFFFFFFFFFFFFF; int main() { return 0; }" NUMERIC_64_FOUND) CHECK_CXX_SOURCE_COMPILES(" unsigned long long x = 0xFFFFFFFFFFFFFFFFLL; int main() { return 0; }" NUMERIC_64_LL_FOUND) ENDIF(LONG_LONG_FOUND) # Dependencies used by all components of Regina FIND_PACKAGE(ZLIB) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIR}) MACRO_LOG_FEATURE(ZLIB_FOUND "zlib" "Essential: compression support" "http://www.gzip.org/zlib/" TRUE) FIND_PACKAGE(LibXml2) INCLUDE_DIRECTORIES(${LIBXML2_INCLUDE_DIR}) MACRO_LOG_FEATURE(LIBXML2_FOUND "libxml2" "Essential: XML support" "ftp.gnome.org" TRUE) FIND_PACKAGE(GMP) INCLUDE_DIRECTORIES(${GMP_INCLUDE_DIR}) MACRO_LOG_FEATURE(GMP_FOUND "GMP/C" "Essential: large integer arithmetic support for C" "http://gmplib.org/" TRUE) FIND_PACKAGE(GMPXX) INCLUDE_DIRECTORIES(${GMPXX_INCLUDE_DIR}) MACRO_LOG_FEATURE(GMPXX_FOUND "GMP/C++" "Essential: large integer arithmetic support for C++" "http://gmplib.org/" TRUE) FIND_PACKAGE(ICONV) INCLUDE_DIRECTORIES(${ICONV_INCLUDE_DIR}) MACRO_LOG_FEATURE(ICONV_FOUND "iconv" "Essential: internationalisation support" "http://www.gnu.org/s/libiconv/" TRUE) FIND_PACKAGE(Boost COMPONENTS python regex) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) MACRO_LOG_FEATURE(Boost_FOUND "Boost" "Essential: C++ components (including Boost.Python and Boost.Regex)" "http://www.boost.org/" TRUE) FIND_PACKAGE(Threads REQUIRED) IF(NOT CMAKE_USE_PTHREADS_INIT) MESSAGE(FATAL_ERROR "Regina requires pthread support.") ENDIF(NOT CMAKE_USE_PTHREADS_INIT) # Dependencies used by only some components of Regina FIND_PACKAGE(POPT) MACRO_LOG_FEATURE(POPT_FOUND "Popt" "Essential: command-line option processing" "http://rpm5.org/files/popt/" TRUE) # Optionals IF(NOT DISABLE_MPI) FIND_PACKAGE(MPI) MACRO_LOG_FEATURE(MPI_FOUND "MPI" "Build command-line tools for high-performance computing" "http://www.open-mpi.org/" REGINA_MANDATORY_MPI) ENDIF(NOT DISABLE_MPI) FIND_PACKAGE(Doxygen) MACRO_LOG_FEATURE(DOXYGEN_FOUND "Doxygen" "Generate C++/Python API docs" "http://www.doxygen.org/" REGINA_MANDATORY) IF (REGINA_DOCS) get_filename_component(REGINA_DOCS_FILE "${REGINA_DOCS}" ABSOLUTE) IF (NOT EXISTS ${REGINA_DOCS_FILE}) MESSAGE (FATAL_ERROR "The argument to REGINA_DOCS should be a zip file containing pre-built handbooks. The file you gave (${REGINA_DOCS}) does not exist.") ENDIF (NOT EXISTS ${REGINA_DOCS_FILE}) MESSAGE (STATUS "Using pre-built handbooks from ${REGINA_DOCS_FILE}") FIND_PROGRAM(UNZIP_EXECUTABLE NAMES unzip DOC "Command-line unzip tool") IF (UNZIP_EXECUTABLE) SET (UNZIP_FOUND TRUE) ENDIF (UNZIP_EXECUTABLE) SET (REGINA_BUILD_HANDBOOK ${UNZIP_FOUND}) MACRO_LOG_FEATURE(UNZIP_FOUND "unzip" "Extract the user handbook" "http://www.info-zip.org/pub/infozip/" REGINA_MANDATORY) ELSE (REGINA_DOCS) SET (REGINA_DOCS "" CACHE STRING "Extract handbooks from the given pre-built zip file instead of building them manually." FORCE) FIND_PROGRAM(XSLTPROC_EXECUTABLE NAMES xsltproc DOC "XSLT processor") IF (XSLTPROC_EXECUTABLE) SET (XSLTPROC_FOUND TRUE) ENDIF (XSLTPROC_EXECUTABLE) SET (REGINA_BUILD_HANDBOOK ${XSLTPROC_FOUND}) MACRO_LOG_FEATURE(XSLTPROC_FOUND "xsltproc" "Generate the user handbook" "http://xmlsoft.org/XSLT/" REGINA_MANDATORY) ENDIF (REGINA_DOCS) # Test suite ENABLE_TESTING() # This must appear before any calls to ADD_SUBDIRECTORY(). # Core directories ADD_SUBDIRECTORY(engine) ADD_SUBDIRECTORY(utils) # Python support if (WIN32 AND NOT CYGWIN) SET (REGINA_PYTHON_EXTENSION "pyd") SET (REGINA_PYTHON_EXTENSION_NONSTANDARD TRUE) else () SET (REGINA_PYTHON_EXTENSION "so") SET (REGINA_PYTHON_EXTENSION_NONSTANDARD FALSE) endif () IF(NOT DISABLE_PYTHON) FIND_PACKAGE(PythonInterp) FIND_PACKAGE(PythonLibs) ENDIF(NOT DISABLE_PYTHON) IF(Boost_FOUND AND PYTHONINTERP_FOUND AND PYTHONLIBS_FOUND) # We can support python. # Find the site-packages location in which the XDG build needs to put # the python module. EXECUTE_PROCESS(COMMAND "${PYTHON_EXECUTABLE}" -c "import distutils.sysconfig; print distutils.sysconfig.get_python_lib(True)" OUTPUT_VARIABLE SITE_PACKAGES OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE DISTUTILS_RESULT) IF ((NOT DISTUTILS_RESULT) AND IS_DIRECTORY "${SITE_PACKAGES}") MESSAGE(STATUS "Python site-packages directory: ${SITE_PACKAGES}") ELSE () MESSAGE(FATAL_ERROR "Could not query the python site-packages directory. Please either set the correct python interpreter (-DPYTHON_EXECUTABLE=...), or else disable python bindings (-DDISABLE_PYTHON).") ENDIF () ADD_SUBDIRECTORY(pylib) ADD_SUBDIRECTORY(python) SET(BOOST_PYTHON_FOUND TRUE) MESSAGE(STATUS "Python bindings enabled") ELSEIF(Boost_FOUND) # Boost itself is mandatory - if it was not found then we have # bigger problems. MESSAGE(WARNING "Could not find Python interpreter and/or development files: Python bindings disabled.") ENDIF() MACRO_LOG_FEATURE(PYTHONINTERP_FOUND "Python interpreter" "Build Python bindings for Regina" "http://www.python.org/" REGINA_MANDATORY) MACRO_LOG_FEATURE(PYTHONLIBS_FOUND "Python development files" "Build Python bindings for Regina" "http://www.python.org/" REGINA_MANDATORY) # The Qt GUI IF(NOT DISABLE_GUI) SET (DISABLE_GUI OFF CACHE STRING "Disables the graphical user interface. The calculation engine, Python bindings and other command-line utilities will still be built. This option is suitable for use on high-performance clusters." FORCE) ENDIF(NOT DISABLE_GUI) IF(DISABLE_GUI) if (NOT ${REGINA_INSTALL_TYPE} STREQUAL XDG) MESSAGE(FATAL_ERROR "You can only set DISABLE_GUI=1 with REGINA_INSTALL_TYPE=XDG.") endif (NOT ${REGINA_INSTALL_TYPE} STREQUAL XDG) MESSAGE(WARNING "User set DISABLE_GUI=1: graphical user interface disabled.") ELSE(DISABLE_GUI) set(GUI_REQUIRED TRUE) FIND_PACKAGE(Qt4 COMPONENTS QtGui QtCore) FIND_PACKAGE(SourceHighlight) MACRO_LOG_FEATURE(SRCHILITE_FOUND "Source-highlight" "Syntax highlighting" "http://www.gnu.org/software/src-highlite/" REGINA_MANDATORY) if (${REGINA_INSTALL_TYPE} STREQUAL XDG) set(SHARED_MIME_INFO_MINIMUM_VERSION "0.30") find_package(SharedMimeInfo) MACRO_LOG_FEATURE(SHARED_MIME_INFO_FOUND "SharedMimeInfo" "Required for Regina's graphical user interface" "http://freedesktop.org/wiki/Software/shared-mime-info" TRUE "0.30") endif (${REGINA_INSTALL_TYPE} STREQUAL XDG) if (${REGINA_INSTALL_TYPE} STREQUAL Bundle) include(BundleUtilities) endif (${REGINA_INSTALL_TYPE} STREQUAL Bundle) # This must come after SharedMimeInfo is included, since the qtui/ # makefiles use SharedMimeInfo macros. if (QT4_FOUND) ADD_SUBDIRECTORY(qtui) endif (QT4_FOUND) MESSAGE(STATUS "Graphical user interface enabled") ENDIF(DISABLE_GUI) MACRO_LOG_FEATURE(QT4_FOUND "Qt4" "Required for Regina's graphical user interface" "http://qt.nokia.com/" GUI_REQUIRED "" "To disable the graphical user interface, run: cmake -DDISABLE_GUI=1") # Test suite, continued FIND_PATH(CPPUNIT_INCLUDE_DIR cppunit/Test.h) FIND_LIBRARY(CPPUNIT_LIBRARY NAMES cppunit) IF (CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) SET(CPPUNIT_FOUND TRUE) MESSAGE(STATUS "Found CppUnit: ${CPPUNIT_LIBRARY}") ADD_SUBDIRECTORY(testsuite) ELSE (CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) MESSAGE(WARNING "Could not find CppUnit: test suite disabled.") ENDIF (CPPUNIT_INCLUDE_DIR AND CPPUNIT_LIBRARY) MACRO_LOG_FEATURE(CPPUNIT_FOUND "CppUnit" "Build the full test suite for Regina" "http://sourceforge.net/projects/cppunit/" REGINA_MANDATORY) # Miscellaneous subdirectories ADD_SUBDIRECTORY(timing) ADD_SUBDIRECTORY(docs) ADD_SUBDIRECTORY(examples) # Configure file CONFIGURE_FILE ( "${PROJECT_SOURCE_DIR}/engine/regina-config.h.in" "${PROJECT_BINARY_DIR}/engine/regina-config.h" ) MACRO_DISPLAY_FEATURE_LOG() # CPack configuration to allow the developers to build a source tarball: set(CPACK_PACKAGE_VERSION_MAJOR ${PACKAGE_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PACKAGE_VERSION_MINOR}) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Regina: Software for 3-manifold topology and normal surface theory") set(CPACK_PACKAGE_VENDOR "The Regina development team") set(CPACK_PACKAGE_DESCRIPTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/README.txt) set(CPACK_GENERATOR TGZ) set(CPACK_SOURCE_PACKAGE_FILE_NAME "regina-${PACKAGE_VERSION}") set(CPACK_SOURCE_GENERATOR TGZ) # The following regexes match anywhere: set(CPACK_SOURCE_IGNORE_FILES "~$" "^${PROJECT_BINARY_DIR}/" "^${PROJECT_SOURCE_DIR}/engine/snappea/kernel/unused/" "^${PROJECT_SOURCE_DIR}/icons/povray/" "^${PROJECT_SOURCE_DIR}/icons/src/" "^${PROJECT_SOURCE_DIR}/packaging/" "^${PROJECT_SOURCE_DIR}/utils/local/" "^${PROJECT_SOURCE_DIR}/utils/snappea/" "^${PROJECT_SOURCE_DIR}/www/" "^${PROJECT_SOURCE_DIR}/\\\\.git/" "^${PROJECT_SOURCE_DIR}.*/\\\\.svn/" "\\\\.DS_Store$" ) include(CPack) regina-4.95/docs/CMakeLists.txt000644 000765 000024 00000000133 12234011536 016271 0ustar00babstaff000000 000000 ADD_SUBDIRECTORY(man) IF(DOXYGEN_FOUND) ADD_SUBDIRECTORY(engine) ENDIF(DOXYGEN_FOUND) regina-4.95/docs/engine/CMakeLists.txt000644 000765 000024 00000001131 12234011536 017535 0ustar00babstaff000000 000000 # TODO: The doxygen files generated in the build tree are not removed # by "make clean". ADD_CUSTOM_COMMAND(OUTPUT index.html COMMAND ${DOXYGEN_EXECUTABLE} ${PROJECT_BINARY_DIR}/engine/doxygen/docs.conf COMMAND cp `find "${PROJECT_SOURCE_DIR}/engine" -name '*.png'` . ) ADD_CUSTOM_TARGET(docs ALL DEPENDS index.html) INSTALL( DIRECTORY ${PROJECT_BINARY_DIR}/docs/engine/ DESTINATION ${PKGDATADIR}/engine-docs FILES_MATCHING PATTERN CMakeFiles EXCLUDE PATTERN "*.css" PATTERN "*.dot" PATTERN "*.gif" PATTERN "*.html" PATTERN "*.jpg" PATTERN "*.png" PATTERN "*.txt" ) regina-4.95/docs/man/CMakeLists.txt000644 000765 000024 00000000453 12234011536 017051 0ustar00babstaff000000 000000 SET(man1dir ${MANDIR}/man1) SET(man1_files regconcat.1 regconvert.1 regfiledump.1 regfiletype.1 sigcensus.1 tricensus.1 tricensus-mpi.1 tricensus-mpi-status.1 trisetcmp.1 regina-gui.1 regina-python.1 ) INSTALL(FILES ${man1_files} DESTINATION ${man1dir} COMPONENT Runtime) regina-4.95/docs/man/Makefile.man000644 000765 000024 00000000472 12234011536 016524 0ustar00babstaff000000 000000 default: # This Makefile should only be used by the software authors. # To rebuild manpages, run "make -f Makefile.man man". # You will need a working docbook2man installation. # man: cd ../../qtui/doc/regina && \ docbook2man -o ../../../docs/man manonly.docbook clean: rm -f manpage.links manpage.refs regina-4.95/docs/man/regconcat.1000644 000765 000024 00000003534 12236546232 016353 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGCONCAT" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regconcat \- Combine several Regina data files .SH SYNOPSIS \fBregconcat\fR [ \fB-o \fIoutput-file\fB\fR ] \fB\fIinput-file\fB\fR\fI ...\fR .SH "DESCRIPTION" .PP This utility combines several individual Regina data files into a single larger data file. The new file will have a container as its top-level packet, and beneath this container will be the packet trees from each of the input files that you supply. .PP If an output file is specified through option \fB-o\fR, the new file will be written as compressed XML to this output file. Otherwise the new file will be written as uncompressed XML to standard output. .SH "OPTIONS" .TP \fB-o \fIoutput-file\fB\fR Specifies that the new combined data file should be written as compressed XML to the given output file. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/regconcat\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\regconcat.exe\&. .SH "SEE ALSO" .PP regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/regconvert.1000644 000765 000024 00000004244 12236546232 016563 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGCONVERT" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regconvert \- Convert between different Regina file formats .SH SYNOPSIS \fBregconvert\fR [ \fB-x\fR | \fB-u\fR ] \fB\fIold-file\fB\fR [ \fB\fInew-file\fB\fR ] .SH "DESCRIPTION" .PP This utility converts back and forth between different Regina file formats. .sp .RS .B "Note:" As of September\~2013, the ancient binary file format that was used with Regina\~2.x is no longer supported. This format has not been in use for over a decade now. If you still have files in this format, you will need to download Regina\~4.93 to convert them. .RE .PP The argument \fIold-file\fR should be the file to read and convert; the argument \fInew-file\fR should be the name of the new converted file to write. These two filenames may be the same, in which case the old file will be replaced with the new. .PP If the argument \fInew-file\fR is missing then the new file will be written to standard output, which forces the output to be uncompressed XML (see option \fB-u\fR). .SH "OPTIONS" .TP \fB-x (default)\fR Convert to compressed XML\&. .TP \fB-u\fR Convert to plain (uncompressed) XML\&. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/regconvert\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\regconvert.exe\&. .SH "SEE ALSO" .PP regfiletype, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/regfiledump.1000644 000765 000024 00000005605 12236546232 016712 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGFILEDUMP" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regfiledump \- Dump the contents of a Regina data file .SH SYNOPSIS \fBregfiledump\fR [ \fB-f\fR | \fB-l\fR | \fB-n\fR ] [ \fB-c\fR ] \fB\fIfile\fB\fR [ \fB\fIpacket-label\fB\fR\fI ...\fR ] .SH "DESCRIPTION" .PP This utility dumps the contents of the given Regina data file to standard output in a human-readable format. .PP If a list of packet labels is given on the command-line, only those packets will be output. Otherwise all packets in the given file will be output. .SH "OPTIONS" .TP \fB-f (default)\fR Output full packet details. The output for each packet will cover several lines, beginning with basic details (such as the packet label and type) followed by the packet's long description. .TP \fB-l\fR Output a list of packet labels and types only, one packet per line. .TP \fB-n\fR Don't output any packets at all; this option forces a packet count (see option \fB-c\fR). .TP \fB-c\fR Finish the output with a total count of all packets in the file. .SH "INTERNATIONALISATION" .PP If any packets contain international characters, Regina will attempt to convert these to your local character encoding as it writes them to the output. .PP You can tell Regina what character encoding to use by setting standard locale-related environment variables, such as \fBLANG\fR, \fBLC_CTYPE\fR or \fBLC_ALL\fR\&. .PP For example, if \fBLANG\fR is set to en_AU then output will be written in the Western European character set ISO-8859-1, and if \fBLANG\fR is set to en_AU.UTF-8 then output will be written in the universal character set UTF-8\&. .PP Typically these environment variables will already be set for you when you install your \fBGNU/Linux\fR system, and Regina will just use the right character set out of the box. See your \fBGNU/Linux\fR system reference for further information on supporting different locales. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/regfiledump\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\regfiledump.exe\&. .SH "SEE ALSO" .PP regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/regfiletype.1000644 000765 000024 00000003131 12236546232 016716 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGFILETYPE" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regfiletype \- Identify the formats of Regina data files .SH SYNOPSIS \fBregfiletype\fR \fB\fIfile\fB\fR\fI ...\fR .SH "DESCRIPTION" .PP Between Regina versions 2.4 and 3.0, the data files changed from using an impenetrable and undocumented binary format to using (optionally compressed) XML\&. .PP This utility determines which of these formats a given Regina data file is in. .PP You may pass multiple files on the command line; the format of each file will be written to standard output. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/regfiletype\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\regfiletype.exe\&. .SH "SEE ALSO" .PP regconvert, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/regina-gui.1000644 000765 000024 00000003140 12236546232 016426 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGINA-GUI" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regina-gui \- Regina's graphical user interface .SH SYNOPSIS \fBregina-gui\fR [ \fB\fIfile\fB\fR\fI ...\fR ] .SH "DESCRIPTION" .PP Regina is a software package for studying 3-manifold triangulations and normal surfaces. Other key features include angle structures, census enumeration, combinatorial recognition of triangulations, and high-level tasks such as 3-sphere recognition and connected sum decomposition. Regina comes with a full graphical user interface, and also offers Python bindings and a low-level C++ programming interface. .PP This starts the full graphical user interface, and is the usual way of starting Regina. Any files passed on the command-line will be opened on startup. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this is the program that runs when you open it. The executable itself is simply called \fBRegina\fR, not \fBregina-gui\fR\&. .SH "SEE ALSO" .PP regina-python\&. .PP Regina comes with a rich users' handbook, which you can access via Help->Regina Handbook in the menu. You can also read the users' handbook online at \fBhttp://regina.sourceforge.net/docs/\fR\&. .SH "AUTHOR" .PP Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/regina-python.1000644 000765 000024 00000017035 12236546232 017173 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "REGINA-PYTHON" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME regina-python \- Regina's command-line Python interface .SH SYNOPSIS \fBregina-python\fR [ \fB-q, --quiet\fR | \fB-v, --verbose\fR ] [ \fB-n, --nolibs\fR ] [ \fB-a, --noautoimport\fR ] \fBregina-python\fR [ \fB-q, --quiet\fR | \fB-v, --verbose\fR ] [ \fB-n, --nolibs\fR ] [ \fB-a, --noautoimport\fR ] [ \fB-i, --interactive\fR ] \fB\fIscript\fB\fR [ \fB\fIscript-args\fB\fR ] .SH "DESCRIPTION" .PP Regina is a software package for studying 3-manifold triangulations and normal surfaces. Other key features include angle structures, census enumeration, combinatorial recognition of triangulations, and high-level tasks such as 3-sphere recognition and connected sum decomposition. Regina comes with a full graphical user interface, and also offers Python bindings and a low-level C++ programming interface. .PP This command starts an interactive Python session for Regina. This will be a command-line Python session, with direct text input/output and no graphical user interface. All of the objects, clases and methods from Regina's mathematical engine will be made available through the module \fIregina\fR, which will be imported on startup (effectively running import regina). Moreover, unless the option \fB--noautoimport\fR is passed, all of Regina's objects, classes and methods will be imported directly into the current namespace (effectively running from regina import\~*). .PP If you have frequently-used code, you can store it in a \fIuser library\fR\&. At the beginning of each Python session, Regina will automatically run all of the code in all of your user libraries. The list of user libraries will be read from the text file \fI~/.regina-libs\fR, which should contain one library filename per line. Blank lines and lines beginning with a hash (#) will be ignored. You can also configure this list of libraries through the graphical user interface: see the Python options page. .PP Instead of starting an interactive Python session, you can pass a Python script (with arguments if desired). In this case Regina will run the script (after first importing the \fIregina\fR module and loading any user libraries). If you pass \fB--interactive\fR, Regina will leave you at a Python prompt once the script finishes; otherwise it will exit Python and return you to the command line. .SH "OPTIONS" .TP \fB-q\fR .TP \fB--quiet\fR Start in quiet mode. No output will be produced except for serious errors. In particular, warnings will be suppressed. This is equivalent to setting the environment variable \fIREGINA_VERBOSITY\fR=0\&. .TP \fB-v\fR .TP \fB--verbose\fR Start in verbose mode. Additional diagnostic information will be output. This is equivalent to setting the environment variable \fIREGINA_VERBOSITY\fR=2\&. .TP \fB-n\fR .TP \fB--nolibs\fR Do not load any user libraries when the session starts. User libraries are discussed in the overview above. .TP \fB-a\fR .TP \fB--noautoimport\fR Still import the \fIregina\fR module, but do not automatically import all of Regina's objects, classes and methods into the current namespace (that is, do not run from regina import\~*). This means that (for example) the main 3-manifold triangulation class must be accessed as regina.NTriangulation, not just NTriangulation\&. .TP \fB-i\fR .TP \fB--interactive\fR Run the script in interactive mode. After executing the given script, Regina will leave you in the Python interpreter to run your own additional commands. This option is only available when a script is passed. If no script is passed, \fBregina-python\fR will always start in interactive mode. .SH "ENVIRONMENT VARIABLES" .PP The following environment variables influence the behaviour of this program. Each variable can also be set in the local configuration file \fI~/.regina-python\fR using a line of the form \fIoption\fR=\fIvalue\fR\&. Environment variables will take precedence over values in the configuration file. .TP \fB\fIREGINA_VERBOSITY\fB\fR Specifies how much output should be generated. Recognised values are: .RS .TP \fB0\fR Display errors only; this is equivalent to passing the option \fB--quiet\fR\&. .TP \fB1\fR Display errors and warnings; this is the default. .TP \fB2\fR Display errors, warnings and diagnostic output; this is equivalent to passing the option \fB--verbose\fR\&. .RE .TP \fB\fIREGINA_PYTHON\fB\fR The command used to start the Python interpreter. By default, Regina tries to run the same version of Python that it was built against. In general you should use the same version of Python that Regina was built against; otherwise Python might not be able to load the \fIregina\fR module. In normal situations you should never need to set this option yourself. .TP \fB\fIREGINA_HOME\fB\fR The directory in which Regina's data files are installed. This should be the directory containing the \fIicons/\fR subdirectory, the \fIexamples/\fR subdirectory and so on. If you are running Regina directly out of the source tree, this defaults to the top-level source directory. If you are running Regina from a proper installation, this defaults to the corresponding installation directory. In normal situations you should never need to set this option yourself. .sp .RS .B "Warning:" When running from a proper installation, the default \fIREGINA_HOME\fR is hard-wired into the startup script (it is set at compile time). If you install Regina into one directory but then move it by hand into another, the default \fIREGINA_HOME\fR will be incorrect. .RE .TP \fB\fIREGINA_PYLIBDIR\fB\fR The directory containing the Python module \fIregina.so\fR\&. If you are running Regina directly out of the source tree, this defaults to a directory within this source tree. If you are running Regina from a proper installation, this defaults to the corresponding installation directory. If you have installed Regina's Python module in a standard Python location (i.e., Python can import it directly without extending sys.path), then \fIREGINA_PYLIBDIR\fR should be left empty or undefined. In normal situations you should never need to set this option yourself. .sp .RS .B "Warning:" Like \fIREGINA_HOME\fR, when running from a proper installation the default \fIREGINA_PYLIBDIR\fR is hard-wired into the startup script. If you install Regina into one directory but then move it by hand into another, the default \fIREGINA_PYLIBDIR\fR will be incorrect. .RE .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/regconcat\&. .SH "WINDOWS USERS" .PP The command \fBregina-python\fR is not available under \fBWindows\fR\&. However, you can still use Python scripting in Regina's graphical user interface, by opening a graphical Python console or using script packets. .SH "SEE ALSO" .PP regina-gui\&. .PP Regina comes with thorough API documentation, which describes in detail all of the objects, classes and methods that Regina makes available to Python. You can access this documentation via Help->Python API Reference in the graphical user interface, or read it online at \fBhttp://regina.sourceforge.net/engine-docs/\fR\&. .SH "AUTHOR" .PP Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/sigcensus.1000644 000765 000024 00000004315 12236546232 016407 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "SIGCENSUS" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME sigcensus \- Form a census of splitting surface signatures .SH SYNOPSIS \fBsigcensus\fR \fB\fIorder\fB\fR .SH "DESCRIPTION" .PP Forms a census of all splitting surface signatures of the given order. The \fIorder\fR is the number of quadrilaterals in the resulting splitting surface. .PP The signatures will be written to standard output, one per line, followed by a count of the total number of signatures found. .PP Each signature will be output precisely once up to equivalence. Signatures are considered equivalent if they are related by some combination of: .TP 0.2i \(bu relabelling symbols; .TP 0.2i \(bu rotating an individual cycle; .TP 0.2i \(bu inverting an individual cycle (i.e., reversing the cycle and changing the case of each symbol in the cycle); .TP 0.2i \(bu reversing all cycles without changing the case of any symbols. .PP Upper-case symbols in signatures are not yet supported; this program will only output signatures whose symbols are all lower-case. .PP For more information on splitting surface signatures, see Burton's PhD thesis at \fBhttp://www.maths.uq.edu.au/~bab/papers/\fR\&. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/sigcensus\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\sigcensus.exe\&. .SH "SEE ALSO" .PP tricensus, tricensus-mpi, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/tricensus-mpi-status.1000644 000765 000024 00000004461 12236546232 020531 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "TRICENSUS-MPI-STATUS" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME tricensus-mpi-status \- Summarise the log file of an MPI census of triangulations .SH SYNOPSIS \fBtricensus-mpi-status\fR \fB\fIlog-file\fB\fR .SH "DESCRIPTION" .PP This utility reads a log file produced by a \fBtricensus-mpi\fR job, and writes a human-readable summary to standard output. It can be used for either jobs that have finished or jobs that are still running, and it can happily read logs that have been compressed using gzip or bzip2. .PP The logs produced by \fBtricensus-mpi\fR are very detailed, including timestamps, details of which slaves have taken which tasks, and how many triangulations each task has produced. This utility distills this detailed log into an easy-to-read summary, with one line for each face pairing. .PP Output will only appear for face pairings that have been examined so far (which includes face pairings still being processed). This output will include: .TP 0.2i \(bu whether processing for each face pairing has finished; .TP 0.2i \(bu the number of triangulations found so far for each face pairing; .TP 0.2i \(bu the number of subsearches generated and/or finished for each face pairing (only relevant when running in subsearch mode). .PP The final line of output will list the total number of triangulations found so far, whether the census has finished, and if not, when the last log entry was written. .PP For further explanation of the terminology used above, see the \fBtricensus-mpi\fR reference. .SH "EXAMPLES" .PP See the \fBtricensus-mpi\fR reference for a sample session in which \fBtricensus-mpi-status\fR is used. .SH "MACOS\\~X AND WINDOWS USERS" .PP This utility is not shipped with the drag-and-drop app bundle for \fBMacOS\~X\fR or with the \fBWindows\fR installer. .SH "SEE ALSO" .PP tricensus-mpi, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/tricensus-mpi.1000644 000765 000024 00000024770 12236546232 017215 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "TRICENSUS-MPI" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME tricensus-mpi \- Distribute a triangulation census amongst several machines using MPI .SH SYNOPSIS \fBtricensus-mpi\fR [ \fB-D, --depth=\fIlevels\fB\fR ] [ \fB-x, --dryrun\fR ] [ \fB-2, --dim2\fR ] [ \fB-o, --orientable\fR | \fB-n, --nonorientable\fR ] [ \fB-f, --finite\fR | \fB-d, --ideal\fR ] [ \fB-m, --minimal\fR | \fB-M, --minprime\fR | \fB-N, --minprimep2\fR ] [ \fB-s, --sigs\fR ] \fB\fIpairs-file\fB\fR \fB\fIoutput-file-prefix\fB\fR .SH "DESCRIPTION" .PP Allows multiple processes, possibly running on a cluster of different machines, to collaborate in forming a census of 3-manifold or 2-manifold triangulations. Coordination is done through MPI (the Message Passing Interface), and the entire census is run as a single MPI job. This program is well suited for high-performance clusters. .PP The default behaviour is to enumerate 3-manifold triangulations. If you wish to enumerate 2-manifold triangulations instead, you must pass \fB--dim2\fR\&. .PP To prepare a census for distribution amongst several processes or machines, the census must be split into smaller pieces. Running \fBtricensus\fR with option \fB--genpairs\fR (which is very fast) will create a list of face pairings, each of which must be analysed in order to complete the census. .PP The full list of face pairings should be stored in a single file, which is passed on the command-line as \fIpairs-file\fR\&. This file must contain one face pairing per line, and each of these face pairings must be in canonical form (i.e., must be a minimal representative of its isomorphism class). The face pairings generated by \fBtricensus --genpairs\fR are guaranteed to satisfy these conditions. .PP The \fBtricensus-mpi\fR utility has two modes of operation: default mode, and subsearch mode. These are explained separately under modes of operation below. .PP In both modes, one MPI process acts as the controller and the remaining processes all act as slaves. The controller reads the list of face pairings from \fIpairs-file\fR, constructs a series of tasks based on these, and farms these tasks out to the slaves for processing. Each slave processes one task at a time, asking the controller for a new task when it is finished with the previous one. .PP At the end of each task, if any triangulations were found then the slave responsible will save these triangulations to an output file. The output file will have a name of the form \fIoutput-file-prefix_p\&.rga\fR in default mode or \fIoutput-file-prefix_p-s\&.rga\fR in subsearch mode. Here \fIoutput-file-prefix\fR is passed on the command line, \fIp\fR is the number of the face pairing being processed, and \fIs\fR is the number of the subsearch within that face pairing (both face pairings and subsearches are numbered from 1 upwards). If no triangulations were found then the slave will not write any output file at all. .PP The controller and slave processes all take the same \fBtricensus-mpi\fR options (excluding MPI-specific options, which are generally supplied by an MPI wrapper program such as \fBmpirun\fR or \fBmpiexec\fR). The different roles of the processes are determined solely by their MPI process rank (the controller is always the process with rank 0). It should therefore be possible to start all MPI processes by running a single command, as illustrated in the examples below. .PP As the census progresses, the controller keeps a detailed log of each slave's activities, including how long each slave task has taken and how many triangulations have been found. This log is written to the file \fIoutput-file-prefix\&.log\fR\&. The utility \fBtricensus-mpi-status\fR can parse this log and produce a shorter human-readable summary. .sp .RS .B "Important:" It is \fBhighly recommended\fR that you use the \fB--sigs\fR option. This will keep output files small, and will significantly reduce the memory footprint of \fBtricensus-mpi\fR itself. .RE .SH "MODES OF OPERATION" .PP As discussed above, there are two basic modes of operation. These are default mode (used when \fB--depth\fR is not passed), and subsearch mode (used when \fB--depth\fR is passed). .TP 0.2i \(bu In \fBdefault mode\fR, the controller simply reads the list of face pairings and gives each pairing to a slave for processing, one after another. .TP 0.2i \(bu In \fBsubsearch mode\fR, more work is pushed to the controller and the slave tasks are shorter. Here the controller reads one face pairing at a time and begins processing that face pairing. A fixed depth is supplied in the argument \fB--depth\fR; each time that depth is reached in the search tree, the subsearch from that point on is given as a task to the next idle slave. Meanwhile the controller backtracks (as though the subsearch had finished) and continues, farming the next subsearch out when the given depth is reached again, and so on. .PP The modes can be visualised as follows. For each face pairing, consider the corresponding recursive search as a large search tree. In default mode, the entire tree is processed at once as a single slave task. In subsearch mode, each subtree rooted at the given depth is processed as a separate slave task (and all processing between the root and the given depth is done by the controller). .PP The main difference between the different modes of operation is the lengths of the slave tasks, which can have a variety of effects. .TP 0.2i \(bu In default mode the slave tasks are quite long. This means the parallelisation can become very poor towards the end of the census, with some slaves sitting idle for a long time as they wait for the remaining slaves to finish. .TP 0.2i \(bu As we move to subsearch mode with increasing depth, the slave tasks become shorter and the slaves' finish times will be closer together (thus avoiding the idle slave inefficiency described above). Moreover, with a more refined subsearch, the progress information stored in the log will be more detailed, giving a better idea of how long the census has to go. On the other hand, more work is pushed to the single-process controller (risking a bottleneck if the depth is too great, with slaves now sitting idle as they wait for new tasks). In addition the MPI overhead is greater, and the number of output files can become extremely large. .PP In the end, experimentation is the best way to decide whether to run in subsearch mode and at what depth. Be aware of the option \fB--dryrun\fR, which can give a quick overview of the search space (and in particular, show how many subsearches are required for each face pairing at any given depth). .SH "OPTIONS" .PP The census options accepted by \fBtricensus-mpi\fR are identical to the options for \fBtricensus\fR See the \fBtricensus\fR reference for details. .PP Some options from \fBtricensus\fR are not available here (e.g., tetrahedra and boundary options), since these must be supplied earlier on when generating the initial list of face pairings. .PP There are new options specific to \fBtricensus-mpi\fR, which are as follows. .TP \fB-D, --depth=\fIlevels\fB\fR Indicates that subsearch mode should be used (instead of default mode). The argument \fIlevels\fR specifies at what depth in the search tree processing should pass from the controller to a new slave task. The given depth must be strictly positive (running at depth zero is equivalent to running in default mode). See the modes of operation section above for further information, as well as hints on choosing a good value for \fIlevels\fR\&. .TP \fB-x, --dryrun\fR Specifies that a fast dry run should be performed, instead of a full census. In a dry run, each time a slave accepts a task it will immediately mark it as finished with no triangulations found. The behaviour of the controller remains unchanged. The result will be an empty census. The benefit of a dry run is the log file it produces, which will show precisely how face pairings would be divided into subsearches in a real census run. In particular, the log file will show how many subsearches each face pairing produces (the utility \fBtricensus-mpi-status\fR can help extract this information from the log). At small subsearch depths, a dry run should be extremely fast. As the depth increases however, the dry run will become slower due to the extra work given to the controller. This option is only useful in subsearch mode (it can be used in default mode, but the results are uninteresting). See the modes of operation section above for further details. .SH "EXAMPLES" .PP Suppose we wish to form a census of all 6-tetrahedron closed non-orientable triangulations, optimised for prime minimal P2-irreducible triangulations (so some non-prime, non-minimal or non-P2-irreducible triangulations may be omitted). .PP We begin by using \fBtricensus\fR to generate a full list of face pairings. .nf example$ \fBtricensus --genpairs -t 6 -i > 6.pairs\fR Total face pairings: 97 example$ .fi .PP We now use \fBtricensus-mpi\fR to run the distributed census. A wrapper program such as \fBmpirun\fR or \fBmpiexec\fR can generally be used to start the MPI processes, though this depends on your specific MPI implementation. The following command runs a distributed census on 10 processors using the MPICH implementation of MPI\&. .nf example$ \fBmpirun -np 10 /usr/bin/tricensus-mpi -Nnf 6.pairs 6-nor\fR example$ .fi .PP The current state of processing is kept in the controller log \fI6-nor.log\fR\&. You can watch this log with the help of \fBtricensus-mpi-status\fR\&. .nf example$ \fBtricensus-mpi-status 6-nor.log\fR Pairing 1: done, 0 found ... Pairing 85: done, 0 found Pairing 86: done, 7 found Pairing 87: running Pairing 88: running Still running, 15 found, last activity: Wed Jun 10 05:57:34 2009 example$ .fi .PP Once the census is finished, the resulting triangulations will be saved in files such as \fI6-nor_8.rga\fR, \fI6-nor_86.rga\fR and so on. .SH "MACOS\\~X AND WINDOWS USERS" .PP This utility is not shipped with the drag-and-drop app bundle for \fBMacOS\~X\fR or with the \fBWindows\fR installer. .SH "SEE ALSO" .PP regconcat, sigcensus, tricensus, tricensus-mpi-status, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/tricensus.1000644 000765 000024 00000030717 12236546232 016430 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "TRICENSUS" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME tricensus \- Form a census of 3-manifold triangulations .SH SYNOPSIS \fBtricensus\fR [ \fB-t, --tetrahedra=\fItetrahedra\fB\fR ] [ \fB-2, --dim2\fR ] [ \fB-b, --boundary\fR | \fB-i, --internal\fR | \fB-B, --bdryfaces=\fItriangles\fB\fR ] [ \fB-o, --orientable\fR | \fB-n, --nonorientable\fR ] [ \fB-f, --finite\fR | \fB-d, --ideal\fR ] [ \fB-m, --minimal\fR | \fB-M, --minprime\fR | \fB-N, --minprimep2\fR ] [ \fB-s, --sigs\fR | \fB-c, --subcontainers\fR ] [ \fB-p, --genpairs\fR | \fB-P, --usepairs\fR ] \fB\fIoutput-file\fB\fR \fBtricensus\fR \fB--help\fR .SH "DESCRIPTION" .PP Forms a census of all 3-manifold or 2-manifold triangulations that satisfy some set of conditions. .PP These conditions are specified using various command-line arguments. The only condition that you \fBmust\fR provide is the number of tetrahedra, but there are many other options available. .PP The default behaviour is to enumerate 3-manifold triangulations. If you wish to enumerate 2-manifold triangulations instead, you must pass \fB--dim2\fR\&. .PP Each triangulation will be output precisely once up to combinatorial isomorphism. Invalid 3-manifold triangulations (i.e., triangulations with edges identified to themselves in reverse, or vertices whose links have boundary but are not discs) will not be output at all. .PP As the census progresses, the state of progress will be written (slowly) to standard output. Once the census is complete, the full census will be saved to the given output file. .PP You can use the options \fB--genpairs\fR and \fB--usepairs\fR to split a census into smaller pieces. See also \fBtricensus-mpi\fR, a more powerful tool that allows you to distribute a census across a high-performance computing cluster. .sp .RS .B "Caution:" .PP A census with even a small number of tetrahedra can take an incredibly long time to run, and can chew up massive amounts of memory. It is recommended that you try very small censuses to begin with (such as 3 or 4 tetrahedra), and work upwards to establish the limits of your machine. .PP For very large census runs, it is \fBhighly recommended\fR that you use the \fB--sigs\fR option, which will keep the output file small and significantly reduce the memory footprint. .RE .SH "OPTIONS" .TP \fB-t, --tetrahedra=\fItetrahedra\fB\fR Specifies the number of tetrahedra used to build the triangulations. If \fB--dim2\fR is passed, this same option must be used to specify the number of triangles instead. .TP \fB-2, --dim2\fR Build a census of 2-manifold triangulations, not 3-manifold triangulations. This is incompatible with several options; for other options it simply translates the relevant constraint into two dimensions. See each individual option for details on how it interacts with \fB--dim2\fR\&. .TP \fB-b, --boundary\fR Only produce triangulations with at least one boundary triangle. If \fB--dim2\fR is passed, this specifies at least one boundary edge. .TP \fB-i, --internal\fR Only produce triangulations with all triangles internal (i.e., with no boundary triangles). If \fB--dim2\fR is passed, this indicates that all edges must be internal. .TP \fB-B, --bdryfaces=\fItriangles\fB\fR Only produce triangulations with the precise number of boundary triangles specified. If \fB--dim2\fR is passed, this specifies the number of boundary edges. .TP \fB-o, --orientable\fR Only produce orientable triangulations. .TP \fB-n, --nonorientable\fR Only produce non-orientable triangulations. .TP \fB-f, --finite\fR Only produce finite triangulations (triangulations with no ideal vertices). This option cannot be used with \fB--dim2\fR\&. .TP \fB-d, --ideal\fR Only produce triangulations with at least one ideal vertex. There might or might not be internal vertices (whose links are spheres) as well. This option cannot be used with \fB--dim2\fR\&. .TP \fB-m, --minimal\fR Do not include triangulations that are obviously non-minimal. This option uses a series of fast tests that try to eliminate non-minimal triangulations, but that are not always conclusive. If Regina cannot quickly tell whether a triangulation is non-minimal, it will place the triangulation in the census regardless. .TP \fB-M, --minprime\fR Do not include triangulations that are obviously non-minimal, non-prime and/or disc-reducible. This can significantly speed up the census and vastly reduce the final number of triangulations produced. As above, this option uses a series of fast tests that are not always conclusive. If Regina cannot quickly tell whether a triangulation is non-minimal, non-prime or disc-reducible, it will place the triangulation in the census regardless. This option cannot be used with \fB--dim2\fR\&. .TP \fB-N, --minprimep2\fR Do not include triangulations that are obviously non-minimal, non-prime, P2-reducible and/or disc-reducible. This can significantly speed up the census and vastly reduce the final number of triangulations produced, even more so than \fB--minprime\fR\&. As above, this option uses a series of fast tests that are not always conclusive. If Regina cannot quickly tell whether a triangulation is non-minimal, non-prime, P2-reducible or disc-reducible, it will place the triangulation in the census regardless. This option cannot be used with \fB--dim2\fR\&. .TP \fB-s, --sigs\fR Instead of writing a full Regina data file, just output a list of isomorphism signatures. The output file will be a plain text file. Each line will be a short string of letters, digits and punctuation that uniquely encodes a triangulation up to combinatorial isomorphism. You can import this text file from within Regina by selecting File->Import->Isomorphism Signature List from the menu. This option is highly recommended for large census enumerations. First, the output file will be considerably smaller. More importantly, the memory footprint of \fBtricensus\fR will also be much smaller: triangulations can be written to the output file and forgotten immediately, instead of being kept in memory to construct a final Regina data file. .TP \fB-c, --subcontainers\fR For each face pairing, a new container will be created, and resultant triangulations will be placed into these containers. These containers will be created even if the face pairing results in no triangulations. This option cannot be used with \fB--sigs\fR\&. .TP \fB-p, --genpairs\fR Only generate face pairings, not triangulations. The outermost layer of the census code involves pairing off the faces of individual tetrahedra without determining the corresponding gluing permutations. For each face pairing that is produced, Regina will try many different sets of gluing permutations and generated the corresponding triangulations. Face pairing generation consumes a very small fraction of the total census runtime, and effectively divides the census into multiple pieces. This option allows you to quickly generate a complete list of possible face pairings, so that you can feed subsets of this list to different machines to work on simultaneously. You can coordinate this manually, or you can use \fBtricensus-mpi\fR to coordinate it for you on a high-performance cluster. The list of all face pairings will be written to the given output file in text format (though you may omit the output file from the command line, in which case the face pairings will be written to standard output). If you are coordinating your sub-censuses manually, you can use the option \fB--usepairs\fR to generate triangulations from a subset of these face pairings. Options for orientability, finiteness or minimality cannot be used with \fB--genpairs\fR; instead you should use them later with \fB--usepairs\fR, or pass them to \fBtricensus-mpi\fR\&. This option does not come with progress reporting, though typically it runs fast enough that this does not matter. You can always track the state of progress by counting lines in the output file. If \fB--dim2\fR is passed, this generates edge pairings accordingly. .TP \fB-P, --usepairs\fR Use only the given subset of face pairings to build the triangulations. Each face pairing that is processed must be in canonical form, i.e., must be a minimal representative of its isomorphism class. All face pairings generated using \fB--genpairs\fR are guaranteed to satisfy this condition. Face pairings should be supplied on standard input, one per line. They should be listed in the format produced by the option \fB--genpairs\fR\&. This option effectively lets you run a subset of a larger census. See \fB--genpairs\fR for further details on how to split a census into subsets that can run simultaneously on different machines, or \fBtricensus-mpi\fR which can coordinate this process using MPI on a high-performance cluster. Options for tetrahedra or boundary triangles cannot be used with \fB--usepairs\fR; instead you should pass them earlier along with \fB--genpairs\fR when you split the original census into pieces. If \fB--dim2\fR is passed, this takes a list of edge pairings accordingly. .SH "EXAMPLES" .PP The following command forms a census of all 3-tetrahedron closed non-orientable triangulations and puts the results in the file \fIresults.rga\fR\&. To ensure that triangulations are closed we use the options \fB-i\fR (no boundary triangles) and \fB-f\fR (no ideal vertices). .nf example$ \fBtricensus -t 3 -nif results.rga\fR Starting census generation... 0:1 0:0 1:0 1:1 | 0:2 0:3 2:0 2:1 | 1:2 1:3 2:3 2:2 0:1 0:0 1:0 2:0 | 0:2 1:2 1:1 2:1 | 0:3 1:3 2:3 2:2 0:1 0:0 1:0 2:0 | 0:2 2:1 2:2 2:3 | 0:3 1:1 1:2 1:3 1:0 1:1 2:0 2:1 | 0:0 0:1 2:2 2:3 | 0:2 0:3 1:2 1:3 Finished. Total triangulations: 5 example$ .fi .PP The following command forms a census of 4-tetrahedron closed orientable triangulations, where the census creation is optimised for prime minimal triangulations. Although all prime minimal triangulations will be included, there may be some non-prime or non-minimal triangulations in the census also. .nf example$ \fBtricensus -t 4 -oifM results.rga\fR Starting census generation... 0:1 0:0 1:0 1:1 | 0:2 0:3 2:0 2:1 | 1:2 1:3 3:0 3:1 | 2:2 ... 0:1 0:0 1:0 1:1 | 0:2 0:3 2:0 3:0 | 1:2 2:2 2:1 3:1 | 1:3 ... ... 1:0 1:1 2:0 3:0 | 0:0 0:1 2:1 3:1 | 0:2 1:2 3:2 3:3 | 0:3 ... Finished. Total triangulations: 17 example$ .fi .PP The following command generates all face pairings for a 5-tetrahedron census in which all triangulations have precisely two boundary triangles. The face pairings will be written to \fIpairings.txt\fR, whereupon they can be broken up and distributed for processing at a later date. .nf example$ \fBtricensus --genpairs -t 5 -B 2 pairings.txt\fR Total face pairings: 118 example$ .fi .PP The face pairings generated in the previous example can then be fleshed out into a full census of all 3-manifold triangulations with five tetrahedra, precisely two boundary triangles and no ideal vertices as follows. The number of tetrahedra and boundary triangles were already specified in the previous command, and cannot be supplied here. The face pairings will be read from \fIpairings.txt\fR, and the final census will be written to \fIresults.rga\fR\&. .nf example$ \fBtricensus --usepairs -f results.rga < pairings.txt\fR Trying face pairings... 0:1 0:0 1:0 1:1 | 0:2 0:3 2:0 2:1 | 1:2 1:3 3:0 3:1 | 2:2 ... 0:1 0:0 1:0 1:1 | 0:2 0:3 2:0 2:1 | 1:2 1:3 3:0 3:1 | 2:2 ... ... ... (running through all 118 face pairings) ... 1:0 2:0 3:0 4:0 | 0:0 2:1 3:1 4:1 | 0:1 1:1 3:2 4:2 | 0:2 ... Total triangulations: 5817 example$ .fi .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/tricensus\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\tricensus.exe\&. .SH "SEE ALSO" .PP sigcensus, tricensus-mpi, regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/man/trisetcmp.1000644 000765 000024 00000007267 12236546232 016427 0ustar00babstaff000000 000000 .\" This manpage has been automatically generated by docbook2man .\" from a DocBook document. This tool can be found at: .\" .\" Please send any bug reports, improvements, comments, patches, .\" etc. to Steve Cheng . .TH "TRISETCMP" "1" "06 November 2013" "" "The Regina Handbook" .SH NAME trisetcmp \- Compare triangulations between two Regina data files .SH SYNOPSIS \fBtrisetcmp\fR [ \fB-m\fR | \fB-n\fR ] [ \fB-s\fR ] \fB\fIfile1\fB\fR \fB\fIfile2\fB\fR .SH "DESCRIPTION" .PP Compares all triangulations in the first file against all triangulations in the second file, looking for pairs of triangulations that are combinatorially isomorphic. .PP The two given files must be Regina data files. A full list of matches (or a full list of non-matches if \fB-n\fR is passed) is written to standard output. A match occurs when some triangulation from \fIfile1\fR is combinatorially isomorphic to some triangulation from \fIfile2\fR (i.e., identical up to a relabelling of tetrahedra and their vertices). .PP This utility can also do subcomplex testing instead of full isomorphism testing. See the option \fB-s\fR for details. .SH "OPTIONS" .TP \fB-m (default)\fR Output matches only. All isomorphic matches between triangulations in \fIfile1\fR and triangulations in \fIfile2\fR will be listed. .TP \fB-n\fR Output non-matches only. All triangulations from \fIfile1\fR with no isomorphic match in \fIfile2\fR will be listed, and vice versa. If \fB-s\fR is passed then non-matches are tested in one direction only, not both; see below for details. .TP \fB-s\fR Instead of testing triangulations for isomorphism, test whether one triangulation is isomorphic to a subcomplex of the other. In the default case of \fB-m\fR (output matches only), this program outputs all instances where a triangulation from \fIfile1\fR is isomorphic to a subcomplex of a triangulation from \fIfile2\fR\&. In the case of \fB-n\fR (output non-matches only), this program outputs all triangulations from \fIfile1\fR that are not isomorphic to a subcomplex of any triangulation from \fIfile2\fR\&. .SH "INTERNATIONALISATION" .PP If any packets contain international characters, Regina will attempt to convert these to your local character encoding as it writes them to the output. .PP You can tell Regina what character encoding to use by setting standard locale-related environment variables, such as \fBLANG\fR, \fBLC_CTYPE\fR or \fBLC_ALL\fR\&. .PP For example, if \fBLANG\fR is set to en_AU then output will be written in the Western European character set ISO-8859-1, and if \fBLANG\fR is set to en_AU.UTF-8 then output will be written in the universal character set UTF-8\&. .PP Typically these environment variables will already be set for you when you install your \fBGNU/Linux\fR system, and Regina will just use the right character set out of the box. See your \fBGNU/Linux\fR system reference for further information on supporting different locales. .SH "MACOS\\~X USERS" .PP If you downloaded a drag-and-drop app bundle, this utility is shipped inside it. If you dragged Regina to the main Applications folder, you can run it as /Applications/Regina.app/Contents/MacOS/trisetcmp\&. .SH "WINDOWS USERS" .PP The command-line utilities are installed beneath the \fIProgram\~Files\fR directory; on some machines this directory is called \fIProgram\~Files\~(x86)\fR\&. You can start this utility by running c:\\Program\~Files\\Regina\\Regina\~4.95\\bin\\trisetcmp.exe\&. .SH "SEE ALSO" .PP regina-gui\&. .SH "AUTHOR" .PP This utility was written by Benjamin Burton \&. Many people have been involved in the development of Regina; see the users' handbook for a full list of credits. regina-4.95/docs/README.txt000644 000765 000024 00000000521 12234011536 015230 0ustar00babstaff000000 000000 Regina Documentation Directory ------------------------------ This directory tree is used to generate man pages and API documentation. The man pages are extracted from the Regina handbook, which is located in ../qtui/doc/regina/. The API documentation is generated via doxygen from the extensive comments throughout the source code. regina-4.95/engine/algebra/CMakeLists.txt000644 000765 000024 00000001077 12236713375 020227 0ustar00babstaff000000 000000 # algebra # Files to compile SET ( FILES nabeliangroup ngrouppresentation nhomgrouppresentation nmarkedabeliangroup nxmlalgebrareader ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} algebra/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nabeliangroup.h ngrouppresentation.h nhomgrouppresentation.h nmarkedabeliangroup.h nxmlalgebrareader.h DESTINATION ${INCLUDEDIR}/algebra COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/algebra/nabeliangroup.cpp000644 000765 000024 00000025445 12234011536 021012 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "maths/matrixops.h" namespace regina { const NLargeInteger& NAbelianGroup::getInvariantFactor( unsigned long index) const { std::multiset::const_iterator it = invariantFactors.begin(); advance(it, index); return *it; } void NAbelianGroup::addTorsionElement(const NLargeInteger& degree, unsigned mult) { // If there are no current torsion elements, just throw in the new // ones. if (invariantFactors.empty()) { for (unsigned j=0; j::const_iterator it; for (it = invariantFactors.begin(); it != invariantFactors.end(); it++) { a.entry(i,i) = *it; i++; } // Put the passed torsion elements beneath. for (unsigned j=0; j& torsion) { // Build a presentation matrix for the torsion. size_t len = invariantFactors.size() + torsion.size(); NMatrixInt a(len, len); // Put our own invariant factors in the top. unsigned i=0; std::multiset::const_iterator it; for (it = invariantFactors.begin(); it != invariantFactors.end(); it++) { a.entry(i,i) = *it; i++; } // Put the passed torsion elements beneath. for (it = torsion.begin(); it != torsion.end(); it++) { a.entry(i,i) = *it; i++; } // Go calculate! smithNormalForm(a); replaceTorsion(a); } void NAbelianGroup::addGroup(const NMatrixInt& presentation) { // Prepare to calculate invariant factors. size_t len = invariantFactors.size(); NMatrixInt a(len + presentation.rows(), len + presentation.columns()); // Fill in the complete presentation matrix. // Fill the bottom half of the matrix with the presentation. unsigned i,j; for (i=0; i::const_iterator it; for (it = invariantFactors.begin(); it != invariantFactors.end(); it++) { a.entry(i,i) = *it; i++; } // Go calculate! smithNormalForm(a); replaceTorsion(a); } void NAbelianGroup::addGroup(const NAbelianGroup& group) { rank += group.rank; // Work out the torsion elements. if (invariantFactors.empty()) { // Copy the other group's factors! invariantFactors = group.invariantFactors; return; } if (group.invariantFactors.empty()) return; // We will have to calculate the invariant factors ourselves. size_t len = invariantFactors.size() + group.invariantFactors.size(); NMatrixInt a(len, len); // Put our own invariant factors in the top. unsigned i = 0; std::multiset::const_iterator it; for (it = invariantFactors.begin(); it != invariantFactors.end(); it++) { a.entry(i,i) = *it; i++; } // Put the other group's invariant factors beneath. for (it = group.invariantFactors.begin(); it != group.invariantFactors.end(); it++) { a.entry(i,i) = *it; i++; } // Go calculate! smithNormalForm(a); replaceTorsion(a); } unsigned NAbelianGroup::getTorsionRank(const NLargeInteger& degree) const { std::multiset::const_reverse_iterator it; unsigned ans = 0; // Because we have SNF, we can bail as soon as we reach a factor // that is not divisible by degree. for (it = invariantFactors.rbegin(); it != invariantFactors.rend(); it++) if (((*it) % degree) == 0) ans++; else return ans; return ans; } void NAbelianGroup::writeTextShort(std::ostream& out) const { bool writtenSomething = false; if (rank > 0) { if (rank > 1) out << rank << ' '; out << 'Z'; writtenSomething = true; } std::multiset::const_iterator it = invariantFactors.begin(); NLargeInteger currDegree; unsigned currMult = 0; while(true) { if (it != invariantFactors.end()) { if ((*it) == currDegree) { currMult++; it++; continue; } } if (currMult > 0) { if (writtenSomething) out << " + "; if (currMult > 1) out << currMult << ' '; out << "Z_" << currDegree.stringValue(); writtenSomething = true; } if (it == invariantFactors.end()) break; currDegree = *it; currMult = 1; it++; } if (! writtenSomething) out << '0'; } void NAbelianGroup::replaceTorsion(const NMatrixInt& matrix) { // Delete any preexisting torsion. invariantFactors.clear(); // Run up the diagonal until we hit 1. // Hopefully this will be faster than running down the diagonal // looking for 0 because the SNF calculation should end up with // many 1s for a unnecessarily large presentation matrix such as // is produced for instance by homology calculations. unsigned long rows = matrix.rows(); unsigned long i = matrix.columns(); if (i > rows) { rank += (i - rows); i = rows; } while (i > 0) { if (matrix.entry(i-1, i-1) == 0) rank++; else if (matrix.entry(i-1, i-1) == 1) return; else invariantFactors.insert(invariantFactors.begin(), matrix.entry(i-1, i-1)); i--; } } void NAbelianGroup::writeXMLData(std::ostream& out) const { out << " "; for (std::multiset::const_iterator it = invariantFactors.begin(); it != invariantFactors.end(); it++) out << (*it) << ' '; out << ""; } // ---N--> CC --M--> ie: M*N = 0. NAbelianGroup::NAbelianGroup(const NMatrixInt& M, const NMatrixInt& N) { rank = N.rows(); NMatrixInt tempN(N); metricalSmithNormalForm(tempN); unsigned long lim = (tempN.rows() < tempN.columns() ? tempN.rows() : tempN.columns() ); std::multiset torsion; for (unsigned long i=0; i 1) torsion.insert(tempN.entry(i,i)); } } addTorsionElements(torsion); NMatrixInt tempM(M); metricalSmithNormalForm(tempM); lim = (tempM.rows() < tempM.columns() ? tempM.rows() : tempM.columns()); for (unsigned long i=0; i torsion; if (cof == 0) { for (unsigned long i=0; i 1) torsion.insert(tempN.entry(i,i)); } } else { for (unsigned long i=0; i 1) torsion.insert(g); } } NMatrixInt tempM(M); metricalSmithNormalForm(tempM); lim = (tempM.rows() < tempM.columns() ? tempM.rows() : tempM.columns() ); for (unsigned long i=0; i1) torsion.insert(g); } } } if (cof != 0) { for (unsigned long i=0; i #include "regina-core.h" #include "maths/ninteger.h" #include "shareableobject.h" namespace regina { class NMatrixInt; /** * \addtogroup algebra Algebraic Structures * Various algebraic structures. * @{ */ /** * Represents a finitely generated abelian group. * * The torsion elements of the group are stored in terms of their * invariant factors. For instance, Z_2+Z_3 will appear as * Z_6, and Z_2+Z_2+Z_3 will appear as Z_2+Z_6. * * In general the factors will appear as Z_d0+...+Z_dn, * where the invariant factors di are all greater than 1 and satisfy * d0|d1|...|dn. Note that this representation is * unique. * * \testpart * * \todo \optlong Look at using sparse matrices for storage of SNF and * the like. */ class REGINA_API NAbelianGroup : public ShareableObject { protected: unsigned rank; /**< The rank of the group (the number of Z components). */ std::multiset invariantFactors; /**< The invariant factors d0,...,dn as * described in the NAbelianGroup notes. */ public: /** * Creates a new trivial group. */ NAbelianGroup(); /** * Creates a clone of the given group. * * @param cloneMe the group to clone. */ NAbelianGroup(const NAbelianGroup& cloneMe); /** * Creates an abelian group as the homology of a chain complex. * * \pre M.columns() = N.rows(). * \pre The product M*N = 0. * * @param M the `right' matrix in the chain complex; that is, * the matrix that one takes the kernel of when computing homology. * @param N the `left' matrix in the chain complex; that is, the * matrix that one takes the image of when computing homology. * * @author Ryan Budney */ NAbelianGroup(const NMatrixInt& M, const NMatrixInt& N); /** * Creates an abelian group as the homology of a chain complex, * using mod-\a p coefficients. * * \pre M.columns() = N.rows(). * \pre The product M*N = 0. * * @param M the `right' matrix in the chain complex; that is, * the matrix that one takes the kernel of when computing homology. * @param N the `left' matrix in the chain complex; that is, the * matrix that one takes the image of when computing homology. * @param p the modulus, which may be any NLargeInteger. * Zero is interpreted as a request for integer coefficents, * which will give the same result as the * NAbelianGroup(const NMatrixInt&, const NMatrixInt&) constructor. * * @author Ryan Budney */ NAbelianGroup(const NMatrixInt& M, const NMatrixInt& N, const NLargeInteger &p); /** * Destroys the group. */ virtual ~NAbelianGroup(); /** * Increments the rank of the group by the given integer. * This integer may be positive, negative or zero. * * \pre The current rank plus the given integer is non-negative. * In other words, if we are subtracting rank then we are not * trying to subtract more rank than the group actually has. * * @param extraRank the extra rank to add; this defaults to 1. */ void addRank(int extraRank = 1); /** * Adds the given torsion element to the group. * Note that this routine might be slow since calculating the * new invariant factors is not trivial. If many different * torsion elements are to be added, consider using * addTorsionElements() instead so the invariant factors need * only be calculated once. * * In this routine we add a specified number of copies of * Z_d, where d is some given degree. * * \pre The given degree is at least 2 and the * given multiplicity is at least 1. * * @param degree d, where we are adding copies of * Z_d to the torsion. * @param mult the multiplicity m, where we are adding * precisely m copies of Z_d; this defaults to 1. */ void addTorsionElement(const NLargeInteger& degree, unsigned mult = 1); /** * Adds the given torsion element to the group. * Note that this routine might be slow since calculating the * new invariant factors is not trivial. If many different * torsion elements are to be added, consider using * addTorsionElements() instead so the invariant factors need * only be calculated once. * * In this routine we add a specified number of copies of * Z_d, where d is some given degree. * * \pre The given degree is at least 2 and the * given multiplicity is at least 1. * * @param degree d, where we are adding copies of * Z_d to the torsion. * @param mult the multiplicity m, where we are adding * precisely m copies of Z_d; this defaults to 1. */ void addTorsionElement(unsigned long degree, unsigned mult = 1); /** * Adds the given set of torsion elements to this group. * Note that this routine might be slow since calculating the * new invariant factors is not trivial. * * The torsion elements to add are described by a list of * integers k1,...,km, where we are adding * Z_k1,...,Z_km. Unlike invariant factors, the * ki are not required to divide each other. * * \pre Each integer in the given list is strictly greater than 1. * * \ifacespython This routine takes a python list as its argument. * * @param torsion a list containing the torsion elements to add, * as described above. */ void addTorsionElements(const std::multiset& torsion); /** * Adds the abelian group defined by the given presentation to this * group. * Note that this routine might be slow since calculating the * new invariant factors is not trivial. * * @param presentation a presentation matrix for the group to be * added to this group, where each column represents a generator * and each row a relation. */ void addGroup(const NMatrixInt& presentation); /** * Adds the given abelian group to this group. * Note that this routine might be slow since calculating the * new invariant factors is not trivial. * * @param group the group to add to this one. */ void addGroup(const NAbelianGroup& group); /** * Returns the rank of the group. * This is the number of included copies of Z. * * @return the rank of the group. */ unsigned getRank() const; /** * Returns the rank in the group of the torsion term of given degree. * If the given degree is d, this routine will return the * largest m for which mZ_d is a subgroup * of this group. * * For instance, if this group is Z_6+Z_12, the torsion term of * degree 2 has rank 2 (one occurrence in Z_6 and one in Z_12), * and the torsion term of degree 4 has rank 1 (one occurrence * in Z_12). * * \pre The given degree is at least 2. * * @param degree the degree of the torsion term to query. * @return the rank in the group of the given torsion term. */ unsigned getTorsionRank(const NLargeInteger& degree) const; /** * Returns the rank in the group of the torsion term of given degree. * If the given degree is d, this routine will return the * largest m for which mZ_d is a subgroup * of this group. * * For instance, if this group is Z_6+Z_12, the torsion term of * degree 2 has rank 2 (one occurrence in Z_6 and one in Z_12), * and the torsion term of degree 4 has rank 1 (one occurrence * in Z_12). * * \pre The given degree is at least 2. * * @param degree the degree of the torsion term to query. * @return the rank in the group of the given torsion term. */ unsigned getTorsionRank(unsigned long degree) const; /** * Returns the number of invariant factors that describe the * torsion elements of this group. * See the NAbelianGroup class notes for further details. * * @return the number of invariant factors. */ unsigned long getNumberOfInvariantFactors() const; /** * Returns the given invariant factor describing the torsion * elements of this group. * See the NAbelianGroup class notes for further details. * * If the invariant factors are d0|d1|...|dn, * this routine will return di where i is the * value of parameter \a index. * * @param index the index of the invariant factor to return; * this must be between 0 and getNumberOfInvariantFactors()-1 * inclusive. * @return the requested invariant factor. */ const NLargeInteger& getInvariantFactor(unsigned long index) const; /** * Determines whether this is the trivial (zero) group. * * @return \c true if and only if this is the trivial group. */ bool isTrivial() const; /** * Determines whether this is the infinite cyclic group (Z). * * @return \c true if and only if this is the infinite cyclic group. */ bool isZ() const; /** * Determines whether this is the non-trivial cyclic group on * the given number of elements. * * As a special case, if \a n = 0 then this routine will test for the * infinite cyclic group (i.e., it will behave the same as isZ()). * If \a n = 1, then this routine will test for the trivial group * (i.e., it will behave the same as isTrivial()). * * @param n the number of elements of the cyclic group in question. * @return \c true if and only if this is the cyclic group Z_n. */ bool isZn(unsigned long n) const; /** * Determines whether this and the given abelian group are * isomorphic. * * @param other the group with which this should be compared. * @return \c true if and only if the two groups are isomorphic. */ bool operator == (const NAbelianGroup& other) const; /** * Writes a chunk of XML containing this abelian group. * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ void writeXMLData(std::ostream& out) const; /** * The text representation will be of the form * 3 Z + 4 Z_2 + Z_120. * The torsion elements will be written in terms of the * invariant factors of the group, as described in the * NAbelianGroup notes. */ virtual void writeTextShort(std::ostream& out) const; protected: /** * Replaces the torsion elements of this group with those * in the abelian group represented by the given Smith normal * form presentation matrix. Any zero columns in the matrix * will also be added to the rank as additional copies of Z. * Note that preexisting torsion elements will be deleted, but * preexisting rank will not. * * \pre The given matrix is in Smith normal * form, with the diagonal consisting of a series of positive, * non-decreasing integers followed by zeroes. * * @param matrix a matrix containing the Smith normal form * presentation matrix for the new torsion elements, * where each column represents a generator * and each row a relation. */ void replaceTorsion(const NMatrixInt& matrix); }; /*@}*/ // Inline functions for NAbelianGroup inline NAbelianGroup::NAbelianGroup() : rank(0) { } inline NAbelianGroup::NAbelianGroup(const NAbelianGroup& g) : ShareableObject(), rank(g.rank), invariantFactors(g.invariantFactors) { } inline NAbelianGroup::~NAbelianGroup() { } inline void NAbelianGroup::addRank(int extraRank) { rank += extraRank; } inline void NAbelianGroup::addTorsionElement(unsigned long degree, unsigned mult) { addTorsionElement(NLargeInteger(degree), mult); } inline unsigned NAbelianGroup::getRank() const { return rank; } inline unsigned NAbelianGroup::getTorsionRank(unsigned long degree) const { return getTorsionRank(NLargeInteger(degree)); } inline unsigned long NAbelianGroup::getNumberOfInvariantFactors() const { return invariantFactors.size(); } inline bool NAbelianGroup::isTrivial() const { return (rank == 0 && invariantFactors.empty()); } inline bool NAbelianGroup::isZ() const { return (rank == 1 && invariantFactors.empty()); } inline bool NAbelianGroup::isZn(unsigned long n) const { return (n == 0 ? isZ() : n == 1 ? isTrivial() : (rank == 0 && invariantFactors.size() == 1 && *invariantFactors.begin() == n)); } inline bool NAbelianGroup::operator == (const NAbelianGroup& other) const { return (rank == other.rank && invariantFactors == other.invariantFactors); } } // namespace regina #endif regina-4.95/engine/algebra/ngrouppresentation.cpp000644 000765 000024 00000112124 12236713375 022135 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "algebra/ngrouppresentation.h" #include "algebra/nhomgrouppresentation.h" #include "algebra/nmarkedabeliangroup.h" #include "algebra/nabeliangroup.h" #include "maths/numbertheory.h" #include "utilities/boostutils.h" #include "utilities/stlutils.h" namespace regina { typedef std::list::iterator TermIterator; typedef std::list::const_iterator TermIteratorConst; typedef std::vector::iterator RelIterator; typedef std::vector::const_iterator RelIteratorConst; typedef std::list::iterator TmpRelIterator; std::ostream& operator << (std::ostream& out, const NGroupExpressionTerm& term) { if (term.exponent == 0) out << '1'; else if (term.exponent == 1) out << 'g' << term.generator; else out << 'g' << term.generator << '^' << term.exponent; return out; } NGroupExpressionTerm& NGroupExpression::getTerm(unsigned long index) { TermIterator pos = terms.begin(); advance(pos, index); return *pos; } const NGroupExpressionTerm& NGroupExpression::getTerm( unsigned long index) const { TermIteratorConst pos = terms.begin(); advance(pos, index); return *pos; } NGroupExpression* NGroupExpression::inverse() const { NGroupExpression* ans = new NGroupExpression(); transform(terms.begin(), terms.end(), front_inserter(ans->terms), std::mem_fun_ref(&NGroupExpressionTerm::inverse)); return ans; } void NGroupExpression::invert() { reverse(terms.begin(), terms.end()); std::list< NGroupExpressionTerm >::iterator it; for (it = terms.begin(); it != terms.end(); it++) (*it).exponent = -(*it).exponent; } NGroupExpression* NGroupExpression::power(long exponent) const { NGroupExpression* ans = new NGroupExpression(); if (exponent == 0) return ans; long i; if (exponent > 0) for (i = 0; i < exponent; i++) ans->terms.insert(ans->terms.end(), terms.begin(), terms.end()); else for (i = 0; i > exponent; i--) transform(terms.begin(), terms.end(), front_inserter(ans->terms), std::mem_fun_ref(&NGroupExpressionTerm::inverse)); return ans; } bool NGroupExpression::simplify(bool cyclic) { bool changed = false; TermIterator next, tmpIt; for (next = terms.begin(); next != terms.end(); ) { // Take a look at merging next forwards. if ((*next).exponent == 0) { // Zero exponent. // Delete this term and step back to the previous term in // case we can now merge the previous and next terms. next = terms.erase(next); if (next != terms.begin()) next--; changed = true; continue; } tmpIt = next; tmpIt++; // Now tmpIt points to the term after next. if (tmpIt == terms.end()) { // No term to merge forwards with. next++; } else if ((*tmpIt) += (*next)) { // Successfully merged this with the following term. next = terms.erase(next); changed = true; // Look at this term again to see if it can be merged further. } else { // Different generators; cannot merge. next++; } } if (! cyclic) return changed; // Now trying merging front and back terms. // We shall do this by popping terms off the back and merging them // with the front term. while (terms.begin() != terms.end() && ++terms.begin() != terms.end()) { // Thus terms.size() > 1. The unusual test above is used to // avoid calling terms.size() which takes linear time. if (terms.front() += terms.back()) { // Merged! terms.pop_back(); changed = true; // Did we create an empty term? if (terms.front().exponent == 0) terms.pop_front(); } else break; } return changed; } bool NGroupExpression::substitute(unsigned long generator, const NGroupExpression& expansion, bool cyclic) { bool changed = false; NGroupExpression* inverse = 0; const NGroupExpression* use; long exponent, i; for (TermIterator current = terms.begin(); current != terms.end(); ) { if ((*current).generator != generator) current++; else { exponent = (*current).exponent; if (exponent != 0) { if (exponent > 0) use = &expansion; else { if (inverse == 0) inverse = expansion.inverse(); use = inverse; exponent = -exponent; } // Fill in exponent copies of use. // // Note that the following insertion will invalidate // current if the wrong type of data structure is being used! for (i = 0; i < exponent; i++) terms.insert(current, use->terms.begin(), use->terms.end()); } current = terms.erase(current); changed = true; } } if (inverse) delete inverse; if (changed) simplify(cyclic); return changed; } NGroupPresentation::NGroupPresentation(const NGroupPresentation& cloneMe) : ShareableObject(), nGenerators(cloneMe.nGenerators) { transform(cloneMe.relations.begin(), cloneMe.relations.end(), back_inserter(relations), FuncNewCopyPtr()); } std::string NGroupPresentation::recogniseGroup() const { std::ostringstream out; unsigned long nRels = relations.size(); NGroupExpression* rel; long exp; // Run through cases. if (nGenerators == 0) out << 0; else if (nGenerators == 1) { // Each term is of the form g^k=1. This is Z_d where d is the // gcd of the various values of k. unsigned long d = 0; for (unsigned long i = 0; i < nRels; i++) { rel = relations[i]; if (rel->getNumberOfTerms() > 1) rel->simplify(); // The relation should have at most one term now. if (rel->getNumberOfTerms() == 1) { exp = rel->getExponent(0); if (exp > 0) d = gcd(d, exp); else if (exp < 0) d = gcd(d, -exp); } } if (d == 0) out << 'Z'; else if (d == 1) out << 0; else out << "Z_" << d; } else if (nRels == 0) { out << "Free (" << nGenerators << " generators)"; } else if (nGenerators == 2 && nRels == 1) { // See if it's the abelian Z + Z. rel = relations[0]; rel->simplify(true); // Look for a relation (x y x^-1 y^-1). if (rel->getNumberOfTerms() == 4) { if (rel->getGenerator(0) == rel->getGenerator(2) && rel->getGenerator(1) == rel->getGenerator(3) && rel->getGenerator(0) != rel->getGenerator(1) && labs(rel->getExponent(0)) == 1 && labs(rel->getExponent(1)) == 1 && rel->getExponent(0) + rel->getExponent(2) == 0 && rel->getExponent(1) + rel->getExponent(3) == 0) out << "Z + Z (abelian)"; } // TODO: To add: platonic groups, octahedral/cubical, dihedral, // icosahedral/dodecahedral, tetrahedral and binary versions of them. // Free products of torsion groups, free products with amalgamation. // currently intelligentSimplify() isn't smart enough for this. } else if (nGenerators == 2 && nRels == 2) { // TODO: See if it's the quaternions. } else { // nGenerators >= 2 and nRels >= 2. // Don't have anything intelligent to say at this point. } return out.str(); } std::auto_ptr NGroupPresentation::abelianisation() const { // create presentation matrices to pass to NAbelianGroup(M, N) NMatrixInt M(1, getNumberOfGenerators() ); // zero matrix NMatrixInt N(getNumberOfGenerators(), getNumberOfRelations() ); // run through rels, increment N entries appropriately for (unsigned long j=0; j(new NAbelianGroup(M,N)); } std::auto_ptr NGroupPresentation::markedAbelianisation() const { // create presentation matrices to pass to NMarkedAbelianGroup(M, N) NMatrixInt M(1, getNumberOfGenerators() ); // zero matrix NMatrixInt N(getNumberOfGenerators(), getNumberOfRelations() ); // run through rels, increment N entries appropriately for (unsigned long j=0; j(new NMarkedAbelianGroup(M,N)); } void NGroupPresentation::dehnAlgorithmSubMetric( const NGroupExpression &this_word, const NGroupExpression &that_word, std::set< NWordSubstitutionData > &sub_list, unsigned long step ) { unsigned long this_length ( this_word.wordLength() ); unsigned long that_length ( that_word.wordLength() ); // generic early exit strategy if ( (this_length < 2) || (that_length==0) ) return; // early exit strategy based on step. if ( (step==1) && ((step+1)*this_length < that_length) ) return; // TODO: should check to whatever extent the above is of much use... // this -> splayed to this_word, that_word -> reducer std::vector< NGroupExpressionTerm > this_word_vec( 0 ); std::vector< NGroupExpressionTerm > reducer( 0 ); this_word_vec.reserve( this_length ); reducer.reserve( that_length ); std::list::const_iterator it; for (it = this_word.getTerms().begin(); it!=this_word.getTerms().end(); it++) { for (unsigned long i=0; i0) ? 1 : -1 ) ); } for (it = that_word.getTerms().begin(); it!=that_word.getTerms().end(); it++) { for (unsigned long i=0; i0) ? 1 : -1 ) ); } std::vector< NGroupExpressionTerm > inv_reducer( that_length ); for (unsigned long i=0; i 0 ) { subData.score = 2*comp_length - that_length; if ( subData.score > -((long int)step) ) sub_list.insert(subData); } // and the corresponding search with the inverse of reducer. comp_length = 0; while ( (this_word_vec[(i+comp_length) % this_length] == inv_reducer[(j+comp_length) % that_length]) && (comp_length < that_length) && (comp_length < this_length) ) comp_length++; subData.invertB=true; subData.sub_length=comp_length; if (comp_length == that_length) { subData.score = that_length; unsigned long a=1; while ( (this_word_vec[( (i+this_length)-a )%this_length].inverse()== this_word_vec[( (i+comp_length)+(a-1) )%this_length]) && (2*a+that_length <= this_length ) ) { a++; subData.score++; } sub_list.insert(subData); } else if ( comp_length > 0 ) { subData.score = 2*comp_length - that_length; if ( subData.score > -((long int)step) ) sub_list.insert(subData); } } } /** * This applies a substitution generated by dehnAlgorithmSubMetric. */ void NGroupPresentation::applySubstitution( NGroupExpression& this_word, const NGroupExpression &that_word, const NWordSubstitutionData &sub_data ) { // okay, so let's do a quick cut-and-replace, reduce the word and hand it back. unsigned long this_length ( this_word.wordLength() ); unsigned long that_length ( that_word.wordLength() ); std::vector< NGroupExpressionTerm > this_word_vec( 0 ); std::vector< NGroupExpressionTerm > reducer( 0 ); // we'll splay-out *this and // that_word so that it's easier to search for commonalities. this_word_vec.reserve( this_length ); reducer.reserve( that_length ); std::list::const_iterator it; // start the splaying of terms for (it = this_word.getTerms().begin(); it!=this_word.getTerms().end(); it++) { for (unsigned long i=0; i0) ? 1 : -1 ) ); } // and that_word for (it = that_word.getTerms().begin(); it!=that_word.getTerms().end(); it++) { for (unsigned long i=0; i0) ? 1 : -1 ) ); } // done splaying, produce inv_reducer std::vector< NGroupExpressionTerm > inv_reducer( that_length ); for (unsigned long i=0; iwordLength() < second->wordLength() ); } /** * This routine takes a list of words, together with expVec. It's assumed * expVec is initialized to be zero and as long as the number of generators * in the group. What this routine does is, for each generator of the * group, it records the sum of the absolute value of the exponents of that * generator in word. For the i-th generator this number is recorded in * expVec[i]. */ void build_exponent_vec( const std::list< NGroupExpressionTerm > & word, std::vector &expVec ) { std::list::const_iterator tit; for ( tit = word.begin(); tit != word.end(); tit++) expVec[ (*tit).generator ] += abs( (*tit).exponent ); } /* * Commenting out since this is not used, and requires access to a * private struct. If you want to put it back, it should probably * be a member function of NWordSubstitutionData. - bab * // gives a string that describes the substitution std::string substitutionString( const NGroupExpression &word, const NGroupPresentation::NWordSubstitutionData &subData ) { std::string retval; // cut subData into bits, assemble what we're cutting // out and what we're pasting in. unsigned long word_length ( word.wordLength() ); std::vector< NGroupExpressionTerm > reducer( 0 ); reducer.reserve( word_length ); std::list::const_iterator it; // splay word for (it = word.getTerms().begin(); it!=word.getTerms().end(); it++) { for (unsigned long i=0; i0) ? 1 : -1 ) ); } // done splaying, produce inv_reducer std::vector< NGroupExpressionTerm > inv_reducer( word_length ); for (unsigned long i=0; i "+rep_word.str(); return retval; } */ } void NGroupExpression::addTermsLast( const NGroupExpression& word) { std::list< NGroupExpressionTerm >::const_iterator it; for (it = word.terms.begin(); it != word.terms.end(); it++) addTermLast( *it ); } void NGroupExpression::addTermsFirst( const NGroupExpression& word) { // traverse word's terms in reverse order. std::list< NGroupExpressionTerm >::const_reverse_iterator it; for (it = word.terms.rbegin(); it != word.terms.rend(); it++) addTermFirst( *it ); } /** * Given a word of the form g_i1^j1 g_i2^j2 ... g_in^jn * converts the word into g_i2^j2 ... g_in^jn g_i1^j1 */ void NGroupExpression::cycleRight() { if (terms.size() > 1) { NGroupExpressionTerm temp(terms.front()); terms.pop_front(); terms.push_back(temp); } } /** * Given a word of the form g_i1^j1 g_i2^j2 ... g_in^jn * converts the word into g_in^jn g_i1^j1 g_i1^j1 ... g_in-1^jn-1 */ void NGroupExpression::cycleLeft() { if (terms.size() > 1) { NGroupExpressionTerm temp(terms.back()); terms.pop_back(); terms.push_front(temp); } } bool NGroupPresentation::intelligentSimplify() { // This is not inline because it needs the NHomGroupPresentation // class definition, so that the auto_ptr destructor works correctly. return intelligentSimplifyDetail().get(); } std::auto_ptr NGroupPresentation::intelligentSimplifyDetail() { bool didSomething(false); // start by taking a copy of *this group, for construction of reductionMap NGroupPresentation oldGroup( *this ); // let's start with the shortest relators first, and see to what extent we can // use them to simplify the other relators. To do this we'll need a temporary // relator table, sorted on the length of the relators. std::list< NGroupExpression* >::iterator it; std::list< NGroupExpression* > relatorList; for (unsigned long i=0; i substitutionTable( nGenerators ); for (unsigned long i=0; isimplify(true); relatorList.sort( compare_length ); // (1) // start (2) deletion of 0-length relators it = relatorList.begin(); while ( it != relatorList.end() ) { if ( (*it)->wordLength() == 0 ) { delete (*it); it = relatorList.erase(it); } else break; } // end (2) deletion of 0-length relators // start (3) - apply shorter relators to longer. for (it = relatorList.begin(); it != relatorList.end(); it++) { if ( (*it)->wordLength() > 0 ) // don't bother if this is a trivial word. { std::list< NGroupExpression* >::iterator tit; // target of it manips. tit = it; tit++; while (tit != relatorList.end()) {// attempt to apply *it to *tit std::set< NWordSubstitutionData > sub_list; dehnAlgorithmSubMetric( **tit, **it, sub_list ); // for now let's just choose the first suggested substitution, // provided it exists and has a score of at least one. if (sub_list.size() != 0) if ( (*sub_list.begin()).score > 0 ) { applySubstitution( **tit, **it, *sub_list.begin() ); we_value_iteration = true; didSomething = true; } tit++; } } } // end (3) - application of shorter to longer relators. // (4) Build and sort a list (by length) of generator-killing relations. // So we need to: // (a) Find generator-killing relators // (b) Apply all possible length 1 and length 2 relators // (c) If any length 3 relators, apply a single one then loop back to (1) // (d) Keep a running list of generators we've killed and their // substitutions in terms of the new generators. relatorList.sort( compare_length ); // okay, lets start walking through relatorList. Terminate either when we hit // the end or a length >=3 relator. If we can use the relator *it, do. And // record in substitutionTable. begin (4) for (it = relatorList.begin(); it!=relatorList.end(); it++) { bool word_length_3_trigger(false); unsigned long WL ( (*it)->wordLength() ); // build a table expressing number of times each generator is used in *it. std::vector< unsigned long > genUsage( nGenerators ); build_exponent_vec( (*it)->getTerms(), genUsage ); std::list::iterator tit; for (unsigned long i=0; igetTerms().begin(); ( tit != (*it)->getTerms().end() ); tit++) { // skip the term whose generator is the i-th, record whether // or not it appears as an inverse... if ( (*tit).generator == i ) { inv = ((*tit).exponent != 1); before_flag=false; } else { if (before_flag) prefix.addTermLast( (*tit) ); else complement.addTermLast( (*tit) ); } } complement.addTermsLast(prefix); if (!inv) complement.invert(); // so we sub gi --> complement, in both substitutionTable and relatorList for (unsigned long j=0; j::iterator pit = relatorList.begin(); pit != relatorList.end(); pit++) { // aha! using it in a nested way!! (*pit)->substitute( i, complement ); // except this } we_value_iteration = true; didSomething = true; if (WL>3) word_length_3_trigger=true; goto found_a_generator_killer; } } // end (4) found_a_generator_killer: if (word_length_3_trigger) break; } // end (4) // (5) TODO: look for convienient Tietze moves. These are automorphisms of the // the free group that fix all but one generator, which maps as // g_i --> g_i g_j^k for some j \neq i, and any k, similarly // g_i --> g_j^k g_i. // // For example, < a b | b^2a^2, abababab > // In this situation if we make f the automorphism of the free group // with f(a)=ab, f(b)=b, we get f^{-1}(b)=b, f^{-1}(a)=ab^-1, so // f^{-1}(b^2a^2)=b^2ab^-1ab^-1, f^{-1}(abababab)=a^4. // the former cyclically reduces to bab^-1a. This is good. // So how should we weigh the change of coordinates? Probably some // strict greedy algorithm for now, just to be safe. Or perhaps // only when the move can amalgamate consecutive appearances of // generators? Like abababab -> a^4 is good, but // b^2a^2 -> b^2ab^-1ab^-1 is kind of bad. // } // end of main_while_loop (6) // We need to remove the generators that have been killed or expressed // in terms of other generators. We have to similarly re-index the // remaining generators. nGenerators = 0; for (unsigned long i=0; i genReductionMapping( nGenerators ); unsigned long indx(0); for (unsigned long i=0; i i for (it = relatorList.begin(); it != relatorList.end(); it++) for (unsigned long i=0; isubstitute( genReductionMapping[i], gi ); } // and might as well do substitutionTable, too. for (unsigned long j=0; j(new NHomGroupPresentation( oldGroup, *this, substitutionTable)); } else return std::auto_ptr(); }// end dehnAlgorithm() NGroupPresentation& NGroupPresentation::operator=( const NGroupPresentation& copyMe) { nGenerators = copyMe.nGenerators; for (unsigned long i=0; i\n"; for (RelIteratorConst it = relations.begin(); it != relations.end(); it++) { out << " "; (*it)->writeXMLData(out); out << '\n'; } out << "\n"; } void NGroupExpression::writeXMLData(std::ostream& out) const { out << " "; for (TermIteratorConst it = terms.begin(); it != terms.end(); it++) out << (*it).generator << '^' << (*it).exponent << ' '; out << ""; } // group expression output routines void NGroupExpression::writeText(std::ostream& out, bool shortword) const { if (terms.empty()) out << '1'; else { std::list::const_iterator i; for (i = terms.begin(); i!=terms.end(); i++) { if (i != terms.begin()) out << ' '; if (shortword) out << char('a' + i->generator); else out << "g_" << i->generator; if ( i->exponent != 1 ) out << '^' << i->exponent; } } } std::string NGroupExpression::toTeX() const { std::ostringstream out; writeTeX(out); return out.str(); } void NGroupExpression::writeTeX(std::ostream& out) const { if (terms.empty()) out << 'e'; else { std::list::const_iterator i; for (i = terms.begin(); i!=terms.end(); i++) { out << "g_{" << i->generator << '}'; if ( i->exponent != 1 ) out << "^{" << i->exponent << '}'; } } } void NGroupExpression::writeTextShort(std::ostream& out) const { if (terms.empty()) out << '1'; else { TermIteratorConst last = --terms.end(); copy(terms.begin(), last, std::ostream_iterator(out, " ")); out << *last; } } // presentation output routines below std::string NGroupPresentation::toTeX() const { std::ostringstream out; writeTeX(out); return out.str(); } void NGroupPresentation::writeTeX(std::ostream& out) const { out << "\\langle "; if (nGenerators == 0) out << "\\cdot"; else if (nGenerators == 1) out << "g_0"; else if (nGenerators == 2) out << "g_0, g_1"; else { out << "g0, \\cdots, g" <<(nGenerators - 1); } out << " | "; if (relations.empty()) out << "\\cdot"; else for (RelIteratorConst it = relations.begin(); it != relations.end(); it++) { if (it != relations.begin()) out << ", "; (*it)->writeTeX(out); } out << " \\rangle"; } void NGroupPresentation::writeTextLong(std::ostream& out) const { out << "Generators: "; if (nGenerators == 0) out << "(none)"; else if (nGenerators == 1) out << "a"; else if (nGenerators == 2) out << "a, b"; else if (nGenerators <= 26) out << "a .. " << char('a' + nGenerators - 1); else out << "g0 .. g" << (nGenerators - 1); out << std::endl; out << "Relations:\n"; if (relations.empty()) out << " (none)\n"; else for (RelIteratorConst it = relations.begin(); it != relations.end(); it++) { out << " "; (*it)->writeText(out, nGenerators <= 26); out << std::endl; } } std::string NGroupPresentation::toStringCompact() const { std::ostringstream out; writeTextCompact(out); return out.str(); } std::string NGroupPresentation::compact() const { std::ostringstream out; writeTextCompact(out); return out.str(); } void NGroupPresentation::writeTextCompact(std::ostream& out) const { if (nGenerators == 0) { out << "< >"; return; } out << "<"; if (nGenerators <= 26) { for (unsigned long i=0; i"; return; } out << " | "; for (RelIteratorConst it = relations.begin(); it != relations.end(); it++) { if (it != relations.begin()) out << ", "; (*it)->writeText(out, nGenerators <= 26); } out << " >"; } // We will go through, apply dehnAlgSubMetric look for substitutions, // then apply all of them within a reasonable length. // if user requests we will go further and do a 2nd iteration with more care... // depth==1 by default. void NGroupPresentation::proliferateRelators(unsigned long depth) { std::list< NGroupExpression* > newRels; for (unsigned long i=0; i sub_list; dehnAlgorithmSubMetric( *(relations[i]), *(relations[j]), sub_list, depth ); while (!sub_list.empty()) { NGroupExpression* newRel( new NGroupExpression( *(relations[i]) ) ); applySubstitution( *newRel, *(relations[j]), *sub_list.begin() ); sub_list.erase( sub_list.begin() ); newRels.push_back(newRel); } } depth--; while (depth>0) { std::list< NGroupExpression* > tempRels; for (unsigned long i=0; i::iterator j=newRels.begin(); j!=newRels.end(); j++) { // attempt to tack relation[i] to *j. To do this, we should perhaps // keep a record of how *j was created, as in where the two junction // points are so as to ensure what we're adding spans at least one // of the junctions. std::set< NWordSubstitutionData > sub_list; dehnAlgorithmSubMetric( **j, *(relations[i]), sub_list, depth ); while (!sub_list.empty()) { // TODO: we might want to avoid some obviously repetitive subs // as noted above? NGroupExpression* newRel( new NGroupExpression( *(*j) ) ); applySubstitution( *newRel, *(relations[i]), *sub_list.begin() ); sub_list.erase( sub_list.begin() ); tempRels.push_back(newRel); } } depth--; while (!tempRels.empty()) { newRels.push_back( *tempRels.begin() ); tempRels.erase( tempRels.begin() ); } } while (!newRels.empty()) { relations.push_back( newRels.front() ); newRels.pop_front(); } } } // namespace regina regina-4.95/engine/algebra/ngrouppresentation.h000644 000765 000024 00000113571 12236713375 021611 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file algebra/ngrouppresentation.h * \brief Deals with finite presentations of groups. */ #ifndef __NGROUPPRESENTATION_H #ifndef __DOXYGEN #define __NGROUPPRESENTATION_H #endif #include #include #include #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/memutils.h" #include "utilities/ptrutils.h" namespace regina { class NAbelianGroup; class NHomGroupPresentation; class NMarkedAbelianGroup; /** * \weakgroup algebra * @{ */ /** * Represents a power of a generator in a group presentation. */ struct REGINA_API NGroupExpressionTerm { unsigned long generator; /**< The number that identifies the generator in this term. */ long exponent; /**< The exponent to which the generator is raised. */ /** * Creates a new uninitialised term. */ NGroupExpressionTerm(); /** * Creates a new term initialised to the given value. * * @param newGen the number that identifies the generator in the new term. * @param newExp the exponent to which this generator is raised. */ NGroupExpressionTerm(unsigned long newGen, long newExp); /** * Creates a new term initialised to the given value. * * @param cloneMe a term whose data will be copied to the new term. */ NGroupExpressionTerm(const NGroupExpressionTerm& cloneMe); /** * Makes this term identical to the given term. * * @param cloneMe the term whose data will be copied to this term. * @return a reference to this term. */ NGroupExpressionTerm& operator = (const NGroupExpressionTerm& cloneMe); /** * Determines whether this and the given term contain identical data. * * @param other the term with which this term will be compared. * @return \c true if and only if this and the given term have both the * same generator and exponent. */ bool operator == (const NGroupExpressionTerm& other) const; /** * Returns the inverse of this term. The inverse has the same * generator but a negated exponent. * * Note that this term will remain unchanged. * * @return the inverse of this term. */ NGroupExpressionTerm inverse() const; /** * Attempts to merge this term with the given term. * If both terms have the same generator, the two exponents will be * added and stored in this term. If the generators are different, * this routine will do nothing. * * Note that this term might be changed but the given term will remain * unchanged. * * @param other the term to merge with this term. * @return \c true if the two terms were merged into this term, or * \c false if the two terms have different generators. */ bool operator += (const NGroupExpressionTerm& other); }; /** * Writes the given term to the given output stream. * The term will be written in the format g3^-7, where in this * example the term represents generator number 3 raised to the -7th power. * * If the term has exponent 0 or 1, the output format will be * appropriately simplified. * * @param out the output stream to which to write. * @param term the term to write. * @return a reference to the given output stream. */ REGINA_API std::ostream& operator << (std::ostream& out, const NGroupExpressionTerm& term); /** * Represents an expression involving generators from a group presentation * or a free group. An expression is represented as word, i.e, a sequence * of powers of generators all of which are multiplied in order. Each power * of a generator corresponds to an individual NGroupExpressionTerm. * * For instance, the expression g1^2 g3^-1 g6 contains the * three terms g1^2, g3^-1 and g6^1 in that * order. */ class REGINA_API NGroupExpression : public ShareableObject { private: std::list terms; /** The terms that make up this expression. */ public: /** * Creates a new expression with no terms. */ NGroupExpression(); /** * Creates a new expression that is a clone of the given * expression. * * @param cloneMe the expression to clone. */ NGroupExpression(const NGroupExpression& cloneMe); /** * Returns the list of terms in this expression. * These are the actual terms stored internally; any * modifications made to this list will show up in the * expression itself. * * For instance, the expression g1^2 g3^-1 g6 has list * consisting of three terms g1^2, g3^-1 and * g6^1 in that order. * * \ifacespython Not present; only the const version of this * routine is available. * * @return the list of terms. */ std::list& getTerms(); /** * Returns a constant reference to the list of terms in this * expression. * * For instance, the expression g1^2 g3^-1 g6 has list * consisting of three terms g1^2, g3^-1 and * g6^1 in that order. * * \ifacespython This routine returns a python list of copied * NGroupExpressionTerm objects. In particular, modifying this * list or the terms within it will not modify the group * expression from which they came. * * @return the list of terms. */ const std::list& getTerms() const; /** * Returns the number of terms in this expression. * * For instance, the expression g1^2 g3^-1 g6 contains three * terms. See also getWordLength(). * * @return the number of terms. */ unsigned long getNumberOfTerms() const; /** * Returns the length of the word, i.e. the number of letters * with exponent +1 or -1 for which this word is expressable as a * product. * * For instance, the expression g1^2 g3^-1 g6 is a word of * length four. See also getNumberOfTerms(). * * No attempt is made to remove redundant terms (so the word * g g^-1 will count as length two). * * @return the length of the word. */ unsigned long wordLength() const; /** * Erases all terms from this this word. * This effectively turns this word into the identity element. */ void erase(); /** * Returns the term at the given index in this expression. * Index 0 represents the first term, index 1 * represents the second term and so on. * * \warning This routine is O(n) where \a n is the number * of terms in this expression. * * @param index the index of the term to return; this must be * between 0 and getNumberOfTerms()-1 inclusive. * @return the requested term. */ NGroupExpressionTerm& getTerm(unsigned long index); /** * Returns a constant reference to the term at the given * index in this expression. * Index 0 represents the first term, index 1 * represents the second term and so on. * * \warning This routine is O(n) where \a n is the number * of terms in this expression. * * \ifacespython Not present; only the non-const version of this * routine is available. * * @param index the index of the term to return; this must be * between 0 and getNumberOfTerms()-1 inclusive. * @return the requested term. */ const NGroupExpressionTerm& getTerm(unsigned long index) const; /** * Returns the generator corresonding to the * term at the given index in this expression. * Index 0 represents the first term, index 1 * represents the second term and so on. * * \warning This routine is O(n) where \a n is the number * of terms in this expression. * * @param index the index of the term to return; this must be * between 0 and getNumberOfTerms()-1 inclusive. * @return the number of the requested generator. */ unsigned long getGenerator(unsigned long index) const; /** * Returns the exponent corresonding to the * term at the given index in this expression. * Index 0 represents the first term, index 1 * represents the second term and so on. * * \warning This routine is O(n) where \a n is the number * of terms in this expression. * * @param index the index of the term to return; this must be * between 0 and getNumberOfTerms()-1 inclusive. * @return the requested exponent. */ long getExponent(unsigned long index) const; /** * Adds the given term to the beginning of this expression. * * @param term the term to add. */ void addTermFirst(const NGroupExpressionTerm& term); /** * Adds the given term to the beginning of this expression. * * @param generator the number of the generator corresponding to * the new term. * @param exponent the exponent to which the given generator is * raised. */ void addTermFirst(unsigned long generator, long exponent); /** * Adds the given term to the end of this expression. * * @param term the term to add. */ void addTermLast(const NGroupExpressionTerm& term); /** * Adds the given term to the end of this expression. * * @param generator the number of the generator corresponding to * the new term. * @param exponent the exponent to which the given generator is * raised. */ void addTermLast(unsigned long generator, long exponent); /** * Multiplies *this on the right by word. */ void addTermsLast( const NGroupExpression& word); /** * Multiplies *this on the left by word. */ void addTermsFirst( const NGroupExpression& word); /** * Given a word of the form g_i1^j1 g_i2^j2 ... g_in^jn * converts the word into g_i2^j2 ... g_in^jn g_i1^j1 */ void cycleRight(); /** * Given a word of the form g_i1^j1 g_i2^j2 ... g_in^jn * converts the word into g_in^jn g_i1^j1 g_i1^j1 ... g_in-1^jn-1 */ void cycleLeft(); /** * Returns a newly created expression that is the inverse of * this expression. The terms will be reversed and the * exponents negated. * * @return the inverse of this expression. */ NGroupExpression* inverse() const; /** * Inverts this expression. Does not allocate or deallocate anything. */ void invert(); /** * Returns a newly created expression that is * this expression raised to the given power. * Note that the given exponent may be positive, zero or negative. * * @param exponent the power to which this expression should be raised. * @return this expression raised to the given power. */ NGroupExpression* power(long exponent) const; /** * Simplifies this expression. * Adjacent powers of the same generator will be combined, and * terms with an exponent of zero will be removed. * Note that it is \e not assumed that the underlying group is * abelian. * * You may declare that the expression is cyclic, in which case * it is assumed that terms may be moved from the back to the * front and vice versa. Thus expression g1 g2 g1 g2 g1 * simplifies to g1^2 g2 g1 g2 if it is cyclic, but does not * simplify at all if it is not cyclic. * * @param cyclic \c true if and only if the expression may be * assumed to be cyclic. * @return \c true if and only if this expression was changed. */ bool simplify(bool cyclic = false); /** * Replaces every occurrence of the given generator with the * given substite expression. If the given generator was found, * the expression will be simplified once the substitution is * complete. * * @param generator the generator to be replaced. * @param expansion the substitute expression that will replace * every occurrence of the given generator. * @param cyclic \c true if and only if the expression may be * assumed to be cyclic; see simplify() for further details. * @return \c true if and only if any substitutions were made. */ bool substitute(unsigned long generator, const NGroupExpression& expansion, bool cyclic = false); /** * Writes a chunk of XML containing this expression. * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ void writeXMLData(std::ostream& out) const; /** * Returns a TeX representation of this expression. * See writeTeX() for details on how this is formed. * * @return a TeX representation of this expression. */ std::string toTeX() const; /** * Writes a TeX represesentation of this expression to the given * output stream. * * The text representation will be of the form * g_2^4 g_{13}^{-5} g_4. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. */ void writeTeX(std::ostream& out) const; /** * Writes a text representation of this expression to the given * output stream, using either numbered generators or * alphabetic generators. * * The text representation will be of the form * g2^4 g13^-5 g4. If the shortword flag is * true, it will assume your word is in an alphabet of * no more than 26 letters, and will write the word using * lower-case ASCII, i.e. c^4 n^-5 e. * * \pre If \a shortword is \c true, the number of generators in * the corresponding group must be 26 or fewer. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. * @param shortword indicates whether to use numbered or * alphabetic generators, as described above. */ void writeText(std::ostream& out, bool shortword=false) const; /** * The text representation will be of the form * g2^4 g13^-5 g4. */ virtual void writeTextShort(std::ostream& out) const; }; /** * Represents a finite presentation of a group. * * A presentation consists of a number of generators and a set of * relations between these generators that together define the group. * * If there are \a g generators, they will be numbered 0, 1, ..., g-1. * * \testpart * * \todo Implement a procedure to attempt Reidemeister-Schreir, perhaps with * respect to a homomorphism to a known group. Something good-enough to * detect if the group is a semi-direct product, for 2 and 3-manifold groups. */ class REGINA_API NGroupPresentation : public ShareableObject { protected: unsigned long nGenerators; /**< The number of generators. */ std::vector relations; /**< The relations between the generators. */ public: /** * Creates a new presentation with no generators and no * relations. */ NGroupPresentation(); /** * Creates a clone of the given group presentation. * * @param cloneMe the presentation to clone. */ NGroupPresentation(const NGroupPresentation& cloneMe); /** * Destroys the group presentation. * All relations that are stored will be deallocated. */ virtual ~NGroupPresentation(); /** * Assignment operator. * * @param copyMe the group presentation that this will become a * copy of. * @return a reference to this group presentation. */ NGroupPresentation& operator=(const NGroupPresentation& copyMe); /** * Adds one or more generators to the group presentation. * If the new presentation has \a g generators, the new * generators will be numbered g-1, g-2 and so on. * * @param numToAdd the number of generators to add. * @return the number of generators in the new presentation. */ unsigned long addGenerator(unsigned long numToAdd = 1); /** * Adds the given relation to the group presentation. * The relation must be of the form expression = 1. * * This presentation will take ownership of the given * expression, may change it and will be responsible for its * deallocation. * * \ifacespython Since this group presentation takes ownership * of the given expression, the python object containing the * given expression becomes a null object and should no longer * be used. * * @param rel the expression that the relation sets to 1; for * instance, if the relation is g1^2 g2 = 1 then this * parameter should be the expression g1^2 g2. */ void addRelation(NGroupExpression* rel); /** * Returns the number of generators in this group presentation. * * @return the number of generators. */ unsigned long getNumberOfGenerators() const; /** * Returns the number of relations in this group presentation. * * @return the number of relations. */ unsigned long getNumberOfRelations() const; /** * Returns the relation at the given index in this group * presentation. The relation will be of the form expresson * = 1. * * @param index the index of the desired relation; this must be * between 0 and getNumberOfRelations()-1 inclusive. * @return the expression that the requested relation sets to 1; * for instance, if the relation is g1^2 g2 = 1 then * this will be the expression g1^2 g2. */ const NGroupExpression& getRelation(unsigned long index) const; /** * Attempts to simplify the group presentation as intelligently * as possible without further input. * * See intelligentSimplifyDetail() for further details on how * the simplification is done. * * @return \c true if and only if the group presentation was changed. */ bool intelligentSimplify(); /** * Attempts to simplify the group presentation as intelligently * as possible without further input. * * The current simplification method is based on the Dehn algorithm * for hyperbolic groups, i.e. small cancellation theory. This means * we look to see if part of one relator can be used to simplify * others. If so, make the substitution and simplify. We continue * until no more presentation-shortening substitutions are available. * We follow that by killing any available generators using words * where generators appear a single time. * * \todo \optlong This routine could use some small tweaks -- * recognition of utility of some score==0 moves, such as * commutators, for example. * * @return a newly allocated homomorphism describing the * reduction map from the original presentation to the new * presentation, or a null pointer if this presentation was not * changed. */ std::auto_ptr intelligentSimplifyDetail(); /** * A routine that attempts to simplify presentations, which can * help when small cancellation theory can't find the simplest * relators. * * Given a presentation <g_i | r_i>, this routine appends * consequences of the relators {r_i} to the presentation that * are of the form ab, where both a and b are cyclic permutations * of relators from the collection {r_i}. * * Passing depth=1 means it will only form products of two * relators. Depth=2 means products of three, etc. Depth=4 is * typically the last depth before the exponential growth of * the operation grows out of hand. It also conveniently trivializes * all the complicated trivial group presentations that we've come * across so far. * * \warning Do not call this routine with depth n before having called * it at depth n-1 first. Depth=0 is invalid, and depth=1 should be * your first call to this routine. This routine gobbles up an * exponential amount of memory (exponential in your presentation * size times n). So do be careful when using it. * * @param depth controls the depth of the proliferation, as * described above; this must be strictly positive. */ void proliferateRelators(unsigned long depth=1); /** * Attempts to recognise the group corresponding to this * presentation. This routine is much more likely to be * successful if you have already called intelligentSimplify(). * * Note that the presentation might be simplified a little * during the execution of this routine, although not nearly as * much as would be done by intelligentSimplify(). * * Currently, if successful the only groups this routine * recognises is the trivial group, cyclic groups, free groups, * and the free abelian group of rank two. * * Return strings have the form "0" for the trivial * group, "Z_n" for cyclic groups with n > 1, "Free(n generators)" * for free groups with n>1, and "Z" and "Z + Z (abelian)" * are the only two free abelian groups supported at present. * * \todo \featurelong Make this recognition more effective. * * @return a simple string representation of the group if it is * recognised, or an empty string if the group is not * recognised. */ std::string recogniseGroup() const; /** * Writes a chunk of XML containing this group presentation. * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ void writeXMLData(std::ostream& out) const; /** * The sum of the word lengths of the relators. * Word lengths are computing using NGroupExpression::wordLength(). * Used as a coarse measure of the complexity of the presentation. * * @return the sum of word lengths. */ unsigned long relatorLength() const; /** * Computes the abelianisation of this group. * * @return a newly allocated abelianisation of this group. */ std::auto_ptr abelianisation() const; /** * Computes the abelianisation of this group. * The coordinates in the chain complex correspond * to the generators and relators for this group. * * @return a newly allocated abelianisation of this group. */ std::auto_ptr markedAbelianisation() const; /** * Returns a TeX representation of this group presentation. * See writeTeX() for details on how this is formed. * * @return a TeX representation of this group presentation. */ std::string toTeX() const; /** * Writes a TeX represesentation of this group presentation * to the given output stream. * * The output will be of the form < generators | relators >. * There will be no final newline. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. */ void writeTeX(std::ostream& out) const; /** * A deprecated alias for compact(), which returns a * compact one-line representation of this group presentation. * * \deprecated This routine has been deprecated; use the * simpler-to-type compact() instead. * * @return a compact representation of this group presentation. */ std::string toStringCompact() const; /** * Returns a compact one-line representation of this group presentation, * including details of all generators and relations. * See writeTextCompact() for details on how this is formed. * * @return a compact representation of this group presentation. */ std::string compact() const; /** * Writes a compact represesentation of this group to the given * output stream. * * The output will be of the form < generators | relators >. * The full relations will be included, and the entire output * will be written on a single line. There will be no final newline. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. */ void writeTextCompact(std::ostream& out) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; private: /** * A structure internal to the small cancellation simplification * algorithm. * * Given two words, A and B, one wants to know how one can make * substitutions into A using variants of the word B. This * structure holds that data. For example, if: * * A == a^5b^2abababa^4b^1 and B == bababa^-1 * == aaaaabbabababaaaab * start_sub_at == 6, start_from == 0, sub_length == 5 makes sense, * this singles out the subword aaaaab[babab]abaaaab. Since it would * reduce the length by four, the score is 4. * * Similarly, if A == baba^4b^1a^5b^2aba == babaaaabaaaaabbaba * and B == baba^-1ba start_sub_at == 14, start_from == 5, * sub_length == 5 makes sense, and is a cyclic variation * on the above substitution, so the score is also 4. */ struct NWordSubstitutionData { unsigned long start_sub_at; /**< Where in A do we start? */ unsigned long start_from; /**< Where in B do we start? */ unsigned long sub_length; /**< The number of letters from B to use. */ bool invertB; /**< Invert B before making the substitution? */ long int score; /**< The score, i.e., the in word letter count provided this substitution is made. */ bool operator<( const NWordSubstitutionData &other ) const { if (score < other.score) return false; if (score > other.score) return true; if (sub_length < other.sub_length) return false; if (sub_length > other.sub_length) return true; if ( (invertB == true) && (other.invertB == false) ) return false; if ( (invertB == false) && (other.invertB == true) ) return true; if (start_from < other.start_from) return false; if (start_from > other.start_from) return true; if (start_sub_at < other.start_sub_at) return false; if (start_sub_at > other.start_sub_at) return true; return false; } void writeTextShort(std::ostream& out) const { out<<"Target position "<1 * is used primarily when building relator tables for group * recognition. */ static void dehnAlgorithmSubMetric( const NGroupExpression &this_word, const NGroupExpression &that_word, std::set< NWordSubstitutionData > &sub_list, unsigned long step=1 ); /** * A routine internal to the small cancellation simplification * algorithm. * * Given a word this_word and that_word, apply the substitution * specified by sub_data to *this. See dehnAlgorithm() and struct * NWordSubstitutionData. In particular sub_data needs to be a * valid substitution, usually it will be generated by * dehnAlgorithmSubMetric. */ static void applySubstitution( NGroupExpression& this_word, const NGroupExpression &that_word, const NWordSubstitutionData &sub_data ); }; /*@}*/ // Inline functions for NGroupExpressionTerm inline NGroupExpressionTerm::NGroupExpressionTerm() { } inline NGroupExpressionTerm::NGroupExpressionTerm(unsigned long newGen, long newExp) : generator(newGen), exponent(newExp) { } inline NGroupExpressionTerm::NGroupExpressionTerm( const NGroupExpressionTerm& cloneMe) : generator(cloneMe.generator), exponent(cloneMe.exponent) { } inline NGroupExpressionTerm& NGroupExpressionTerm::operator = ( const NGroupExpressionTerm& cloneMe) { generator = cloneMe.generator; exponent = cloneMe.exponent; return *this; } inline bool NGroupExpressionTerm::operator == ( const NGroupExpressionTerm& other) const { return (generator == other.generator) && (exponent == other.exponent); } inline NGroupExpressionTerm NGroupExpressionTerm::inverse() const { return NGroupExpressionTerm(generator, -exponent); } inline bool NGroupExpressionTerm::operator += ( const NGroupExpressionTerm& other) { if (generator == other.generator) { exponent += other.exponent; return true; } else return false; } // Inline functions for NGroupExpression inline NGroupExpression::NGroupExpression() { } inline NGroupExpression::NGroupExpression(const NGroupExpression& cloneMe) : ShareableObject(), terms(cloneMe.terms) { } inline std::list& NGroupExpression::getTerms() { return terms; } inline const std::list& NGroupExpression::getTerms() const { return terms; } inline unsigned long NGroupExpression::getNumberOfTerms() const { return terms.size(); } inline unsigned long NGroupExpression::wordLength() const { unsigned long retval(0); std::list::const_iterator it; for (it = terms.begin(); it!=terms.end(); it++) retval += labs((*it).exponent); return retval; } inline unsigned long NGroupExpression::getGenerator(unsigned long index) const { return getTerm(index).generator; } inline long NGroupExpression::getExponent(unsigned long index) const { return getTerm(index).exponent; } inline void NGroupExpression::addTermFirst(const NGroupExpressionTerm& term) { terms.push_front(term); } inline void NGroupExpression::addTermFirst(unsigned long generator, long exponent) { terms.push_front(NGroupExpressionTerm(generator, exponent)); } inline void NGroupExpression::addTermLast(const NGroupExpressionTerm& term) { terms.push_back(term); } inline void NGroupExpression::addTermLast(unsigned long generator, long exponent) { terms.push_back(NGroupExpressionTerm(generator, exponent)); } inline void NGroupExpression::erase() { terms.clear(); } // Inline functions for NGroupPresentation inline NGroupPresentation::NGroupPresentation() : nGenerators(0) { } inline NGroupPresentation::~NGroupPresentation() { for_each(relations.begin(), relations.end(), FuncDelete()); } inline unsigned long NGroupPresentation::addGenerator(unsigned long num) { return (nGenerators += num); } inline void NGroupPresentation::addRelation(NGroupExpression* rel) { relations.push_back(rel); } inline unsigned long NGroupPresentation::getNumberOfGenerators() const { return nGenerators; } inline unsigned long NGroupPresentation::getNumberOfRelations() const { return relations.size(); } inline const NGroupExpression& NGroupPresentation::getRelation( unsigned long index) const { return *relations[index]; } inline void NGroupPresentation::writeTextShort(std::ostream& out) const { out << "Group presentation: " << nGenerators << " generators, " << relations.size() << " relations"; } inline unsigned long NGroupPresentation::relatorLength() const { unsigned long retval(0); for (unsigned long i=0; iwordLength(); return retval; } } // namespace regina #endif regina-4.95/engine/algebra/nhomgrouppresentation.cpp000644 000765 000024 00000014505 12236713375 022645 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "algebra/ngrouppresentation.h" #include "algebra/nhomgrouppresentation.h" #include "maths/numbertheory.h" #include "utilities/boostutils.h" #include "utilities/stlutils.h" namespace regina { NHomGroupPresentation::NHomGroupPresentation( const NGroupPresentation& groupForIdentity) : domain_(groupForIdentity), range_(groupForIdentity), map_(groupForIdentity.getNumberOfGenerators()) { for (unsigned long i=0; iaddTermFirst(i, 1); } } NGroupExpression NHomGroupPresentation::evaluate( const NGroupExpression &arg) const { // evaluate at arg NGroupExpression retval(arg); // Use substitute to build evaluation. // increment the generator num in retval to be larger than // the largest generator used in range, N. unsigned long N( range_.getNumberOfGenerators() ); for (unsigned long i=0; i "; map_[i]->writeTextShort(out); } out<<"]"; out<<"Range "; range_.writeTextCompact(out); out< rangeMap = range_.intelligentSimplifyDetail(); // step 2: simplify presentation of domain. // the strategy is to completely mimic // NGroupPresentation::intelligentSimplify(), and change the map accordingly std::auto_ptr domainMap = domain_.intelligentSimplifyDetail(); // doesn't call intelligentSimplify if we changed something? // step 3: find a hacky inverse to domainMap // by the way domainMap is constructed we know each gi // comes from some gk, so look them up. bool didSomething = rangeMap.get() || domainMap.get(); // Since we need to compose rangeMap and domainMap, build identity maps // if either map is null. if (! domainMap.get()) domainMap.reset(new NHomGroupPresentation(domain_)); if (! rangeMap.get()) rangeMap.reset(new NHomGroupPresentation(range_)); std::vector< unsigned long > invDomainMap( domain_.getNumberOfGenerators() ); for (unsigned long i=0; igetDomain().getNumberOfGenerators(); i++) { // if map_[i] == "gk" then invDomainMap[k] = gi. if (domainMap->evaluate(i).getNumberOfTerms()==1) if (domainMap->evaluate(i).getExponent(0)==1) invDomainMap[ domainMap->evaluate(i).getGenerator(0) ] = i; } // step 4: compute rangeMap*(*this)*domainMap.inverse() // and replace "map" appropriately. std::vector< NGroupExpression > newMap( domain_.getNumberOfGenerators() ); for (unsigned long i=0; ievaluate( *map_[ invDomainMap[i] ] ) ); for (unsigned long i=0; i #include "regina-core.h" #include "shareableobject.h" namespace regina { class NGroupPresentation; /** * \weakgroup algebra * @{ */ /** * Represents a homomorphism between groups which are described via finite * presentations. * * \testpart * * \todo add routine to attempt to verify validity of homomorphism. */ class REGINA_API NHomGroupPresentation : public ShareableObject { private: NGroupPresentation domain_; /**< The domain of the homomorphism. */ NGroupPresentation range_; /**< The range of the homomorphism. */ std::vector map_; /**< A map whose ith element is the image of the ith generator from the domain. */ public: /** * Creates a new homomorphism from the given data. * * @param domain the domain of the homomorphism. * @param range the range of the homomorphism. * @param map a vector of length \a g, where \a g is the number * of generators of the domain, and where this homomorphism * sends the ith generator of the domain to the * element map[i] of the range. * * \ifacespython Not present. */ NHomGroupPresentation(const NGroupPresentation &domain, const NGroupPresentation &range, const std::vector &map); /** * Creates a new identity homomorphism for the given group. * * @param groupForIdentity both the range and domain of the * new identity homomorphism. */ NHomGroupPresentation(const NGroupPresentation& groupForIdentity); /** * Creates a clone of the given group presentation. * * @param cloneMe the presentation to clone. */ NHomGroupPresentation(const NHomGroupPresentation& cloneMe); /** * Destroys the group homomorphism. */ ~NHomGroupPresentation(); /** * The domain of the map. * * @return a reference to the domain. */ const NGroupPresentation& getDomain() const; /** * The range of the map. * * @return a reference to the range. */ const NGroupPresentation& getRange() const; /** * Evaluate the homomorphism at an element. * * @param arg an element of the domain. * @return the image of this element in the range. */ NGroupExpression evaluate(const NGroupExpression &arg) const; /** * Evaluate the homomorphism at a generator of the domain. * * @param i the index of a generator in the domain. * @return the image of the ith generator in the range. */ NGroupExpression evaluate(unsigned long i) const; /** * Simultaneously simplifies: * * - the presentation of the domain; * - the presentation of the range; * - the description of the map. * * Uses Dehn's algorithm / "small cancellation theory". * * @return true if the presentations or map have changed. */ bool intelligentSimplify(); void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; }; /*@}*/ inline NHomGroupPresentation::NHomGroupPresentation( const NGroupPresentation &domain, const NGroupPresentation &range, const std::vector &map ) : domain_(domain), range_(range) { map_.resize(map.size()); for (unsigned long i=0; i namespace regina { NMarkedAbelianGroup::NMarkedAbelianGroup(unsigned long rk, const NLargeInteger &p) : OM(rk, rk), ON(rk,rk), OMR(rk,rk), OMC(rk,rk), OMRi(rk, rk), OMCi(rk, rk), rankOM(0), ornR(0), ornC(0), ornRi(0), ornCi(0), otR(0), otC(0), otRi(0), otCi(0), InvFacList(0), snfrank(0), snffreeindex(0), ifNum(0), ifLoc(0), coeff(NLargeInteger::zero), TORLoc(0), TORVec(0), tensorIfLoc(0), tensorIfNum(0), tensorInvFacList(0) { // special case p==1 trivial group ornR.reset(new NMatrixInt(rk, rk)); ornRi.reset(new NMatrixInt(rk, rk)); ornC.reset(new NMatrixInt(rk, rk)); ornCi.reset(new NMatrixInt(rk, rk)); for (unsigned long i=0; imakeIdentity(); ornRi->makeIdentity(); ornC->makeIdentity(); ornCi->makeIdentity(); if ( (p != 0 ) && ( p != 1 ) ) ifNum=rk; if (ifNum != 0) InvFacList.resize(ifNum); for (unsigned long i=0; i > prod=OMRi*ON; NMatrixInt ORN(N.rows()-rankOM, N.columns()); ornR.reset( new NMatrixInt( ORN.columns(), ORN.columns() ) ); ornRi.reset(new NMatrixInt( ORN.columns(), ORN.columns() ) ); ornC.reset( new NMatrixInt( ORN.rows(), ORN.rows() ) ); ornCi.reset(new NMatrixInt( ORN.rows(), ORN.rows() ) ); for (unsigned long i=0;ientry(i+rankOM,j); // put the presentation matrix in Smith normal form, and // build the list of invariant factors and their row indexes // now compute the rank and column indexes ... metricalSmithNormalForm(ORN, &(*ornR), &(*ornRi), &(*ornC), &(*ornCi)); for (unsigned long i=0; ( (i1) InvFacList.push_back(ORN.entry(i,i)); } ifNum = InvFacList.size(); snfrank = ORN.rows() - ifLoc - ifNum; snffreeindex = ifLoc + InvFacList.size(); } // We'll store H_k(M;Z_p) internally in two different ways. The reason boils down to the pleasant version // of the universal coefficient theorem that you see using Smith normal forms. // Summary: if Z^a --N--> Z^b --M--> Z^c is a chain complex for H_k(M;Z) and p>0 an integer, put M into SNF(M), // this gives SNF(M) == OMC*M*OMR. Let SNF(M) == diag[s_0, ..., s_{k-1}, 0, ... 0] and suppose // only entries s_i, ..., s_{k-1} share common factors with p. Then you immediately get the // presentation matrix for H_k(M;Z_p) which is a product of the two matrices: // [ trunc_top_k_rows[OMRi*N], diag(p,p,...,p) ] \times [ diag(gcd(s_i,p), ..., gcd(s_{k-1},p) ] // here trunc_top_k_rows means remove the first k rows from [OMRi*N]. The left matrix is basically // by design the presentation matrix for H_k(M;Z)\times Z_p, and the right matrix the presentation // matrix for TOR(H_{k-1}(M;Z), Z_p). The 2nd matrix is already in SNF. We apply SNF to the first, // and store the change-of-basis matrices in otR, otC, otRi, otCi. We then concatenate these two // diagonal matrices and apply SNF to them to get a situation NMarkedAbelianGroup will be happy with. // This has the added advantage of us being able to later easily implement the NHomMarkedAbelianGroup // maps for UCT later when we're interested in that kind of stuff. NMarkedAbelianGroup::NMarkedAbelianGroup(const NMatrixInt& M, const NMatrixInt& N, const NLargeInteger &pcoeff): OM(M), ON(N), OMR(M.columns(),M.columns()), OMC(M.rows(),M.rows()), OMRi(M.columns(),M.columns()), OMCi(M.rows(),M.rows()), rankOM(0), ornR(0), ornC(0), ornRi(0), ornCi(0), otR(0), otC(0), otRi(0), otCi(0), InvFacList(0), snfrank(0), snffreeindex(0), ifNum(0), ifLoc(0), coeff(pcoeff), TORLoc(0), TORVec(0), tensorIfLoc(0), tensorInvFacList(0) { // find SNF(M). NMatrixInt tM(M); metricalSmithNormalForm(tM, &OMR, &OMRi, &OMC, &OMCi); for (unsigned i=0; ( (i 0 we need to consider the TOR part of homology. if (coeff > 0) for (unsigned i=0; i 1) { TORVec.push_back(tM.entry(i,i)); } TORLoc = rankOM - TORVec.size(); // okay, lets get a presentation matrix for H_*(M;Z) \otimes Z_p // starting by computing the trunc[OMRi*N] matrix and padding with a diagonal p matrix std::auto_ptr > OMRiN = OMRi*ON; // hmm, if we're using p == 0 coefficients, lets keep it simple if (coeff > 0) { NMatrixInt tensorPres( OMRiN->rows() - rankOM, OMRiN->columns() + OMRiN->rows() - rankOM ); for (unsigned long i=0; icolumns(); j++) tensorPres.entry(i,j) = OMRiN->entry(i+rankOM, j); for (unsigned long i=0; i< OMRiN->rows() - rankOM; i++) tensorPres.entry(i, OMRiN->columns() + i) = coeff; // initialize coordinate-change matrices for the SNF computation. otR.reset(new NMatrixInt(tensorPres.columns(), tensorPres.columns() )); otRi.reset(new NMatrixInt(tensorPres.columns(), tensorPres.columns() )); otC.reset(new NMatrixInt(tensorPres.rows(), tensorPres.rows() )); otCi.reset(new NMatrixInt(tensorPres.rows(), tensorPres.rows() )); metricalSmithNormalForm(tensorPres, &(*otR), &(*otRi), &(*otC), &(*otCi)); // okay, so this group is a direct sum of groups of the form Z_q where q = gcd(p, TORVec[i]), // and groups Z_q where q is on the diagonal of tensorPres, and either q=0 or q>1. // unfortunately in rare occurances these are not the invariant factors of the group, so we // assemble these numbers into a diagonal presentation matrix and apply SNF! First, determine // the size of the matrix we'll need. for (unsigned long i=0; ( (i 1) tensorInvFacList.push_back(tensorPres.entry(i,i)); else if (tensorPres.entry(i,i) == 0) snfrank++; // should always be zero. } tensorIfNum = tensorInvFacList.size(); NMatrixInt diagPres( TORVec.size() + tensorIfNum + snfrank, TORVec.size() + tensorIfNum + snfrank); for (unsigned long i=0; i 1 or == 0. if (diagPres.entry(i,i) > 1) InvFacList.push_back(diagPres.entry(i,i)); } snffreeindex = InvFacList.size(); ifNum = InvFacList.size(); ifLoc = diagPres.rows() - ifNum; } else { // coeff == p == 0 case NMatrixInt tensorPres( OMRiN->rows() - rankOM, OMRiN->columns() ); for (unsigned long i=0; icolumns(); j++) tensorPres.entry(i,j) = OMRiN->entry(i+rankOM, j); // initialize coordinate-change matrices for the SNF computation. ornR.reset(new NMatrixInt(tensorPres.columns(), tensorPres.columns() )); ornRi.reset(new NMatrixInt(tensorPres.columns(), tensorPres.columns() )); ornC.reset(new NMatrixInt(tensorPres.rows(), tensorPres.rows() )); ornCi.reset(new NMatrixInt(tensorPres.rows(), tensorPres.rows() )); metricalSmithNormalForm(tensorPres, &(*ornR), &(*ornRi), &(*ornC), &(*ornCi)); for (unsigned long i=0; ( (i 1) InvFacList.push_back(tensorPres.entry(i,i)); } snffreeindex = ifLoc + InvFacList.size(); ifNum = InvFacList.size(); snfrank = tensorPres.rows() - ifLoc - ifNum; } } bool NMarkedAbelianGroup::isChainComplex() const { if (OM.columns() != ON.rows()) return false; std::auto_ptr > prod = OM*ON; for (unsigned long i=0; irows(); i++) for (unsigned long j=0; jcolumns(); j++) if (prod->entry(i,j) != 0) return false; return true; } unsigned long NMarkedAbelianGroup::getTorsionRank(const NLargeInteger& degree) const { unsigned long ans = 0; for (unsigned long i=0;i 0) { if (snfrank > 1) out << snfrank << ' '; out << 'Z'; writtenSomething = true; } std::vector::const_iterator it = InvFacList.begin(); NLargeInteger currDegree; unsigned currMult = 0; while(true) { if (it != InvFacList.end()) { if ((*it) == currDegree) { currMult++; it++; continue; } } if (currMult > 0) { if (writtenSomething) out << " + "; if (currMult > 1) out << currMult << ' '; out << "Z_" << currDegree.stringValue(); writtenSomething = true; } if (it == InvFacList.end()) break; currDegree = *it; currMult = 1; it++; } if (! writtenSomething) out << '0'; } /* * The marked abelian group was defined by matrices M and N * with M*N==0. Think of M as m by l and N as l by n. Then * this routine returns the index-th free generator of the * ker(M)/img(N) in Z^l. */ std::vector NMarkedAbelianGroup::getFreeRep(unsigned long index) const { static const std::vector nullvec; if (index >= snfrank) return nullvec; std::vector retval(OM.columns(),NLargeInteger::zero); // index corresponds to the (index+snffreeindex)-th column of ornCi // we then pad this vector (at the front) with rankOM 0's // and apply OMR to it. std::vector temp(ornCi->rows()+rankOM,NLargeInteger::zero); for (unsigned long i=0;irows();i++) temp[i+rankOM]=ornCi->entry(i,index+snffreeindex); // the above line takes the index+snffreeindex-th column of ornCi and // pads it. for (unsigned long i=0;i NMarkedAbelianGroup::getTorsionRep( unsigned long index) const { static const std::vector nullvec; if (index >= ifNum) return nullvec; std::vector retval(OM.columns(),NLargeInteger::zero); if (coeff == 0) { std::vector temp(ornCi->rows()+rankOM, NLargeInteger::zero); for (unsigned long i=0;irows();i++) temp[i+TORLoc] = ornCi->entry(i,ifLoc+index); // the above line takes the index+snffreeindex-th column of ornCi and // pads it suitably for (unsigned long i=0;i 0 with coefficients there's the extra step of dealing with the UCT splitting // start with column column index + ifLoc of matrix ornCi, split into two vectors // 1) first TORVec.size() entries and 2) remaining entries. std::vector firstV(TORVec.size(), NLargeInteger::zero); std::vector secondV(ornC->rows()-TORVec.size(), NLargeInteger::zero); for (unsigned long i=0; ientry( i, index + ifLoc ); for (unsigned long i=0; ientry( i + firstV.size(), index + ifLoc ); // 1st vec needs coords scaled appropriately by p/gcd(p,q) and multiplied by appropriate OMR columns for (unsigned long i=0; i otCiSecondV(otCi->rows(), NLargeInteger::zero); for (unsigned long i=0; irows(); i++) for (unsigned long j=tensorIfLoc; jcolumns(); j++) otCiSecondV[i] += otCi->entry(i,j) * secondV[j-tensorIfLoc]; // 2nd vec needs be multiplied by otCi, padded, then have OMR applied. for (unsigned long i=0; i NMarkedAbelianGroup::ccRep(const std::vector& SNFRep) const { static const std::vector nullV; if (SNFRep.size() != snfrank + ifNum) return nullV; std::vector retval(OM.columns(),NLargeInteger::zero); std::vector temp(ornCi->rows()+TORLoc,NLargeInteger::zero); if (coeff == 0) { for (unsigned long j=0; jrows(); i++) temp[i+TORLoc] += ornCi->entry(i,ifLoc+j) * SNFRep[j]; for (unsigned long i=0;i 0 std::vector firstV(TORVec.size(), NLargeInteger::zero); std::vector secondV(ornC->rows()-TORVec.size(), NLargeInteger::zero); for (unsigned long i=0; ientry( i, j + ifLoc ) * SNFRep[j]; for (unsigned long i=0; ientry( i + firstV.size(), j + ifLoc ) * SNFRep[j]; // 1st vec needs coords scaled appropriately by p/gcd(p,q) and multiplied by appropriate OMR columns for (unsigned long i=0; i otCiSecondV(otCi->rows(), NLargeInteger::zero); for (unsigned long i=0; irows(); i++) for (unsigned long j=tensorIfLoc; jcolumns(); j++) otCiSecondV[i] += otCi->entry(i,j) * secondV[j-tensorIfLoc]; // 2nd vec needs be multiplied by otCi, padded, then have OMR applied. for (unsigned long i=0; i NMarkedAbelianGroup::ccRep(unsigned long SNFRep) const { static const std::vector nullV; if (SNFRep >= snfrank + ifNum) return nullV; std::vector retval(OM.columns(),NLargeInteger::zero); std::vector temp(ornCi->rows()+TORLoc,NLargeInteger::zero); if (coeff == 0) { for (unsigned long i=0; irows(); i++) temp[i+TORLoc] = ornCi->entry(i,ifLoc+SNFRep); for (unsigned long i=0;i 0 std::vector firstV(TORVec.size(), NLargeInteger::zero); std::vector secondV(ornC->rows()-TORVec.size(), NLargeInteger::zero); for (unsigned long i=0; ientry( i, SNFRep + ifLoc ); for (unsigned long i=0; ientry( i + firstV.size(), SNFRep + ifLoc ); // 1st vec needs coords scaled appropriately by p/gcd(p,q) and multiplied by appropriate OMR columns for (unsigned long i=0; i otCiSecondV(otCi->rows(), NLargeInteger::zero); for (unsigned long i=0; irows(); i++) for (unsigned long j=tensorIfLoc; jcolumns(); j++) otCiSecondV[i] += otCi->entry(i,j) * secondV[j-tensorIfLoc]; // 2nd vec needs be multiplied by otCi, padded, then have OMR applied. for (unsigned long i=0; i NMarkedAbelianGroup::snfRep( const std::vector& element) const { std::vector retval(snfrank+ifNum, NLargeInteger::zero); // apply OMRi, crop, then apply ornC, tidy up and return. static const std::vector nullvec; // this is returned if // element not in ker(M) // first, does element have the right dimensions? if not, abort. if (element.size() != OM.columns()) return nullvec; // this holds OMRi * element which we will use to check first if // element is in the kernel, and then to construct its SNF rep. std::vector temp(ON.rows(), NLargeInteger::zero); for (unsigned long i=0;ientry(i+snffreeindex,j-rankOM)*temp[j]; for (unsigned long i=0;ientry(i+ifLoc,j-rankOM)*temp[j]; // redundant for loops } else { std::vector diagPresV( ornC->rows(), NLargeInteger::zero); for (unsigned long i=0; icolumns(); j++) diagPresV[i] += otC->entry( i - TORVec.size() + tensorIfLoc, j) * temp[ j + rankOM ]; } // assemble to a diagPres vector, apply ornC for (unsigned long i=0; ientry(i,j) * diagPresV[j]; } // do modular reduction to make things look nice... for (unsigned long i=0; i &input) const { if (input.size() != OM.columns()) return false; for (unsigned long i=0; i &input) const { if (input.size() != OM.columns()) return false; std::vector snF(snfRep(input)); if (snF.size() != getNumberOfInvariantFactors() + getRank()) return false; for (unsigned long i=0; i NMarkedAbelianGroup::boundaryMap(const std::vector &CCrep) const { std::vector retval(OM.rows(), NLargeInteger::zero); if (CCrep.size() == OM.columns()) for (unsigned long i=0; i 0) { retval[i] %= coeff; if (retval[i] < 0) retval[i] += coeff; } } return retval; } // routine checks to see if an object in the CC coords for our group is a boundary, and if so it returns // CC coords of what an object that it is a boundary of. Null vector is returned if not boundary. std::vector NMarkedAbelianGroup::writeAsBoundary(const std::vector &input) const { static const std::vector nullvec; if ( !isCycle(input) ) return nullvec; // okay, it's a cycle so lets determine whether or not it is a boundary. std::vector temp(ON.rows(), NLargeInteger::zero); for (unsigned long i=0; i retval(ON.columns(), NLargeInteger::zero); if (coeff == 0) { std::vector snfV(ornC->rows(), NLargeInteger::zero); for (unsigned long i=0;irows();i++) for (unsigned long j=0;jcolumns();j++) snfV[i] += ornC->entry(i,j)*temp[j+rankOM]; // check divisibility in the invFac coords for (unsigned long i=0; irows(); i++) for (unsigned long j=0; jentry(i, j) * snfV[j]; } else {// find tensorV -- apply otC. std::vector tensorV( otC->rows(), NLargeInteger::zero); for (unsigned long i=0; irows(); i++) for (unsigned long j=0; jcolumns(); j++) tensorV[i] += otC->entry(i, j) * temp[ j + rankOM ]; for (unsigned long i=0; ientry(i,j) * tensorV[j]; // ah! the other coefficients of otR gives the relevant congruence. } return retval; } // returns the j+TORLoc -th column of the matrix OMR, rescaled appropriately if it corresponds to // a TOR vector. std::vector NMarkedAbelianGroup::cycleGen(unsigned long j) const { static const std::vector nullv; if (j >= minNumberCycleGens()) return nullv; std::vector retval( OM.columns(), NLargeInteger::zero); for (unsigned long i=0; i NMarkedAbelianGroup::cycleProjection( const std::vector &ccelt ) const { // multiply by OMRi, truncate, multiply by OMR static const std::vector nullv; if (ccelt.size() != OMRi.columns()) return nullv; std::vector retval( OMRi.columns(), NLargeInteger::zero ); for (unsigned long i=0; i NMarkedAbelianGroup::cycleProjection( unsigned long ccindx ) const { // truncate column cyclenum of OMRi, multiply by OMR static const std::vector nullv; if (ccindx >= OMRi.columns()) return nullv; std::vector retval( OMRi.columns(), NLargeInteger::zero ); for (unsigned long i=0; i NMarkedAbelianGroup::torsionSubgroup() const { NMatrixInt dM(1, getNumberOfInvariantFactors() ); NMatrixInt dN(getNumberOfInvariantFactors(), getNumberOfInvariantFactors() ); for (unsigned long i=0; i(new NMarkedAbelianGroup(dM, dN)); } // and its canonical inclusion map std::auto_ptr NMarkedAbelianGroup::torsionInclusion() const { NMatrixInt iM( getRankCC(), getNumberOfInvariantFactors() ); for (unsigned long j=0; j jtor( getTorsionRep(j) ); for (unsigned long i=0; i(new NHomMarkedAbelianGroup( *torsionSubgroup(), (*this), iM)); } // there appears to be an error for the orientable S^1 bundle over the klein bottle, // for H_2 with Z_2-coefficients, in the STD->MIXed map. NHomMarkedAbelianGroup::NHomMarkedAbelianGroup(const NMatrixInt &tobeRedMat, const NMarkedAbelianGroup &dom, const NMarkedAbelianGroup &ran) : domain(dom), range(ran), matrix(ran.getM().columns(), dom.getM().columns()), reducedMatrix(0), kernel(0), coKernel(0), image(0), reducedKernelLattice(0) { reducedMatrix = new NMatrixInt(tobeRedMat); // If using mod p coeff, p != 0: // // we build up the CC map in reverse from the way we computed the structure of the // domain/range groups. // which was: 3) SNF(M,M'), truncate off first TORLoc coords. // 2) SNF the tensorPres matrix, TOR coords fixed. Truncate off first tensorIfLoc terms. // 1) SNF the combined matrix, truncate off ifLoc terms. // // Step 1: ran.ornCi*[incl tobeRedMat]*[trunc dom.ornC] puts us in diagPres coords // ran.ornCi.rows()-by-dom.ornC.rows() // Step 2: ran.otCi*(step 1)*[trunc dom.otC] puts us in trunc(SNF(M,M')) coords // Step 3: OMR*(step 2)*[trunc OMRi] // If using integer coefficients: // // we build up the CC map in reverse of the process for which we found the structure of // the domain/range // groups, which was: 2) SNF(M,M'), truncate off the first rankOM==TORLoc coords // 1) SNF(N,N'), truncate off the first ifLoc terms. // // Step 1: ran.ornCi*[incl tobeRedMat]*[trunc dom.ornC] puts us in trunc(SNF(M,M')) coords // Step 2: --void-- // Step 3: OMR*(step 1)*[trunc OMRi] // so we have a common Step 1. NMatrixInt step1Mat(ran.ornCi->rows(), dom.ornC->rows()); for (unsigned long i=0; iornCi.entry(i, k)*tobeRedMat.entry(k, l)*dom->ornC.entry(l, j) for (unsigned long k=0; kentry(i,k+ran.ifLoc)*tobeRedMat.entry(k,l)* dom.ornC->entry(l+dom.ifLoc,j); } // with mod p coefficients we have this fiddly middle step 2. NMatrixInt step2Mat( step1Mat.rows()+ran.tensorIfLoc, step1Mat.columns()+dom.tensorIfLoc ); // if coeff==0, we'll just copy the step1Mat, if coeff>0 we multiply the tensor part by // ran.otCi, dom.otC resp. if (dom.coeff == 0) for (unsigned long i=0; iotCi.entry(i, k)*incl_tensorIfLoc)*step1Mat.entry(k, l)* // ID_TOR x trunc_tensorIfLoc*dom->otC.entry(l, j)) appropriately shifted... if (i < ran.TORVec.size()) { if (j < dom.TORVec.size()) { step2Mat.entry(i,j) = step1Mat.entry(i,j); } else { // [step1 UR corner] * [dom->otC first tensorIfLoc rows cropped] for (unsigned long k=dom.tensorIfLoc; krows(); k++) step2Mat.entry(i,j) += step1Mat.entry(i,k-dom.tensorIfLoc+dom.TORVec.size())* dom.otC->entry(k,j-dom.TORVec.size()); } } else if (j < dom.TORVec.size()) { for (unsigned long k=ran.tensorIfLoc; kcolumns(); k++) step2Mat.entry(i,j) += ran.otCi->entry(i-ran.TORVec.size(),k)* step1Mat.entry(k-ran.tensorIfLoc+ran.TORVec.size(),j); } else { for (unsigned long k=ran.tensorIfLoc; krows(); k++) for (unsigned long l=dom.tensorIfLoc; lrows(); l++) step2Mat.entry(i,j) += ran.otCi->entry(i-ran.TORVec.size(),k)* step1Mat.entry(k-ran.tensorIfLoc+ran.TORVec.size(), l-dom.tensorIfLoc+dom.TORVec.size())* dom.otC->entry(l,j-dom.TORVec.size()); } } // now we rescale the TOR components appropriately, various row/column mult and divisions. // multiply first ran.TORLoc rows by p/gcd(p,q) // divide first dom.TORLoc rows by p/gcd(p,q) for (unsigned long i=0; icolumns(); j++) { std::vector colV( (j icv( matrix.rows(), NLargeInteger::zero); for (unsigned long i=0; i midge( range.snfRep(icv) ); for (unsigned long i=0; ientry(i,j) = midge[i]; } } } void NHomMarkedAbelianGroup::computeReducedKernelLattice() { if (!reducedKernelLattice) { computeReducedMatrix(); const NMatrixInt& redMatrix(*reducedMatrix); std::vector dcL(range.getRank() + range.getNumberOfInvariantFactors() ); for (unsigned long i=0; irows(), reducedMatrix->columns() + range.getNumberOfInvariantFactors() ); unsigned i,j; for (i=0;irows();i++) for (j=0;jcolumns();j++) ccrelators.entry(i,j)=reducedMatrix->entry(i,j); for (i=0;icolumns())= range.getInvariantFactor(i); NMatrixInt ccgenerators( 1, reducedMatrix->rows() ); coKernel = new NMarkedAbelianGroup(ccgenerators, ccrelators); } } void NHomMarkedAbelianGroup::computeImage() { if (!image) { computeReducedKernelLattice(); const NMatrixInt& dcLpreimage( *reducedKernelLattice ); NMatrixInt imgCCm(1, dcLpreimage.rows() ); NMatrixInt imgCCn(dcLpreimage.rows(), dcLpreimage.columns() + domain.getNumberOfInvariantFactors() ); for (unsigned long i=0;i NHomMarkedAbelianGroup::operator * (const NHomMarkedAbelianGroup &X) const { std::auto_ptr > prod=matrix*X.matrix; NMatrixInt compMat(matrix.rows(), X.matrix.columns() ); for (unsigned long i=0;irows();i++) for (unsigned long j=0;jcolumns();j++) compMat.entry(i,j) = prod->entry(i, j); return std::auto_ptr(new NHomMarkedAbelianGroup(X.domain, range, compMat)); } std::vector NHomMarkedAbelianGroup::evalCC(const std::vector &input) const { std::vector retval(matrix.rows(), NLargeInteger::zero); for (unsigned long i=0; i NHomMarkedAbelianGroup::evalSNF(const std::vector &input) const { const_cast(this)->computeReducedMatrix(); static const std::vector nullV; if (input.size() != domain.minNumberOfGenerators() ) return nullV; std::vector retval( range.minNumberOfGenerators(), NLargeInteger::zero ); for (unsigned long i=0; i(this)->computeReducedMatrix(); out<<"Reduced Matrix is "<rows()<<" by " <columns()<<" corresponding to domain "; domain.writeTextShort(out); out<<" and range "; range.writeTextShort(out); out<<"\n"; for (unsigned long i=0;irows();i++) { out<<"["; for (unsigned long j=0;jcolumns();j++) { out<entry(i,j); if (j+1 < reducedMatrix->columns()) out<<" "; } out<<"]\n"; } } void NHomMarkedAbelianGroup::writeTextShort(std::ostream& out) const { if (isIsomorphism()) out<<"isomorphism"; else if (isZero()) out<<"zero map"; else if (isMonic()) { // monic not epic out<<"monic, with cokernel "; getCokernel().writeTextShort(out); } else if (isEpic()) { // epic not monic out<<"epic, with kernel "; getKernel().writeTextShort(out); } else { // nontrivial not epic, not monic out<<"kernel "; getKernel().writeTextShort(out); out<<" | cokernel "; getCokernel().writeTextShort(out); out<<" | image "; getImage().writeTextShort(out); } } void NHomMarkedAbelianGroup::writeTextLong(std::ostream& out) const { out<<"hom[ "; domain.writeTextShort(out); out<<" --> "; range.writeTextShort(out); out<<" ] "; writeTextShort(out); } bool NHomMarkedAbelianGroup::isIdentity() const { if (!(domain.equalTo(range))) return false; const_cast(this)->computeReducedMatrix(); if (!reducedMatrix->isIdentity()) return false; return true; } bool NHomMarkedAbelianGroup::isCycleMap() const { for (unsigned long j=0; j cycJ( domain.cycleGen(j) ); std::vector FcycJ( range.getRankCC(), NLargeInteger::zero ); for (unsigned long i=0; i NHomMarkedAbelianGroup::torsionSubgroup() const { std::auto_ptr dom = domain.torsionSubgroup(); std::auto_ptr ran = range.torsionSubgroup(); NMatrixInt mat(range.getNumberOfInvariantFactors(), domain.getNumberOfInvariantFactors() ); for (unsigned long j=0; j in range's snfRep coords std::vector temp(range.snfRep(evalCC( domain.getTorsionRep(j)))); for (unsigned long i=0; i(new NHomMarkedAbelianGroup( *dom, *ran, mat)); } /** * Given two NHomMarkedAbelianGroups, you have two diagrams: * Z^a --N1--> Z^b --M1--> Z^c Z^g --N3--> Z^h --M3--> Z^i * ^ ^ * | this | other * Z^d --N2--> Z^e --M2--> Z^f Z^j --N4--> Z^k --M4--> Z^l * @return true if and only if M1 == N3, M2 == N4 and diagram commutes * commutes. */ bool NHomMarkedAbelianGroup::isChainMap(const NHomMarkedAbelianGroup &other) const { if ( (getRange().getM().rows() != other.getRange().getN().rows()) || (getRange().getM().columns() != other.getRange().getN().columns()) || (getDomain().getM().rows() != other.getDomain().getN().rows()) || (getDomain().getM().columns() != other.getDomain().getN().columns()) ) return false; if ( (getRange().getM() != other.getRange().getN()) || (getDomain().getM() != other.getDomain().getN()) ) return false; std::auto_ptr< NMatrixRing > prodLU = range.getM() * getDefiningMatrix(); std::auto_ptr< NMatrixRing > prodBR = other.getDefiningMatrix() * domain.getM(); if ( (*prodLU) != (*prodBR) ) return false; return true; } // Start with the reduced matrix which is a 2x2 block matrix: // // [A|B] // [---] where D is an inveritble square matrix, 0 is a zero matrix, // [0|D] A a square matrix and B maybe not square. // the columns of D represent the free factors of the domain, // the rows of D the free factors of the range, // the columns/rows of A represent the torsion factors of the domain/range // respectively. So the inverse matrix must have the form // [A'|B'] // [-----] // [0 |D'] where D' is the inverse of D, A' represents the inverse automorphism of // Z_p1 + Z_p2 + ... Z_pk, etc. And so B' must equal -A'BD', which is readily // computable. So it all boils down to computing A'. So we need a routine which // takes a matrix A representing an automorphism of Z_p1 + ... Z_pk and then computes // the matrix representing the inverse automorphism. // So to do this we'll need a new matrixops.cpp command -- call it torsionAutInverse. std::auto_ptr NHomMarkedAbelianGroup::inverseHom() const { const_cast(this)->computeReducedMatrix(); NMatrixInt invMat( reducedMatrix->columns(), reducedMatrix->rows() ); if (!isIsomorphism()) return std::auto_ptr( new NHomMarkedAbelianGroup( invMat, range, domain )); // get A, B, D from reducedMatrix // A must be square with domain/range.getNumberOfInvariantFactors() columns // D must be square with domain/range.getRank() columns // B may not be square with domain.getRank() columns and range.getNumberOfInvariantFactors() rows. NMatrixInt A(range.getNumberOfInvariantFactors(), domain.getNumberOfInvariantFactors()); NMatrixInt B(range.getNumberOfInvariantFactors(), domain.getRank()); NMatrixInt D(range.getRank(), domain.getRank()); for (unsigned long i=0; ientry(i,j); for (unsigned long i=0; ientry(i, j + A.columns()); for (unsigned long i=0; ientry( i + A.rows(), j + A.columns() ); // compute A', B', D' // let's use void columnEchelonForm(NMatrixInt &M, NMatrixInt &R, NMatrixInt &Ri, // const std::vector &rowList); from matrixOps to compute the inverse of D. NMatrixInt Di(D.rows(), D.columns()); Di.makeIdentity(); NMatrixInt Dold(D.rows(), D.columns()); Dold.makeIdentity(); std::vector rowList(D.rows()); for (unsigned i=0; i &invF); // can be used to compute A'. So we need to make a vector containing the invariant factors. std::vector invF(domain.getNumberOfInvariantFactors()); for (unsigned long i=0; i Ai = torsionAutInverse( A, invF); // then Bi is given by Bi = -AiBDi NMatrixInt Bi(range.getNumberOfInvariantFactors(), domain.getRank()); NMatrixInt Btemp(range.getNumberOfInvariantFactors(), domain.getRank()); // Btemp will give -BDi // Bi will be AiBtemp for (unsigned long i=0; icolumns(); k++) Bi.entry(i,j) += Ai->entry(i,k)*Btemp.entry(k,j); // reduce Ai and Bi respectively. for (unsigned long i=0; irows(); i++) { for (unsigned long j=0; jcolumns(); j++) { Ai->entry(i,j) %= domain.getInvariantFactor(i); if (Ai->entry(i,j) < 0) Ai->entry(i,j) += domain.getInvariantFactor(i); } for (unsigned long j=0; jrows(); i++) for (unsigned long j=0; jcolumns(); j++) invMat.entry(i,j) = Ai->entry(i,j); for (unsigned long i=0; irows(),j+Ai->columns()) = Di.entry(i,j); for (unsigned long i=0; icolumns()) = Bi.entry(i,j); return std::auto_ptr(new NHomMarkedAbelianGroup( invMat, range, domain )); } } // namespace regina regina-4.95/engine/algebra/nmarkedabeliangroup.h000644 000765 000024 00000170523 12236713375 021655 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file algebra/nmarkedabeliangroup.h * \brief Deals with abelian groups given by chain complexes. */ #ifndef __NMARKEDABELIANGROUP_H #ifndef __DOXYGEN #define __NMARKEDABELIANGROUP_H #endif #include #include #include "regina-core.h" #include "maths/nmatrixint.h" #include "utilities/ptrutils.h" namespace regina { class NHomMarkedAbelianGroup; /** * \weakgroup algebra * @{ */ /** * Represents a finitely generated abelian group given by a chain complex. * * This class is initialized with a chain complex. The chain complex is given * in terms of two integer matrices \a M and \a N such that M*N=0. The abelian * group is the kernel of \a M mod the image of \a N. * * In other words, we are computing the homology of the chain complex * Z^a --N--> Z^b --M--> Z^c * where a=N.columns(), M.columns()=b=N.rows(), and c=M.rows(). An additional * constructor allows one to take the homology with coefficients in an arbitrary * cyclic group. * * This class allows one to retrieve the invariant factors, the rank, and * the corresponding vectors in the kernel of \a M. Moreover, given a * vector in the kernel of \a M, it decribes the homology class of the * vector (the free part, and its position in the invariant factors). * * The purpose of this class is to allow one to not only * represent homology groups, but it gives coordinates on the group allowing * for the construction of homomorphisms, and keeping track of subgroups. * * Some routines in this class refer to the internal presentation * matrix. This is a proper presentation matrix for the abelian group, * and is created by constructing the product getMRBi() * \a N, and then * removing the first getRankM() rows. * * @author Ryan Budney * * \todo \optlong Look at using sparse matrices for storage of SNF and the like. * \todo Testsuite additions: isBoundary(), boundaryMap(), writeAsBdry(), * cycleGen(). */ class REGINA_API NMarkedAbelianGroup : public ShareableObject { private: /** Internal original M */ NMatrixInt OM; // copy of initializing M /** Internal original N */ NMatrixInt ON; // copy of initializing N assumes M*N == 0 /** Internal change of basis. SNF(OM) == OMC*OM*OMR */ NMatrixInt OMR, OMC; /** Internal change of basis. OM = OMCi*SNF(OM)*OMRi */ NMatrixInt OMRi, OMCi; /** Internal rank of M */ unsigned long rankOM; // this is the index of the first zero entry // in SNF(OM) /* Internal reduced N matrix: */ // In the notes below, ORN refers to the internal presentation // matrix [OMRi * ON], where the brackets indicate removal of the // first rankOM rows. /** Internal change of basis. ornC * ORN * ornR is the SNF(ORN). */ std::auto_ptr ornR, ornC; /** Internal change of basis. These are the inverses of ornR and ornC respectively. */ std::auto_ptr ornRi, ornCi; /** Internal change of basis matrix for homology with coefficents. otC * tensorPres * otR == SNF(tensorPres) */ std::auto_ptr otR, otC, otRi, otCi; /** Internal list of invariant factors. */ std::vector InvFacList; /** The number of free generators, from SNF(ORN) */ unsigned long snfrank; /** The row index of the first zero along the diagonal in SNF(ORN). */ unsigned long snffreeindex; /** Number of invariant factors. */ unsigned long ifNum; /** Row index of first invariant factor (ie entry > 1) in SNF(ORN) */ unsigned long ifLoc; // These variables store information for mod-p homology computations. /** coefficients to use in homology computation **/ NLargeInteger coeff; /** TORLoc stores the location of the first TOR entry from the SNF of OM: TORLoc == rankOM-TORVec.size() */ unsigned long TORLoc; /** TORVec's i-th entry stores the entries q where Z_p --q-->Z_p is the i-th TOR entry from the SNF of OM */ std::vector TORVec; /** invariant factor data in the tensor product presentation matrix SNF */ unsigned long tensorIfLoc; unsigned long tensorIfNum; std::vector tensorInvFacList; // and NHomMarkedAbelianGroup at present needs to see some of the // internals of NMarkedAbelianGroup // at present this is only used for inverseHom(). friend class NHomMarkedAbelianGroup; public: /** * Creates a marked abelian group from a chain complex. This constructor * assumes you're interested in homology with integer coefficents of * the chain complex. Creates a marked abelian group given by * the quotient of the kernel of \a M modulo the image of \a N. * * See the class notes for further details. * * \pre M.columns() = N.rows(). * \pre The product M*N = 0. * * @param M the `right' matrix in the chain complex; that is, * the matrix that one takes the kernel of when computing homology. * @param N the `left' matrix in the chain complex; that is, the * matrix that one takes the image of when computing homology. */ NMarkedAbelianGroup(const NMatrixInt& M, const NMatrixInt& N); /** * Creates a marked abelian group from a chain complex with * coefficients in Z_p. * * \pre M.columns() = N.rows(). * \pre The product M*N = 0. * * @param M the `right' matrix in the chain complex; that is, * the matrix that one takes the kernel of when computing homology. * @param N the `left' matrix in the chain complex; that is, the * matrix that one takes the image of when computing homology. * @param pcoeff specifies the coefficient ring, Z_pcoeff. We require * \a pcoeff >= 0. If you know beforehand that \a pcoeff=0, it's * more efficient to use the previous constructor. */ NMarkedAbelianGroup(const NMatrixInt& M, const NMatrixInt& N, const NLargeInteger &pcoeff); /** * Creates a free Z_p-module of a given rank using the direct sum * of the standard chain complex 0 --> Z --p--> Z --> 0. * So this group is isomorphic to n Z_p. Moreover, if * constructed using the previous constructor, \a M would be zero * and \a N would be diagonal and square with \a p down the diagonal. * * @param rk the rank of the group as a Z_p-module. That is, if the * group is n Z_p, then \a rk should be \a n. * @param p describes the type of ring that we use to talk about * the "free" module. */ NMarkedAbelianGroup(unsigned long rk, const NLargeInteger &p); /** * Creates a clone of the given group. * * @param cloneMe the group to clone. */ NMarkedAbelianGroup(const NMarkedAbelianGroup& cloneMe); /** * Determines whether or not the defining maps for this group * actually give a chain complex. This is helpful for debugging. * * Specifically, this routine returns \c true if and only if * M*N = 0 where M and N are the definining matrices. * * @return \c true if and only if M*N = 0. */ bool isChainComplex() const; /** * Destroys the group. */ ~NMarkedAbelianGroup(); /** * Returns the rank of the group. * This is the number of included copies of Z. * * @return the rank of the group. */ unsigned long getRank() const; /** * Returns the rank in the group of the torsion term of given degree. * If the given degree is d, this routine will return the * largest m for which mZ_d is a subgroup * of this group. * * For instance, if this group is Z_6+Z_12, the torsion term of * degree 2 has rank 2 (one occurrence in Z_6 and one in Z_12), * and the torsion term of degree 4 has rank 1 (one occurrence * in Z_12). * * \pre The given degree is at least 2. * * @param degree the degree of the torsion term to query. * @return the rank in the group of the given torsion term. */ unsigned long getTorsionRank(const NLargeInteger& degree) const; /** * Returns the rank in the group of the torsion term of given degree. * If the given degree is d, this routine will return the * largest m for which mZ_d is a subgroup * of this group. * * For instance, if this group is Z_6+Z_12, the torsion term of * degree 2 has rank 2 (one occurrence in Z_6 and one in Z_12), * and the torsion term of degree 4 has rank 1 (one occurrence * in Z_12). * * \pre The given degree is at least 2. * * @param degree the degree of the torsion term to query. * @return the rank in the group of the given torsion term. */ unsigned long getTorsionRank(unsigned long degree) const; /** * Returns the number of invariant factors that describe the * torsion elements of this group. This is the minimal number * of torsion generators. * See the NMarkedAbelianGroup class notes for further details. * * @return the number of invariant factors. */ unsigned long getNumberOfInvariantFactors() const; /** * Returns the minimum number of generators for the group. * * @return the minimum number of generators. */ unsigned long minNumberOfGenerators() const; /** * Returns the given invariant factor describing the torsion * elements of this group. * See the NMarkedAbelianGroup class notes for further details. * * If the invariant factors are d0|d1|...|dn, * this routine will return di where i is the * value of parameter \a index. * * @param index the index of the invariant factor to return; * this must be between 0 and getNumberOfInvariantFactors()-1 * inclusive. * @return the requested invariant factor. */ const NLargeInteger& getInvariantFactor(unsigned long index) const; /** * Determines whether this is the trivial (zero) group. * * @return \c true if and only if this is the trivial group. */ bool isTrivial() const; /** * Determines whether this and the given abelian group are * isomorphic. * * \deprecated This routine will be removed in a future version * of Regina. Users should switch to the less ambiguously named * routine isIsomorphicTo() instead. * * @param other the group with which this should be compared. * @return \c true if and only if the two groups are isomorphic. */ bool operator == (const NMarkedAbelianGroup &other) const; /** * Determines whether this and the given abelian group are * isomorphic. * * @param other the group with which this should be compared. * @return \c true if and only if the two groups are isomorphic. */ bool isIsomorphicTo(const NMarkedAbelianGroup &other) const; /** * Determines whether or not the two NMarkedAbelianGroups are * identical, which means they have exactly the same presentation * matrices. This is useful for determining if two * NHomMarkedAbelianGroups are composable. See isIsomorphicTo() if * all you care about is the isomorphism relation among groups * defined by presentation matrices. * * @param other the NMarkedAbelianGroup with which this should be * compared. * @return \c true if and only if the two groups have identical * chain-complex definitions. */ bool equalTo(const NMarkedAbelianGroup& other) const; /** * The text representation will be of the form * 3 Z + 4 Z_2 + Z_120. * The torsion elements will be written in terms of the * invariant factors of the group, as described in the * NMarkedAbelianGroup notes. * * @param out the stream to write to. */ void writeTextShort(std::ostream& out) const; /** * Returns the requested free generator in the original chain * complex defining the group. * * As described in the class overview, this marked abelian group * is defined by matrices \a M and \a N where M*N = 0. * If \a M is an \a m by \a l matrix and \a N is an \a l by \a n * matrix, then this routine returns the (\a index)th free * generator of ker(M)/img(N) in \a Z^l. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython The return value will be a python list. * * @param index specifies which free generator to look up; * this must be between 0 and getRank()-1 inclusive. * @return the coordinates of the free generator in the nullspace of * \a M; this vector will have length M.columns() (or * equivalently, N.rows()). If this generator does not exist, * you will receive an empty vector. */ std::vector getFreeRep(unsigned long index) const; /** * Returns the requested generator of the torsion subgroup but * represented in the original chain complex defining the group. * * As described in the class overview, this marked abelian group * is defined by matrices \a M and \a N where M*N = 0. * If \a M is an \a m by \a l matrix and \a N is an \a l by \a n * matrix, then this routine returns the (\a index)th torsion * generator of ker(M)/img(N) in \a Z^l. * * \ifacespython The return value will be a python list. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * @param index specifies which generator in the torsion subgroup; * this must be at least 0 and strictly less than the number of * non-trivial invariant factors. If not, you receive an empty * vector. * @return the coordinates of the generator in the nullspace of * \a M; this vector will have length M.columns() (or * equivalently, N.rows()). */ std::vector getTorsionRep(unsigned long index) const; /** * A combination of getFreeRep and getTorsion rep, this routine takes * a vector which represents an element in the group in the SNF * coordinates and returns a corresponding vector in the original * chain complex. * * This routine is the inverse to snfRep() described below. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param SNFRep a vector of size the number of generators of * the group, i.e., it must be valid in the SNF coordinates. If not, * an empty vector is returned. * @return a corresponding vector whose length is M.columns(), * where \a M is one of the matrices that defines the chain * complex; see the class notes for details. */ std::vector ccRep( const std::vector& SNFRep) const; /** * Same as ccRep(const std::vector&), but we assume you * only want the chain complex representation of a standard basis * vector from SNF coordinates. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param SNFRep specifies which standard basis vector from SNF * coordinates; this must be between 0 and * minNumberOfGenerators()-1 inclusive. * @return a corresponding vector whose length is M.columns(), * where \a M is one of the matrices that defines the chain * complex; see the class notes for details. */ std::vector ccRep(unsigned long SNFRep) const; /** * Projects an element of the chain complex to the subspace of cycles. * Returns an empty vector if the input element does not have * dimensions of the chain complex. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param ccelt a vector whose length is M.columns(), * where \a M is one of the matrices that defines the chain * complex (see the class notes for details). * @return a corresponding vector, also in the chain complex * coordinates. */ std::vector cycleProjection( const std::vector &ccelt) const; /** * Projects an element of the chain complex to the subspace of cycles. * Returns an empty vector if the input index is out of bounds. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param ccindx the index of the standard basis vector in chain * complex coordinates. * @return the resulting projection, in the chain complex * coordinates. */ std::vector cycleProjection(unsigned long ccindx) const; /** * Given a vector, determines if it represents a cycle in the chain * complex. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param input an input vector in chain complex coordinates. * @return \c true if and only if the given vector represents a cycle. */ bool isCycle(const std::vector &input) const; /** * Computes the differential of the given vector in the chain * complex whose kernel is the cycles. In other words, this * routine returns M*CCrep. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param CCrep a vector whose length is M.columns(), * where \a M is one of the matrices that defines the chain * complex (see the class notes for details). * @return the differential, expressed as a vector of length M.rows(). */ std::vector boundaryMap( const std::vector &CCrep) const; /** * Given a vector, determines if it represents a boundary in the chain * complex. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param input a vector whose length is M.columns(), * where \a M is one of the matrices that defines the chain * complex (see the class notes for details). * @return \c true if and only if the given vector represents a * boundary. */ bool isBoundary(const std::vector &input) const; /** * Expresses the given vector as a boundary in the chain complex * (if the vector is indeed a boundary at all). This routine * uses chain complex coordinates for both the input and the * return value. * * \warning If you're using mod-p coefficients and if your element * projects to a non-trivial element of TOR, then Nv != input as * elements of TOR aren't in the image of N. In this case, * input-Nv represents the projection to TOR. * * \warning The return value may change from version to version * of Regina, since it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @return a length zero vector if the input is not a boundary; * otherwise a vector \a v such that Nv=input. */ std::vector writeAsBoundary( const std::vector &input) const; /** * Returns the rank of the chain complex supporting the homology * computation. In the description of this class, this is also given * by M.columns() and N.rows() from the constructor that takes as * input two matrices, M and N. * * @return the rank of the chain complex. */ unsigned long getRankCC() const; /** * Expresses the given vector as a combination of free and torsion * generators. This answer is coordinate dependant, meaning the answer * may change depending on how the Smith normal form is computed. * * Recall that this marked abelian was defined by matrices \a M * and \a N with M*N=0; suppose that \a M is an \a m by \a l matrix * and \a N is an \a l by \a n matrix. This abelian group is then * the quotient ker(M)/img(N) in \a Z^l. * * When it is constructed, this group is computed to be isomorphic to * some Z_{d0} + ... + Z_{dk} + Z^d, where: * * - \a d is the number of free generators, as returned by getRank(); * - \a d1, ..., \a dk are the invariant factors that describe the * torsion elements of the group, where * 1 < \a d1 | \a d2 | ... | \a dk. * * This routine takes a single argument \a v, which must be a * vector in \a Z^l. * * If \a v belongs to ker(M), this routine describes how it * projects onto the group ker(M)/img(N). Specifically, it * returns a vector of length \a d + \a k, where: * * - The first \a k elements describe the projection of \a v * to the torsion component Z_{d1} + ... + Z_{dk}. These * elements are returned as non-negative integers modulo * \a d1, ..., \a dk respectively. * - The remaining \a d elements describe the projection of \a v * to the free component \a Z^d. * * In other words, suppose \a v belongs to ker(M) and snfRep(v) * returns the vector (\a b1, ..., \a bk, \a a1, ..., \a ad). * Suppose furthermore that the free generators returned * by getFreeRep(0..(d-1)) are \a f1, ..., \a fd respectively, and * that the torsion generators returned by getTorsionRep(0..(k-1)) * are \a t1, ..., \a tk respectively. Then * \a v = \a b1.t1 + ... + \a bk.tk + \a a1.f1 + ... + \a ad.fd * modulo img(N). * * If \a v does not belong to ker(M), this routine simply returns * the empty vector. * * \warning The return value may change from version to version * of Regina, as it depends on the choice of Smith normal form. * * \pre Vector \a v has length M.columns(), or equivalently N.rows(). * * \ifacespython Both \a v and the return value are python lists. * * @param v a vector of length M.columns(). M.columns() is also * getRankCC(). * * @return a vector that describes \a v in the standard * Z_{d1} + ... + Z_{dk} + Z^d form, or the empty vector if * \a v is not in the kernel of \a M. k+d is equal to * minNumberOfGenerators(). * */ std::vector snfRep( const std::vector& v) const; /** * A deprecated alternative to snfRep(). * * \deprecated This routine has been renamed to snfRep(). See * snfRep() for details, preconditions and warnings. * * \ifacespython Both \a v and the return value are python lists. * * @param v a vector of length M.columns(). * @return a vector that describes \a v in the standard * Z_{d1} + ... + Z_{dk} + Z^d form, or the empty vector if * \a v is not in the kernel of \a M. */ std::vector getSNFIsoRep( const std::vector& v) const; /** * Returns the number of generators of ker(M), where M is one of * the defining matrices of the chain complex. * * @return the number of generators of ker(M). */ unsigned long minNumberCycleGens() const; /** * Returns the ith generator of the cycles, i.e., the kernel of * M in the chain complex. * * \warning The return value may change from version to version * of Regina, as it depends on the choice of Smith normal form. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param i between 0 and minNumCycleGens()-1. * @return the corresponding generator in chain complex coordinates. */ std::vector cycleGen(unsigned long i) const; /** * Returns a change-of-basis matrix for the Smith normal form of \a M. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * Recall from the class overview that this marked abelian group * is defined by matrices \a M and \a N, where M*N = 0. * * - getMCB() * M * getMRB() is the Smith normal form of \a M; * - getMCBi() and getMRBi() are the inverses of getMCB() and getMRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getMRB() as described above. */ const NMatrixInt& getMRB() const; /** * Returns an inverse change-of-basis matrix for the Smith normal * form of \a M. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * Recall from the class overview that this marked abelian group * is defined by matrices \a M and \a N, where M*N = 0. * * - getMCB() * M * getMRB() is the Smith normal form of \a M; * - getMCBi() and getMRBi() are the inverses of getMCB() and getMRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getMRBi() as described above. */ const NMatrixInt& getMRBi() const; /** * Returns a change-of-basis matrix for the Smith normal form of \a M. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * Recall from the class overview that this marked abelian group * is defined by matrices \a M and \a N, where M*N = 0. * * - getMCB() * M * getMRB() is the Smith normal form of \a M; * - getMCBi() and getMRBi() are the inverses of getMCB() and getMRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getMCB() as described above. */ const NMatrixInt& getMCB() const; /** * Returns an inverse change-of-basis matrix for the Smith normal * form of \a M. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * Recall from the class overview that this marked abelian group * is defined by matrices \a M and \a N, where M*N = 0. * * - getMCB() * M * getMRB() is the Smith normal form of \a M; * - getMCBi() and getMRBi() are the inverses of getMCB() and getMRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getMCBi() as described above. */ const NMatrixInt& getMCBi() const; /** * Returns a change-of-basis matrix for the Smith normal form of * the internal presentation matrix. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * For details on the internal presentation matrix, see the class * overview. If \a P is the internal presentation matrix, then: * * - getNCB() * P * getNRB() is the Smith normal form of \a P; * - getNCBi() and getNRBi() are the inverses of getNCB() and getNRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getNRB() as described above. */ const NMatrixInt& getNRB() const; /** * Returns an inverse change-of-basis matrix for the Smith normal * form of the internal presentation matrix. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * For details on the internal presentation matrix, see the class * overview. If \a P is the internal presentation matrix, then: * * - getNCB() * P * getNRB() is the Smith normal form of \a P; * - getNCBi() and getNRBi() are the inverses of getNCB() and getNRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getNRBi() as described above. */ const NMatrixInt& getNRBi() const; /** * Returns a change-of-basis matrix for the Smith normal form of * the internal presentation matrix. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * For details on the internal presentation matrix, see the class * overview. If \a P is the internal presentation matrix, then: * * - getNCB() * P * getNRB() is the Smith normal form of \a P; * - getNCBi() and getNRBi() are the inverses of getNCB() and getNRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getNCB() as described above. */ const NMatrixInt& getNCB() const; /** * Returns an inverse change-of-basis matrix for the Smith normal * form of the internal presentation matrix. * * This is one of several routines that returns information on * how we determine the isomorphism-class of this group. * * For details on the internal presentation matrix, see the class * overview. If \a P is the internal presentation matrix, then: * * - getNCB() * P * getNRB() is the Smith normal form of \a P; * - getNCBi() and getNRBi() are the inverses of getNCB() and getNRB() * respectively. * * \deprecated This routine will be removed in Regina 5.0. * * @return the matrix getNCBi() as described above. */ const NMatrixInt& getNCBi() const; /** * Returns the rank of the defining matrix \a M. * * The matrix \a M is the `right' matrix used in defining the chain * complex. See the class overview for further details. * * \deprecated This routine will be removed in Regina 5.0. * * @return the rank of the defining matrix \a M. */ unsigned long getRankM() const; /** * Returns the index of the first free generator in the Smith * normal form of the internal presentation matrix. See the class * overview for details. * * \deprecated This routine will be removed in Regina 5.0. * * @return the index of the first free generator. */ unsigned long getFreeLoc() const; /** * Returns the index of the first torsion generator in the Smith * normal form of the internal presentation matrix. See the class * overview for details. * * \deprecated This routine will be removed in Regina 5.0. * * @return the index of the first torsion generator. */ unsigned long getTorsionLoc() const; /** * Returns the `right' matrix used in defining the chain complex. * Our group was defined as the kernel of \a M mod the image of \a N. * This is the matrix \a M. * * This is a copy of the matrix \a M that was originally passed to the * class constructor. See the class overview for further details on * matrices \a M and \a N and their roles in defining the chain complex. * * @return a reference to the defining matrix M. */ const NMatrixInt& getM() const; /** * Returns the `left' matrix used in defining the chain complex. * Our group was defined as the kernel of \a M mod the image of \a N. * This is the matrix \a N. * * This is a copy of the matrix \a N that was originally passed to the * class constructor. See the class overview for further details on * matrices \a M and \a N and their roles in defining the chain complex. * * @return a reference to the defining matrix N. */ const NMatrixInt& getN() const; /** * Returns the coefficients used for the computation of homology. * That is, this routine returns the integer \a p where we use * coefficients in Z_p. If we use coefficients in the integers Z, * then this routine returns 0. * * @return the coefficients used in the homology calculation. */ const NLargeInteger& coefficients() const; /** * Returns an NMarkedAbelianGroup representing the torsion subgroup * of this group. */ std::auto_ptr torsionSubgroup() const; /** * Returns an NHomMarkedAbelianGroup representing the inclusion of the * torsion subgroup into this group. */ std::auto_ptr torsionInclusion() const; }; /** * Represents a homomorphism of finitely generated abelian groups. * * One initializes such a homomorphism by providing: * * - two finitely generated abelian groups, which act as domain and range; * - a matrix describing the linear map between the free abelian * groups in the centres of the respective chain complexes that were * used to define the domain and range. If the abelian groups are computed * via homology with coefficients, the range coefficients must be a quotient * of the domain coefficients. * * So for example, if the domain was initialized by the chain complex * Z^a --A--> Z^b --B--> Z^c with mod p coefficients, and the range * was initialized by Z^d --D--> Z^e --E--> Z^f with mod q * coefficients, then the matrix needs to be an e-by-b matrix. * Furthermore, you only obtain a well-defined * homomorphism if this matrix extends to a cycle map, which this class * assumes but which the user can confirm with isCycleMap(). Moreover, * \a q should divide \a p: this allows for \a q > 0 and \a p = 0, * which means the domain has Z coefficients and the range has mod \a q * coefficients. * * \todo \optlong preImageOf in CC and SNF coordinates. This routine would * return a generating list of elements in the preimage, thought of as an * affine subspace. Or maybe just one element together with the kernel * inclusion. IMO smarter to be a list because that way there's a more * pleasant way to make it empty. Or we could have a variety of routines * among these themes. Store some minimal data for efficient computations of * preImage, eventually replacing the internals of inverseHom() with a more * flexible set of tools. Also add an isInImage() in various coordinates. * * \todo \optlong writeTextShort() have completely different set of * descriptors if an endomorphism domain = range (not so important at the * moment though). New descriptors would include things like automorphism, * projection, differential, finite order, etc. * * \todo \optlong Add map factorization, so that every homomorphism can be * split as a composite of a projection followed by an inclusion. Add * kernelInclusion(), coKerMap(), etc. Add a liftMap() call, i.e., a * procedure to find a lift of a map if one exists. * * @author Ryan Budney */ class REGINA_API NHomMarkedAbelianGroup : public ShareableObject { private: /** internal rep of domain of the homomorphism */ NMarkedAbelianGroup domain; /** internal rep of range of the homomorphism */ NMarkedAbelianGroup range; /** matrix describing map from domain to range, in the coordinates of the chain complexes used to construct domain and range, see above description */ NMatrixInt matrix; /** short description of matrix in SNF coordinates -- this means we've conjugated matrix by the relevant change-of-basis maps in both the domain and range so that we are using the coordinates of Smith Normal form. We also truncate off the trivial Z/Z factors so that reducedMatrix will not have the same dimensions as matrix. This means the torsion factors appear first, followed by the free factors. */ NMatrixInt* reducedMatrix; /** pointer to kernel of map */ NMarkedAbelianGroup* kernel; /** pointer to coKernel of map */ NMarkedAbelianGroup* coKernel; /** pointer to image */ NMarkedAbelianGroup* image; /** pointer to a lattice which describes the kernel of the homomorphism. */ NMatrixInt* reducedKernelLattice; /** compute the ReducedKernelLattice */ void computeReducedKernelLattice(); /** compute the ReducedMatrix */ void computeReducedMatrix(); /** compute the Kernel */ void computeKernel(); /** compute the Cokernel */ void computeCokernel(); /** compute the Image */ void computeImage(); public: /** * Constructs a homomorphism from two marked abelian groups and * a matrix that indicates where the generators are sent. * The roles of the two groups and the matrix are described in * detail in the NHomMarkedAbelianGroup class overview. * * The matrix must be given in the chain-complex coordinates. * Specifically, if the domain was defined via the chain complex * Z^a --N1--> Z^b --M1--> Z^c and the range was * defined via Z^d --N2--> Z^e --M2--> Z^f, then \a mat is * an e-by-b matrix that describes a homomorphism from Z^b to Z^e. * * In order for this to make sense as a homomorphism of the groups * represented by the domain and range respectively, one requires * img(mat*N1) to be a subset of img(N2). Similarly, ker(M1) must * be sent into ker(M2). These facts are not checked, but are * assumed as preconditions of this constructor. * * \pre The matrix \a mat has the required dimensions e-by-b, * gives img(mat*N1) as a subset of img(N2), and sends ker(M1) * into ker(M2), as explained in the detailed notes above. * * @param dom the domain group. * @param ran the range group. * @param mat the matrix that describes the homomorphism from * \a dom to \a ran. */ NHomMarkedAbelianGroup(const NMarkedAbelianGroup& dom, const NMarkedAbelianGroup& ran, const NMatrixInt &mat); /** * Copy constructor. * * @param h the homomorphism to clone. */ NHomMarkedAbelianGroup(const NHomMarkedAbelianGroup& h); /** * Destructor. */ ~NHomMarkedAbelianGroup(); /** * Determines whether this and the given homomorphism together * form a chain map. * * Given two NHomMarkedAbelianGroups, you have two diagrams: *
         * Z^a --N1--> Z^b --M1--> Z^c   Z^g --N3--> Z^h --M3--> Z^i
         *                   ^                             ^
         *                   |this.matrix                  |other.matrix
         * Z^d --N2--> Z^e --M2--> Z^f   Z^j --N4--> Z^k --M4--> Z^l
         * 
* If c=g and f=j and M1=N3 and M2=N4, you can ask if these maps * commute, i.e., whether you have a map of chain complexes. * * @param other the other homomorphism to analyse in conjunction * with this. * @return true if and only if c=g, M1=N3, f=j, M2=N4, * and the diagram commutes. */ bool isChainMap(const NHomMarkedAbelianGroup &other) const; /** * Is this at least a cycle map? If not, pretty much any further * computations you try with this class will be give you nothing * more than carefully-crafted garbage. Technically, this routine * only checks that cycles are sent to cycles, since it only has access * to three of the four maps you need to verify you have a cycle map. * * @return \c true if and only if this is a chain map. */ bool isCycleMap() const; /** * Is this an epic homomorphism? * * @return true if this homomorphism is epic. */ bool isEpic() const; /** * Is this a monic homomorphism? * * @return true if this homomorphism is monic. */ bool isMonic() const; /** * A deprecated alternative to isIsomorphism(). * * \deprecated This routine will be removed in a future version * of Regina; please use the identical routine isIsomorphism() instead. * * @return true if this homomorphism is an isomorphism. */ bool isIso() const; /** * Is this an isomorphism? * * @return true if this homomorphism is an isomorphism. */ bool isIsomorphism() const; /** * Is this the zero map? * * @return true if this homomorphism is the zero map. */ bool isZero() const; /** * Is this the identity automorphism? * * @return true if and only if the domain and range are defined via * the same chain complexes and the induced map on homology is the * identity. */ bool isIdentity() const; /** * Returns the kernel of this homomorphism. * * @return the kernel of the homomorphism, as a marked abelian group. */ const NMarkedAbelianGroup& getKernel() const; /** * Returns the cokernel of this homomorphism. * * @return the cokernel of the homomorphism, as a marked abelian group. */ const NMarkedAbelianGroup& getCokernel() const; /** * Returns the image of this homomorphism. * * @return the image of the homomorphism, as a marked abelian group. */ const NMarkedAbelianGroup& getImage() const; /** * Short text representation. This will state some basic * properties of the homomorphism, such as: * * - whether the map is the identity; * - whether the map is an isomorphism; * - whether the map is monic or epic; * - if it is not monic, describes the kernel; * - if it is not epic, describes the co-kernel; * - if it is neither monic nor epic, describes the image. * * @param out the stream to write to. */ void writeTextShort(std::ostream& out) const; /** * A more detailed text representation of the homomorphism. * * @param out the stream to write to. */ void writeTextLong(std::ostream& out) const; /** * Returns the domain of this homomorphism. * * @return the domain that was used to define the homomorphism. */ const NMarkedAbelianGroup& getDomain() const; /** * Returns the range of this homomorphism. * * @return the range that was used to define the homomorphism. */ const NMarkedAbelianGroup& getRange() const; /** * Returns the defining matrix for the homomorphism. * * @return the matrix that was used to define the homomorphism. */ const NMatrixInt& getDefiningMatrix() const; /** * Returns the internal reduced matrix representing the homomorphism. * This is where the rows/columns of the matrix represent * first the free generators, then the torsion summands in the order * of the invariant factors: * * Z^d + Z_{d0} + ... + Z_{dk} * where: * * - \a d is the number of free generators, as returned by getRank(); * - \a d1, ..., \a dk are the invariant factors that describe the * torsion elements of the group, where * 1 < \a d1 | \a d2 | ... | \a dk. * * @return a copy of the internal representation of the homomorphism. */ const NMatrixInt& getReducedMatrix() const; /** * Evaluate the image of a vector under this homomorphism, using * the original chain complexes' coordinates. This involves * multiplication by the defining matrix. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param input an input vector in the domain chain complex's * coordinates, of length getDomain().getM().columns(). * @return the image of this vector in the range chain complex's * coordinates, of length getRange().getM().columns(). */ std::vector evalCC( const std::vector &input) const; /** * Evaluate the image of a vector under this homomorphism, using * the Smith normal form coordinates. This is just multiplication by * the reduced matrix, returning the empty vector if the input vector * has the wrong dimensions. * * \warning Smith normal form coordinates are sensitive to the * implementation of the Smith Normal Form, i.e., they are not * canonical. * * \ifacespython Not available yet. This routine will be made * accessible to Python in a future release. * * @param input an input vector in the domain SNF coordinates, * of length getDomain().minNumberOfGenerators(). * @return the image of this vector in the range chain complex's * coordinates, of length getRange().minNumberOfGenerators(). */ std::vector evalSNF( const std::vector &input) const; /** * Returns the inverse to an NHomMarkedAbelianGroup. If this * homomorphism is not invertible, this routine returns the zero * homomorphism. * * If you are computing with mod-p coefficients, this routine will * further require that this invertible map preserves the UCT * splitting of the group, i.e., it gives an isomorphism of the * tensor product parts and the TOR parts. At present this suffices * since we're only using this to construct maps between * homology groups in different coordinate systems. * * @return the inverse homomorphism, or the zero homomorphism if * this is not invertible. */ std::auto_ptr inverseHom() const; /** * Returns the composition of two homomorphisms. * * \pre the homomorphisms must be composable, meaning that the * range of X must have the same presentation matrices as the * domain of this homomorphism. * * @param X the homomorphism to compose this with. * @return a newly created composite homomorphism. */ std::auto_ptr operator * ( const NHomMarkedAbelianGroup &X) const; /** * Returns an NHomMarkedAbelianGroup representing the induced map * on the torsion subgroups. */ std::auto_ptr torsionSubgroup() const; /** * Writes a human-readable version of the reduced matrix to the * given output stream. This is a description of the homomorphism * in some specific coordinates at present only meant to be * internal to NHomMarkedAbelianGroup. At present, these coordinates * have the torsion factors of the group appearing first, followed by * the free factors. * * \ifacespython The \a out argument is missing; instead this is * assumed to be standard output. * * @param out the output stream. */ void writeReducedMatrix(std::ostream& out) const; private: /** * For those situations where you want to define an NHomMarkedAbelianGroup * from its reduced matrix, not from a chain map. This is in the situation where * the SNF coordinates have particular meaning to the user. At present I only use this * for NHomMarkedAbelianGroup::inverseHom(). Moreover, this routine assumes tebeRedMat * actually can be the reduced matrix of some chain map -- this is not a restriction in * the coeff==0 case, but it is if coeff > 0. * * \todo Erase completely once made obsolete by right/left inverse. */ NHomMarkedAbelianGroup(const NMatrixInt &tobeRedMat, const NMarkedAbelianGroup &dom, const NMarkedAbelianGroup &ran); }; /*@}*/ // Inline functions for NMarkedAbelianGroup // copy constructor inline NMarkedAbelianGroup::NMarkedAbelianGroup(const NMarkedAbelianGroup& g) : ShareableObject(), OM(g.OM), ON(g.ON), OMR(g.OMR), OMC(g.OMC), OMRi(g.OMRi), OMCi(g.OMCi), rankOM(g.rankOM), ornR(clonePtr(g.ornR)), ornC(clonePtr(g.ornC)), ornRi(clonePtr(g.ornRi)), ornCi(clonePtr(g.ornCi)), otR(clonePtr(g.otR)), otC(clonePtr(g.otC)), otRi(clonePtr(g.otRi)), otCi(clonePtr(g.otCi)), InvFacList(g.InvFacList), snfrank(g.snfrank), snffreeindex(g.snffreeindex), ifNum(g.ifNum), ifLoc(g.ifLoc), coeff(g.coeff), TORLoc(g.TORLoc), TORVec(g.TORVec), tensorIfLoc(g.tensorIfLoc), tensorIfNum(g.tensorIfNum), tensorInvFacList(g.tensorInvFacList) { } // destructor inline NMarkedAbelianGroup::~NMarkedAbelianGroup() { } inline unsigned long NMarkedAbelianGroup::getTorsionRank(unsigned long degree) const { return getTorsionRank(NLargeInteger(degree)); } inline unsigned long NMarkedAbelianGroup::getNumberOfInvariantFactors() const { return ifNum; } inline const NLargeInteger& NMarkedAbelianGroup::getInvariantFactor( unsigned long index) const { return InvFacList[index]; } inline unsigned long NMarkedAbelianGroup::getRank() const { return snfrank; } inline unsigned long NMarkedAbelianGroup::minNumberOfGenerators() const { return snfrank + ifNum; } inline unsigned long NMarkedAbelianGroup::getRankCC() const { return OM.columns(); } inline std::vector NMarkedAbelianGroup::getSNFIsoRep( const std::vector& v) const { return snfRep(v); } inline unsigned long NMarkedAbelianGroup::minNumberCycleGens() const { return OM.columns() - TORLoc; } inline bool NMarkedAbelianGroup::isTrivial() const { return ( (snfrank==0) && (InvFacList.size()==0) ); } inline bool NMarkedAbelianGroup::equalTo(const NMarkedAbelianGroup& other) const { return ( (OM == other.OM) && (ON == other.ON) && (coeff == other.coeff) ); } inline bool NMarkedAbelianGroup::operator == ( const NMarkedAbelianGroup &other) const { return isIsomorphicTo(other); } inline bool NMarkedAbelianGroup::isIsomorphicTo( const NMarkedAbelianGroup &other) const { return ((InvFacList == other.InvFacList) && (snfrank == other.snfrank)); } inline const NMatrixInt& NMarkedAbelianGroup::getMRB() const { return OMR; } inline const NMatrixInt& NMarkedAbelianGroup::getMRBi() const { return OMRi; } inline const NMatrixInt& NMarkedAbelianGroup::getMCB() const { return OMC; } inline const NMatrixInt& NMarkedAbelianGroup::getMCBi() const { return OMCi; } inline const NMatrixInt& NMarkedAbelianGroup::getNRB() const { return *ornR; } inline const NMatrixInt& NMarkedAbelianGroup::getNRBi() const { return *ornRi; } inline const NMatrixInt& NMarkedAbelianGroup::getNCB() const { return *ornC; } inline const NMatrixInt& NMarkedAbelianGroup::getNCBi() const { return *ornCi; } inline unsigned long NMarkedAbelianGroup::getRankM() const { return rankOM; } inline unsigned long NMarkedAbelianGroup::getFreeLoc() const { return snffreeindex; } inline unsigned long NMarkedAbelianGroup::getTorsionLoc() const { return ifLoc; } inline const NMatrixInt& NMarkedAbelianGroup::getM() const { return OM; } inline const NMatrixInt& NMarkedAbelianGroup::getN() const { return ON; } inline const NLargeInteger& NMarkedAbelianGroup::coefficients() const { return coeff; } // Inline functions for NHomMarkedAbelianGroup inline NHomMarkedAbelianGroup::NHomMarkedAbelianGroup( const NMarkedAbelianGroup& dom, const NMarkedAbelianGroup& ran, const NMatrixInt &mat) : domain(dom), range(ran), matrix(mat), reducedMatrix(0), kernel(0), coKernel(0), image(0), reducedKernelLattice(0) { } inline NHomMarkedAbelianGroup::~NHomMarkedAbelianGroup() { if (reducedMatrix) delete reducedMatrix; if (kernel) delete kernel; if (coKernel) delete coKernel; if (image) delete image; if (reducedKernelLattice) delete reducedKernelLattice; } inline const NMarkedAbelianGroup& NHomMarkedAbelianGroup::getDomain() const { return domain; } inline const NMarkedAbelianGroup& NHomMarkedAbelianGroup::getRange() const { return range; } inline const NMatrixInt& NHomMarkedAbelianGroup::getDefiningMatrix() const { return matrix; } inline const NMatrixInt& NHomMarkedAbelianGroup::getReducedMatrix() const { // Cast away const to compute the reduced matrix -- the only reason we're // changing data members now is because we delayed calculations // until they were really required. const_cast(this)->computeReducedMatrix(); return *reducedMatrix; } inline bool NHomMarkedAbelianGroup::isEpic() const { return getCokernel().isTrivial(); } inline bool NHomMarkedAbelianGroup::isMonic() const { return getKernel().isTrivial(); } inline bool NHomMarkedAbelianGroup::isIso() const { return isIsomorphism(); } inline bool NHomMarkedAbelianGroup::isIsomorphism() const { return (getCokernel().isTrivial() && getKernel().isTrivial()); } inline bool NHomMarkedAbelianGroup::isZero() const { return getImage().isTrivial(); } inline const NMarkedAbelianGroup& NHomMarkedAbelianGroup::getKernel() const { // Cast away const to compute the kernel -- the only reason we're // changing data members now is because we delayed calculations // until they were really required. const_cast(this)->computeKernel(); return *kernel; } inline const NMarkedAbelianGroup& NHomMarkedAbelianGroup::getImage() const { // Cast away const to compute the kernel -- the only reason we're // changing data members now is because we delayed calculations // until they were really required. const_cast(this)->computeImage(); return *image; } inline const NMarkedAbelianGroup& NHomMarkedAbelianGroup::getCokernel() const { // Cast away const to compute the kernel -- the only reason we're // changing data members now is because we delayed calculations // until they were really required. const_cast(this)->computeCokernel(); return *coKernel; } } // namespace regina #endif regina-4.95/engine/algebra/nxmlalgebrareader.cpp000644 000765 000024 00000014112 12234011536 021630 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nxmlalgebrareader.h" #include "utilities/stringutils.h" namespace regina { /** * A unique namespace containing various task-specific packet readers. */ namespace { /** * Reads a single relation in a group presentation. */ class NExpressionReader : public NXMLElementReader { private: NGroupExpression* exp; long nGens; public: NExpressionReader(long newNGens) : exp(new NGroupExpression()), nGens(newNGens) { } NGroupExpression* getExpression() { return exp; } virtual void initialChars(const std::string& chars) { std::list tokens; basicTokenise(back_inserter(tokens), chars); std::string genStr, powStr; long gen, pow; std::string::size_type split; for (std::list::const_iterator it = tokens.begin(); it != tokens.end(); it++) { split = (*it).find('^'); if (split == (*it).length()) { delete exp; exp = 0; break; } genStr = (*it).substr(0, split); powStr = (*it).substr(split + 1, (*it).length() - split - 1); if ((! valueOf(genStr, gen)) || (! valueOf(powStr, pow))) { delete exp; exp = 0; break; } if (gen < 0 || gen >= nGens) { delete exp; exp = 0; break; } exp->addTermLast(gen, pow); } } }; } void NXMLAbelianGroupReader::startElement(const std::string&, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader*) { long rank; if (valueOf(tagProps.lookup("rank"), rank)) if (rank >= 0) { group = new NAbelianGroup(); if (rank) group->addRank(rank); } } void NXMLAbelianGroupReader::initialChars(const std::string& chars) { if (group) { std::list tokens; if (basicTokenise(back_inserter(tokens), chars) > 0) { std::multiset torsion; NLargeInteger val; for (std::list::const_iterator it = tokens.begin(); it != tokens.end(); it++) if (valueOf(*it, val)) torsion.insert(val); if (! torsion.empty()) group->addTorsionElements(torsion); } } } void NXMLGroupPresentationReader::startElement(const std::string&, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader*) { long nGen; if (valueOf(tagProps.lookup("generators"), nGen)) if (nGen >= 0) { group = new NGroupPresentation(); if (nGen) group->addGenerator(nGen); } } NXMLElementReader* NXMLGroupPresentationReader::startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& /* subTagProps */) { if (group) if (subTagName == "reln") return new NExpressionReader(group->getNumberOfGenerators()); return new NXMLElementReader(); } void NXMLGroupPresentationReader::endSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (group) if (subTagName == "reln") { NGroupExpression* exp = dynamic_cast(subReader)->getExpression(); if (exp) group->addRelation(exp); } } } // namespace regina regina-4.95/engine/algebra/nxmlalgebrareader.h000644 000765 000024 00000012431 12234011536 021277 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file algebra/nxmlalgebrareader.h * \brief Deals with parsing XML data for various algebraic structures. */ #ifndef __NXMLALGEBRAREADER_H #ifndef __DOXYGEN #define __NXMLALGEBRAREADER_H #endif #include "regina-core.h" #include "algebra/nabeliangroup.h" #include "algebra/ngrouppresentation.h" #include "file/nxmlelementreader.h" namespace regina { /** * \weakgroup algebra * @{ */ /** * An XML element reader that reads a single abelian group. * An abelian group is generally contained within an * \ ... \ pair. * * \ifacespython Not present. */ class REGINA_API NXMLAbelianGroupReader : public NXMLElementReader { private: NAbelianGroup* group; /**< The abelian group currently being read. */ public: /** * Creates a new abelian group reader. */ NXMLAbelianGroupReader(); /** * Returns the newly allocated abelian group that has been read by * this element reader. * * @return the group that has been read, or 0 if an error occurred. */ virtual NAbelianGroup* getGroup(); virtual void startElement(const std::string& tagName, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader* parentReader); virtual void initialChars(const std::string& chars); }; /** * An XML element reader that reads a single group presentation. * A group presentation is generally contained within a * \ ... \ pair. * * \ifacespython Not present. */ class REGINA_API NXMLGroupPresentationReader : public NXMLElementReader { private: NGroupPresentation* group; /**< The group presentation currently being read. */ public: /** * Creates a new group presentation reader. */ NXMLGroupPresentationReader(); /** * Returns the newly allocated group presentation that has been read by * this element reader. * * @return the group that has been read, or 0 if an error occurred. */ virtual NGroupPresentation* getGroup(); virtual void startElement(const std::string& tagName, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader* parentReader); virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLAbelianGroupReader inline NXMLAbelianGroupReader::NXMLAbelianGroupReader() : group(0) { } inline NAbelianGroup* NXMLAbelianGroupReader::getGroup() { return group; } // Inline functions for NXMLGroupPresentationReader inline NXMLGroupPresentationReader::NXMLGroupPresentationReader() : group(0) { } inline NGroupPresentation* NXMLGroupPresentationReader::getGroup() { return group; } } // namespace regina #endif regina-4.95/engine/angle/CMakeLists.txt000644 000765 000024 00000000743 12234011536 017703 0ustar00babstaff000000 000000 # angle # Files to compile SET ( FILES nanglestructure nanglestructurelist nxmlanglestructreader ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} angle/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nanglestructure.h nanglestructurelist.h nxmlanglestructreader.h DESTINATION ${INCLUDEDIR}/angle COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/angle/nanglestructure.cpp000644 000765 000024 00000023611 12234011536 021073 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "angle/nanglestructure.h" #include "triangulation/ntriangulation.h" #include "utilities/xmlutils.h" // Property IDs: #define PROPID_FLAGS 1 namespace regina { const unsigned long NAngleStructure::flagStrict = 1; const unsigned long NAngleStructure::flagTaut = 2; const unsigned long NAngleStructure::flagCalculatedType = 4; const unsigned long NAngleStructure::flagVeering = 8; NAngleStructure* NAngleStructure::clone() const { NAngleStructure* ans = new NAngleStructure(triangulation, new NAngleStructureVector(*vector)); ans->flags = flags; return ans; } NRational NAngleStructure::getAngle(unsigned long tetIndex, int edgePair) const { const NLargeInteger& num = (*vector)[3 * tetIndex + edgePair]; const NLargeInteger& den = (*vector)[3 * triangulation->getNumberOfTetrahedra()]; NLargeInteger gcd = den.gcd(num); if (gcd < 0) gcd.negate(); return NRational(num.divExact(gcd), den.divExact(gcd)); } void NAngleStructure::writeTextShort(std::ostream& out) const { unsigned long nTets = triangulation->getNumberOfTetrahedra(); unsigned j; for (unsigned long tet = 0; tet < nTets; tet++) { if (tet > 0) out << " ; "; for (j=0; j<3; j++) { if (j > 0) out << ' '; out << getAngle(tet, j); } } } void NAngleStructure::writeXMLData(std::ostream& out) const { // Write the vector length. size_t vecLen = vector->size(); out << " "; // Write the non-zero elements. NLargeInteger entry; for (size_t i = 0; i < vecLen; i++) { entry = (*vector)[i]; if (entry != 0) out << i << ' ' << entry << ' '; } // Write properties. /** Flags in data files are deprecated as of Regina 4.93. out << regina::xml::xmlValueTag("flags", flags); */ // Write the closing tag. out << "\n"; } void NAngleStructure::calculateType() const { unsigned long size = vector->size(); if (size == 1) { // We have no tetrahedra, which means this angle structure has it all: // strict, taut and veering. flags |= flagStrict; flags |= flagTaut; flags |= flagVeering; flags |= flagCalculatedType; return; } bool taut = true; bool strict = true; // Run through the tetrahedra one by one. const NLargeInteger& scale = (*vector)[size - 1]; unsigned long pair; for (unsigned long base = 0; base < size - 1; base += 3) { for (pair = 0; pair < 3; pair++) { if ((*vector)[base + pair] == scale) { // We have a pi; thus all three angles in this // tetrahedron are pi or zero. strict = false; break; } else if ((*vector)[base + pair] == NLargeInteger::zero) strict = false; else taut = false; } if ((! strict) && (! taut)) break; } // Update the flags as appropriate. if (strict) flags |= flagStrict; else flags &= (~flagStrict); if (taut) { // This structure is taut. flags |= flagTaut; // Is it veering also? bool veering = true; if (triangulation->isOrientable()) { long nEdges = triangulation->getNumberOfEdges(); int* edgeColour = new int[nEdges]; std::fill(edgeColour, edgeColour + nEdges, (int)0); NTetrahedron* tet; int orient; long e; for (unsigned i = 0; i < triangulation->getNumberOfTetrahedra(); ++i) { tet = triangulation->getTetrahedron(i); orient = tet->orientation(); if ((*vector)[3 * i] > 0) { // Edges 0,5 are marked as pi. // For a positively oriented tetrahedron: // Edges 1,4 vs 2,3 are of colour +1 vs -1. e = triangulation->edgeIndex(tet->getEdge(1)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(4)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(2)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; e = triangulation->edgeIndex(tet->getEdge(3)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; } else if ((*vector)[3 * i + 1] > 0) { // Edges 1,4 are marked as pi. // For a positively oriented tetrahedron: // Edges 2,3 vs 0,5 are of colour +1 vs -1. e = triangulation->edgeIndex(tet->getEdge(2)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(3)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(0)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; e = triangulation->edgeIndex(tet->getEdge(5)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; } else if ((*vector)[3 * i + 2] > 0) { // Edges 2,3 are marked as pi. // For a positively oriented tetrahedron: // Edges 0,5 vs 1,4 are of colour +1 vs -1. e = triangulation->edgeIndex(tet->getEdge(0)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(5)); if (edgeColour[e] == -orient) veering = false; else edgeColour[e] = orient; e = triangulation->edgeIndex(tet->getEdge(1)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; e = triangulation->edgeIndex(tet->getEdge(4)); if (edgeColour[e] == orient) veering = false; else edgeColour[e] = -orient; } if (! veering) break; } delete[] edgeColour; } else { // Only orientable triangulations can be veering. veering = false; } if (veering) flags |= flagVeering; else flags &= (~flagVeering); } else { // Not taut, and therefore not veering either. flags &= (~flagTaut); flags &= (~flagVeering); } flags |= flagCalculatedType; } } // namespace regina regina-4.95/engine/angle/nanglestructure.h000644 000765 000024 00000027610 12236247215 020552 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file angle/nanglestructure.h * \brief Deals with angle structures on triangulations. */ #ifndef __NANGLESTRUCTURE_H #ifndef __DOXYGEN #define __NANGLESTRUCTURE_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nrational.h" #include "maths/nray.h" namespace regina { class NTriangulation; class NXMLAngleStructureReader; /** * \addtogroup angle Angle Structures * Angle structures on triangulations. * @{ */ /** * A vector of integers used to indirectly store the individual angles * in an angle structure. * * This vector will contain one member per angle plus a final scaling * member; to obtain the actual angle in the angle structure one should * divide the corresonding angle member by the scaling member and then * multiply by pi. * * The reason for using this obfuscated representation is so we can * use the NDoubleDescription vertex enumeration routines to * calculate vertex angle structures. * * If there are \a t tetrahedra in the underlying triangulation, there * will be precisely 3t+1 elements in this vector. The first * three elements will be the angle members for the first tetrahedron, * the next three for the second tetrahedron and so on. For each * tetraheron, the three individual elements are the angle members * for vertex splittings 0, 1 and 2 (see NAngleStructure::getAngle()). * The final element of the vector is the scaling member as described * above. * * \testpart * * \ifacespython Not present. */ class REGINA_API NAngleStructureVector : public NRay { public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NAngleStructureVector(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NAngleStructureVector(const NVector& cloneMe); }; /** * Represents an angle structure on a triangulation. * Once the underlying triangulation changes, this angle structure * is no longer valid. * * \testpart */ class REGINA_API NAngleStructure : public ShareableObject { private: NAngleStructureVector* vector; /**< Stores (indirectly) the individual angles in this angle * structure. */ NTriangulation* triangulation; /**< The triangulation on which this angle structure is placed. */ mutable unsigned long flags; /**< Stores a variety of angle structure properties as * described by the flag constants in this class. * Flags can be combined using bitwise OR. */ static const unsigned long flagStrict; /**< Signals that this angle structure is strict. */ static const unsigned long flagTaut; /**< Signals that this angle structure is taut. A taut structure might also be veering, in which case the flag \a flagVeering will be set also. */ static const unsigned long flagVeering; /**< Signals that this angle structure is veering (in which case that the \a flagTaut flag must be set also). */ static const unsigned long flagCalculatedType; /**< Signals that the type (strict/taut/veering) has been calculated. */ public: /** * Creates a new angle structure on the given triangulation with * the given coordinate vector. * * \ifacespython Not present. * * @param triang the triangulation on which this angle structure lies. * @param newVector a vector containing the individual angles in the * angle structure. */ NAngleStructure(NTriangulation* triang, NAngleStructureVector* newVector); /** * Destroys this angle structure. * The underlying vector of angles will also be deallocated. */ virtual ~NAngleStructure(); /** * Creates a newly allocated clone of this angle structure. * * @return a clone of this angle structure. */ NAngleStructure* clone() const; /** * Returns the requested angle in this angle structure. * The angle returned will be scaled down; the actual angle is * the returned value multiplied by pi. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested angle lives; this should * be between 0 and NTriangulation::getNumberOfTetrahedra()-1 * inclusive. * @param edgePair the number of the vertex splitting * representing the pair of edges holding the requested angle; * this should be between 0 and 2 inclusive. See ::vertexSplit * and ::vertexSplitDefn for details regarding vertex splittings. * @return the requested angle scaled down by pi. */ NRational getAngle(unsigned long tetIndex, int edgePair) const; /** * Returns the triangulation on which this angle structure lies. * * @return the underlying triangulation. */ NTriangulation* getTriangulation() const; /** * Determines whether this is a strict angle structure. * A strict angle structure has all angles strictly between (not * including) 0 and pi. * * @return \c true if and only if this is a strict angle structure. */ bool isStrict() const; /** * Determines whether this is a taut angle structure. * A taut angle structure contains only angles 0 and pi. * * Here we use the Kang-Rubinstein definition of a taut * angle structure [1], which is based on the angles alone. * In his original paper [2], Lackenby has an extra condition * whereby 2-faces of the triangulation must have consistent * coorientations, which we do not enforce here. * * [1] E. Kang and J. H. Rubinstein, "Ideal triangulations of * 3-manifolds II; Taut and angle structures", Algebr. Geom. Topol. * 5 (2005), pp. 1505-1533. * * [2] M. Lackenby, "Taut ideal triangulations of 3-manifolds", * Geom. Topol. 4 (2000), pp. 369-395. * * @return \c true if and only if this is a taut structure. */ bool isTaut() const; /** * Determines whether this is a veering structure. * A veering structure is taut angle structure with additional * strong combinatorial constraints, which we do not outline here. * For a definition, see C. D. Hodgson, J. H. Rubinstein, * H. Segerman, and S. Tillmann, "Veering triangulations admit * strict angle structures", Geom. Topol., 15 (2011), pp. 2073-2089. * * Note that the Hodgson et al. definition is slightly more general * than Agol's veering taut triangulations from his original paper: * I. Agol, " Ideal triangulations of pseudo-Anosov mapping tori", * in "Topology and Geometry in Dimension Three", volume 560 of * Contemp. Math., pp. 1-17, Amer. Math. Soc., 2011. * This mirrors the way in which the Kang-Rubinstein definition of * taut angle structure is slightly more general than Lackenby's. * See the Hodgson et al. paper for full details and comparisons * between the two settings. * * If this angle structure is not taut, or if the underlying * triangulation is non-orientable, then this routine will * return \c false. * * @return \c true if and only if this is a veering structure. */ bool isVeering() const; void writeTextShort(std::ostream& out) const; /** * Writes a chunk of XML containing this angle structure and all * of its properties. This routine will be called from within * NAngleStructureList::writeXMLPacketData(). * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ void writeXMLData(std::ostream& out) const; protected: /** * Calculates the structure type (strict or taut) and stores it * as a property. */ void calculateType() const; friend class regina::NXMLAngleStructureReader; }; /*@}*/ // Inline functions for NAngleStructureVector inline NAngleStructureVector::NAngleStructureVector(size_t length) : NRay(length) { } inline NAngleStructureVector::NAngleStructureVector( const NVector& cloneMe) : NRay(cloneMe) { } // Inline functions for NAngleStructure inline NAngleStructure::NAngleStructure(NTriangulation* triang, NAngleStructureVector* newVector) : vector(newVector), triangulation(triang), flags(0) { } inline NAngleStructure::~NAngleStructure() { delete vector; } inline NTriangulation* NAngleStructure::getTriangulation() const { return triangulation; } inline bool NAngleStructure::isStrict() const { if ((flags & flagCalculatedType) == 0) calculateType(); return ((flags & flagStrict) != 0); } inline bool NAngleStructure::isTaut() const { if ((flags & flagCalculatedType) == 0) calculateType(); return ((flags & flagTaut) != 0); } inline bool NAngleStructure::isVeering() const { if ((flags & flagCalculatedType) == 0) calculateType(); return ((flags & flagVeering) != 0); } } // namespace regina #endif regina-4.95/engine/angle/nanglestructurelist.cpp000644 000765 000024 00000023136 12234011536 021771 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "angle/nanglestructurelist.h" #include "enumerate/ndoubledescription.h" #include "maths/nmatrixint.h" #include "progress/nprogresstracker.h" #include "surfaces/nnormalsurface.h" #include "triangulation/ntriangulation.h" #include "utilities/xmlutils.h" // Property IDs: #define PROPID_ALLOWSTRICT 1 #define PROPID_ALLOWTAUT 2 namespace regina { typedef std::vector::const_iterator StructureIteratorConst; void* NAngleStructureList::Enumerator::run(void*) { if (tracker) tracker->newStage("Enumerating vertex angle structures"); // Form the matching equations (one per non-boundary edge plus // one per tetrahedron). unsigned long nTetrahedra = triang->getNumberOfTetrahedra(); unsigned long nCoords = 3 * nTetrahedra + 1; long nEquations = long(triang->getNumberOfEdges()) + long(triang->getNumberOfTetrahedra()); for (NTriangulation::BoundaryComponentIterator bit = triang->getBoundaryComponents().begin(); bit != triang->getBoundaryComponents().end(); bit++) nEquations -= (*bit)->getNumberOfEdges(); NMatrixInt eqns(nEquations, nCoords); unsigned long row = 0; std::deque::const_iterator embit; NPerm4 perm; unsigned long index; for (NTriangulation::EdgeIterator eit = triang->getEdges().begin(); eit != triang->getEdges().end(); eit++) { if ((*eit)->isBoundary()) continue; for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { index = triang->tetrahedronIndex((*embit).getTetrahedron()); perm = (*embit).getVertices(); eqns.entry(row, 3 * index + vertexSplit[perm[0]][perm[1]]) += 1; } eqns.entry(row, nCoords - 1) = -2; row++; } for (index = 0; index < nTetrahedra; index++) { eqns.entry(row, 3 * index) = 1; eqns.entry(row, 3 * index + 1) = 1; eqns.entry(row, 3 * index + 2) = 1; eqns.entry(row, nCoords - 1) = -1; row++; } // Form the taut constraints, if we need them. NEnumConstraintList* constraints = 0; if (list->tautOnly_) { constraints = new NEnumConstraintList(triang->getNumberOfTetrahedra()); unsigned base = 0; for (unsigned c = 0; c < constraints->size(); ++c) { (*constraints)[c].insert((*constraints)[c].end(), base++); (*constraints)[c].insert((*constraints)[c].end(), base++); (*constraints)[c].insert((*constraints)[c].end(), base++); } } // Find the angle structures. NDoubleDescription::enumerateExtremalRays( StructureInserter(*list, triang), eqns, constraints, tracker); // All done! delete constraints; if (! (tracker && tracker->isCancelled())) triang->insertChildLast(list); if (tracker) tracker->setFinished(); return 0; } NAngleStructureList* NAngleStructureList::enumerate(NTriangulation* owner, bool tautOnly, NProgressTracker* tracker) { NAngleStructureList* ans = new NAngleStructureList(tautOnly); Enumerator* e = new Enumerator(ans, owner, tracker); if (tracker) { if (! e->start(0, true)) { delete ans; return 0; } return ans; } else { e->run(0); delete e; return ans; } } NTriangulation* NAngleStructureList::getTriangulation() const { return dynamic_cast(getTreeParent()); } void NAngleStructureList::writeTextShort(std::ostream& o) const { o << structures.size() << " vertex angle structure"; if (structures.size() != 1) o << 's'; o << " (" << (tautOnly_ ? "taut only" : "no restrictions") << ')'; } void NAngleStructureList::writeTextLong(std::ostream& o) const { writeTextShort(o); o << ":\n"; for (StructureIteratorConst it = structures.begin(); it != structures.end(); it++) { (*it)->writeTextShort(o); o << '\n'; } } void NAngleStructureList::writeXMLPacketData(std::ostream& out) const { using regina::xml::xmlValueTag; // Write the enumeration parameters. out << " \n"; // Write the individual structures. for (StructureIteratorConst it = structures.begin(); it != structures.end(); it++) (*it)->writeXMLData(out); // Write the properties. if (doesSpanStrict.known()) out << " " << xmlValueTag("spanstrict", doesSpanStrict.value()) << '\n'; if (doesSpanTaut.known()) out << " " << xmlValueTag("spantaut", doesSpanTaut.value()) << '\n'; } NPacket* NAngleStructureList::internalClonePacket(NPacket* /* parent */) const { NAngleStructureList* ans = new NAngleStructureList(tautOnly_); transform(structures.begin(), structures.end(), back_inserter(ans->structures), FuncNewClonePtr()); if (doesSpanStrict.known()) ans->doesSpanStrict = doesSpanStrict; if (doesSpanTaut.known()) ans->doesSpanTaut = doesSpanTaut; return ans; } void NAngleStructureList::calculateSpanStrict() const { if (structures.empty()) { doesSpanStrict = false; return; } unsigned long nTets = getTriangulation()->getNumberOfTetrahedra(); if (nTets == 0) { doesSpanStrict = true; return; } // We run into trouble if there's a 0 or pi angle that never changes. NRational* fixedAngles = new NRational[nTets * 3]; unsigned long nFixed = 0; // Get the list of bad unchanging angles from the first structure. StructureIteratorConst it = structures.begin(); const NAngleStructure* s = *it; NRational angle; unsigned long tet; int edges; for (tet = 0; tet < nTets; tet++) for (edges = 0; edges < 3; edges++) { angle = s->getAngle(tet, edges); if (angle == NRational::zero || angle == NRational::one) { fixedAngles[3 * tet + edges] = angle; nFixed++; } else fixedAngles[3 * tet + edges] = NRational::undefined; } if (nFixed == 0) { doesSpanStrict = true; delete[] fixedAngles; return; } // Run through the rest of the structures to see if these bad angles // do ever change. for (it++; it != structures.end(); it++) { s = *it; for (tet = 0; tet < nTets; tet++) for (edges = 0; edges < 3; edges++) { if (fixedAngles[3 * tet + edges] == NRational::undefined) continue; if (s->getAngle(tet, edges) != fixedAngles[3 * tet + edges]) { // Here's a bad angle that finally changed. fixedAngles[3 * tet + edges] = NRational::undefined; nFixed--; if (nFixed == 0) { doesSpanStrict = true; delete[] fixedAngles; return; } } } } // Some of the bad angles never changed. doesSpanStrict = false; delete[] fixedAngles; } void NAngleStructureList::calculateSpanTaut() const { doesSpanTaut = (find_if(structures.begin(), structures.end(), std::mem_fun(&NAngleStructure::isTaut)) != structures.end()); } } // namespace regina regina-4.95/engine/angle/nanglestructurelist.h000644 000765 000024 00000047175 12236524106 021453 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file angle/nanglestructurelist.h * \brief Contains a packet representing a collection of angle * structures on a triangulation. */ #ifndef __NANGLESTRUCTURELIST_H #ifndef __DOXYGEN #define __NANGLESTRUCTURELIST_H #endif #include #include #include #include "regina-core.h" #include "angle/nanglestructure.h" #include "packet/npacket.h" #include "utilities/memutils.h" #include "utilities/nproperty.h" #include "utilities/nthread.h" namespace regina { class NAngleStructureList; class NProgressTracker; class NXMLPacketReader; class NXMLAngleStructureListReader; /** * \weakgroup angle * @{ */ /** * Stores information about the angle structure list packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NAngleStructureList Class; inline static const char* name() { return "Angle Structure List"; } }; /** * A packet representing a collection of angle structures on a triangulation. * Such a packet must always be a child packet of the triangulation on * which the angle structures lie. If this triangulation changes, the * information contained in this packet will become invalid. * * Angle structure lists should be created using the routine enumerate(), * which is new as of Regina 3.95. * * \testpart */ class REGINA_API NAngleStructureList : public NPacket { REGINA_PACKET(NAngleStructureList, PACKET_ANGLESTRUCTURELIST) private: std::vector structures; /**< Contains the angle structures stored in this packet. */ bool tautOnly_; /**< Stores whether we are only interested in taut structures. This is an option selected by the user before enumeration takes place. */ mutable NProperty doesSpanStrict; /**< Does the convex span of this list include a strict angle structure? This is determined by looking at the output angle structues after enumeration has taken place. */ mutable NProperty doesSpanTaut; /**< Does this list include a taut structure? This is determined by looking at the output angle structues after enumeration has taken place. */ public: /** * Destroys this list and all the angle structures within. */ virtual ~NAngleStructureList(); /** * Returns the triangulation on which these angle structures * lie. * * @return the corresponding triangulation. */ NTriangulation* getTriangulation() const; /** * Returns whether this list was produced by enumerating taut angle * structures only. * * @return \c true if this list was produced by enumerating * taut angle structures only, or \c false if the enumeration * procedure allowed for any angle structures. */ bool isTautOnly() const; /** * Returns the number of angle structures stored in this list. * * @return the number of angle structures. */ unsigned long getNumberOfStructures() const; /** * Returns the angle structure at the requested index in this * list. * * @param index the index of the requested angle structure in * this list; this must be between 0 and * getNumberOfStructures()-1 inclusive. * @return the angle structure at the requested index. */ const NAngleStructure* getStructure(unsigned long index) const; /** * Determines whether any convex combination of the angle * structures in this list is a strict angle structure. * See NAngleStructure::isStrict() for details on strict angle * structures. * * @return \c true if and only if a strict angle structure can * be produced. */ bool spansStrict() const; /** * Determines whether any angle structure in this list is a * taut structure. Because taut structures always appear as * vertices of the angle structure solution space, this routine * is equivalent to testing whether any convex combination of * the angle structures in this list is a taut structure. * * See NAngleStructure::isTaut() for details on taut * structures. * * @return \c true if and only if a taut structure can be produced. */ bool spansTaut() const; /** * Determines whether any convex combination of the angle * structures in this list is a strict angle structure. * * \deprecated This routine will be removed in a future version * of Regina. Users should switch to the identical routine * spansStrict() instead. * * @return \c true if and only if a strict angle structure can * be produced. */ bool allowsStrict() const; /** * Determines whether any angle structure in this list is a * taut structure. * * \deprecated This routine will be removed in a future version * of Regina. Users should switch to the identical routine * spansTaut() instead. * * @return \c true if and only if a taut angle structure can * be produced. */ bool allowsTaut() const; /** * Enumerates all angle structures on the given triangulation. * A list containing all vertices of the angle structure solution * space will be returned. * * The option is offered to find only taut structures (which are * considerably faster to enumerate) instead of enumerating all * vertex angle structures. See the \a tautOnly argument below. * * The angle structure list that is created will be inserted as the * last child of the given triangulation. This triangulation \b must * remain the parent of this angle structure list, and must not * change while this angle structure list remains in existence. * * If a progress tracker is passed, the angle structure * enumeration will take place in a new thread and this routine * will return immediately. If the user cancels the operation * from another thread, then the angle structure list will \e not * be inserted into the packet tree (but the caller of this * routine will still need to delete it). Regarding progress tracking, * this routine will declare and work through a series of stages * whose combined weights sum to 1; typically this means that the * given tracker must not have been used before. * * If no progress tracker is passed, the enumeration will run * in the current thread and this routine will return only when * the enumeration is complete. Note that this enumeration can * be extremely slow for larger triangulations. * * @param owner the triangulation for which the vertex * angle structures will be enumerated. * @param tautOnly \c true if only taut structures are to be * enuemrated, or \c false if we should enumerate all vertices * of the angle structure solution space; this defaults to \c false. * @param tracker a progress tracker through which progress will * be reported, or 0 if no progress reporting is required. * @return the newly created angle structure list. Note that if * a progress tracker is passed then this list may not be completely * filled when this routine returns. If a progress tracker is * passed and a new thread could not be started, this routine * returns 0 (and no angle structure list is created). */ static NAngleStructureList* enumerate(NTriangulation* owner, bool tautOnly = false, NProgressTracker* tracker = 0); virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; protected: /** * Creates a new empty angle structure list. * All properties are marked as unknown. * * @param tautOnly \c true if only taut structures are to be * enuemrated (when the time comes for enumeration to be performed), * or \c false if we should enumerate all vertices of the angle * structure solution space. */ NAngleStructureList(bool tautOnly); virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; /** * Calculate whether the convex span of this list includes a * strict angle structure. */ void calculateSpanStrict() const; /** * Calculate whether the convex span of this list includes a * taut structure. */ void calculateSpanTaut() const; /** * An output iterator used to insert angle structures into an * NAngleStructureList. * * Objects of type NAngleStructure* and * NAngleStructureVector* can be assigned to this * iterator. In the latter case, a surrounding NAngleStructure * will be automatically created. */ struct StructureInserter : public std::iterator< std::output_iterator_tag, NAngleStructureVector*> { NAngleStructureList* list; /**< The list into which angle structures will be inserted. */ NTriangulation* owner; /**< The triangulation on which the angle structures to * be inserted lie. */ /** * Creates a new uninitialised output iterator. * * \warning This iterator must not be used until its * structure list and triangulation have been initialised. */ StructureInserter(); /** * Creates a new output iterator. The member variables of * this iterator will be initialised according to the * parameters passed to this constructor. * * @param newList the list into which angle structures will * be inserted. * @param newOwner the triangulation on which the structures * to be inserted lie. */ StructureInserter(NAngleStructureList& newList, NTriangulation* newOwner); /** * Creates a new output iterator that is a clone of the * given iterator. * * @param cloneMe the output iterator to clone. */ StructureInserter(const StructureInserter& cloneMe); /** * Sets this iterator to be a clone of the given output iterator. * * @param cloneMe the output iterator to clone. * @return this output iterator. */ StructureInserter& operator =(const StructureInserter& cloneMe); /** * Appends an angle structure to the end of the appropriate * structure list. * * The given angle structure will be deallocated with the * other angle structures in this list when the list is * eventually destroyed. * * @param structure the angle structure to insert. * @return this output iterator. */ StructureInserter& operator =(NAngleStructure* structure); /** * Appends the angle structure corresponding to the given * vector to the end of the appropriate structure list. * * The given vector will be owned by the newly created * angle structure and will be deallocated with the * other angle structures in this list when the list is * eventually destroyed. * * @param vector the vector of the angle structure to insert. * @return this output iterator. */ StructureInserter& operator =(NAngleStructureVector* vector); /** * Returns a reference to this output iterator. * * @return this output iterator. */ StructureInserter& operator *(); /** * Returns a reference to this output iterator. * * @return this output iterator. */ StructureInserter& operator ++(); /** * Returns a reference to this output iterator. * * @return this output iterator. */ StructureInserter& operator ++(int); }; private: /** * A thread class that actually performs the angle structure * enumeration. */ class Enumerator : public NThread { private: NAngleStructureList* list; /**< The angle structure list to be filled. */ NTriangulation* triang; /**< The triangulation upon which this angle structure list will be based. */ NProgressTracker* tracker; /**< The progress tracker through which progress is reported, or 0 if no progress tracker is in use. */ public: /** * Creates a new enumerator thread with the given * parameters. * * @param newList the angle structure list to be filled. * @param useTriang the triangulation upon which this * angle structure list will be based. * @param useTracker the progress tracker to use for * progress reporting, or 0 if progress reporting is not * required. */ Enumerator(NAngleStructureList* newList, NTriangulation* useTriang, NProgressTracker* useTracker); void* run(void*); }; friend class regina::NXMLAngleStructureListReader; }; /*@}*/ // Inline functions for NAngleStructureList inline NAngleStructureList::~NAngleStructureList() { for_each(structures.begin(), structures.end(), FuncDelete()); } inline bool NAngleStructureList::isTautOnly() const { return tautOnly_; } inline unsigned long NAngleStructureList::getNumberOfStructures() const { return structures.size(); } inline const NAngleStructure* NAngleStructureList::getStructure( unsigned long index) const { return structures[index]; } inline bool NAngleStructureList::spansStrict() const { if (! doesSpanStrict.known()) calculateSpanStrict(); return doesSpanStrict.value(); } inline bool NAngleStructureList::spansTaut() const { if (! doesSpanTaut.known()) calculateSpanTaut(); return doesSpanTaut.value(); } inline bool NAngleStructureList::allowsStrict() const { return spansStrict(); } inline bool NAngleStructureList::allowsTaut() const { return spansTaut(); } inline bool NAngleStructureList::dependsOnParent() const { return true; } inline NAngleStructureList::NAngleStructureList(bool tautOnly) : tautOnly_(tautOnly) { } inline NAngleStructureList::StructureInserter::StructureInserter() : list(0), owner(0) { } inline NAngleStructureList::StructureInserter::StructureInserter( NAngleStructureList& newList, NTriangulation* newOwner) : list(&newList), owner(newOwner) { } inline NAngleStructureList::StructureInserter::StructureInserter( const StructureInserter& cloneMe) : list(cloneMe.list), owner(cloneMe.owner) { } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator =( const StructureInserter& cloneMe) { list = cloneMe.list; owner = cloneMe.owner; return *this; } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator =( NAngleStructure* structure) { list->structures.push_back(structure); return *this; } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator =( NAngleStructureVector* vector) { list->structures.push_back(new NAngleStructure(owner, vector)); return *this; } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator *() { return *this; } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator ++() { return *this; } inline NAngleStructureList::StructureInserter& NAngleStructureList::StructureInserter::operator ++(int) { return *this; } inline NAngleStructureList::Enumerator::Enumerator(NAngleStructureList* newList, NTriangulation* useTriang, NProgressTracker* useTracker) : list(newList), triang(useTriang), tracker(useTracker) { } } // namespace regina #endif regina-4.95/engine/angle/nxmlanglestructreader.cpp000644 000765 000024 00000012614 12236524106 022270 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "angle/nxmlanglestructreader.h" #include "triangulation/ntriangulation.h" #include "utilities/stringutils.h" namespace regina { void NXMLAngleStructureReader::startElement(const std::string&, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { if (! valueOf(props.lookup("len"), vecLen)) vecLen = -1; } void NXMLAngleStructureReader::initialChars(const std::string& chars) { if (vecLen < 0 || tri == 0) return; std::vector tokens; if (basicTokenise(back_inserter(tokens), chars) % 2 != 0) return; // Create a new vector and read all non-zero entries. NAngleStructureVector* vec = new NAngleStructureVector(vecLen); long pos; NLargeInteger value; for (unsigned long i = 0; i < tokens.size(); i += 2) { if (valueOf(tokens[i], pos)) if (valueOf(tokens[i + 1], value)) if (pos >= 0 && pos < vecLen) { // All looks valid. vec->setElement(pos, value); continue; } // Found something invalid. delete vec; return; } angles = new NAngleStructure(tri, vec); } NXMLElementReader* NXMLAngleStructureReader::startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (! angles) return new NXMLElementReader(); /** Flags in data files are deprecated as of Regina 4.93. if (subTagName == "flags") { if (! valueOf(props.lookup("value"), angles->flags)) angles->flags = 0; } */ return new NXMLElementReader(); } NXMLElementReader* NXMLAngleStructureListReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { bool b; if (subTagName == "angleparams") { if (valueOf(props.lookup("tautonly"), b)) list->tautOnly_ = b; } else if (subTagName == "struct") { return new NXMLAngleStructureReader(tri); } else if (subTagName == "spanstrict") { if (valueOf(props.lookup("value"), b)) list->doesSpanStrict = b; } else if (subTagName == "spantaut") { if (valueOf(props.lookup("value"), b)) list->doesSpanTaut = b; } else if (subTagName == "allowstrict") { if (valueOf(props.lookup("value"), b)) list->doesSpanStrict = b; } else if (subTagName == "allowtaut") { if (valueOf(props.lookup("value"), b)) list->doesSpanTaut = b; } return new NXMLElementReader(); } void NXMLAngleStructureListReader::endContentSubElement( const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "struct") if (NAngleStructure* s = dynamic_cast(subReader)-> getStructure()) list->structures.push_back(s); } NXMLPacketReader* NAngleStructureList::getXMLReader(NPacket* parent, NXMLTreeResolver& resolver) { return new NXMLAngleStructureListReader( dynamic_cast(parent), resolver); } } // namespace regina regina-4.95/engine/angle/nxmlanglestructreader.h000644 000765 000024 00000013501 12236524106 021731 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file angle/nxmlanglestructreader.h * \brief Deals with parsing XML data for angle structure lists. */ #ifndef __NXMLANGLESTRUCTREADER_H #ifndef __DOXYGEN #define __NXMLANGLESTRUCTREADER_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "angle/nanglestructurelist.h" namespace regina { /** * \weakgroup angle * @{ */ /** * An XML element reader that reads a single angle structure. * * \ifacespython Not present. */ class REGINA_API NXMLAngleStructureReader : public NXMLElementReader { private: NAngleStructure* angles; /**< The angle structure currently being read. */ NTriangulation* tri; /**< The triangulation on which this angle structure is placed. */ long vecLen; /**< The length of corresponding angle structure vector. */ public: /** * Creates a new angle structure reader. * * @param newTri the triangulation on which this angle structure lies. */ NXMLAngleStructureReader(NTriangulation* newTri); /** * Returns the angle structure that has been read. * * @return the newly allocated angle structure, or 0 if an error * occurred. */ NAngleStructure* getStructure(); virtual void startElement(const std::string& tagName, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader* parentReader); virtual void initialChars(const std::string& chars); virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); }; /** * An XML packet reader that reads a single angle structure list. * * \pre The parent XML element reader is in fact an * NXMLTriangulationReader. * * \ifacespython Not present. */ class REGINA_API NXMLAngleStructureListReader : public NXMLPacketReader { private: NAngleStructureList* list; /**< The angle structure list currently being read. */ NTriangulation* tri; /**< The triangulation on which these angle structures are placed. */ public: /** * Creates a new angle structure list reader. * * @param newTri the triangulation on which these angle * structures are placed. * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLAngleStructureListReader(NTriangulation* newTri, NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLAngleStructureReader inline NXMLAngleStructureReader::NXMLAngleStructureReader( NTriangulation* newTri) : angles(0), tri(newTri), vecLen(-1) { } inline NAngleStructure* NXMLAngleStructureReader::getStructure() { return angles; } // Inline functions for NXMLAngleStructureListReader inline NXMLAngleStructureListReader::NXMLAngleStructureListReader( NTriangulation* newTri, NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), list(new NAngleStructureList(false)), tri(newTri) { } inline NPacket* NXMLAngleStructureListReader::getPacket() { return list; } } // namespace regina #endif regina-4.95/engine/census/closedprimemin.cpp000644 000765 000024 00000112052 12234011536 021070 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "census/ncensus.h" #include "census/ngluingpermsearcher.h" #include "triangulation/nedge.h" #include "triangulation/nfacepair.h" #include "triangulation/ntriangulation.h" #include "utilities/boostutils.h" #include "utilities/memutils.h" namespace regina { const unsigned NClosedPrimeMinSearcher::EDGE_CHAIN_END = 1; const unsigned NClosedPrimeMinSearcher::EDGE_CHAIN_INTERNAL_FIRST = 2; const unsigned NClosedPrimeMinSearcher::EDGE_CHAIN_INTERNAL_SECOND = 3; const unsigned NClosedPrimeMinSearcher::EDGE_DOUBLE_FIRST = 4; const unsigned NClosedPrimeMinSearcher::EDGE_DOUBLE_SECOND = 5; const unsigned NClosedPrimeMinSearcher::EDGE_MISC = 6; const char NClosedPrimeMinSearcher::ECLASS_TWISTED = 1; const char NClosedPrimeMinSearcher::ECLASS_LOWDEG = 2; const char NClosedPrimeMinSearcher::ECLASS_HIGHDEG = 4; const char NClosedPrimeMinSearcher::ECLASS_CONE = 8; const char NClosedPrimeMinSearcher::ECLASS_L31 = 16; const unsigned NClosedPrimeMinSearcher::coneEdge[12][2] = { { 0, 1 }, { 0, 2 }, { 1, 2 }, { 0, 3 }, { 0, 4 }, { 3, 4 }, { 1, 3 }, { 1, 5 }, { 3, 5 }, { 2, 4 }, { 2, 5 }, { 4, 5 }, }; const char NClosedPrimeMinSearcher::coneNoTwist[12] = { 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1 }; const char NClosedPrimeMinSearcher::dataTag_ = 'c'; NClosedPrimeMinSearcher::NClosedPrimeMinSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, UseGluingPerms use, void* useArgs) : NCompactSearcher(pairing, autos, orientableOnly, NCensus::PURGE_NON_MINIMAL_PRIME | NCensus::PURGE_P2_REDUCIBLE, use, useArgs) { // Initialise internal arrays, specifically those relating to face // orderings and properties of chains, to accurately reflect the // underlying face pairing. // // Although the NGluingPermSearcher constructor initialises the order[] // array in a vanilla fashion (front to back), we reorder things now // to match specific structures that are found in the face pairing graph. // Preconditions: // Only closed prime minimal P2-irreducible triangulations are needed. // The given face pairing is closed with order >= 3. // ---------- Selecting an ordering of faces ---------- // We fill permutations in the order: // 1. One-ended chains (== layered solid tori) from loop to // boundary, though chains may be interlaced in the // processing order; // 2. Everything else ordered by tetrahedron faces. // // Both permutations for each double edge will be processed // consecutively, the permutation for the smallest face involved // in the double edge being processed first. // // Note from the tests above that there are no triple edges. unsigned nTets = getNumberOfTetrahedra(); orderType = new unsigned[nTets * 2]; bool* orderAssigned = new bool[nTets * 4]; /**< Have we placed a tetrahedron face or its partner in the order[] array yet? */ // Hunt for structures within the face pairing graph. NTetFace face, adj; unsigned orderDone = 0; std::fill(orderAssigned, orderAssigned + 4 * nTets, false); // Begin by searching for tetrahedra that are joined to themselves. // Note that each tetrahedra can be joined to itself at most once, // since we are guaranteed that the face pairing is connected with // order >= 3. for (face.setFirst(); ! face.isPastEnd(nTets, true); face++) { if (orderAssigned[face.simp * 4 + face.facet]) continue; adj = (*pairing)[face]; if (adj.simp != face.simp) continue; order[orderDone] = face; orderType[orderDone] = EDGE_CHAIN_END; orderAssigned[face.simp * 4 + face.facet] = true; orderAssigned[adj.simp * 4 + adj.facet] = true; orderDone++; } // Record the number of one-ended chains. unsigned nChains = orderDone; // Continue by following each one-ended chain whose base was // identified in the previous loop. unsigned i; int tet; NTetFace dest1, dest2; NFacePair faces; for (i = 0; i < nChains; i++) { tet = order[i].simp; faces = NFacePair(order[i].facet, (*pairing)[order[i]].facet).complement(); dest1 = pairing->dest(tet, faces.lower()); dest2 = pairing->dest(tet, faces.upper()); // Currently tet and faces refer to the two faces of the base // tetrahedron that are pointing outwards. while (dest1.simp == dest2.simp && dest1.simp != tet && (! orderAssigned[tet * 4 + faces.lower()]) && (! orderAssigned[tet * 4 + faces.upper()])) { // Insert this pair of edges into the ordering and follow // the chain. orderType[orderDone] = EDGE_CHAIN_INTERNAL_FIRST; orderType[orderDone + 1] = EDGE_CHAIN_INTERNAL_SECOND; if (tet < dest1.simp) { order[orderDone] = NTetFace(tet, faces.lower()); order[orderDone + 1] = NTetFace(tet, faces.upper()); } orderAssigned[tet * 4 + faces.lower()] = true; orderAssigned[tet * 4 + faces.upper()] = true; orderAssigned[dest1.simp * 4 + dest1.facet] = true; orderAssigned[dest2.simp * 4 + dest2.facet] = true; faces = NFacePair(dest1.facet, dest2.facet); if (dest1.simp < tet) { order[orderDone] = NTetFace(dest1.simp, faces.lower()); order[orderDone + 1] = NTetFace(dest1.simp, faces.upper()); } faces = faces.complement(); tet = dest1.simp; dest1 = pairing->dest(tet, faces.lower()); dest2 = pairing->dest(tet, faces.upper()); orderDone += 2; } } // Record the number of edges in the face pairing graph // belonging to one-ended chains. nChainEdges = orderDone; // Run through the remaining faces. for (face.setFirst(); ! face.isPastEnd(nTets, true); face++) if (! orderAssigned[face.simp * 4 + face.facet]) { order[orderDone] = face; if (face.facet < 3 && pairing->dest(boost::next(face)).simp == pairing->dest(face).simp) orderType[orderDone] = EDGE_DOUBLE_FIRST; else if (face.facet > 0 && pairing->dest(boost::prior(face)).simp == pairing->dest(face).simp) orderType[orderDone] = EDGE_DOUBLE_SECOND; else orderType[orderDone] = EDGE_MISC; orderDone++; adj = (*pairing)[face]; orderAssigned[face.simp * 4 + face.facet] = true; orderAssigned[adj.simp * 4 + adj.facet] = true; } // All done for the order[] array. Tidy up. delete[] orderAssigned; // ---------- Calculating the possible gluing permutations ---------- // For each face in the order[] array of type EDGE_CHAIN_END or // EDGE_CHAIN_INTERNAL_FIRST, we calculate the two gluing permutations // that must be tried. // // For the remaining faces we try all possible permutations. chainPermIndices = (nChainEdges == 0 ? 0 : new int[nChainEdges * 2]); NFacePair facesAdj, comp, compAdj; NPerm4 trial1, trial2; for (i = 0; i < nChainEdges; i++) { if (orderType[i] == EDGE_CHAIN_END) { faces = NFacePair(order[i].facet, pairing->dest(order[i]).facet); comp = faces.complement(); // order[i].facet == faces.lower(), // pairing->dest(order[i]).facet == faces.upper(). chainPermIndices[2 * i] = gluingToIndex(order[i], NPerm4(faces.lower(), faces.upper(), faces.upper(), comp.lower(), comp.lower(), comp.upper(), comp.upper(), faces.lower())); chainPermIndices[2 * i + 1] = gluingToIndex(order[i], NPerm4(faces.lower(), faces.upper(), faces.upper(), comp.upper(), comp.upper(), comp.lower(), comp.lower(), faces.lower())); } else if (orderType[i] == EDGE_CHAIN_INTERNAL_FIRST) { faces = NFacePair(order[i].facet, order[i + 1].facet); comp = faces.complement(); facesAdj = NFacePair(pairing->dest(order[i]).facet, pairing->dest(order[i + 1]).facet); compAdj = facesAdj.complement(); // order[i].facet == faces.lower(), // order[i + 1].facet == faces.upper(), // pairing->dest(order[i]).facet == facesAdj.lower(). // pairing->dest(order[i + 1]).facet == facesAdj.upper(). trial1 = NPerm4(faces.lower(), facesAdj.lower(), faces.upper(), compAdj.lower(), comp.lower(), compAdj.upper(), comp.upper(), facesAdj.upper()); trial2 = NPerm4(faces.lower(), facesAdj.lower(), faces.upper(), compAdj.upper(), comp.lower(), compAdj.lower(), comp.upper(), facesAdj.upper()); if (trial1.compareWith(trial2) < 0) { chainPermIndices[2 * i] = gluingToIndex(order[i], trial1); chainPermIndices[2 * i + 2] = gluingToIndex(order[i + 1], NPerm4(faces.lower(), compAdj.upper(), faces.upper(), facesAdj.upper(), comp.lower(), facesAdj.lower(), comp.upper(), compAdj.lower())); } else { chainPermIndices[2 * i] = gluingToIndex(order[i], trial2); chainPermIndices[2 * i + 2] = gluingToIndex(order[i + 1], NPerm4(faces.lower(), compAdj.lower(), faces.upper(), facesAdj.upper(), comp.lower(), facesAdj.lower(), comp.upper(), compAdj.upper())); } trial1 = NPerm4(faces.lower(), facesAdj.lower(), faces.upper(), compAdj.lower(), comp.lower(), facesAdj.upper(), comp.upper(), compAdj.upper()); trial2 = NPerm4(faces.lower(), facesAdj.lower(), faces.upper(), compAdj.upper(), comp.lower(), facesAdj.upper(), comp.upper(), compAdj.lower()); if (trial1.compareWith(trial2) < 0) { chainPermIndices[2 * i + 1] = gluingToIndex(order[i], trial1); chainPermIndices[2 * i + 3] = gluingToIndex(order[i + 1], NPerm4(faces.lower(), compAdj.upper(), faces.upper(), facesAdj.upper(), comp.lower(), compAdj.lower(), comp.upper(), facesAdj.lower())); } else { chainPermIndices[2 * i + 1] = gluingToIndex(order[i], trial2); chainPermIndices[2 * i + 3] = gluingToIndex(order[i + 1], NPerm4(faces.lower(), compAdj.lower(), faces.upper(), facesAdj.upper(), comp.lower(), compAdj.upper(), comp.upper(), facesAdj.lower())); } } } // ---------- Tracking of vertex / edge equivalence classes ---------- #if PRUNE_HIGH_DEG_EDGE_SET // Only allow degree three edges if the face pairing graph supports // a (1,3,4) layered solid torus. We can test this easily using the // precondition that the face pairing graph must be in canonical form. if (pairing->dest(0, 0).simp == 0 && pairing->dest(0, 2).simp == 1 && pairing->dest(0, 3).simp == 1) highDegLimit = 3; else highDegLimit = 4; highDegSum = 0; highDegBound = (6 - highDegLimit) * nTets - highDegLimit; #endif } // TODO (net): See what was removed when we brought in vertex link checking. void NClosedPrimeMinSearcher::runSearch(long maxDepth) { // Preconditions: // Only closed prime minimal P2-irreducible triangulations are needed. // The given face pairing is closed with order >= 3. unsigned nTets = getNumberOfTetrahedra(); if (maxDepth < 0) { // Larger than we will ever see (and in fact grossly so). maxDepth = nTets * 4 + 1; } if (! started) { // Search initialisation. started = true; // Begin by testing for face pairings that can never lead to such a // triangulation. if (pairing_->hasTripleEdge() || pairing_->hasBrokenDoubleEndedChain() || pairing_->hasOneEndedChainWithDoubleHandle() || pairing_->hasOneEndedChainWithStrayBigon() || pairing_->hasWedgedDoubleEndedChain() || pairing_->hasTripleOneEndedChain()) { use_(0, useArgs_); return; } orderElt = 0; if (nChainEdges < nTets * 2) orientation[order[nChainEdges].simp] = 1; } // Is it a partial search that has already finished? if (orderElt == static_cast(nTets) * 2) { if (isCanonical()) use_(this, useArgs_); use_(0, useArgs_); return; } // ---------- Selecting the individual gluing permutations ---------- // Observe that in a canonical face pairing, one-ended chains always // follow an increasing sequence of tetrahedra from boundary to end, // or follow the sequence of tetrahedra 0, 1, ..., k from end to // boundary. // // In particular, this means that for any tetrahedron not internal // to a one-ended chain (with the possible exception of tetrahedron // order[nChainEdges].simp), face 0 of this tetrahedron is not // involved in a one-ended chain. // In this generation algorithm, each orientation is simply +/-1. // We won't bother assigning orientations to the tetrahedra internal // to the one-ended chains. int minOrder = orderElt; int maxOrder = orderElt + maxDepth; NTetFace face, adj; bool generic; int mergeResult; while (orderElt >= minOrder) { face = order[orderElt]; adj = (*pairing_)[face]; // TODO (long-term): Check for cancellation. // Move to the next permutation. if (orderType[orderElt] == EDGE_CHAIN_END || orderType[orderElt] == EDGE_CHAIN_INTERNAL_FIRST) { // Choose from one of the two permutations stored in array // chainPermIndices[]. generic = false; if (permIndex(face) < 0) permIndex(face) = chainPermIndices[2 * orderElt]; else if (permIndex(face) == chainPermIndices[2 * orderElt]) permIndex(face) = chainPermIndices[2 * orderElt + 1]; else permIndex(face) = 6; } else if (orderType[orderElt] == EDGE_CHAIN_INTERNAL_SECOND) { // The permutation is predetermined. generic = false; if (permIndex(face) < 0) { if (permIndex(order[orderElt - 1]) == chainPermIndices[2 * orderElt - 2]) permIndex(face) = chainPermIndices[2 * orderElt]; else permIndex(face) = chainPermIndices[2 * orderElt + 1]; } else permIndex(face) = 6; } else { // Generic case. generic = true; // Be sure to preserve the orientation of the permutation if // necessary. if ((! orientableOnly_) || adj.facet == 0) permIndex(face)++; else permIndex(face) += 2; } // Are we out of ideas for this face? if (permIndex(face) >= 6) { // Head back down to the previous face. permIndex(face) = -1; permIndex(adj) = -1; orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } continue; } // We are sitting on a new permutation to try. permIndex(adj) = NPerm4::invS3[permIndex(face)]; // In the following code we use several results from // "Face pairing graphs and 3-manifold enumeration", B. A. Burton, // J. Knot Theory Ramifications 13 (2004). // // These include: // // - We cannot have an edge of degree <= 2, or an edge of degree 3 // meeting three distinct tetrahedra (section 2.1); // - We must have exactly one vertex (lemma 2.6); // - We cannot have a face with two edges identified to form a // cone (lemma 2.8); // - We cannot have a face with all three edges identified to // form an L(3,1) spine (lemma 2.5). // Merge edge links and run corresponding tests. if (mergeEdgeClasses()) { // We created a structure that should not appear in a final // census triangulation (e.g., a low-degree or invalid edge, // or a face whose edges are identified in certain ways). splitEdgeClasses(); continue; } // The final triangulation should have precisely (nTets + 1) edges // (since it must have precisely one vertex). if (nEdgeClasses < nTets + 1) { // We already have too few edge classes, and the count can // only get smaller. // Note that the triangulations we are pruning include ideal // triangulations (with vertex links of Euler characteristic < 2). splitEdgeClasses(); continue; } // In general, one can prove that (assuming no invalid edges or // boundary faces) we will end up with (<= nTets + nVertices) edges // (with strictly fewer edges if some vertex links are non-spherical). // If we must end up with (> nTets + 1) edges we can therefore // prune since we won't have a one-vertex triangulation. if (nEdgeClasses > nTets + 1 + 3 * (nTets * 2 - orderElt - 1)) { // We have (2n - orderElt - 1) more gluings to choose. // Since each merge can reduce the number of edge classes // by at most 3, there is no way we can end up with just // (nTets + 1) edges at the end. splitEdgeClasses(); continue; } // Merge vertex links and run corresponding tests. mergeResult = mergeVertexClasses(); if (mergeResult & VLINK_CLOSED) { // We closed off a vertex link, which means we will end up // with more than one vertex (unless this was our very last // gluing). if (orderElt + 1 < static_cast(nTets) * 2) { splitVertexClasses(); splitEdgeClasses(); continue; } } if (mergeResult & VLINK_NON_SPHERE) { // Our vertex link will never be a 2-sphere. Stop now. splitVertexClasses(); splitEdgeClasses(); continue; } if (nVertexClasses > 1 + 3 * (nTets * 2 - orderElt - 1)) { // We have (2n - orderElt - 1) more gluings to choose. // Since each merge can reduce the number of vertex classes // by at most 3, there is no way we can end up with just one // vertex at the end. splitVertexClasses(); splitEdgeClasses(); continue; } // Fix the orientation if appropriate. if (generic && adj.facet == 0 && orientableOnly_) { // It's the first time we've hit this tetrahedron. if ((permIndex(face) + (face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1)) % 2 == 0) orientation[adj.simp] = -orientation[face.simp]; else orientation[adj.simp] = orientation[face.simp]; } // Move on to the next face. orderElt++; // If we're at the end, try the solution and step back. if (orderElt == static_cast(nTets) * 2) { // We in fact have an entire triangulation. // Run through the automorphisms and check whether our // permutations are in canonical form. if (isCanonical()) use_(this, useArgs_); // Back to the previous face. orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } else { // Not a full triangulation; just one level deeper. // We've moved onto a new face. // Be sure to get the orientation right. face = order[orderElt]; if (orientableOnly_ && pairing_->dest(face).facet > 0) { // permIndex(face) will be set to -1 or -2 as appropriate. adj = (*pairing_)[face]; if (orientation[face.simp] == orientation[adj.simp]) permIndex(face) = 1; else permIndex(face) = 0; if ((face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1) == 1) permIndex(face) = (permIndex(face) + 1) % 2; permIndex(face) -= 2; } if (orderElt == maxOrder) { // We haven't found an entire triangulation, but we've // gone as far as we need to. // Process it, then step back. use_(this, useArgs_); // Back to the previous face. permIndex(face) = -1; orderElt--; // Pull apart vertex links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } } } // And the search is over. // Some extra sanity checking. if (minOrder == 0) { // Our vertex classes had better be 4n standalone vertices. if (nVertexClasses != 4 * nTets) std::cerr << "ERROR: nVertexClasses == " << nVertexClasses << " at end of search!" << std::endl; for (int i = 0; i < static_cast(nTets) * 4; i++) { if (vertexState[i].parent != -1) std::cerr << "ERROR: vertexState[" << i << "].parent == " << vertexState[i].parent << " at end of search!" << std::endl; if (vertexState[i].rank != 0) std::cerr << "ERROR: vertexState[" << i << "].rank == " << vertexState[i].rank << " at end of search!" << std::endl; if (vertexState[i].bdry != 3) std::cerr << "ERROR: vertexState[" << i << "].bdry == " << vertexState[i].bdry << " at end of search!" << std::endl; if (vertexState[i].hadEqualRank) std::cerr << "ERROR: vertexState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; if (vertexState[i].bdryEdges != 3) std::cerr << "ERROR: vertexState[" << i << "].bdryEdges == " << static_cast(vertexState[i].bdryEdges) << " at end of search!" << std::endl; if (vertexState[i].bdryNext[0] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[0] == " << vertexState[i].bdryNext[0] << " at end of search!" << std::endl; if (vertexState[i].bdryNext[1] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[1] == " << vertexState[i].bdryNext[1] << " at end of search!" << std::endl; if (vertexState[i].bdryTwist[0]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; if (vertexState[i].bdryTwist[1]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (vertexStateChanged[i] != -1) std::cerr << "ERROR: vertexStateChanged[" << i << "] == " << vertexStateChanged[i] << " at end of search!" << std::endl; // And our edge classes had better be 6n standalone edges. if (nEdgeClasses != 6 * nTets) std::cerr << "ERROR: nEdgeClasses == " << nEdgeClasses << " at end of search!" << std::endl; for (unsigned i = 0; i < nTets * 6; i++) { if (edgeState[i].parent != -1) std::cerr << "ERROR: edgeState[" << i << "].parent == " << edgeState[i].parent << " at end of search!" << std::endl; if (edgeState[i].rank != 0) std::cerr << "ERROR: edgeState[" << i << "].rank == " << edgeState[i].rank << " at end of search!" << std::endl; if (edgeState[i].size != 1) std::cerr << "ERROR: edgeState[" << i << "].size == " << edgeState[i].size << " at end of search!" << std::endl; if (! edgeState[i].bounded) std::cerr << "ERROR: edgeState[" << i << "].bounded == " "false at end of search!" << std::endl; if (edgeState[i].hadEqualRank) std::cerr << "ERROR: edgeState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (edgeStateChanged[i] != -1) std::cerr << "ERROR: edgeStateChanged[" << i << "] == " << edgeStateChanged[i] << " at end of search!" << std::endl; #if PRUNE_HIGH_DEG_EDGE_SET if (highDegSum != 0) std::cerr << "ERROR: highDegSum == " << highDegSum << " at end of search!" << std::endl; #endif } use_(0, useArgs_); } void NClosedPrimeMinSearcher::dumpData(std::ostream& out) const { NCompactSearcher::dumpData(out); int i; for (i = 0; i < orderSize; i++) { if (i) out << ' '; out << orderType[i]; } out << std::endl; out << nChainEdges << std::endl; if (nChainEdges) { for (i = 0; i < 2 * static_cast(nChainEdges); i++) { if (i) out << ' '; out << chainPermIndices[i]; } out << std::endl; } #if PRUNE_HIGH_DEG_EDGE_SET out << highDegLimit << ' ' << highDegSum << ' ' << highDegBound << std::endl; #endif } NClosedPrimeMinSearcher::NClosedPrimeMinSearcher(std::istream& in, UseGluingPerms use, void* useArgs) : NCompactSearcher(in, use, useArgs), orderType(0), nChainEdges(0), chainPermIndices(0) { if (inputError_) return; unsigned nTets = getNumberOfTetrahedra(); int i; orderType = new unsigned[2 * nTets]; for (i = 0; i < orderSize; i++) in >> orderType[i]; in >> nChainEdges; /* Unnecessary since nChainEdges is unsigned. if (nChainEdges < 0) { inputError_ = true; return; } */ if (nChainEdges) { chainPermIndices = new int[nChainEdges * 2]; for (i = 0; i < 2 * static_cast(nChainEdges); i++) { in >> chainPermIndices[i]; if (chainPermIndices[i] < 0 || chainPermIndices[i] >= 6) { inputError_ = true; return; } } } #if PRUNE_HIGH_DEG_EDGE_SET in >> highDegLimit >> highDegSum >> highDegBound; if (highDegLimit < 3 || highDegLimit > 4 || highDegSum < 0 || highDegSum > 6 * static_cast(nTets) || highDegBound != (6 - highDegLimit) * static_cast(nTets) - highDegLimit) { inputError_ = true; return; } #endif // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } int NClosedPrimeMinSearcher::mergeEdgeClasses() { NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; int retVal = 0; NPerm4 p = gluingPerm(face); int v1, w1, v2, w2; int e, f; int orderIdx; int eRep, fRep; int middleTet; v1 = face.facet; w1 = p[v1]; char parentTwists, hasTwist; for (v2 = 0; v2 < 4; v2++) { if (v2 == v1) continue; w2 = p[v2]; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; f = 5 - NEdge::edgeNumber[w1][w2]; orderIdx = v2 + 4 * orderElt; // We declare the natural orientation of an edge to be smaller // vertex to larger vertex. hasTwist = (p[NEdge::edgeVertex[e][0]] > p[NEdge::edgeVertex[e][1]] ? 1 : 0); parentTwists = 0; eRep = findEdgeClass(e + 6 * face.simp, parentTwists); fRep = findEdgeClass(f + 6 * adj.simp, parentTwists); if (eRep == fRep) { edgeState[eRep].bounded = false; if (edgeState[eRep].size <= 2) retVal |= ECLASS_LOWDEG; else if (edgeState[eRep].size == 3) { // Flag as LOWDEG only if three distinct tetrahedra are used. middleTet = pairing_->dest(face.simp, v2).simp; if (face.simp != adj.simp && adj.simp != middleTet && middleTet != face.simp) retVal |= ECLASS_LOWDEG; } if (hasTwist ^ parentTwists) retVal |= ECLASS_TWISTED; edgeStateChanged[orderIdx] = -1; } else { #if PRUNE_HIGH_DEG_EDGE_SET if (edgeState[eRep].size >= highDegLimit) { if (edgeState[fRep].size >= highDegLimit) highDegSum += highDegLimit; else highDegSum += edgeState[fRep].size; } else if (edgeState[fRep].size >= highDegLimit) highDegSum += edgeState[eRep].size; else if (edgeState[eRep].size + edgeState[fRep].size > highDegLimit) highDegSum += (edgeState[eRep].size + edgeState[fRep].size - highDegLimit); #endif if (edgeState[eRep].rank < edgeState[fRep].rank) { // Join eRep beneath fRep. edgeState[eRep].parent = fRep; edgeState[eRep].twistUp = hasTwist ^ parentTwists; edgeState[fRep].size += edgeState[eRep].size; #if PRUNE_HIGH_DEG_EDGE_SET #else if (edgeState[fRep].size > 3 * getNumberOfTetrahedra()) retVal |= ECLASS_HIGHDEG; #endif if (edgeState[eRep].twistUp) { edgeState[fRep].facesPos += edgeState[eRep].facesNeg; edgeState[fRep].facesNeg += edgeState[eRep].facesPos; } else { edgeState[fRep].facesPos += edgeState[eRep].facesPos; edgeState[fRep].facesNeg += edgeState[eRep].facesNeg; } if (edgeState[fRep].facesPos.hasNonZeroMatch( edgeState[fRep].facesNeg)) retVal |= ECLASS_CONE; if (edgeState[fRep].facesPos.has3() || edgeState[fRep].facesNeg.has3()) retVal |= ECLASS_L31; edgeStateChanged[orderIdx] = eRep; } else { // Join fRep beneath eRep. edgeState[fRep].parent = eRep; edgeState[fRep].twistUp = hasTwist ^ parentTwists; if (edgeState[eRep].rank == edgeState[fRep].rank) { edgeState[eRep].rank++; edgeState[fRep].hadEqualRank = true; } edgeState[eRep].size += edgeState[fRep].size; #if PRUNE_HIGH_DEG_EDGE_SET #else if (edgeState[eRep].size > 3 * getNumberOfTetrahedra()) retVal |= ECLASS_HIGHDEG; #endif if (edgeState[fRep].twistUp) { edgeState[eRep].facesPos += edgeState[fRep].facesNeg; edgeState[eRep].facesNeg += edgeState[fRep].facesPos; } else { edgeState[eRep].facesPos += edgeState[fRep].facesPos; edgeState[eRep].facesNeg += edgeState[fRep].facesNeg; } if (edgeState[eRep].facesPos.hasNonZeroMatch( edgeState[eRep].facesNeg)) retVal |= ECLASS_CONE; if (edgeState[eRep].facesPos.has3() || edgeState[eRep].facesNeg.has3()) retVal |= ECLASS_L31; edgeStateChanged[orderIdx] = fRep; } #if PRUNE_HIGH_DEG_EDGE_SET if (highDegSum > highDegBound) retVal |= ECLASS_HIGHDEG; #endif nEdgeClasses--; } } return retVal; } void NClosedPrimeMinSearcher::splitEdgeClasses() { NTetFace face = order[orderElt]; int v1, v2; int e; int eIdx, orderIdx; int rep, subRep; v1 = face.facet; for (v2 = 3; v2 >= 0; v2--) { if (v2 == v1) continue; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; eIdx = e + 6 * face.simp; orderIdx = v2 + 4 * orderElt; if (edgeStateChanged[orderIdx] < 0) edgeState[findEdgeClass(eIdx)].bounded = true; else { subRep = edgeStateChanged[orderIdx]; rep = edgeState[subRep].parent; edgeState[subRep].parent = -1; if (edgeState[subRep].hadEqualRank) { edgeState[subRep].hadEqualRank = false; edgeState[rep].rank--; } edgeState[rep].size -= edgeState[subRep].size; #if PRUNE_HIGH_DEG_EDGE_SET if (edgeState[rep].size >= highDegLimit) { if (edgeState[subRep].size >= highDegLimit) highDegSum -= highDegLimit; else highDegSum -= edgeState[subRep].size; } else if (edgeState[subRep].size >= highDegLimit) highDegSum -= edgeState[rep].size; else if (edgeState[rep].size + edgeState[subRep].size > highDegLimit) highDegSum -= (edgeState[rep].size + edgeState[subRep].size - highDegLimit); #endif if (edgeState[subRep].twistUp) { edgeState[rep].facesPos -= edgeState[subRep].facesNeg; edgeState[rep].facesNeg -= edgeState[subRep].facesPos; } else { edgeState[rep].facesPos -= edgeState[subRep].facesPos; edgeState[rep].facesNeg -= edgeState[subRep].facesNeg; } edgeStateChanged[orderIdx] = -1; nEdgeClasses++; } } } } // namespace regina regina-4.95/engine/census/CMakeLists.txt000644 000765 000024 00000001532 12236713375 020126 0ustar00babstaff000000 000000 # census # Files to compile SET ( FILES closedprimemin compact dim2census dim2edgepairing dim2gluingperms dim2gluingpermsearcher euler ncensus nfacepairing ngluingperms ngluingpermsearcher ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} census/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES dim2census.h dim2edgepairing.h dim2gluingperms.h dim2gluingpermsearcher.h ncensus.h nfacepairing.h ngenericfacetpairing.h ngenericfacetpairing-impl.h ngenericfacetpairing.tcc ngenericgluingperms.h ngenericgluingperms-impl.h ngenericgluingperms.tcc ngluingperms.h ngluingpermsearcher.h DESTINATION ${INCLUDEDIR}/census COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/census/compact.cpp000644 000765 000024 00000120162 12234204431 017503 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "census/ncensus.h" #include "census/ngluingpermsearcher.h" #include "triangulation/nedge.h" #include "triangulation/nfacepair.h" #include "triangulation/ntriangulation.h" #include "utilities/boostutils.h" #include "utilities/memutils.h" namespace regina { const char NCompactSearcher::VLINK_CLOSED = 1; const char NCompactSearcher::VLINK_NON_SPHERE = 2; const int NCompactSearcher::vertexLinkNextFace[4][4] = { { -1, 2, 3, 1}, { 3, -1, 0, 2}, { 1, 3, -1, 0}, { 1, 2, 0, -1} }; const int NCompactSearcher::vertexLinkPrevFace[4][4] = { { -1, 3, 1, 2}, { 2, -1, 3, 0}, { 3, 0, -1, 1}, { 2, 0, 1, -1} }; const char NCompactSearcher::dataTag_ = 'f'; void NCompactSearcher::TetVertexState::dumpData(std::ostream& out) const { // Be careful with twistUp, which is a char but which should be // written as an int. out << parent << ' ' << rank << ' ' << bdry << ' ' << (twistUp ? 1 : 0) << ' ' << (hadEqualRank ? 1 : 0) << ' ' << static_cast(bdryEdges) << ' ' << bdryNext[0] << ' ' << bdryNext[1] << ' ' << static_cast(bdryTwist[0]) << ' ' << static_cast(bdryTwist[1]) << ' ' << bdryNextOld[0] << ' ' << bdryNextOld[1] << ' ' << static_cast(bdryTwistOld[0]) << ' ' << static_cast(bdryTwistOld[1]); } bool NCompactSearcher::TetVertexState::readData(std::istream& in, unsigned long nStates) { in >> parent >> rank >> bdry; // twistUp is a char, but we need to read it as an int. int twist; in >> twist; twistUp = twist; // hadEqualRank is a bool, but we need to read it as an int. int bRank; in >> bRank; hadEqualRank = bRank; // More chars to ints coming. int bVal; in >> bVal; bdryEdges = bVal; in >> bdryNext[0] >> bdryNext[1]; in >> bVal; bdryTwist[0] = bVal; in >> bVal; bdryTwist[1] = bVal; in >> bdryNextOld[0] >> bdryNextOld[1]; in >> bVal; bdryTwistOld[0] = bVal; in >> bVal; bdryTwistOld[1] = bVal; if (parent < -1 || parent >= static_cast(nStates)) return false; if (rank >= nStates) return false; if (bdry > 3 * nStates) return false; if (twist != 1 && twist != 0) return false; if (bRank != 1 && bRank != 0) return false; if (bdryEdges > 3) /* Never < 0 since this is unsigned. */ return false; if (bdryNext[0] < 0 || bdryNext[0] >= static_cast(nStates)) return false; if (bdryNext[1] < 0 || bdryNext[1] >= static_cast(nStates)) return false; if (bdryNextOld[0] < -1 || bdryNext[0] >= static_cast(nStates)) return false; if (bdryNextOld[1] < -1 || bdryNextOld[1] >= static_cast(nStates)) return false; if (bdryTwist[0] < 0 || bdryTwist[0] > 1) return false; if (bdryTwist[1] < 0 || bdryTwist[1] > 1) return false; if (bdryTwistOld[0] < 0 || bdryTwistOld[0] > 1) return false; if (bdryTwistOld[1] < 0 || bdryTwistOld[1] > 1) return false; return true; } void NCompactSearcher::TetEdgeState::dumpData(std::ostream& out, unsigned nTets) const { // Be careful with twistUp, which is a char but which should be // written as an int. out << parent << ' ' << rank << ' ' << size << ' ' << (bounded ? 1 : 0) << ' ' << (twistUp ? 1 : 0) << ' ' << (hadEqualRank ? 1 : 0) << ' '; unsigned i; for (i = 0; i < nTets * 4 && i < 64; ++i) out << char(facesPos.get(i) + '0'); out << ' '; for (i = 0; i < nTets * 4 && i < 64; ++i) out << char(facesNeg.get(i) + '0'); } bool NCompactSearcher::TetEdgeState::readData(std::istream& in, unsigned nTets) { in >> parent >> rank >> size; // bounded is a bool, but we need to read it as an int. int bBounded; in >> bBounded; bounded = bBounded; // twistUp is a char, but we need to read it as an int. int twist; in >> twist; twistUp = twist; // hadEqualRank is a bool, but we need to read it as an int. int bRank; in >> bRank; hadEqualRank = bRank; char cFaces; bool facesBroken = false; unsigned i; for (i = 0; i < nTets * 4 && i < 64; ++i) { in >> cFaces; if (cFaces >= '0' && cFaces <= '3') facesPos.set(i, cFaces - '0'); else facesBroken = true; } for (i = 0; i < nTets * 4 && i < 64; ++i) { in >> cFaces; if (cFaces >= '0' && cFaces <= '3') facesNeg.set(i, cFaces - '0'); else facesBroken = true; } if (parent < -1 || parent >= static_cast(6 * nTets)) return false; if (rank >= 6 * nTets) return false; if (size >= 6 * nTets) return false; if (bBounded != 1 && bBounded != 0) return false; if (twist != 1 && twist != 0) return false; if (bRank != 1 && bRank != 0) return false; if (facesBroken) return false; return true; } NCompactSearcher::NCompactSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, int whichPurge, UseGluingPerms use, void* useArgs) : NGluingPermSearcher(pairing, autos, orientableOnly, true /* finiteOnly */, whichPurge, use, useArgs) { // Initialise the internal arrays to accurately reflect the underlying // face pairing. unsigned nTets = getNumberOfTetrahedra(); // ---------- Tracking of vertex / edge equivalence classes ---------- unsigned i; nVertexClasses = nTets * 4; vertexState = new TetVertexState[nTets * 4]; vertexStateChanged = new int[nTets * 8]; std::fill(vertexStateChanged, vertexStateChanged + nTets * 8, -1); for (i = 0; i < nTets * 4; i++) { vertexState[i].bdryEdges = 3; vertexState[i].bdryNext[0] = vertexState[i].bdryNext[1] = i; vertexState[i].bdryTwist[0] = vertexState[i].bdryTwist[1] = 0; // Initialise the backup members also so we're not writing // uninitialised data via dumpData(). vertexState[i].bdryNextOld[0] = vertexState[i].bdryNextOld[1] = -1; vertexState[i].bdryTwistOld[0] = vertexState[i].bdryTwistOld[1] = 0; } nEdgeClasses = nTets * 6; edgeState = new TetEdgeState[nTets * 6]; edgeStateChanged = new int[nTets * 8]; std::fill(edgeStateChanged, edgeStateChanged + nTets * 8, -1); // Since NQitmaskLen64 only supports 64 faces, only work with // the first 16 tetrahedra. If n > 16, this just weakens the // optimisation; however, this is no great loss since for n > 16 the // census code is at present infeasibly slow anyway. for (i = 0; i < nTets && i < 16; ++i) { /* 01 on +012, +013 */ edgeState[6 * i ].facesPos.set(4 * i + 3, 1); edgeState[6 * i ].facesPos.set(4 * i + 2, 1); /* 02 on -012 +023 */ edgeState[6 * i + 1].facesNeg.set(4 * i + 3, 1); edgeState[6 * i + 1].facesPos.set(4 * i + 1, 1); /* 03 on -013, -023 */ edgeState[6 * i + 2].facesNeg.set(4 * i + 2, 1); edgeState[6 * i + 2].facesNeg.set(4 * i + 1, 1); /* 12 on +012, +123 */ edgeState[6 * i + 3].facesPos.set(4 * i + 3, 1); edgeState[6 * i + 3].facesPos.set(4 * i + 0, 1); /* 13 on +013 -123 */ edgeState[6 * i + 4].facesPos.set(4 * i + 2, 1); edgeState[6 * i + 4].facesNeg.set(4 * i + 0, 1); /* 23 on +023, +123 */ edgeState[6 * i + 5].facesPos.set(4 * i + 1, 1); edgeState[6 * i + 5].facesPos.set(4 * i + 0, 1); } } // TODO (net): See what was removed when we brought in vertex link checking. void NCompactSearcher::runSearch(long maxDepth) { unsigned nTets = getNumberOfTetrahedra(); if (maxDepth < 0) { // Larger than we will ever see (and in fact grossly so). maxDepth = nTets * 4 + 1; } if (! started) { // Search initialisation. started = true; // Do we in fact have no permutation at all to choose? if (maxDepth == 0 || pairing_->dest(0, 0).isBoundary(nTets)) { use_(this, useArgs_); use_(0, useArgs_); return; } orderElt = 0; orientation[0] = 1; } // Is it a partial search that has already finished? if (orderElt == orderSize) { if (isCanonical()) use_(this, useArgs_); use_(0, useArgs_); return; } // ---------- Selecting the individual gluing permutations ---------- int minOrder = orderElt; int maxOrder = orderElt + maxDepth; NTetFace face, adj; int mergeResult; while (orderElt >= minOrder) { face = order[orderElt]; adj = (*pairing_)[face]; // TODO (long-term): Check for cancellation. // Move to the next permutation. // Be sure to preserve the orientation of the permutation if necessary. if ((! orientableOnly_) || adj.facet == 0) permIndex(face)++; else permIndex(face) += 2; // Are we out of ideas for this face? if (permIndex(face) >= 6) { // Yep. Head back down to the previous face. permIndex(face) = -1; permIndex(adj) = -1; orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } continue; } // We are sitting on a new permutation to try. permIndex(adj) = NPerm4::invS3[permIndex(face)]; // Merge edge links and run corresponding tests. if (mergeEdgeClasses()) { // We created an invalid edge. splitEdgeClasses(); continue; } // Merge vertex links and run corresponding tests. mergeResult = mergeVertexClasses(); if (mergeResult & VLINK_NON_SPHERE) { // Our vertex link will never be a 2-sphere. Stop now. splitVertexClasses(); splitEdgeClasses(); continue; } // Fix the orientation if appropriate. if (adj.facet == 0 && orientableOnly_) { // It's the first time we've hit this tetrahedron. if ((permIndex(face) + (face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1)) % 2 == 0) orientation[adj.simp] = -orientation[face.simp]; else orientation[adj.simp] = orientation[face.simp]; } // Move on to the next face. orderElt++; // If we're at the end, try the solution and step back. if (orderElt == orderSize) { // We in fact have an entire triangulation. // Run through the automorphisms and check whether our // permutations are in canonical form. if (isCanonical()) use_(this, useArgs_); // Back to the previous face. orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } else { // Not a full triangulation; just one level deeper. // We've moved onto a new face. // Be sure to get the orientation right. face = order[orderElt]; if (orientableOnly_ && pairing_->dest(face).facet > 0) { // permIndex(face) will be set to -1 or -2 as appropriate. adj = (*pairing_)[face]; if (orientation[face.simp] == orientation[adj.simp]) permIndex(face) = 1; else permIndex(face) = 0; if ((face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1) == 1) permIndex(face) = (permIndex(face) + 1) % 2; permIndex(face) -= 2; } if (orderElt == maxOrder) { // We haven't found an entire triangulation, but we've // gone as far as we need to. // Process it, then step back. use_(this, useArgs_); // Back to the previous face. permIndex(face) = -1; orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } } } // And the search is over. // Some extra sanity checking. if (minOrder == 0) { // Our vertex classes had better be 4n standalone vertices. if (nVertexClasses != 4 * nTets) std::cerr << "ERROR: nVertexClasses == " << nVertexClasses << " at end of search!" << std::endl; for (int i = 0; i < static_cast(nTets) * 4; i++) { if (vertexState[i].parent != -1) std::cerr << "ERROR: vertexState[" << i << "].parent == " << vertexState[i].parent << " at end of search!" << std::endl; if (vertexState[i].rank != 0) std::cerr << "ERROR: vertexState[" << i << "].rank == " << vertexState[i].rank << " at end of search!" << std::endl; if (vertexState[i].bdry != 3) std::cerr << "ERROR: vertexState[" << i << "].bdry == " << vertexState[i].bdry << " at end of search!" << std::endl; if (vertexState[i].hadEqualRank) std::cerr << "ERROR: vertexState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; if (vertexState[i].bdryEdges != 3) std::cerr << "ERROR: vertexState[" << i << "].bdryEdges == " << static_cast(vertexState[i].bdryEdges) << " at end of search!" << std::endl; if (vertexState[i].bdryNext[0] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[0] == " << vertexState[i].bdryNext[0] << " at end of search!" << std::endl; if (vertexState[i].bdryNext[1] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[1] == " << vertexState[i].bdryNext[1] << " at end of search!" << std::endl; if (vertexState[i].bdryTwist[0]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; if (vertexState[i].bdryTwist[1]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (vertexStateChanged[i] != -1) std::cerr << "ERROR: vertexStateChanged[" << i << "] == " << vertexStateChanged[i] << " at end of search!" << std::endl; // And our edge classes had better be 6n standalone edges. if (nEdgeClasses != 6 * nTets) std::cerr << "ERROR: nEdgeClasses == " << nEdgeClasses << " at end of search!" << std::endl; for (unsigned i = 0; i < nTets * 6; i++) { if (edgeState[i].parent != -1) std::cerr << "ERROR: edgeState[" << i << "].parent == " << edgeState[i].parent << " at end of search!" << std::endl; if (edgeState[i].rank != 0) std::cerr << "ERROR: edgeState[" << i << "].rank == " << edgeState[i].rank << " at end of search!" << std::endl; if (edgeState[i].size != 1) std::cerr << "ERROR: edgeState[" << i << "].size == " << edgeState[i].size << " at end of search!" << std::endl; if (! edgeState[i].bounded) std::cerr << "ERROR: edgeState[" << i << "].bounded == " "false at end of search!" << std::endl; if (edgeState[i].hadEqualRank) std::cerr << "ERROR: edgeState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (edgeStateChanged[i] != -1) std::cerr << "ERROR: edgeStateChanged[" << i << "] == " << edgeStateChanged[i] << " at end of search!" << std::endl; } use_(0, useArgs_); } void NCompactSearcher::dumpData(std::ostream& out) const { NGluingPermSearcher::dumpData(out); unsigned nTets = getNumberOfTetrahedra(); unsigned i; out << nVertexClasses << std::endl; for (i = 0; i < 4 * nTets; i++) { vertexState[i].dumpData(out); out << std::endl; } for (i = 0; i < 8 * nTets; i++) { if (i) out << ' '; out << vertexStateChanged[i]; } out << std::endl; out << nEdgeClasses << std::endl; for (i = 0; i < 6 * nTets; i++) { edgeState[i].dumpData(out, nTets); out << std::endl; } for (i = 0; i < 8 * nTets; i++) { if (i) out << ' '; out << edgeStateChanged[i]; } out << std::endl; } NCompactSearcher::NCompactSearcher(std::istream& in, UseGluingPerms use, void* useArgs) : NGluingPermSearcher(in, use, useArgs), nVertexClasses(0), vertexState(0), vertexStateChanged(0), nEdgeClasses(0), edgeState(0), edgeStateChanged(0) { if (inputError_) return; unsigned nTets = getNumberOfTetrahedra(); unsigned i; in >> nVertexClasses; if (nVertexClasses > 4 * nTets) { inputError_ = true; return; } vertexState = new TetVertexState[4 * nTets]; for (i = 0; i < 4 * nTets; i++) if (! vertexState[i].readData(in, 4 * nTets)) { inputError_ = true; return; } vertexStateChanged = new int[8 * nTets]; for (i = 0; i < 8 * nTets; i++) { in >> vertexStateChanged[i]; if (vertexStateChanged[i] < -1 || vertexStateChanged[i] >= 4 * static_cast(nTets)) { inputError_ = true; return; } } in >> nEdgeClasses; if (nEdgeClasses > 6 * nTets) { inputError_ = true; return; } edgeState = new TetEdgeState[6 * nTets]; for (i = 0; i < 6 * nTets; i++) if (! edgeState[i].readData(in, nTets)) { inputError_ = true; return; } edgeStateChanged = new int[8 * nTets]; for (i = 0; i < 8 * nTets; i++) { in >> edgeStateChanged[i]; if (edgeStateChanged[i] < -1 || edgeStateChanged[i] >= 6 * static_cast(nTets)) { inputError_ = true; return; } } // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } int NCompactSearcher::mergeVertexClasses() { // Merge all three vertex pairs for the current face. NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; int retVal = 0; int v, w; int vIdx, wIdx, tmpIdx, nextIdx; unsigned orderIdx; int vRep, wRep; int vNext[2], wNext[2]; char vTwist[2], wTwist[2]; NPerm4 p = gluingPerm(face); char parentTwists, hasTwist, tmpTwist; for (v = 0; v < 4; v++) { if (v == face.facet) continue; w = p[v]; vIdx = v + 4 * face.simp; wIdx = w + 4 * adj.simp; orderIdx = v + 4 * orderElt; // Are the natural 012 representations of the two faces joined // with reversed orientations? // Here we combine the sign of permutation p with the mappings // from 012 to the native tetrahedron vertices, i.e., v <-> 3 and // w <-> 3. hasTwist = (p.sign() < 0 ? 0 : 1); if ((v == 3 && w != 3) || (v != 3 && w == 3)) hasTwist ^= 1; parentTwists = 0; for (vRep = vIdx; vertexState[vRep].parent >= 0; vRep = vertexState[vRep].parent) parentTwists ^= vertexState[vRep].twistUp; for (wRep = wIdx; vertexState[wRep].parent >= 0; wRep = vertexState[wRep].parent) parentTwists ^= vertexState[wRep].twistUp; if (vRep == wRep) { vertexState[vRep].bdry -= 2; if (vertexState[vRep].bdry == 0) retVal |= VLINK_CLOSED; // Have we made the vertex link non-orientable? if (hasTwist ^ parentTwists) retVal |= VLINK_NON_SPHERE; vertexStateChanged[orderIdx] = -1; // Examine the cycles of boundary components. if (vIdx == wIdx) { // Either we are folding together two adjacent edges of the // vertex link, or we are making the vertex link // non-orientable. // The possible cases are: // // 1) hasTwist is true. The vertex becomes // non-orientable, but we should already have flagged // this above. Don't touch anything. // // 2) hasTwist is false, and vertexState[vIdx].bdryEdges is 3. // Here we are taking a stand-alone triangle and folding // two of its edges together. Nothing needs to change. // // 3) hasTwist is false, and vertexState[vIdx].bdryEdges is 2. // This means we are folding together two edges of a // triangle whose third edge is already joined elsewhere. // We deal with this as follows: // if ((! hasTwist) && vertexState[vIdx].bdryEdges < 3) { // Although bdryEdges is 2, we don't bother keeping // a backup in bdryTwistOld[]. This is because // bdryEdges jumps straight from 2 to 0, and the // neighbours in bdryNext[] / bdryTwist[] never get // overwritten. if (vertexState[vIdx].bdryNext[0] == vIdx) { // We are closing off a single boundary of length // two. All good. } else { // Adjust each neighbour to point to the other. vtxBdryJoin(vertexState[vIdx].bdryNext[0], 1 ^ vertexState[vIdx].bdryTwist[0], vertexState[vIdx].bdryNext[1], vertexState[vIdx].bdryTwist[1] ^ vertexState[vIdx].bdryTwist[0]); } } vertexState[vIdx].bdryEdges -= 2; } else { // We are joining two distinct tetrahedron vertices that // already contribute to the same vertex link. if (vertexState[vIdx].bdryEdges == 2) vtxBdryBackup(vIdx); if (vertexState[wIdx].bdryEdges == 2) vtxBdryBackup(wIdx); if (vtxBdryLength1(vIdx) && vtxBdryLength1(wIdx)) { // We are joining together two boundaries of length one. // Do nothing and mark the non-trivial genus. // std::cerr << "NON-SPHERE: 1 >-< 1" << std::endl; retVal |= VLINK_NON_SPHERE; } else if (vtxBdryLength2(vIdx, wIdx)) { // We are closing off a single boundary of length two. // All good. } else { vtxBdryNext(vIdx, face.simp, v, face.facet, vNext, vTwist); vtxBdryNext(wIdx, adj.simp, w, adj.facet, wNext, wTwist); if (vNext[0] == wIdx && wNext[1 ^ vTwist[0]] == vIdx) { // We are joining two adjacent edges of the vertex link. // Simply eliminate them. vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ vTwist[0]], (vTwist[0] ^ wTwist[0 ^ vTwist[0]]) ^ vTwist[1]); } else if (vNext[1] == wIdx && wNext[0 ^ vTwist[1]] == vIdx) { // Again, joining two adjacent edges of the vertex link. vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ vTwist[1]], (vTwist[1] ^ wTwist[1 ^ vTwist[1]]) ^ vTwist[0]); } else { // See if we are joining two different boundary cycles // together; if so, we have created non-trivial genus in // the vertex link. tmpIdx = vertexState[vIdx].bdryNext[0]; tmpTwist = vertexState[vIdx].bdryTwist[0]; while (tmpIdx != vIdx && tmpIdx != wIdx) { nextIdx = vertexState[tmpIdx]. bdryNext[0 ^ tmpTwist]; tmpTwist ^= vertexState[tmpIdx]. bdryTwist[0 ^ tmpTwist]; tmpIdx = nextIdx; } if (tmpIdx == vIdx) { // Different boundary cycles. // Don't touch anything; just flag a // high genus error. // std::cerr << "NON-SPHERE: (X)" << std::endl; retVal |= VLINK_NON_SPHERE; } else { // Same boundary cycle. vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ hasTwist], vTwist[0] ^ (hasTwist ^ wTwist[1 ^ hasTwist])); vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ hasTwist], vTwist[1] ^ (hasTwist ^ wTwist[0 ^ hasTwist])); } } } vertexState[vIdx].bdryEdges--; vertexState[wIdx].bdryEdges--; } } else { // We are joining two distinct vertices together and merging // their vertex links. if (vertexState[vRep].rank < vertexState[wRep].rank) { // Join vRep beneath wRep. vertexState[vRep].parent = wRep; vertexState[vRep].twistUp = hasTwist ^ parentTwists; vertexState[wRep].bdry = vertexState[wRep].bdry + vertexState[vRep].bdry - 2; if (vertexState[wRep].bdry == 0) retVal |= VLINK_CLOSED; vertexStateChanged[orderIdx] = vRep; } else { // Join wRep beneath vRep. vertexState[wRep].parent = vRep; vertexState[wRep].twistUp = hasTwist ^ parentTwists; if (vertexState[vRep].rank == vertexState[wRep].rank) { vertexState[vRep].rank++; vertexState[wRep].hadEqualRank = true; } vertexState[vRep].bdry = vertexState[vRep].bdry + vertexState[wRep].bdry - 2; if (vertexState[vRep].bdry == 0) retVal |= VLINK_CLOSED; vertexStateChanged[orderIdx] = wRep; } nVertexClasses--; // Adjust the cycles of boundary components. if (vertexState[vIdx].bdryEdges == 2) vtxBdryBackup(vIdx); if (vertexState[wIdx].bdryEdges == 2) vtxBdryBackup(wIdx); if (vtxBdryLength1(vIdx)) { if (vtxBdryLength1(wIdx)) { // Both vIdx and wIdx form entire boundary components of // length one; these are joined together and the vertex // link is closed off. // No changes to make for the boundary cycles. } else { // Here vIdx forms a boundary component of length one, // and wIdx does not. Ignore vIdx, and simply excise the // relevant edge from wIdx. // There is nothing to do here unless wIdx only has one // boundary edge remaining (in which case we know it // joins to some different tetrahedron vertex). if (vertexState[wIdx].bdryEdges == 1) { wNext[0] = vertexState[wIdx].bdryNext[0]; wNext[1] = vertexState[wIdx].bdryNext[1]; wTwist[0] = vertexState[wIdx].bdryTwist[0]; wTwist[1] = vertexState[wIdx].bdryTwist[1]; vtxBdryJoin(wNext[0], 1 ^ wTwist[0], wNext[1], wTwist[0] ^ wTwist[1]); } } } else if (vtxBdryLength1(wIdx)) { // As above, but with the two vertices the other way around. if (vertexState[vIdx].bdryEdges == 1) { vNext[0] = vertexState[vIdx].bdryNext[0]; vNext[1] = vertexState[vIdx].bdryNext[1]; vTwist[0] = vertexState[vIdx].bdryTwist[0]; vTwist[1] = vertexState[vIdx].bdryTwist[1]; vtxBdryJoin(vNext[0], 1 ^ vTwist[0], vNext[1], vTwist[0] ^ vTwist[1]); } } else { // Each vertex belongs to a boundary component of length // at least two. Merge the components together. vtxBdryNext(vIdx, face.simp, v, face.facet, vNext, vTwist); vtxBdryNext(wIdx, adj.simp, w, adj.facet, wNext, wTwist); vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ hasTwist], vTwist[0] ^ (hasTwist ^ wTwist[1 ^ hasTwist])); vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ hasTwist], vTwist[1] ^ (hasTwist ^ wTwist[0 ^ hasTwist])); } vertexState[vIdx].bdryEdges--; vertexState[wIdx].bdryEdges--; } } return retVal; } void NCompactSearcher::splitVertexClasses() { // Split all three vertex pairs for the current face. NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; int v, w; int vIdx, wIdx; unsigned orderIdx; int rep, subRep; NPerm4 p = gluingPerm(face); // Do everything in reverse. This includes the loop over vertices. for (v = 3; v >= 0; v--) { if (v == face.facet) continue; w = p[v]; vIdx = v + 4 * face.simp; wIdx = w + 4 * adj.simp; orderIdx = v + 4 * orderElt; if (vertexStateChanged[orderIdx] < 0) { for (rep = vIdx; vertexState[rep].parent >= 0; rep = vertexState[rep].parent) ; vertexState[rep].bdry += 2; } else { subRep = vertexStateChanged[orderIdx]; rep = vertexState[subRep].parent; vertexState[subRep].parent = -1; if (vertexState[subRep].hadEqualRank) { vertexState[subRep].hadEqualRank = false; vertexState[rep].rank--; } vertexState[rep].bdry = vertexState[rep].bdry + 2 - vertexState[subRep].bdry; vertexStateChanged[orderIdx] = -1; nVertexClasses++; } // Restore cycles of boundary components. if (vIdx == wIdx) { vertexState[vIdx].bdryEdges += 2; // Adjust neighbours to point back to vIdx if required. if (vertexState[vIdx].bdryEdges == 2) vtxBdryFixAdj(vIdx); } else { vertexState[wIdx].bdryEdges++; vertexState[vIdx].bdryEdges++; switch (vertexState[wIdx].bdryEdges) { case 3: vertexState[wIdx].bdryNext[0] = vertexState[wIdx].bdryNext[1] = wIdx; vertexState[wIdx].bdryTwist[0] = vertexState[wIdx].bdryTwist[1] = 0; break; case 2: vtxBdryRestore(wIdx); // Fall through to the next case, so we can // adjust the neighbours. case 1: // Nothing was changed for wIdx during the merge, // so there is nothing there to restore. // Adjust neighbours to point back to wIdx. vtxBdryFixAdj(wIdx); } switch (vertexState[vIdx].bdryEdges) { case 3: vertexState[vIdx].bdryNext[0] = vertexState[vIdx].bdryNext[1] = vIdx; vertexState[vIdx].bdryTwist[0] = vertexState[vIdx].bdryTwist[1] = 0; break; case 2: vtxBdryRestore(vIdx); // Fall through to the next case, so we can // adjust the neighbours. case 1: // Nothing was changed for vIdx during the merge, // so there is nothing there to restore. // Adjust neighbours to point back to vIdx. vtxBdryFixAdj(vIdx); } } } } bool NCompactSearcher::mergeEdgeClasses() { NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; bool retVal = false; NPerm4 p = gluingPerm(face); int v1, w1, v2, w2; int e, f; int orderIdx; int eRep, fRep; v1 = face.facet; w1 = p[v1]; char parentTwists, hasTwist; for (v2 = 0; v2 < 4; v2++) { if (v2 == v1) continue; w2 = p[v2]; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; f = 5 - NEdge::edgeNumber[w1][w2]; orderIdx = v2 + 4 * orderElt; // We declare the natural orientation of an edge to be smaller // vertex to larger vertex. hasTwist = (p[NEdge::edgeVertex[e][0]] > p[NEdge::edgeVertex[e][1]] ? 1 : 0); parentTwists = 0; eRep = findEdgeClass(e + 6 * face.simp, parentTwists); fRep = findEdgeClass(f + 6 * adj.simp, parentTwists); if (eRep == fRep) { edgeState[eRep].bounded = false; if (hasTwist ^ parentTwists) retVal = true; edgeStateChanged[orderIdx] = -1; } else { if (edgeState[eRep].rank < edgeState[fRep].rank) { // Join eRep beneath fRep. edgeState[eRep].parent = fRep; edgeState[eRep].twistUp = hasTwist ^ parentTwists; edgeState[fRep].size += edgeState[eRep].size; edgeStateChanged[orderIdx] = eRep; } else { // Join fRep beneath eRep. edgeState[fRep].parent = eRep; edgeState[fRep].twistUp = hasTwist ^ parentTwists; if (edgeState[eRep].rank == edgeState[fRep].rank) { edgeState[eRep].rank++; edgeState[fRep].hadEqualRank = true; } edgeState[eRep].size += edgeState[fRep].size; edgeStateChanged[orderIdx] = fRep; } nEdgeClasses--; } } return retVal; } void NCompactSearcher::splitEdgeClasses() { NTetFace face = order[orderElt]; int v1, v2; int e; int eIdx, orderIdx; int rep, subRep; v1 = face.facet; for (v2 = 3; v2 >= 0; v2--) { if (v2 == v1) continue; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; eIdx = e + 6 * face.simp; orderIdx = v2 + 4 * orderElt; if (edgeStateChanged[orderIdx] < 0) edgeState[findEdgeClass(eIdx)].bounded = true; else { subRep = edgeStateChanged[orderIdx]; rep = edgeState[subRep].parent; edgeState[subRep].parent = -1; if (edgeState[subRep].hadEqualRank) { edgeState[subRep].hadEqualRank = false; edgeState[rep].rank--; } edgeState[rep].size -= edgeState[subRep].size; edgeStateChanged[orderIdx] = -1; nEdgeClasses++; } } } void NCompactSearcher::vtxBdryConsistencyCheck() { int adj, id, end; for (id = 0; id < static_cast(getNumberOfTetrahedra()) * 4; id++) if (vertexState[id].bdryEdges > 0) for (end = 0; end < 2; end++) { adj = vertexState[id].bdryNext[end]; if (vertexState[adj].bdryEdges == 0) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " runs into an internal vertex." << std::endl; if (vertexState[adj].bdryNext[(1 ^ end) ^ vertexState[id].bdryTwist[end]] != id) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " has a mismatched adjacency." << std::endl; if (vertexState[adj].bdryTwist[(1 ^ end) ^ vertexState[id].bdryTwist[end]] != vertexState[id].bdryTwist[end]) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " has a mismatched twist." << std::endl; } } void NCompactSearcher::vtxBdryDump(std::ostream& out) { for (unsigned id = 0; id < getNumberOfTetrahedra() * 4; id++) { if (id > 0) out << ' '; out << vertexState[id].bdryNext[0] << (vertexState[id].bdryTwist[0] ? '~' : '-') << id << (vertexState[id].bdryTwist[1] ? '~' : '-') << vertexState[id].bdryNext[1] << " [" << int(vertexState[id].bdryEdges) << ']'; } out << std::endl; } } // namespace regina regina-4.95/engine/census/dim2census.cpp000644 000765 000024 00000014003 12234011536 020127 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "census/dim2census.h" #include "census/dim2gluingpermsearcher.h" #include "dim2/dim2triangulation.h" #include "utilities/memutils.h" namespace regina { unsigned long Dim2Census::formCensus(NPacket* parent, unsigned nTriangles, NBoolSet orientability, NBoolSet boundary, int nBdryEdges, AcceptTriangulation sieve, void* sieveArgs) { // If obviously nothing is going to happen but we won't realise // it until we've actually generated the facet pairings, change // nTriangles to 0 so we'll realise it immediately once the new // thread starts. if (orientability == NBoolSet::sNone) nTriangles = 0; // Start the census! Dim2Census* census = new Dim2Census(parent, orientability, sieve, sieveArgs); Dim2EdgePairing::findAllPairings(nTriangles, boundary, nBdryEdges, Dim2Census::foundEdgePairing, census, false); unsigned long ans = census->whichSoln_ - 1; delete census; return ans; } unsigned long Dim2Census::formPartialCensus(const Dim2EdgePairing* pairing, NPacket* parent, NBoolSet orientability, AcceptTriangulation sieve, void* sieveArgs) { // Is it obvious that nothing will happen? if (orientability == NBoolSet::sNone) return 0; // Make a list of automorphisms. Dim2EdgePairing::IsoList autos; pairing->findAutomorphisms(autos); // Select the individual gluing permutations. Dim2Census census(parent, orientability, sieve, sieveArgs); Dim2GluingPermSearcher::findAllPerms(pairing, &autos, ! census.orientability_.hasFalse(), Dim2Census::foundGluingPerms, &census); // Clean up. std::for_each(autos.begin(), autos.end(), FuncDelete()); return census.whichSoln_ - 1; } Dim2Census::Dim2Census(NPacket* parent, const NBoolSet& orientability, AcceptTriangulation sieve, void* sieveArgs) : parent_(parent), orientability_(orientability), sieve_(sieve), sieveArgs_(sieveArgs), whichSoln_(1) { } void Dim2Census::foundEdgePairing(const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, void* census) { Dim2Census* realCensus = static_cast(census); if (pairing) { // We've found another edge pairing. // Select the individual gluing permutations. Dim2GluingPermSearcher::findAllPerms(pairing, autos, ! realCensus->orientability_.hasFalse(), Dim2Census::foundGluingPerms, census); } else { // Census generation has finished. } } void Dim2Census::foundGluingPerms(const Dim2GluingPermSearcher* perms, void* census) { if (perms) { // We've found another permutation set. // Triangulate and see what we've got. Dim2Triangulation* tri = perms->triangulate(); Dim2Census* realCensus = static_cast(census); bool ok = true; if ((! realCensus->orientability_.hasTrue()) && tri->isOrientable()) ok = false; else if (realCensus->sieve_ && ! realCensus->sieve_(tri, realCensus->sieveArgs_)) ok = false; if (ok) { // Put it in the census! // Make sure it has a charming label. // Don't insist on unique labels, since this requirement // will soon be dropped and it multiplies the running time // by a factor of #triangulations. std::ostringstream out; out << "Item " << realCensus->whichSoln_; tri->setPacketLabel(out.str()); realCensus->parent_->insertChildLast(tri); realCensus->whichSoln_++; } else { // Bad triangulation. delete tri; } } } } // namespace regina regina-4.95/engine/census/dim2census.h000644 000765 000024 00000034377 12234011536 017614 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/dim2census.h * \brief Deals with forming a census of all 2-manifold triangulations * of a given size. */ #ifndef __DIM2CENSUS_H #ifndef __DOXYGEN #define __DIM2CENSUS_H #endif #include "regina-core.h" #include "census/dim2edgepairing.h" #include "utilities/nbooleans.h" namespace regina { class Dim2GluingPerms; class Dim2GluingPermSearcher; class Dim2Triangulation; class NPacket; /** * \weakgroup census * @{ */ /** * A utility class used to form a complete census of 2-manifold * triangulations satisfying certain constraints. * * \testpart */ class REGINA_API Dim2Census { public: /** * A routine used to determine whether a particular 2-manifold * triangulation should be included in a census. Routines of this * type are used by Dim2Census::formCensus(). * * The first parameter passed should be a triangulation currently * under consideration. * The second parameter may contain arbitrary data as passed to * Dim2Census::formCensus(). * * The return value should be \c true if the triangulation passed * should be included in the census, or \c false otherwise. */ typedef bool (*AcceptTriangulation)(Dim2Triangulation*, void*); private: NPacket* parent_; /**< The argument passed to formCensus(). */ NBoolSet orientability_; /**< The argument passed to formCensus(). */ AcceptTriangulation sieve_; /**< The arbitrary constraint function to run triangulations through. */ void* sieveArgs_; /**< The second argument to pass to function \a sieve. */ unsigned long whichSoln_; /**< The number of the solution we are up to. */ public: /** * Fills the given packet with all triangulations in a census of * 2-manifold triangulations satisfying the given constraints. * Each triangulation in the census will appear as a child of the * given packet. * * This routine will conduct a census of all valid triangulations * containing a given number of triangles. All such triangulations * are included in the census up to combinatorial isomorphism; given * any isomorphism class, exactly one representative will appear in * the census. * * The census can be optionally restricted to only include * triangulations satisfying further constraints (such as * orientability); see the individual parameter * descriptions for further details. In particular, parameter * \a sieve can be used to impose arbitrary restrictions that are * not hard-coded into this class. * * Note that if constraints may be imposed using the hard-coded * parameters (such as orientability), it is * generally better to do this than to use the arbitrary * constraint parameter \a sieve. Hard-coded parameters will be * tested earlier, and some (such as orientability) can be * incorporated directly into the census algorithm to give a vast * performance increase. * * Note that this routine should only be used if the census * contains a small enough total number of triangulations to * avoid any memory disasters. * * \ifacespython Parameters \a sieve and \a sieveArgs * are not present (and will be treated as 0). * * @param parent the packet beneath which members of the census will * be placed. * @param nTriangles the number of triangles in each triangulation * in the census. * @param orientability determines whether to include orientable * and/or non-orientable triangulations. The set should contain \c true * if orientable triangulations are to be included, and should contain * \c false if non-orientable triangulations are to be included. * @param boundary determines whether to include triangulations with * and/or without boundary edges. The set should contain \c true * if triangulations with boundary edges are to be included, and * should contain \c false if triangulations with only internal * edges are to be included. * @param nBdryEdges specifies the precise number of boundary edges * that should be present in the triangulations produced. If this * parameter is negative, it is ignored and no additional restriction * is imposed. See the documentation for routine * Dim2EdgePairing::findAllPairings() for details regarding this * parameter and how it interacts with parameter \a boundary. * @param sieve an additional constraint function that may be * used to exclude certain triangulations from the census. If * this parameter is non-zero, each triangulation produced (after * passing all other criteria) will be passed through this * function. If this function returns \c true then the triangulation * will be included in the census; otherwise it will not. * When this function is called, the first (triangulation) * argument will be a triangulation under consideration for * inclusion in the census. The second argument will be * parameter \a sieveArgs as passed to formCensus(). * Parameter \a sieve may be passed as \c null (in which case no * additional constraint function will be used). * @param sieveArgs the pointer to pass as the final parameter * for the function \a sieve which will be called upon each * triangulation found. If \a sieve is \c null then \a sieveArgs * will be ignored. * @return the number of triangulations produced in the census. */ static unsigned long formCensus(NPacket* parent, unsigned nTriangles, NBoolSet orientability, NBoolSet boundary, int nBdryEdges, AcceptTriangulation sieve = 0, void* sieveArgs = 0); /** * Fills the given packet with all triangulations in a partial census * of 2-manifold triangulations satisfying the given constraints. * Each triangulation in the partial census will appear as a child of * the given packet. * * This routine will conduct a census of all valid 2-manifold * triangulations that are modelled by the given triangle edge * pairing. All such triangulations are included in the census up to * combinatorial isomorphism; given any isomorphism class, exactly * one representative will appear in the census. * * The census can be optionally restricted to only include * triangulations satisfying further constraints (such as * orientability); see the individual parameter * descriptions for further details. In particular, parameter * \a sieve can be used to impose arbitrary restrictions that are * not hard-coded into this class. * * Note that if constraints may be imposed using the hard-coded * parameters (such as orientability), it is * generally better to do this than to use the arbitrary * constraint parameter \a sieve. Hard-coded parameters will be * tested earlier, and some (such as orientability) can be * incorporated directly into the census algorithm to give a vast * performance increase. * * Note that this routine should only be used if the partial census * contains a small enough total number of triangulations to * avoid any memory disasters. * * The partial census will run in the current thread. This * routine will only return once the partial census is complete. * * \pre The given edge pairing is connected, i.e., it is possible * to reach any triangle from any other triangle via a * series of matched edge pairs. * \pre The given edge pairing is in canonical form as described * by Dim2EdgePairing::isCanonical(). Note that all facet pairings * constructed by Dim2EdgePairing::findAllPairings() are of this form. * * \ifacespython Parameters \a sieve and \a sieveArgs * are not present (and will be treated as 0). * * @param pairing the edge pairing that * triangulations in this partial census must be modelled by. * @param parent the packet beneath which members of the partial * census will be placed. * @param orientability determines whether to include orientable * and/or non-orientable triangulations. The set should contain \c true * if orientable triangulations are to be included, and should contain * \c false if non-orientable triangulations are to be included. * @param sieve an additional constraint function that may be * used to exclude certain triangulations from the census. If * this parameter is non-zero, each triangulation produced (after * passing all other criteria) will be passed through this * function. If this function returns \c true then the triangulation * will be included in the census; otherwise it will not. * When this function is called, the first (triangulation) * argument will be a triangulation under consideration for * inclusion in the census. The second argument will be * parameter \a sieveArgs as passed to formPartialCensus(). * Parameter \a sieve may be passed as \c null (in which case no * additional constraint function will be used). * @param sieveArgs the pointer to pass as the final parameter * for the function \a sieve which will be called upon each * triangulation found. If \a sieve is \c null then \a sieveArgs * will be ignored. * @return the number of triangulations produced in the partial census. */ static unsigned long formPartialCensus(const Dim2EdgePairing* pairing, NPacket* parent, NBoolSet orientability, AcceptTriangulation sieve = 0, void* sieveArgs = 0); private: /** * Creates a new structure to hold the given information. * All parameters not explained are taken directly from * formCensus(). */ Dim2Census(NPacket* parent, const NBoolSet& orientability, AcceptTriangulation sieve, void* sieveArgs); /** * Called when a particular triangle edge pairing has been * found. This routine hooks up the edge pairing generation with * the gluing permutation generation. * * @param pairing the edge pairing that has been found. * @param autos the set of all automorphisms of the given edge * pairing. * @param census the census currently being generated; * this must really be of class Dim2Census. */ static void foundEdgePairing(const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, void* census); /** * Called when a particular set of gluing permutations has been * found. This routine generates the corresponding triangulation * and decides whether it really belongs in the census. * * \pre The given set of gluing permutations is complete, i.e., * it is not just a partially-complete search state. Partial * searches are formed by calling Dim2GluingPermSearcher::runSearch() * with a non-negative depth argument. * * @param perms the gluing permutation set that has been found. * @param census the census currently being generated; * this must really be of class Dim2Census. */ static void foundGluingPerms(const Dim2GluingPermSearcher* perms, void* census); }; /*@}*/ } // namespace regina #endif regina-4.95/engine/census/dim2edgepairing.cpp000644 000765 000024 00000007313 12234011536 021113 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "census/dim2edgepairing.h" #include "census/ngenericfacetpairing-impl.h" #include "dim2/dim2triangle.h" #include "dim2/dim2triangulation.h" namespace regina { // Instantiate all templates from the -impl.h file. template NGenericFacetPairing<2>::NGenericFacetPairing( const NGenericFacetPairing<2>&); template NGenericFacetPairing<2>::NGenericFacetPairing( const Dim2Triangulation&); template bool NGenericFacetPairing<2>::isClosed() const; template std::string NGenericFacetPairing<2>::str() const; template std::string NGenericFacetPairing<2>::dotHeader(const char*); template void NGenericFacetPairing<2>::writeDotHeader(std::ostream&, const char*); template std::string NGenericFacetPairing<2>::dot(const char*, bool, bool) const; template void NGenericFacetPairing<2>::writeDot(std::ostream&, const char*, bool, bool) const; template std::string NGenericFacetPairing<2>::toTextRep() const; template Dim2EdgePairing* NGenericFacetPairing<2>::fromTextRep( const std::string&); template bool NGenericFacetPairing<2>::isCanonical() const; template bool NGenericFacetPairing<2>::isCanonicalInternal( NGenericFacetPairing<2>::IsoList&) const; template void NGenericFacetPairing<2>::findAutomorphisms( NGenericFacetPairing<2>::IsoList&) const; template bool NGenericFacetPairing<2>::findAllPairings(unsigned, NBoolSet, int, NGenericFacetPairing<2>::Use, void*, bool); template void* NGenericFacetPairing<2>::run(void*); } // namespace regina regina-4.95/engine/census/dim2edgepairing.h000644 000765 000024 00000012054 12234011536 020556 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/dim2edgepairing.h * \brief Deals with pairing off edges of triangles in a census of * 2-manifold triangulations. */ #ifndef __DIM2EDGEPAIRING_H #ifndef __DOXYGEN #define __DIM2EDGEPAIRING_H #endif #include "regina-core.h" #include "census/ngenericfacetpairing.h" #include "dim2/dim2isomorphism.h" namespace regina { /** * \weakgroup census * @{ */ /** * Represents a specific pairwise matching of edges of triangles. * Given a fixed number of triangles, each triangle edge is either * paired with some other triangle edge (which is in turn paired with * it) or remains unmatched. A triangle edge cannot be paired with itself. * * Such a matching models part of the structure of a 2-manifold triangulation, * in which each triangle edge is either glued to some other triangle edge * facet (which is in turn glued to it) or is an unglued boundary edge. * * Note that if this pairing is used to construct an actual 2-manifold * triangulation, the individual gluing orientations will still need to * be specified; they are not a part of this structure. * * \testpart */ class REGINA_API Dim2EdgePairing : public NGenericFacetPairing<2> { public: /** * Creates a new edge pairing that is a clone of the given edge * pairing. * * @param cloneMe the facet pairing to clone. */ Dim2EdgePairing(const Dim2EdgePairing& cloneMe); /** * Creates the edge pairing of the given 2-manifold triangulation. * This is the edge pairing that describes how the triangle edges of * the given 2-manifold triangulation are joined together, as * described in the class notes. * * \pre The given triangulation is not empty. * * @param tri the 2-manifold triangulation whose edge pairing should * be constructed. */ Dim2EdgePairing(const Dim2Triangulation& tri); private: /** * Creates a new edge pairing. All internal arrays will be * allocated but not initialised. * * \pre \a nTriangles is at least 1. * * @param nTriangles the number of triangles under * consideration in this new edge pairing. */ Dim2EdgePairing(unsigned nTriangles); // Make sure the parent class can call the private constructor. friend class NGenericFacetPairing<2>; }; /*@}*/ // Inline functions for Dim2EdgePairing inline Dim2EdgePairing::Dim2EdgePairing(const Dim2EdgePairing& cloneMe) : NGenericFacetPairing<2>(cloneMe) { } inline Dim2EdgePairing::Dim2EdgePairing(unsigned nTriangles) : NGenericFacetPairing<2>(nTriangles) { } inline Dim2EdgePairing::Dim2EdgePairing(const Dim2Triangulation& tri) : NGenericFacetPairing<2>(tri) { } } // namespace regina #endif regina-4.95/engine/census/dim2gluingperms.cpp000644 000765 000024 00000005723 12234011536 021174 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "census/dim2gluingperms.h" #include "census/ngenericgluingperms-impl.h" #include "dim2/dim2triangulation.h" namespace regina { // Instantiate all templates from the -impl.h file. template NGenericGluingPerms<2>::NGenericGluingPerms( const NGenericGluingPerms<2>&); template NGenericGluingPerms<2>::NGenericGluingPerms(std::istream&); template Dim2Triangulation* NGenericGluingPerms<2>::triangulate() const; template int NGenericGluingPerms<2>::gluingToIndex( const Dim2TriangleEdge&, const NPerm3&) const; template int NGenericGluingPerms<2>::gluingToIndex( unsigned, unsigned, const NPerm3&) const; template void NGenericGluingPerms<2>::dumpData(std::ostream&) const; } // namespace regina regina-4.95/engine/census/dim2gluingperms.h000644 000765 000024 00000013226 12234011536 020636 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/dim2gluingperms.h * \brief Deals with selecting gluing permutations to complement a * particular pairing of triangle edges. */ #ifndef __DIM2GLUINGPERMS_H #ifndef __DOXYGEN #define __DIM2GLUINGPERMS_H #endif #include "regina-core.h" #include "census/dim2edgepairing.h" #include "census/ngenericgluingperms.h" namespace regina { /** * \weakgroup census * @{ */ /** * Represents a specific set of gluing permutations to complement a * particular pairwise matching of triangle edges. Given a * pairwise matching of edges (as described by class Dim2EdgePairing), each * edge that is matched with some other edge will have an associated * permutation of three elements (as described by class NPerm3). * * If an edge is matched with some other edge, the two associated * permutations in this set will be inverses. If an edge is left * deliberately unmatched, it will have no associated permutation in this set. * * Such a set of permutations models part of the structure of a 2-manifold * triangulation, in which each triangle edge that is glued to another * edge has a corresponding gluing permutation (and the matched edge has * the inverse gluing permutation). * * \ifacespython Not present. */ class REGINA_API Dim2GluingPerms : public NGenericGluingPerms<2> { public: /** * Creates a new set of gluing permutations that is a clone of * the given permutation set. * * @param cloneMe the gluing permutations to clone. */ Dim2GluingPerms(const Dim2GluingPerms& cloneMe); /** * Reads a new set of gluing permutations from the given input * stream. This routine reads data in the format written by * dumpData(). * * If the data found in the input stream is invalid or * incorrectly formatted, the routine inputError() will return * \c true but the contents of this object will be otherwise * undefined. * * \warning The data format is liable to change between * Regina releases. Data in this format should be used on a * short-term temporary basis only. * * @param in the input stream from which to read. */ Dim2GluingPerms(std::istream& in); protected: /** * Creates a new permutation set. All internal arrays will be * allocated but not initialised. * * \pre The given edge pairing is connected, i.e., it is possible * to reach any triangle from any other triangle via a * series of matched edge pairs. * \pre The given edge pairing is in canonical form as described * by Dim2EdgePairing::isCanonical(). Note that all edge pairings * constructed by Dim2EdgePairing::findAllPairings() are of this form. * * @param pairing the specific pairing of triangle edges * that this permutation set will complement. */ Dim2GluingPerms(const Dim2EdgePairing* pairing); }; /*@}*/ // Inline functions for Dim2GluingPerms inline Dim2GluingPerms::Dim2GluingPerms(const Dim2GluingPerms& cloneMe) : NGenericGluingPerms<2>(cloneMe) { } inline Dim2GluingPerms::Dim2GluingPerms(std::istream& in) : NGenericGluingPerms<2>(in) { } inline Dim2GluingPerms::Dim2GluingPerms(const Dim2EdgePairing* pairing) : NGenericGluingPerms<2>(pairing) { } } // namespace regina #endif regina-4.95/engine/census/dim2gluingpermsearcher.cpp000644 000765 000024 00000031533 12234011536 022524 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "census/dim2census.h" #include "census/dim2gluingpermsearcher.h" #include "dim2/dim2triangulation.h" #include "utilities/memutils.h" namespace regina { const char Dim2GluingPermSearcher::dataTag_ = 'g'; Dim2GluingPermSearcher::Dim2GluingPermSearcher( const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs) : Dim2GluingPerms(pairing), autos_(autos), autosNew(autos == 0), orientableOnly_(orientableOnly), use_(use), useArgs_(useArgs), started(false), orientation(new int[pairing->size()]) { // Generate the list of edge pairing automorphisms if necessary. // This will require us to remove the const for a wee moment. if (autosNew) { const_cast(this)->autos_ = new Dim2EdgePairing::IsoList(); pairing->findAutomorphisms( const_cast(*autos_)); } // Initialise arrays. unsigned nTris = size(); std::fill(orientation, orientation + nTris, 0); std::fill(permIndices_, permIndices_ + nTris* 3, -1); // Just fill the order[] array in a default left-to-right fashion. // Subclasses can rearrange things if they choose. order = new Dim2TriangleEdge[(nTris * 3) / 2]; orderElt = orderSize = 0; Dim2TriangleEdge edge, adj; for (edge.setFirst(); ! edge.isPastEnd(nTris, true); edge++) if (! pairing->isUnmatched(edge)) if (edge < pairing->dest(edge)) order[orderSize++] = edge; } Dim2GluingPermSearcher::~Dim2GluingPermSearcher() { delete[] orientation; delete[] order; if (autosNew) { // We made them, so we'd better remove the const again and // delete them. Dim2EdgePairing::IsoList* autos = const_cast(autos_); std::for_each(autos->begin(), autos->end(), FuncDelete()); delete autos; } } Dim2GluingPermSearcher* Dim2GluingPermSearcher::bestSearcher( const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs) { // We only have one algorithm for now. return new Dim2GluingPermSearcher(pairing, autos, orientableOnly, use, useArgs); } void Dim2GluingPermSearcher::findAllPerms(const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs) { Dim2GluingPermSearcher* searcher = bestSearcher(pairing, autos, orientableOnly, use, useArgs); searcher->runSearch(); delete searcher; } void Dim2GluingPermSearcher::runSearch(long maxDepth) { // In this generation algorithm, each orientation is simply +/-1. unsigned nTriangles = size(); if (maxDepth < 0) { // Larger than we will ever see (and in fact grossly so). maxDepth = nTriangles * 3 + 1; } if (! started) { // Search initialisation. started = true; // Do we in fact have no permutation at all to choose? if (maxDepth == 0 || pairing_->dest(0, 0).isBoundary(nTriangles)) { use_(this, useArgs_); use_(0, useArgs_); return; } orderElt = 0; orientation[0] = 1; } // Is it a partial search that has already finished? if (orderElt == orderSize) { if (isCanonical()) use_(this, useArgs_); use_(0, useArgs_); return; } // ---------- Selecting the individual gluing permutations ---------- int minOrder = orderElt; int maxOrder = orderElt + maxDepth; Dim2TriangleEdge edge, adj; while (orderElt >= minOrder) { edge = order[orderElt]; adj = (*pairing_)[edge]; // TODO: Check for cancellation. // Move to the next permutation. // Be sure to preserve the orientation of the permutation if necessary. if ((! orientableOnly_) || adj.facet == 0) permIndex(edge)++; else permIndex(edge) += 2; // Are we out of ideas for this edge? if (permIndex(edge) >= 2) { // Yep. Head back down to the previous edge. permIndex(edge) = -1; permIndex(adj) = -1; orderElt--; continue; } // We are sitting on a new permutation to try. permIndex(adj) = permIndex(edge); // S2 elements are their own inverses. // Fix the orientation if appropriate. if (adj.facet == 0 && orientableOnly_) { // It's the first time we've hit this triangle. if ((permIndex(edge) + (edge.facet == 2 ? 0 : 1) + (adj.facet == 2 ? 0 : 1)) % 2 == 0) orientation[adj.simp] = -orientation[edge.simp]; else orientation[adj.simp] = orientation[edge.simp]; } // Move on to the next edge. orderElt++; // If we're at the end, try the solution and step back. if (orderElt == orderSize) { // We in fact have an entire triangulation. // Run through the automorphisms and check whether our // permutations are in canonical form. if (isCanonical()) use_(this, useArgs_); // Back to the previous face. orderElt--; } else { // Not a full triangulation; just one level deeper. // We've moved onto a new edge. // Be sure to get the orientation right. edge = order[orderElt]; if (orientableOnly_ && pairing_->dest(edge).facet > 0) { // permIndex(edge) will be set to -1 or -2 as appropriate. adj = (*pairing_)[edge]; if (orientation[edge.simp] == orientation[adj.simp]) permIndex(edge) = 1; else permIndex(edge) = 0; if ((edge.facet == 2 ? 0 : 1) + (adj.facet == 2 ? 0 : 1) == 1) permIndex(edge) = (permIndex(edge) + 1) % 2; permIndex(edge) -= 2; } if (orderElt == maxOrder) { // We haven't found an entire triangulation, but we've // gone as far as we need to. // Process it, then step back. use_(this, useArgs_); // Back to the previous edge. permIndex(edge) = -1; orderElt--; } } } // And the search is over. use_(0, useArgs_); } void Dim2GluingPermSearcher::dumpTaggedData(std::ostream& out) const { out << dataTag() << std::endl; dumpData(out); } Dim2GluingPermSearcher* Dim2GluingPermSearcher::readTaggedData(std::istream& in, UseDim2GluingPerms use, void* useArgs) { // Read the class marker. char c; in >> c; if (in.eof()) return 0; Dim2GluingPermSearcher* ans; if (c == Dim2GluingPermSearcher::dataTag_) ans = new Dim2GluingPermSearcher(in, use, useArgs); else return 0; if (ans->inputError()) { delete ans; return 0; } return ans; } void Dim2GluingPermSearcher::dumpData(std::ostream& out) const { Dim2GluingPerms::dumpData(out); out << (orientableOnly_ ? 'o' : '.'); out << (started ? 's' : '.'); out << std::endl; int nTris = size(); int i; for (i = 0; i < nTris; i++) { if (i) out << ' '; out << orientation[i]; } out << std::endl; out << orderElt << ' ' << orderSize << std::endl; for (i = 0; i < orderSize; i++) { if (i) out << ' '; out << order[i].simp << ' ' << order[i].facet; } out << std::endl; } Dim2GluingPermSearcher::Dim2GluingPermSearcher(std::istream& in, UseDim2GluingPerms use, void* useArgs) : Dim2GluingPerms(in), autos_(0), autosNew(false), use_(use), useArgs_(useArgs), orientation(0), order(0), orderSize(0), orderElt(0) { if (inputError_) return; // Recontruct the face pairing automorphisms. const_cast(this)->autos_ = new Dim2EdgePairing::IsoList(); pairing_->findAutomorphisms(const_cast(*autos_)); autosNew = true; // Keep reading. char c; in >> c; if (c == 'o') orientableOnly_ = true; else if (c == '.') orientableOnly_ = false; else { inputError_ = true; return; } in >> c; if (c == 's') started = true; else if (c == '.') started = false; else { inputError_ = true; return; } int nTris = pairing_->size(); int t; orientation = new int[nTris]; for (t = 0; t < nTris; t++) in >> orientation[t]; order = new Dim2TriangleEdge[(nTris * 3) / 2]; in >> orderElt >> orderSize; for (t = 0; t < orderSize; t++) { in >> order[t].simp >> order[t].facet; if (order[t].simp >= nTris || order[t].simp < 0 || order[t].facet >= 3 || order[t].facet < 0) { inputError_ = true; return; } } // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } bool Dim2GluingPermSearcher::isCanonical() const { Dim2TriangleEdge edge, edgeDest, edgeImage; int ordering; for (Dim2EdgePairing::IsoList::const_iterator it = autos_->begin(); it != autos_->end(); it++) { // Compare the current set of gluing permutations with its // preimage under each edge pairing automorphism, to see whether // our current permutation set is closest to canonical form. for (edge.setFirst(); edge.simp < static_cast(pairing_->size()); edge++) { edgeDest = pairing_->dest(edge); if (pairing_->isUnmatched(edge) || edgeDest < edge) continue; edgeImage = (**it)[edge]; ordering = gluingPerm(edge).compareWith( (*it)->edgePerm(edgeDest.simp).inverse() * gluingPerm(edgeImage) * (*it)->edgePerm(edge.simp)); if (ordering < 0) { // This permutation set is closer. break; } else if (ordering > 0) { // The transformed permutation set is closer. return false; } // So far it's an automorphism of gluing permutations also. // Keep running through edges. } // Nothing broke with this automorphism. On to the next one. } // Nothing broke at all. return true; } } // namespace regina regina-4.95/engine/census/dim2gluingpermsearcher.h000644 000765 000024 00000047761 12234011536 022203 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/dim2gluingpermsearcher.h * \brief Supports searching through all possible sets of triangle * gluing permutations for a given triangle edge pairing. */ #ifndef __DIM2GLUINGPERMSEARCHER_H #ifndef __DOXYGEN #define __DIM2GLUINGPERMSEARCHER_H #endif #include "regina-core.h" #include "census/dim2gluingperms.h" namespace regina { /** * \weakgroup census * @{ */ class Dim2GluingPermSearcher; /** * A routine used to do arbitrary processing upon a particular set of * triangle gluing permutations. Such routines are used to process * permutation sets found when running Dim2GluingPermSearcher::findAllPerms(). * * The first parameter passed will be a set of gluing permutations * (in fact it will be of the subclass Dim2GluingPermSearcher in order to * support partial searches as well as full searches). This set of * gluing permutations must not be deallocated by this routine, since it * may be used again later by the caller. The second parameter may contain * arbitrary data as passed to either Dim2GluingPerms::findAllPerms() or * the Dim2GluingPermSearcher class constructor. * * Note that the first parameter passed might be \c null to signal that * gluing permutation generation has finished. */ typedef void (*UseDim2GluingPerms)(const Dim2GluingPermSearcher*, void*); /** * A utility class for searching through all possible gluing permutation * sets that correspond to a given triangle edge pairing. In the future, * there may be subclasses of Dim2GluingPermSearcher that correspond to * specialised search algorithms for use in certain scenarios. * The main class Dim2GluingPermSearcher offers a default search * algorithm that may be used in a general context. * * The simplest way of performing a search through all possible gluing * permutations is by calling the static method findAllPerms(). This will * examine the search parameters and ensure that the best possible algorithm * is used. For finer control over the program flow, the static method * bestSearcher() can be used to create a search manager of the most * suitable class and then runSearch() can be called on this object directly. * For absolute control, a specific algorithm can be forced by explicitly * constructing an object of the corresponding class (and again * calling runSearch() on that object directly). * * Note that this class derives from Dim2GluingPerms. The search will * involve building and repeatedly modifying the inherited Dim2GluingPerms * data in-place. * * \ifacespython Not present. */ class REGINA_API Dim2GluingPermSearcher : public Dim2GluingPerms { public: static const char dataTag_; /**< A character used to identify this class when reading and writing tagged data in text format. */ protected: const Dim2EdgePairing::IsoList* autos_; /**< The set of isomorphisms that define equivalence of gluing permutation sets. Generally this is the set of all automorphisms of the underlying edge pairing. */ bool autosNew; /**< Did we create the isomorphism list autos_ ourselves (in which case we must destroy it also)? */ bool orientableOnly_; /**< Are we only searching for gluing permutations that correspond to orientable triangulations? */ UseDim2GluingPerms use_; /**< A routine to call each time a gluing permutation set is found during the search. */ void* useArgs_; /**< Additional user-supplied data to be passed as the second argument to the \a use_ routine. */ bool started; /**< Has the search started yet? This helps distinguish between a new search and the resumption of a partially completed search. */ int* orientation; /**< Keeps track of the orientation of each triangle in the underlying triangulation. Orientation is positive/negative, or 0 if unknown. Note that in some algorithms the orientation is simply +/-1, and in some algorithms the orientation counts forwards or backwards from 0 according to how many times the orientation has been set or verified. */ protected: Dim2TriangleEdge* order; /**< Describes the order in which gluing permutations are assigned to edges. Specifically, this order is order[0], order[1], ..., order[orderSize-1]. Note that each element of this array corresponds to a single edge of the underlying edge pairing graph, which in turn represents a triangle edge and its image under the given edge pairing. The specific triangle edge stored in this array for each edge of the underlying edge pairing graph will be the smaller of the two identified triangle edges (unless otherwise specified by a subclass that uses a specialised search algorithm. */ int orderSize; /**< The total number of edges in the edge pairing graph, i.e., the number of elements of interest in the order[] array. */ int orderElt; /**< Marks which element of order[] we are currently examining at this stage of the search. */ public: /** * Initialises a new search for gluing permutation sets. The * search is started by calling runSearch(). Note that the * static method findAllPerms() handles both construction and * searching, and is the preferred entry point for end users. * * The arguments to this constructor describe the search * parameters in detail, as well as what should be done with * each gluing permutation set that is found. * * \pre The given edge pairing is connected, i.e., it is possible * to reach any triangle from any other triangle via a * series of matched edge pairs. * \pre The given edge pairing is in canonical form as described * by Dim2EdgePairing::isCanonical(). Note that all edge pairings * constructed by Dim2EdgePairing::findAllPairings() are of this form. * * @param pairing the specific pairing of triangle edges * that the generated permutation sets will complement. * @param autos the collection of isomorphisms that define equivalence * of permutation sets. These are used by runSearch(), which produces * each permutation set precisely once up to equivalence. These * isomorphisms must all be automorphisms of the given edge pairing, * and will generally be the set of all such automorphisms. This * parameter may be 0, in which case the set of all automorphisms * of the given edge pairing will be generated and used. * @param orientableOnly \c true if only gluing permutations * corresponding to orientable triangulations should be * generated, or \c false if no such restriction should be imposed. * @param use the function to call upon each permutation set that * is found. The first parameter passed to this function will be * a gluing permutation set. The second parameter will be * parameter \a useArgs as was passed to this routine. * @param useArgs the pointer to pass as the final parameter for * the function \a use which will be called upon each permutation * set found. */ Dim2GluingPermSearcher(const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs = 0); /** * Initialises a new search manager based on data read from the * given input stream. This may be a new search or a partially * completed search. * * This routine reads data in the format written by dumpData(). * If you wish to read data whose precise class is unknown, * consider using dumpTaggedData() and readTaggedData() instead. * * If the data found in the input stream is invalid or incorrectly * formatted, the routine inputError() will return \c true but * the contents of this object will be otherwise undefined. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. * @param use as for the main Dim2GluingPermSearcher constructor. * @param useArgs as for the main Dim2GluingPermSearcher constructor. */ Dim2GluingPermSearcher(std::istream& in, UseDim2GluingPerms use, void* useArgs = 0); /** * Destroys this search manager and all supporting data * structures. */ virtual ~Dim2GluingPermSearcher(); /** * Generates all possible gluing permutation sets that satisfy * the current search criteria. The search criteria are * specified in the class constructor, or through the static * method findAllPerms(). * * Each set of gluing permutations will be produced precisely * once up to equivalence, where equivalence is defined by the * given set of automorphisms of the given edge pairing. * * For each permutation set that is generated, routine \a use_ (as * passed to the class constructor) will be called with that * permutation set as an argument. * * Once the generation of permutation sets has finished, routine * \a use_ will be called once more, this time with \c null as its * first (permutation set) argument. * * Subclasses corresponding to more specialised search criteria * should override this routine to use a better optimised algorithm * where possible. * * It is possible to run only a partial search, branching to a * given depth but no further. In this case, rather than * producing complete gluing permutation sets, the search will * produce a series of partially-complete Dim2GluingPermSearcher * objects. These partial searches may then be restarted by * calling runSearch() once more (usually after being frozen or * passed on to a different processor). If necessary, the \a use_ * routine may call completePermSet() to distinguish between * a complete set of gluing permutations and a partial search state. * * Note that a restarted search will never drop below its * initial depth. That is, calling runSearch() with a fixed * depth can be used to subdivide the overall search space into * many branches, and then calling runSearch() on each resulting * partial search will complete each of these branches without overlap. * * \todo \feature Allow cancellation of permutation set generation. * * @param maxDepth the depth of the partial search to run, or a * negative number if a full search should be run (the default). */ virtual void runSearch(long maxDepth = -1); /** * Determines whether this search manager holds a complete * gluing permutation set or just a partially completed search * state. * * This may assist the \a use_ routine when running partial * depth-based searches. See runSearch() for further details. * * @return \c true if a complete gluing permutation set is held, * or \c false otherwise. */ bool completePermSet() const; /** * Dumps all internal data in a plain text format, along with a * marker to signify which precise class the data belongs to. * This routine can be used with readTaggedData() to transport * objects from place to place whose precise class is unknown. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param out the output stream to which the data should be * written. */ void dumpTaggedData(std::ostream& out) const; // Overridden methods: virtual void dumpData(std::ostream& out) const; /** * The main entry routine for running a search for all gluing * permutation sets that complement a given edge pairing. * * This routine examines the search parameters, chooses the best * possible search algorithm, constructs an object of the * corresponding subclass of Dim2GluingPermSearcher and then calls * runSearch(). * * See the Dim2GluingPermSearcher constructor for documentation on * the arguments to this routine. See the runSearch() method * for documentation on how the search runs and returns its * results. * * \pre The given edge pairing is connected, i.e., it is possible * to reach any triangle from any other triangle via a * series of matched edge pairs. * \pre The given edge pairing is in canonical form as described * by Dim2EdgePairing::isCanonical(). Note that all edge pairings * constructed by Dim2EdgePairing::findAllPairings() are of this form. */ static void findAllPerms(const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs = 0); /** * Constructs a search manager of the best possible class for the * given search parameters. Different subclasses of * Dim2GluingPermSearcher provide optimised search algorithms for * different types of search. * * Calling this routine and then calling runSearch() on the * result has the same effect as the all-in-one routine * findAllPerms(). Unless you have specialised requirements * (such as partial searching), you are probably better calling * findAllPerms() instead. * * The resulting object is newly created, and must be destroyed * by the caller of this routine. * * See the Dim2GluingPermSearcher constructor for documentation on * the arguments to this routine. * * \pre The given edge pairing is connected, i.e., it is possible * to reach any triangle from any other triangle via a * series of matched edge pairs. * \pre The given edge pairing is in canonical form as described * by Dim2EdgePairing::isCanonical(). Note that all edge pairings * constructed by Dim2EdgePairing::findAllPairings() are of this form. * * @return the newly created search manager. */ static Dim2GluingPermSearcher* bestSearcher( const Dim2EdgePairing* pairing, const Dim2EdgePairing::IsoList* autos, bool orientableOnly, UseDim2GluingPerms use, void* useArgs = 0); /** * Creates a new search manager based on tagged data read from * the given input stream. This may be a new search or a * partially completed search. * * The tagged data should be in the format written by * dumpTaggedData(). The precise class of the search manager * will be determined from the tagged data, and does not need to * be known in advance. This is in contrast to dumpData() and * the input stream constructors, where the class of the data being * read must be known at compile time. * * If the data found in the input stream is invalid or * incorrectly formatted, a null pointer will be returned. * Otherwise a newly constructed search manager will be returned, * and it is the responsibility of the caller of this routine to * destroy it after use. * * The arguments \a use and \a useArgs are the same as for the * Dim2GluingPermSearcher constructor. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. */ static Dim2GluingPermSearcher* readTaggedData(std::istream& in, UseDim2GluingPerms use, void* useArgs = 0); protected: /** * Compares the current set of gluing permutations with its * preimage under each automorphism of the underlying edge pairing, * in order to see whether the current set is in canonical form * (i.e., is lexicographically smallest). * * @return \c true if the current set is in canonical form, * or \c false otherwise. */ bool isCanonical() const; /** * Returns the character used to identify this class when * storing tagged data in text format. * * @return the class tag. */ virtual char dataTag() const; }; /*@}*/ // Inline functions for Dim2GluingPermSearcher inline bool Dim2GluingPermSearcher::completePermSet() const { return (orderElt == orderSize); } inline char Dim2GluingPermSearcher::dataTag() const { return Dim2GluingPermSearcher::dataTag_; } } // namespace regina #endif regina-4.95/engine/census/euler.cpp000644 000765 000024 00000130231 12235500316 017171 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "census/ncensus.h" #include "census/ngluingpermsearcher.h" #include "triangulation/nedge.h" #include "triangulation/nfacepair.h" #include "triangulation/ntriangulation.h" #include "utilities/boostutils.h" #include "utilities/memutils.h" namespace regina { const char NEulerSearcher::VLINK_CLOSED = 1; const char NEulerSearcher::VLINK_BAD_EULER = 2; const int NEulerSearcher::vertexLinkNextFace[4][4] = { { -1, 2, 3, 1}, { 3, -1, 0, 2}, { 1, 3, -1, 0}, { 1, 2, 0, -1} }; const int NEulerSearcher::vertexLinkPrevFace[4][4] = { { -1, 3, 1, 2}, { 2, -1, 3, 0}, { 3, 0, -1, 1}, { 2, 0, 1, -1} }; const char NEulerSearcher::dataTag_ = 'e'; void NEulerSearcher::TetVertexState::dumpData(std::ostream& out) const { // Be careful with twistUp, which is a char but which should be // written as an int. out << parent << ' ' << rank << ' ' << bdry << ' ' << euler << ' ' << (twistUp ? 1 : 0) << ' ' << (hadEqualRank ? 1 : 0) << ' ' << static_cast(bdryEdges) << ' ' << bdryNext[0] << ' ' << bdryNext[1] << ' ' << static_cast(bdryTwist[0]) << ' ' << static_cast(bdryTwist[1]) << ' ' << bdryNextOld[0] << ' ' << bdryNextOld[1] << ' ' << static_cast(bdryTwistOld[0]) << ' ' << static_cast(bdryTwistOld[1]); } bool NEulerSearcher::TetVertexState::readData(std::istream& in, unsigned long nStates) { in >> parent >> rank >> bdry >> euler; // twistUp is a char, but we need to read it as an int. int twist; in >> twist; twistUp = twist; // hadEqualRank is a bool, but we need to read it as an int. int bRank; in >> bRank; hadEqualRank = bRank; // More chars to ints coming. int bVal; in >> bVal; bdryEdges = bVal; in >> bdryNext[0] >> bdryNext[1]; in >> bVal; bdryTwist[0] = bVal; in >> bVal; bdryTwist[1] = bVal; in >> bdryNextOld[0] >> bdryNextOld[1]; in >> bVal; bdryTwistOld[0] = bVal; in >> bVal; bdryTwistOld[1] = bVal; if (parent < -1 || parent >= static_cast(nStates)) return false; if (rank >= nStates) return false; if (bdry > 3 * nStates) return false; if (euler > 2) return false; if (twist != 1 && twist != 0) return false; if (bRank != 1 && bRank != 0) return false; if (bdryEdges > 3) /* Never < 0 since this is unsigned. */ return false; if (bdryNext[0] < 0 || bdryNext[0] >= static_cast(nStates)) return false; if (bdryNext[1] < 0 || bdryNext[1] >= static_cast(nStates)) return false; if (bdryNextOld[0] < -1 || bdryNext[0] >= static_cast(nStates)) return false; if (bdryNextOld[1] < -1 || bdryNextOld[1] >= static_cast(nStates)) return false; if (bdryTwist[0] < 0 || bdryTwist[0] > 1) return false; if (bdryTwist[1] < 0 || bdryTwist[1] > 1) return false; if (bdryTwistOld[0] < 0 || bdryTwistOld[0] > 1) return false; if (bdryTwistOld[1] < 0 || bdryTwistOld[1] > 1) return false; return true; } void NEulerSearcher::TetEdgeState::dumpData(std::ostream& out, unsigned nTets) const { // Be careful with twistUp, which is a char but which should be // written as an int. out << parent << ' ' << rank << ' ' << size << ' ' << (bounded ? 1 : 0) << ' ' << (twistUp ? 1 : 0) << ' ' << (hadEqualRank ? 1 : 0) << ' '; unsigned i; for (i = 0; i < nTets * 4 && i < 64; ++i) out << char(facesPos.get(i) + '0'); out << ' '; for (i = 0; i < nTets * 4 && i < 64; ++i) out << char(facesNeg.get(i) + '0'); } bool NEulerSearcher::TetEdgeState::readData(std::istream& in, unsigned nTets) { in >> parent >> rank >> size; // bounded is a bool, but we need to read it as an int. int bBounded; in >> bBounded; bounded = bBounded; // twistUp is a char, but we need to read it as an int. int twist; in >> twist; twistUp = twist; // hadEqualRank is a bool, but we need to read it as an int. int bRank; in >> bRank; hadEqualRank = bRank; char cFaces; bool facesBroken = false; unsigned i; for (i = 0; i < nTets * 4 && i < 64; ++i) { in >> cFaces; if (cFaces >= '0' && cFaces <= '3') facesPos.set(i, cFaces - '0'); else facesBroken = true; } for (i = 0; i < nTets * 4 && i < 64; ++i) { in >> cFaces; if (cFaces >= '0' && cFaces <= '3') facesNeg.set(i, cFaces - '0'); else facesBroken = true; } if (parent < -1 || parent >= static_cast(6 * nTets)) return false; if (rank >= 6 * nTets) return false; if (size >= 6 * nTets) return false; if (bBounded != 1 && bBounded != 0) return false; if (twist != 1 && twist != 0) return false; if (bRank != 1 && bRank != 0) return false; if (facesBroken) return false; return true; } NEulerSearcher::NEulerSearcher(int useEuler, const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, int whichPurge, UseGluingPerms use, void* useArgs) : NGluingPermSearcher(pairing, autos, orientableOnly, true /* finiteOnly */, whichPurge, use, useArgs), euler_(useEuler) { // Initialise the internal arrays to accurately reflect the underlying // face pairing. unsigned nTets = getNumberOfTetrahedra(); // ---------- Tracking of vertex / edge equivalence classes ---------- unsigned i; nVertexClasses = nTets * 4; vertexState = new TetVertexState[nTets * 4]; vertexStateChanged = new int[nTets * 8]; std::fill(vertexStateChanged, vertexStateChanged + nTets * 8, static_cast(VLINK_JOIN_INIT)); for (i = 0; i < nTets * 4; i++) { vertexState[i].bdryEdges = 3; vertexState[i].bdryNext[0] = vertexState[i].bdryNext[1] = i; vertexState[i].bdryTwist[0] = vertexState[i].bdryTwist[1] = 0; // Initialise the backup members also so we're not writing // uninitialised data via dumpData(). vertexState[i].bdryNextOld[0] = vertexState[i].bdryNextOld[1] = -1; vertexState[i].bdryTwistOld[0] = vertexState[i].bdryTwistOld[1] = 0; } nEdgeClasses = nTets * 6; edgeState = new TetEdgeState[nTets * 6]; edgeStateChanged = new int[nTets * 8]; std::fill(edgeStateChanged, edgeStateChanged + nTets * 8, -1); // Since NQitmaskLen64 only supports 64 faces, only work with // the first 16 tetrahedra. If n > 16, this just weakens the // optimisation; however, this is no great loss since for n > 16 the // census code is at present infeasibly slow anyway. for (i = 0; i < nTets && i < 16; ++i) { /* 01 on +012, +013 */ edgeState[6 * i ].facesPos.set(4 * i + 3, 1); edgeState[6 * i ].facesPos.set(4 * i + 2, 1); /* 02 on -012 +023 */ edgeState[6 * i + 1].facesNeg.set(4 * i + 3, 1); edgeState[6 * i + 1].facesPos.set(4 * i + 1, 1); /* 03 on -013, -023 */ edgeState[6 * i + 2].facesNeg.set(4 * i + 2, 1); edgeState[6 * i + 2].facesNeg.set(4 * i + 1, 1); /* 12 on +012, +123 */ edgeState[6 * i + 3].facesPos.set(4 * i + 3, 1); edgeState[6 * i + 3].facesPos.set(4 * i + 0, 1); /* 13 on +013 -123 */ edgeState[6 * i + 4].facesPos.set(4 * i + 2, 1); edgeState[6 * i + 4].facesNeg.set(4 * i + 0, 1); /* 23 on +023, +123 */ edgeState[6 * i + 5].facesPos.set(4 * i + 1, 1); edgeState[6 * i + 5].facesPos.set(4 * i + 0, 1); } } void NEulerSearcher::runSearch(long maxDepth) { unsigned nTets = getNumberOfTetrahedra(); if (maxDepth < 0) { // Larger than we will ever see (and in fact grossly so). maxDepth = nTets * 4 + 1; } if (! started) { // Search initialisation. started = true; // Do we in fact have no permutation at all to choose? if (maxDepth == 0 || pairing_->dest(0, 0).isBoundary(nTets)) { use_(this, useArgs_); use_(0, useArgs_); return; } orderElt = 0; orientation[0] = 1; } // Is it a partial search that has already finished? if (orderElt == orderSize) { if (isCanonical()) use_(this, useArgs_); use_(0, useArgs_); return; } // ---------- Selecting the individual gluing permutations ---------- int minOrder = orderElt; int maxOrder = orderElt + maxDepth; NTetFace face, adj; int mergeResult; while (orderElt >= minOrder) { face = order[orderElt]; adj = (*pairing_)[face]; // TODO (long-term): Check for cancellation. // Move to the next permutation. // Be sure to preserve the orientation of the permutation if necessary. if ((! orientableOnly_) || adj.facet == 0) permIndex(face)++; else permIndex(face) += 2; // Are we out of ideas for this face? if (permIndex(face) >= 6) { // Yep. Head back down to the previous face. permIndex(face) = -1; permIndex(adj) = -1; orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } continue; } // We are sitting on a new permutation to try. permIndex(adj) = NPerm4::invS3[permIndex(face)]; // Merge edge links and run corresponding tests. if (mergeEdgeClasses()) { // We created an invalid edge. splitEdgeClasses(); continue; } // Merge vertex links and run corresponding tests. mergeResult = mergeVertexClasses(); if (mergeResult & VLINK_BAD_EULER) { // Our vertex link can never obtain the correct // Euler characteristic. Stop now. splitVertexClasses(); splitEdgeClasses(); continue; } // Fix the orientation if appropriate. if (adj.facet == 0 && orientableOnly_) { // It's the first time we've hit this tetrahedron. if ((permIndex(face) + (face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1)) % 2 == 0) orientation[adj.simp] = -orientation[face.simp]; else orientation[adj.simp] = orientation[face.simp]; } // Move on to the next face. orderElt++; // If we're at the end, try the solution and step back. if (orderElt == orderSize) { // We in fact have an entire triangulation. // Run through the automorphisms and check whether our // permutations are in canonical form. if (isCanonical()) use_(this, useArgs_); // Back to the previous face. orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } else { // Not a full triangulation; just one level deeper. // We've moved onto a new face. // Be sure to get the orientation right. face = order[orderElt]; if (orientableOnly_ && pairing_->dest(face).facet > 0) { // permIndex(face) will be set to -1 or -2 as appropriate. adj = (*pairing_)[face]; if (orientation[face.simp] == orientation[adj.simp]) permIndex(face) = 1; else permIndex(face) = 0; if ((face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1) == 1) permIndex(face) = (permIndex(face) + 1) % 2; permIndex(face) -= 2; } if (orderElt == maxOrder) { // We haven't found an entire triangulation, but we've // gone as far as we need to. // Process it, then step back. use_(this, useArgs_); // Back to the previous face. permIndex(face) = -1; orderElt--; // Pull apart vertex and edge links at the previous level. if (orderElt >= minOrder) { splitVertexClasses(); splitEdgeClasses(); } } } } // And the search is over. // Some extra sanity checking. if (minOrder == 0) { // Our vertex classes had better be 4n standalone vertices. if (nVertexClasses != 4 * nTets) std::cerr << "ERROR: nVertexClasses == " << nVertexClasses << " at end of search!" << std::endl; for (int i = 0; i < static_cast(nTets) * 4; i++) { if (vertexState[i].parent != -1) std::cerr << "ERROR: vertexState[" << i << "].parent == " << vertexState[i].parent << " at end of search!" << std::endl; if (vertexState[i].rank != 0) std::cerr << "ERROR: vertexState[" << i << "].rank == " << vertexState[i].rank << " at end of search!" << std::endl; if (vertexState[i].bdry != 3) std::cerr << "ERROR: vertexState[" << i << "].bdry == " << vertexState[i].bdry << " at end of search!" << std::endl; if (vertexState[i].euler != 2) std::cerr << "ERROR: vertexState[" << i << "].euler == " << vertexState[i].euler << " at end of search!" << std::endl; if (vertexState[i].hadEqualRank) std::cerr << "ERROR: vertexState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; if (vertexState[i].bdryEdges != 3) std::cerr << "ERROR: vertexState[" << i << "].bdryEdges == " << static_cast(vertexState[i].bdryEdges) << " at end of search!" << std::endl; if (vertexState[i].bdryNext[0] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[0] == " << vertexState[i].bdryNext[0] << " at end of search!" << std::endl; if (vertexState[i].bdryNext[1] != i) std::cerr << "ERROR: vertexState[" << i << "].bdryNext[1] == " << vertexState[i].bdryNext[1] << " at end of search!" << std::endl; if (vertexState[i].bdryTwist[0]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; if (vertexState[i].bdryTwist[1]) std::cerr << "ERROR: vertexState[" << i << "].bdryTwist == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (vertexStateChanged[i] != VLINK_JOIN_INIT) std::cerr << "ERROR: vertexStateChanged[" << i << "] == " << vertexStateChanged[i] << " at end of search!" << std::endl; // And our edge classes had better be 6n standalone edges. if (nEdgeClasses != 6 * nTets) std::cerr << "ERROR: nEdgeClasses == " << nEdgeClasses << " at end of search!" << std::endl; for (unsigned i = 0; i < nTets * 6; i++) { if (edgeState[i].parent != -1) std::cerr << "ERROR: edgeState[" << i << "].parent == " << edgeState[i].parent << " at end of search!" << std::endl; if (edgeState[i].rank != 0) std::cerr << "ERROR: edgeState[" << i << "].rank == " << edgeState[i].rank << " at end of search!" << std::endl; if (edgeState[i].size != 1) std::cerr << "ERROR: edgeState[" << i << "].size == " << edgeState[i].size << " at end of search!" << std::endl; if (! edgeState[i].bounded) std::cerr << "ERROR: edgeState[" << i << "].bounded == " "false at end of search!" << std::endl; if (edgeState[i].hadEqualRank) std::cerr << "ERROR: edgeState[" << i << "].hadEqualRank == " "true at end of search!" << std::endl; } for (unsigned i = 0; i < nTets * 8; i++) if (edgeStateChanged[i] != -1) std::cerr << "ERROR: edgeStateChanged[" << i << "] == " << edgeStateChanged[i] << " at end of search!" << std::endl; } use_(0, useArgs_); } void NEulerSearcher::dumpData(std::ostream& out) const { NGluingPermSearcher::dumpData(out); out << euler_ << std::endl; unsigned nTets = getNumberOfTetrahedra(); unsigned i; out << nVertexClasses << std::endl; for (i = 0; i < 4 * nTets; i++) { vertexState[i].dumpData(out); out << std::endl; } for (i = 0; i < 8 * nTets; i++) { if (i) out << ' '; out << vertexStateChanged[i]; } out << std::endl; out << nEdgeClasses << std::endl; for (i = 0; i < 6 * nTets; i++) { edgeState[i].dumpData(out, nTets); out << std::endl; } for (i = 0; i < 8 * nTets; i++) { if (i) out << ' '; out << edgeStateChanged[i]; } out << std::endl; } NEulerSearcher::NEulerSearcher(std::istream& in, UseGluingPerms use, void* useArgs) : NGluingPermSearcher(in, use, useArgs), nVertexClasses(0), vertexState(0), vertexStateChanged(0), nEdgeClasses(0), edgeState(0), edgeStateChanged(0) { if (inputError_) return; in >> euler_; if (euler_ > 2) { inputError_ = true; return; } unsigned nTets = getNumberOfTetrahedra(); unsigned i; in >> nVertexClasses; if (nVertexClasses > 4 * nTets) { inputError_ = true; return; } vertexState = new TetVertexState[4 * nTets]; for (i = 0; i < 4 * nTets; i++) if (! vertexState[i].readData(in, 4 * nTets)) { inputError_ = true; return; } vertexStateChanged = new int[8 * nTets]; for (i = 0; i < 8 * nTets; i++) { in >> vertexStateChanged[i]; if (vertexStateChanged[i] >= 4 * static_cast(nTets)) { inputError_ = true; return; } } in >> nEdgeClasses; if (nEdgeClasses > 6 * nTets) { inputError_ = true; return; } edgeState = new TetEdgeState[6 * nTets]; for (i = 0; i < 6 * nTets; i++) if (! edgeState[i].readData(in, nTets)) { inputError_ = true; return; } edgeStateChanged = new int[8 * nTets]; for (i = 0; i < 8 * nTets; i++) { in >> edgeStateChanged[i]; if (edgeStateChanged[i] < -1 || edgeStateChanged[i] >= 6 * static_cast(nTets)) { inputError_ = true; return; } } // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } int NEulerSearcher::mergeVertexClasses() { // Merge all three vertex pairs for the current face. NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; int retVal = 0; int v, w; int vIdx, wIdx, tmpIdx, nextIdx; unsigned orderIdx; int vRep, wRep; int vNext[2], wNext[2]; char vTwist[2], wTwist[2]; NPerm4 p = gluingPerm(face); char parentTwists, hasTwist, tmpTwist; for (v = 0; v < 4; v++) { if (v == face.facet) continue; w = p[v]; vIdx = v + 4 * face.simp; wIdx = w + 4 * adj.simp; orderIdx = v + 4 * orderElt; // Are the natural 012 representations of the two faces joined // with reversed orientations? // Here we combine the sign of permutation p with the mappings // from 012 to the native tetrahedron vertices, i.e., v <-> 3 and // w <-> 3. hasTwist = (p.sign() < 0 ? 0 : 1); if ((v == 3 && w != 3) || (v != 3 && w == 3)) hasTwist ^= 1; parentTwists = 0; for (vRep = vIdx; vertexState[vRep].parent >= 0; vRep = vertexState[vRep].parent) parentTwists ^= vertexState[vRep].twistUp; for (wRep = wIdx; vertexState[wRep].parent >= 0; wRep = vertexState[wRep].parent) parentTwists ^= vertexState[wRep].twistUp; if (vRep == wRep) { vertexState[vRep].bdry -= 2; // Examine the cycles of boundary components. if (vIdx == wIdx) { // We are folding together two adjacent edges of the // vertex link (possibly with a non-orientable twist). if (hasTwist) { vertexStateChanged[orderIdx] = VLINK_JOIN_TWIST; --vertexState[vRep].euler; } else vertexStateChanged[orderIdx] = VLINK_JOIN_BRIDGE; if (vertexState[vIdx].bdryEdges < 3) { // We are folding together (possibly with a twist) // two edges of a triangle whose third edge is already // joined elsewhere. // Although bdryEdges is 2, we don't bother keeping // a backup in bdryTwistOld[]. This is because // bdryEdges jumps straight from 2 to 0, and the // neighbours in bdryNext[] / bdryTwist[] never get // overwritten. if (vertexState[vIdx].bdryNext[0] == vIdx) { // We are closing off a single boundary of length // two. All good. } else { // Adjust each neighbour to point to the other. vtxBdryJoin(vertexState[vIdx].bdryNext[0], 1 ^ vertexState[vIdx].bdryTwist[0], vertexState[vIdx].bdryNext[1], vertexState[vIdx].bdryTwist[1] ^ vertexState[vIdx].bdryTwist[0]); } } vertexState[vIdx].bdryEdges -= 2; } else { // We are joining two distinct tetrahedron vertices that // already contribute to the same vertex link. if (vertexState[vIdx].bdryEdges == 2) vtxBdryBackup(vIdx); if (vertexState[wIdx].bdryEdges == 2) vtxBdryBackup(wIdx); if (vtxBdryLength1(vIdx)) { if (vtxBdryLength1(wIdx)) { // We are joining together two boundaries of length one. vertexStateChanged[orderIdx] = VLINK_JOIN_HANDLE; vertexState[vRep].euler -= 2; // The join closes off both boundary curves. // No changes to make for the boundary cycles. } else { // We are joining a length one loop at vIdx // with a different, longer boundary curve at wIdx. vertexStateChanged[orderIdx] = VLINK_JOIN_HANDLE; vertexState[vRep].euler -= 2; // Ignore vIdx (which will be closed off), and simply // excise the relevant edge from wIdx. // There is nothing to do here unless wIdx only has one // boundary edge remaining (in which case we know it // joins to some different tetrahedron vertex). if (vertexState[wIdx].bdryEdges == 1) { wNext[0] = vertexState[wIdx].bdryNext[0]; wNext[1] = vertexState[wIdx].bdryNext[1]; wTwist[0] = vertexState[wIdx].bdryTwist[0]; wTwist[1] = vertexState[wIdx].bdryTwist[1]; vtxBdryJoin(wNext[0], 1 ^ wTwist[0], wNext[1], wTwist[0] ^ wTwist[1]); } } } else if (vtxBdryLength1(wIdx)) { // As above, but with the two vertices the other way around. vertexStateChanged[orderIdx] = VLINK_JOIN_HANDLE; vertexState[vRep].euler -= 2; if (vertexState[vIdx].bdryEdges == 1) { vNext[0] = vertexState[vIdx].bdryNext[0]; vNext[1] = vertexState[vIdx].bdryNext[1]; vTwist[0] = vertexState[vIdx].bdryTwist[0]; vTwist[1] = vertexState[vIdx].bdryTwist[1]; vtxBdryJoin(vNext[0], 1 ^ vTwist[0], vNext[1], vTwist[0] ^ vTwist[1]); } } else if (vtxBdryLength2(vIdx, wIdx)) { // We are closing off a single boundary curve of length two. if (hasTwist ^ vertexState[vIdx].bdryTwist[0]) { vertexStateChanged[orderIdx] = VLINK_JOIN_TWIST; --vertexState[vRep].euler; } else vertexStateChanged[orderIdx] = VLINK_JOIN_BRIDGE; } else { vtxBdryNext(vIdx, face.simp, v, face.facet, vNext, vTwist); vtxBdryNext(wIdx, adj.simp, w, adj.facet, wNext, wTwist); if (vNext[0] == wIdx && wNext[1 ^ vTwist[0]] == vIdx) { // We are joining two adjacent edges of the vertex link. // Simply eliminate them. if (hasTwist ^ vTwist[0]) { vertexStateChanged[orderIdx] = VLINK_JOIN_TWIST; --vertexState[vRep].euler; } else vertexStateChanged[orderIdx] = VLINK_JOIN_BRIDGE; vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ vTwist[0]], (vTwist[0] ^ wTwist[0 ^ vTwist[0]]) ^ vTwist[1]); } else if (vNext[1] == wIdx && wNext[0 ^ vTwist[1]] == vIdx) { // Again, joining two adjacent edges of the vertex link. if (hasTwist ^ vTwist[1]) { vertexStateChanged[orderIdx] = VLINK_JOIN_TWIST; --vertexState[vRep].euler; } else vertexStateChanged[orderIdx] = VLINK_JOIN_BRIDGE; vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ vTwist[1]], (vTwist[1] ^ wTwist[1 ^ vTwist[1]]) ^ vTwist[0]); } else { // See if we are joining two different boundary cycles // together; if so, we have created a new handle in // the vertex link. tmpIdx = vertexState[vIdx].bdryNext[0]; tmpTwist = vertexState[vIdx].bdryTwist[0]; while (tmpIdx != vIdx && tmpIdx != wIdx) { nextIdx = vertexState[tmpIdx]. bdryNext[0 ^ tmpTwist]; tmpTwist ^= vertexState[tmpIdx]. bdryTwist[0 ^ tmpTwist]; tmpIdx = nextIdx; } if (tmpIdx == vIdx) { // Different boundary cycles. vertexStateChanged[orderIdx] = VLINK_JOIN_HANDLE; vertexState[vRep].euler -= 2; } else { // Same boundary cycle. if (hasTwist ^ tmpTwist) { vertexStateChanged[orderIdx] = VLINK_JOIN_TWIST; --vertexState[vRep].euler; } else vertexStateChanged[orderIdx] = VLINK_JOIN_BRIDGE; } vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ hasTwist], vTwist[0] ^ (hasTwist ^ wTwist[1 ^ hasTwist])); vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ hasTwist], vTwist[1] ^ (hasTwist ^ wTwist[0 ^ hasTwist])); } } vertexState[vIdx].bdryEdges--; vertexState[wIdx].bdryEdges--; } // See what actually happened to the vertex. if (vertexState[vRep].bdry == 0) { retVal |= VLINK_CLOSED; if (vertexState[vRep].euler != euler_) retVal |= VLINK_BAD_EULER; } else if (vertexState[vRep].euler < euler_) retVal |= VLINK_BAD_EULER; } else { // We are joining two distinct vertices together and merging // their vertex links. if (vertexState[vRep].rank < vertexState[wRep].rank) { // Join vRep beneath wRep. vertexState[vRep].parent = wRep; vertexState[vRep].twistUp = hasTwist ^ parentTwists; vertexState[wRep].bdry = vertexState[wRep].bdry + vertexState[vRep].bdry - 2; vertexState[wRep].euler = vertexState[wRep].euler + vertexState[vRep].euler - 2; if (vertexState[wRep].bdry == 0) { retVal |= VLINK_CLOSED; if (vertexState[wRep].euler != euler_) retVal |= VLINK_BAD_EULER; } else if (vertexState[wRep].euler < euler_) retVal |= VLINK_BAD_EULER; vertexStateChanged[orderIdx] = vRep; } else { // Join wRep beneath vRep. vertexState[wRep].parent = vRep; vertexState[wRep].twistUp = hasTwist ^ parentTwists; if (vertexState[vRep].rank == vertexState[wRep].rank) { vertexState[vRep].rank++; vertexState[wRep].hadEqualRank = true; } vertexState[vRep].bdry = vertexState[vRep].bdry + vertexState[wRep].bdry - 2; vertexState[vRep].euler = vertexState[vRep].euler + vertexState[wRep].euler - 2; if (vertexState[vRep].bdry == 0) { retVal |= VLINK_CLOSED; if (vertexState[vRep].euler != euler_) retVal |= VLINK_BAD_EULER; } else if (vertexState[vRep].euler < euler_) retVal |= VLINK_BAD_EULER; vertexStateChanged[orderIdx] = wRep; } nVertexClasses--; // Adjust the cycles of boundary components. if (vertexState[vIdx].bdryEdges == 2) vtxBdryBackup(vIdx); if (vertexState[wIdx].bdryEdges == 2) vtxBdryBackup(wIdx); if (vtxBdryLength1(vIdx)) { if (vtxBdryLength1(wIdx)) { // Both vIdx and wIdx form entire boundary components of // length one; these are joined together and the vertex // link is closed off. // No changes to make for the boundary cycles. } else { // Here vIdx forms a boundary component of length one, // and wIdx does not. Ignore vIdx, and simply excise the // relevant edge from wIdx. // There is nothing to do here unless wIdx only has one // boundary edge remaining (in which case we know it // joins to some different tetrahedron vertex). if (vertexState[wIdx].bdryEdges == 1) { wNext[0] = vertexState[wIdx].bdryNext[0]; wNext[1] = vertexState[wIdx].bdryNext[1]; wTwist[0] = vertexState[wIdx].bdryTwist[0]; wTwist[1] = vertexState[wIdx].bdryTwist[1]; vtxBdryJoin(wNext[0], 1 ^ wTwist[0], wNext[1], wTwist[0] ^ wTwist[1]); } } } else if (vtxBdryLength1(wIdx)) { // As above, but with the two vertices the other way around. if (vertexState[vIdx].bdryEdges == 1) { vNext[0] = vertexState[vIdx].bdryNext[0]; vNext[1] = vertexState[vIdx].bdryNext[1]; vTwist[0] = vertexState[vIdx].bdryTwist[0]; vTwist[1] = vertexState[vIdx].bdryTwist[1]; vtxBdryJoin(vNext[0], 1 ^ vTwist[0], vNext[1], vTwist[0] ^ vTwist[1]); } } else { // Each vertex belongs to a boundary component of length // at least two. Merge the components together. vtxBdryNext(vIdx, face.simp, v, face.facet, vNext, vTwist); vtxBdryNext(wIdx, adj.simp, w, adj.facet, wNext, wTwist); vtxBdryJoin(vNext[0], 1 ^ vTwist[0], wNext[1 ^ hasTwist], vTwist[0] ^ (hasTwist ^ wTwist[1 ^ hasTwist])); vtxBdryJoin(vNext[1], 0 ^ vTwist[1], wNext[0 ^ hasTwist], vTwist[1] ^ (hasTwist ^ wTwist[0 ^ hasTwist])); } vertexState[vIdx].bdryEdges--; vertexState[wIdx].bdryEdges--; } } return retVal; } void NEulerSearcher::splitVertexClasses() { // Split all three vertex pairs for the current face. NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; int v, w; int vIdx, wIdx; unsigned orderIdx; int rep, subRep; NPerm4 p = gluingPerm(face); // Do everything in reverse. This includes the loop over vertices. for (v = 3; v >= 0; v--) { if (v == face.facet) continue; w = p[v]; vIdx = v + 4 * face.simp; wIdx = w + 4 * adj.simp; orderIdx = v + 4 * orderElt; if (vertexStateChanged[orderIdx] < 0) { for (rep = vIdx; vertexState[rep].parent >= 0; rep = vertexState[rep].parent) ; vertexState[rep].bdry += 2; if (vertexStateChanged[orderIdx] == VLINK_JOIN_HANDLE) vertexState[rep].euler += 2; else if (vertexStateChanged[orderIdx] == VLINK_JOIN_TWIST) ++vertexState[rep].euler; } else { subRep = vertexStateChanged[orderIdx]; rep = vertexState[subRep].parent; vertexState[subRep].parent = -1; if (vertexState[subRep].hadEqualRank) { vertexState[subRep].hadEqualRank = false; vertexState[rep].rank--; } vertexState[rep].bdry = vertexState[rep].bdry + 2 - vertexState[subRep].bdry; vertexState[rep].euler = vertexState[rep].euler + 2 - vertexState[subRep].euler; nVertexClasses++; } vertexStateChanged[orderIdx] = VLINK_JOIN_INIT; // Restore cycles of boundary components. if (vIdx == wIdx) { vertexState[vIdx].bdryEdges += 2; // Adjust neighbours to point back to vIdx if required. if (vertexState[vIdx].bdryEdges == 2) vtxBdryFixAdj(vIdx); } else { vertexState[wIdx].bdryEdges++; vertexState[vIdx].bdryEdges++; switch (vertexState[wIdx].bdryEdges) { case 3: vertexState[wIdx].bdryNext[0] = vertexState[wIdx].bdryNext[1] = wIdx; vertexState[wIdx].bdryTwist[0] = vertexState[wIdx].bdryTwist[1] = 0; break; case 2: vtxBdryRestore(wIdx); // Fall through to the next case, so we can // adjust the neighbours. case 1: // Nothing was changed for wIdx during the merge, // so there is nothing there to restore. // Adjust neighbours to point back to wIdx. vtxBdryFixAdj(wIdx); } switch (vertexState[vIdx].bdryEdges) { case 3: vertexState[vIdx].bdryNext[0] = vertexState[vIdx].bdryNext[1] = vIdx; vertexState[vIdx].bdryTwist[0] = vertexState[vIdx].bdryTwist[1] = 0; break; case 2: vtxBdryRestore(vIdx); // Fall through to the next case, so we can // adjust the neighbours. case 1: // Nothing was changed for vIdx during the merge, // so there is nothing there to restore. // Adjust neighbours to point back to vIdx. vtxBdryFixAdj(vIdx); } } } } bool NEulerSearcher::mergeEdgeClasses() { NTetFace face = order[orderElt]; NTetFace adj = (*pairing_)[face]; bool retVal = false; NPerm4 p = gluingPerm(face); int v1, w1, v2, w2; int e, f; int orderIdx; int eRep, fRep; v1 = face.facet; w1 = p[v1]; char parentTwists, hasTwist; for (v2 = 0; v2 < 4; v2++) { if (v2 == v1) continue; w2 = p[v2]; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; f = 5 - NEdge::edgeNumber[w1][w2]; orderIdx = v2 + 4 * orderElt; // We declare the natural orientation of an edge to be smaller // vertex to larger vertex. hasTwist = (p[NEdge::edgeVertex[e][0]] > p[NEdge::edgeVertex[e][1]] ? 1 : 0); parentTwists = 0; eRep = findEdgeClass(e + 6 * face.simp, parentTwists); fRep = findEdgeClass(f + 6 * adj.simp, parentTwists); if (eRep == fRep) { edgeState[eRep].bounded = false; if (hasTwist ^ parentTwists) retVal = true; edgeStateChanged[orderIdx] = -1; } else { if (edgeState[eRep].rank < edgeState[fRep].rank) { // Join eRep beneath fRep. edgeState[eRep].parent = fRep; edgeState[eRep].twistUp = hasTwist ^ parentTwists; edgeState[fRep].size += edgeState[eRep].size; edgeStateChanged[orderIdx] = eRep; } else { // Join fRep beneath eRep. edgeState[fRep].parent = eRep; edgeState[fRep].twistUp = hasTwist ^ parentTwists; if (edgeState[eRep].rank == edgeState[fRep].rank) { edgeState[eRep].rank++; edgeState[fRep].hadEqualRank = true; } edgeState[eRep].size += edgeState[fRep].size; edgeStateChanged[orderIdx] = fRep; } nEdgeClasses--; } } return retVal; } void NEulerSearcher::splitEdgeClasses() { NTetFace face = order[orderElt]; int v1, v2; int e; int eIdx, orderIdx; int rep, subRep; v1 = face.facet; for (v2 = 3; v2 >= 0; v2--) { if (v2 == v1) continue; // Look at the edge opposite v1-v2. e = 5 - NEdge::edgeNumber[v1][v2]; eIdx = e + 6 * face.simp; orderIdx = v2 + 4 * orderElt; if (edgeStateChanged[orderIdx] < 0) edgeState[findEdgeClass(eIdx)].bounded = true; else { subRep = edgeStateChanged[orderIdx]; rep = edgeState[subRep].parent; edgeState[subRep].parent = -1; if (edgeState[subRep].hadEqualRank) { edgeState[subRep].hadEqualRank = false; edgeState[rep].rank--; } edgeState[rep].size -= edgeState[subRep].size; edgeStateChanged[orderIdx] = -1; nEdgeClasses++; } } } void NEulerSearcher::vtxBdryConsistencyCheck() { int adj, id, end; for (id = 0; id < static_cast(getNumberOfTetrahedra()) * 4; id++) if (vertexState[id].bdryEdges > 0) for (end = 0; end < 2; end++) { adj = vertexState[id].bdryNext[end]; if (vertexState[adj].bdryEdges == 0) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " runs into an internal vertex." << std::endl; if (vertexState[adj].bdryNext[(1 ^ end) ^ vertexState[id].bdryTwist[end]] != id) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " has a mismatched adjacency." << std::endl; if (vertexState[adj].bdryTwist[(1 ^ end) ^ vertexState[id].bdryTwist[end]] != vertexState[id].bdryTwist[end]) std::cerr << "CONSISTENCY ERROR: Vertex link boundary " << id << '/' << end << " has a mismatched twist." << std::endl; } } void NEulerSearcher::vtxBdryDump(std::ostream& out) { for (unsigned id = 0; id < getNumberOfTetrahedra() * 4; id++) { if (id > 0) out << ' '; out << vertexState[id].bdryNext[0] << (vertexState[id].bdryTwist[0] ? '~' : '-') << id << (vertexState[id].bdryTwist[1] ? '~' : '-') << vertexState[id].bdryNext[1] << " [" << int(vertexState[id].bdryEdges) << ']'; } out << std::endl; } } // namespace regina regina-4.95/engine/census/ncensus.cpp000644 000765 000024 00000016711 12234011536 017541 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "census/ncensus.h" #include "census/ngluingpermsearcher.h" #include "triangulation/ntriangulation.h" #include "utilities/memutils.h" namespace regina { const int NCensus::PURGE_NON_MINIMAL = 1; const int NCensus::PURGE_NON_PRIME = 2; const int NCensus::PURGE_NON_MINIMAL_PRIME = 3; /**< PURGE_NON_MINIMAL_PRIME = PURGE_NON_MINIMAL | PURGE_NON_PRIME */ const int NCensus::PURGE_P2_REDUCIBLE = 4; unsigned long NCensus::formCensus(NPacket* parent, unsigned nTetrahedra, NBoolSet finiteness, NBoolSet orientability, NBoolSet boundary, int nBdryFaces, int whichPurge, AcceptTriangulation sieve, void* sieveArgs) { // If obviously nothing is going to happen but we won't realise // it until we've actually generated the face pairings, change // nTetrahedra to 0 so we'll realise it immediately once the new // thread starts. if (finiteness == NBoolSet::sNone || orientability == NBoolSet::sNone) nTetrahedra = 0; // Start the census! NCensus* census = new NCensus(parent, finiteness, orientability, whichPurge, sieve, sieveArgs); NFacePairing::findAllPairings(nTetrahedra, boundary, nBdryFaces, NCensus::foundFacePairing, census, false /* separate thread */); unsigned long ans = census->whichSoln - 1; delete census; return ans; } unsigned long NCensus::formPartialCensus(const NFacePairing* pairing, NPacket* parent, NBoolSet finiteness, NBoolSet orientability, int whichPurge, AcceptTriangulation sieve, void* sieveArgs) { // Is it obvious that nothing will happen? if (finiteness == NBoolSet::sNone || orientability == NBoolSet::sNone) return 0; // Make a list of automorphisms. NFacePairing::IsoList autos; pairing->findAutomorphisms(autos); // Select the individual gluing permutations. NCensus census(parent, finiteness, orientability, whichPurge, sieve, sieveArgs); NGluingPermSearcher::findAllPerms(pairing, &autos, ! census.orientability.hasFalse(), ! census.finiteness.hasFalse(), census.whichPurge, NCensus::foundGluingPerms, &census); // Clean up. std::for_each(autos.begin(), autos.end(), FuncDelete()); return census.whichSoln - 1; } NCensus::NCensus(NPacket* newParent, const NBoolSet& newFiniteness, const NBoolSet& newOrientability, int newWhichPurge, AcceptTriangulation newSieve, void* newSieveArgs) : parent(newParent), finiteness(newFiniteness), orientability(newOrientability), whichPurge(newWhichPurge), sieve(newSieve), sieveArgs(newSieveArgs), whichSoln(1) { } void NCensus::foundFacePairing(const NFacePairing* pairing, const NFacePairing::IsoList* autos, void* census) { NCensus* realCensus = static_cast(census); if (pairing) { // We've found another face pairing. // Select the individual gluing permutations. NGluingPermSearcher::findAllPerms(pairing, autos, ! realCensus->orientability.hasFalse(), ! realCensus->finiteness.hasFalse(), realCensus->whichPurge, NCensus::foundGluingPerms, census); } else { // Census generation has finished. } } void NCensus::foundGluingPerms(const NGluingPermSearcher* perms, void* census) { if (perms) { // We've found another permutation set. // Triangulate and see what we've got. NTriangulation* tri = perms->triangulate(); NCensus* realCensus = static_cast(census); bool ok = true; if (! tri->isValid()) ok = false; else if ((! realCensus->finiteness.hasFalse()) && tri->isIdeal()) ok = false; else if ((! realCensus->finiteness.hasTrue()) && (! tri->isIdeal())) ok = false; else if ((! realCensus->orientability.hasTrue()) && tri->isOrientable()) ok = false; else if (realCensus->sieve && ! realCensus->sieve(tri, realCensus->sieveArgs)) ok = false; if (ok) { // Put it in the census! // Make sure it has a charming label. // Don't insist on unique labels, since this requirement // will soon be dropped and it multiplies the running time // by a factor of #triangulations. std::ostringstream out; out << "Item " << realCensus->whichSoln; tri->setPacketLabel(out.str()); realCensus->parent->insertChildLast(tri); realCensus->whichSoln++; } else { // Bad triangulation. delete tri; } } } bool NCensus::mightBeMinimal(NTriangulation* tri, void*) { if (! tri->hasBoundaryTriangles()) { // No boundary faces. // Tests specific to closed finite orientable triangulations: if (tri->isOrientable() && (! tri->isIdeal())) { // Check for too many vertices. if (tri->getNumberOfVertices() > 1 && tri->getNumberOfTetrahedra() > 2) return false; } // Check for obvious simplifications. if (tri->simplifyToLocalMinimum(false)) return false; } return true; } } // namespace regina regina-4.95/engine/census/ncensus.h000644 000765 000024 00000050611 12234011536 017203 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ncensus.h * \brief Deals with forming a census of all 3-manifold triangulations * of a given size. */ #ifndef __NCENSUS_H #ifndef __DOXYGEN #define __NCENSUS_H #endif #include "regina-core.h" #include "census/nfacepairing.h" #include "utilities/nbooleans.h" namespace regina { class NGluingPerms; class NGluingPermSearcher; class NPacket; class NTriangulation; /** * \addtogroup census Census of Triangulations * Census building for triangulations of various dimensions. * @{ */ /** * A legacy typedef that is identical to NCensus::AcceptTriangulation. * See NCensus::AcceptTriangulation for further details. * * \deprecated This global typedef is now deprecated. Please use the * identical class typedef NCensus::AcceptTriangulation instead. */ typedef bool (*AcceptTriangulation)(NTriangulation*, void*); /** * A utility class used to form a complete census of 3-manifold * triangulations satisfying certain constraints. * * \testpart */ class REGINA_API NCensus { public: static const int PURGE_NON_MINIMAL; /**< Indicates that non-minimal triangulations may be ignored. */ static const int PURGE_NON_PRIME; /**< Indicates that any triangulation that is not prime (i.e., can be written as a non-trivial connected sum) and any bounded triangulation that is reducible over a disc may be ignored. */ static const int PURGE_NON_MINIMAL_PRIME; /**< Indicates that any triangulation that is not prime (i.e., can be written as a non-trivial connected sum), any bounded triangulation that is reducible over a disc and any triangulation that is non-minimal may be ignored. Note that this is simply a combination of the constants \a PURGE_NON_MINIMAL and \a PURGE_NON_PRIME. */ static const int PURGE_P2_REDUCIBLE; /**< Indicates that any triangulation containing an embedded two-sided projective plane may be ignored. */ /** * A routine used to determine whether a particular triangulation * should be included in a census. Routines of this type are used by * NCensus::formCensus(). * * The first parameter passed should be a triangulation currently * under consideration. * The second parameter may contain arbitrary data as passed to * NCensus::formCensus(). * * The return value should be \c true if the triangulation passed * should be included in the census, or \c false otherwise. */ typedef bool (*AcceptTriangulation)(NTriangulation*, void*); private: NPacket* parent; /**< The argument passed to formCensus(). */ NBoolSet finiteness; /**< The argument passed to formCensus(). */ NBoolSet orientability; /**< The argument passed to formCensus(). */ int whichPurge; /**< The argument passed to formCensus(). */ AcceptTriangulation sieve; /**< The arbitrary constraint function to run triangulations through. */ void* sieveArgs; /**< The second argument to pass to function \a sieve. */ unsigned long whichSoln; /**< The number of the solution we are up to. */ public: /** * Fills the given packet with all triangulations in a census of * 3-manifold triangulations satisfying the given constraints. * Each triangulation in the census will appear as a child of the * given packet. * * This routine will conduct a census of all valid triangulations * containing a given number of tetrahedra. All such triangulations * are included in the census up to combinatorial isomorphism; given * any isomorphism class, exactly one representative will appear in * the census. * * The census can be optionally restricted to only include * triangulations satisfying further constraints (such as * orientability and finiteness); see the individual parameter * descriptions for further details. In particular, parameter * \a sieve can be used to impose arbitrary restrictions that are * not hard-coded into this class. * * Note that if constraints may be imposed using the hard-coded * parameters (such as orientability and finiteness), it is * generally better to do this than to use the arbitrary * constraint parameter \a sieve. Hard-coded parameters will be * tested earlier, and some (such as orientability) can be * incorporated directly into the census algorithm to give a vast * performance increase. * * Parameter \a whichPurge may be used to further avoid constructing * triangulations satisfying particular constraints (such as * non-minimality). This can significantly speed up the census. * In this case however not all such triangulations will be * avoided, but it is guaranteed that every triangulation that * does \e not satisfy the constraints defined by \a whichPurge * will be produced. * * Only valid triangulations will be produced; see * NTriangulation::isValid() for further details. * * Note that this routine should only be used if the census * contains a small enough total number of triangulations to * avoid any memory disasters. * * \ifacespython Parameters \a sieve and \a sieveArgs * are not present (and will be treated as 0). * * @param parent the packet beneath which members of the census will * be placed. * @param nTetrahedra the number of tetrahedra in each triangulation * in the census. * @param finiteness determines whether to include finite and/or ideal * triangulations. The set should contain \c true if finite (non-ideal) * triangulations are to be included, and should contain \c false if * ideal triangulations are to be included. * @param orientability determines whether to include orientable * and/or non-orientable triangulations. The set should contain \c true * if orientable triangulations are to be included, and should contain * \c false if non-orientable triangulations are to be included. * @param boundary determines whether to include triangulations with * and/or without boundary triangles. The set should contain \c true * if triangulations with boundary triangles are to be included, and * should contain \c false if triangulations with only internal * triangles are to be included. * @param nBdryTris specifies the precise number of boundary triangles * that should be present in the triangulations produced. If this * parameter is negative, it is ignored and no additional restriction * is imposed. See the documentation for routine * NFacePairing::findAllPairings() for details regarding this * parameter and how it interacts with parameter \a boundary. * @param whichPurge specifies which triangulations we may further * avoid constructing (see the function notes above for details). * This should be a bitwise OR of purge constants defined in this * class, or 0 if no additional pruning should take place. * If a variety of purge constants are bitwise ORed together, a * triangulation satisfying \e any of these constraints may be * avoided. Note that not all such triangulations will be * avoided, but enough are avoided that the performance increase * is noticeable. * @param sieve an additional constraint function that may be * used to exclude certain triangulations from the census. If * this parameter is non-zero, each triangulation produced (after * passing all other criteria) will be passed through this * function. If this function returns \c true then the triangulation * will be included in the census; otherwise it will not. * When this function is called, the first (triangulation) * argument will be a triangulation under consideration for * inclusion in the census. The second argument will be * parameter \a sieveArgs as passed to formCensus(). * Parameter \a sieve may be passed as \c null (in which case no * additional constraint function will be used). * @param sieveArgs the pointer to pass as the final parameter * for the function \a sieve which will be called upon each * triangulation found. If \a sieve is \c null then \a sieveArgs * will be ignored. * @return the number of triangulations produced in the census. */ static unsigned long formCensus(NPacket* parent, unsigned nTetrahedra, NBoolSet finiteness, NBoolSet orientability, NBoolSet boundary, int nBdryTris, int whichPurge, AcceptTriangulation sieve = 0, void* sieveArgs = 0); /** * Fills the given packet with all triangulations in a partial census * of 3-manifold triangulations satisfying the given constraints. * Each triangulation in the partial census will appear as a child of * the given packet. * * This routine will conduct a census of all valid triangulations * that are modelled by the given tetrahedron face pairing. * All such triangulations are included in the census up to * combinatorial isomorphism; given any isomorphism class, exactly * one representative will appear in the census. * * The census can be optionally restricted to only include * triangulations satisfying further constraints (such as * orientability and finiteness); see the individual parameter * descriptions for further details. In particular, parameter * \a sieve can be used to impose arbitrary restrictions that are * not hard-coded into this class. * * Note that if constraints may be imposed using the hard-coded * parameters (such as orientability and finiteness), it is * generally better to do this than to use the arbitrary * constraint parameter \a sieve. Hard-coded parameters will be * tested earlier, and some (such as orientability) can be * incorporated directly into the census algorithm to give a vast * performance increase. * * Parameter \a whichPurge may be used to further avoid constructing * triangulations satisfying particular constraints (such as * non-minimality). The use of this parameter, combined with * parameters \a finiteness and \a orientability, can significantly * speed up the census. For some combinations of these * parameters entirely different algorithms are used. * * Note however that not all triangulations described by * parameter \a whichPurge will be avoided. It is guaranteed * however that every triangulation that does \e not satisfy the * constraints defined by \a whichPurge will be produced. * * Only valid triangulations will be produced; see * NTriangulation::isValid() for further details. * * Note that this routine should only be used if the partial census * contains a small enough total number of triangulations to * avoid any memory disasters. * * The partial census will run in the current thread. This * routine will only return once the partial census is complete. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * * \ifacespython Parameters \a sieve and \a sieveArgs * are not present (and will be treated as 0). * * @param pairing the tetrahedron face pairing that * triangulations in this partial census must be modelled by. * @param parent the packet beneath which members of the partial * census will be placed. * @param finiteness determines whether to include finite and/or ideal * triangulations. The set should contain \c true if finite (non-ideal) * triangulations are to be included, and should contain \c false if * ideal triangulations are to be included. * @param orientability determines whether to include orientable * and/or non-orientable triangulations. The set should contain \c true * if orientable triangulations are to be included, and should contain * \c false if non-orientable triangulations are to be included. * @param whichPurge specifies which triangulations we may further * avoid constructing (see the function notes above for details). * This should be a bitwise OR of purge constants defined in this * class, or 0 if no additional pruning should take place. * If a variety of purge constants are bitwise ORed together, a * triangulation satisfying \e any of these constraints may be * avoided. Note that not all such triangulations will be * avoided, but enough are avoided that the performance increase * is noticeable. * @param sieve an additional constraint function that may be * used to exclude certain triangulations from the census. If * this parameter is non-zero, each triangulation produced (after * passing all other criteria) will be passed through this * function. If this function returns \c true then the triangulation * will be included in the census; otherwise it will not. * When this function is called, the first (triangulation) * argument will be a triangulation under consideration for * inclusion in the census. The second argument will be * parameter \a sieveArgs as passed to formPartialCensus(). * Parameter \a sieve may be passed as \c null (in which case no * additional constraint function will be used). * @param sieveArgs the pointer to pass as the final parameter * for the function \a sieve which will be called upon each * triangulation found. If \a sieve is \c null then \a sieveArgs * will be ignored. * @return the number of triangulations produced in the partial census. */ static unsigned long formPartialCensus(const NFacePairing* pairing, NPacket* parent, NBoolSet finiteness, NBoolSet orientability, int whichPurge, AcceptTriangulation sieve = 0, void* sieveArgs = 0); /** * Determines whether the given triangulation even has a chance * at being minimal. This routine can be passed as parameter * \a sieve to routine NCensus::formCensus() to exclude obviously * non-minimal triangulations from a census. * * A variety of tests will be performed; these tests are subject * to change between Regina releases. Currently this routine * counts vertices and also tries to simplify the triangulation using * NTriangulation::simplifyToLocalMinimum(). * * Currently this routine is only useful for triangulations whose * triangles are all internal; if the given triangulation has * boundary triangles then this routine will simply return \c true. * * \ifacespython Parameter \a ignore is not present. * * @param tri the triangulation to examine. * @param ignore a parameter that is ignored. * @return \c false if the given triangulation is known to be * non-minimal, or \c true if minimality of the given * triangulation has not been determined. */ static bool mightBeMinimal(NTriangulation* tri, void* ignore = 0); private: /** * Creates a new structure to hold the given information. * All parameters not explained are taken directly from * formCensus(). */ NCensus(NPacket* newParent, const NBoolSet& newFiniteness, const NBoolSet& newOrientability, int newWhichPurge, AcceptTriangulation newSieve, void* newSieveArgs); /** * Called when a particular tetrahedron face pairing has been * found. This routine hooks up the face pairing generation with * the gluing permutation generation. * * @param pairing the face pairing that has been found. * @param autos the set of all automorphisms of the given face * pairing. * @param census the census currently being generated; * this must really be of class NCensus. */ static void foundFacePairing(const NFacePairing* pairing, const NFacePairing::IsoList* autos, void* census); /** * Called when a particular set of gluing permutations has been * found. This routine generates the corresponding triangulation * and decides whether it really belongs in the census. * * \pre The given set of gluing permutations is complete, i.e., * it is not just a partially-complete search state. Partial * searches are formed by calling NGluingPermSearcher::runSearch() * with a non-negative depth argument. * * @param perms the gluing permutation set that has been found. * @param census the census currently being generated; * this must really be of class NCensus. */ static void foundGluingPerms(const NGluingPermSearcher* perms, void* census); }; /*@}*/ } // namespace regina #endif regina-4.95/engine/census/nfacepairing.cpp000644 000765 000024 00000065442 12234011536 020516 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "census/nfacepairing.h" #include "census/ngenericfacetpairing-impl.h" #include "triangulation/nfacepair.h" #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangulation.h" #include "utilities/memutils.h" #include "utilities/stringutils.h" namespace regina { // Instantiate all templates from the -impl.h file. template NGenericFacetPairing<3>::NGenericFacetPairing( const NGenericFacetPairing<3>&); template NGenericFacetPairing<3>::NGenericFacetPairing(const NTriangulation&); template bool NGenericFacetPairing<3>::isClosed() const; template std::string NGenericFacetPairing<3>::str() const; template std::string NGenericFacetPairing<3>::dotHeader(const char*); template void NGenericFacetPairing<3>::writeDotHeader(std::ostream&, const char*); template std::string NGenericFacetPairing<3>::dot(const char*, bool, bool) const; template void NGenericFacetPairing<3>::writeDot(std::ostream&, const char*, bool, bool) const; template std::string NGenericFacetPairing<3>::toTextRep() const; template NFacePairing* NGenericFacetPairing<3>::fromTextRep(const std::string&); template bool NGenericFacetPairing<3>::isCanonical() const; template bool NGenericFacetPairing<3>::isCanonicalInternal( NGenericFacetPairing<3>::IsoList&) const; template void NGenericFacetPairing<3>::findAutomorphisms( NGenericFacetPairing<3>::IsoList&) const; template bool NGenericFacetPairing<3>::findAllPairings(unsigned, NBoolSet, int, NGenericFacetPairing<3>::Use, void*, bool); template void* NGenericFacetPairing<3>::run(void*); bool NFacePairing::hasTripleEdge() const { unsigned equal, i, j; for (unsigned tet = 0; tet < size_; tet++) { // Is there a triple edge coming from this tetrahedron? equal = 0; for (i = 0; i < 4; i++) if ((! isUnmatched(tet, i)) && dest(tet, i).simp > static_cast(tet)) { // This face joins to a real face of a later tetrahedron. for (j = i + 1; j < 4; j++) if (dest(tet, i).simp == dest(tet, j).simp) equal++; } // Did we find at least three pairs (i,j) joining to the same // real later tetrahedron? A little case analysis shows that the // only way we can achieve this is through a triple edge. if (equal >= 3) return true; } return false; } void NFacePairing::followChain(unsigned& tet, NFacePair& faces) const { NTetFace dest1, dest2; while (true) { // Does the first face lead to a real tetrahedron? if (isUnmatched(tet, faces.lower())) return; // Does the second face lead to the same tetrahedron as the first? dest1 = dest(tet, faces.lower()); dest2 = dest(tet, faces.upper()); if (dest1.simp != dest2.simp) return; // Do the two faces lead to a *different* tetrahedron? if (dest1.simp == static_cast(tet)) return; // Follow the chain along. tet = dest1.simp; faces = NFacePair(dest1.facet, dest2.facet).complement(); } } bool NFacePairing::hasBrokenDoubleEndedChain() const { // Search for the end edge of the first chain. unsigned baseTet; unsigned baseFace; // Skip the last tetrahedron -- any of the two ends will do. for (baseTet = 0; baseTet + 1 < size_; baseTet++) for (baseFace = 0; baseFace < 3; baseFace++) if (dest(baseTet, baseFace).simp == static_cast(baseTet)) { // Here's a face that matches to the same tetrahedron. if (hasBrokenDoubleEndedChain(baseTet, baseFace)) return true; // There's no sense in looking for more // self-identifications in this tetrahedron, since if // there's another (different) one it must be a // one-tetrahedron component (and so not applicable). break; } // Nothing found. Boring. return false; } bool NFacePairing::hasBrokenDoubleEndedChain(unsigned baseTet, unsigned baseFace) const { // Follow the chain along and see how far we get. NFacePair bdryFaces = NFacePair(baseFace, dest(baseTet, baseFace).facet).complement(); unsigned bdryTet = baseTet; followChain(bdryTet, bdryFaces); // Here's where we must diverge and move into the second chain. // We cannot glue the working pair of faces to each other. if (dest(bdryTet, bdryFaces.lower()).simp == static_cast(bdryTet)) return false; // Try each possible direction away from the working faces into the // second chain. NFacePair chainFaces; unsigned chainTet; NTetFace destFace; unsigned ignoreFace; int i; for (i = 0; i < 2; i++) { destFace = dest(bdryTet, i == 0 ? bdryFaces.lower() : bdryFaces.upper()); if (destFace.isBoundary(size_)) continue; for (ignoreFace = 0; ignoreFace < 4; ignoreFace++) { if (destFace.facet == static_cast(ignoreFace)) continue; // Try to follow the chain along from tetrahedron // destFace.simp, using the two faces that are *not* // destFace.facet or ignoreFace. chainTet = destFace.simp; chainFaces = NFacePair(destFace.facet, ignoreFace).complement(); followChain(chainTet, chainFaces); // Did we reach an end edge of the second chain? if (dest(chainTet, chainFaces.lower()).simp == static_cast(chainTet)) return true; } } // Nup. Nothing found. return false; } bool NFacePairing::hasOneEndedChainWithDoubleHandle() const { // Search for the end edge of the chain. unsigned baseTet; unsigned baseFace; for (baseTet = 0; baseTet < size_; baseTet++) for (baseFace = 0; baseFace < 3; baseFace++) if (dest(baseTet, baseFace).simp == static_cast(baseTet)) { // Here's a face that matches to the same tetrahedron. if (hasOneEndedChainWithDoubleHandle(baseTet, baseFace)) return true; // There's no sense in looking for more // self-identifications in this tetrahedron, since if // there's another (different) one it must be a // one-tetrahedron component (and so not applicable). break; } // Nothing found. Boring. return false; } bool NFacePairing::hasOneEndedChainWithDoubleHandle(unsigned baseTet, unsigned baseFace) const { // Follow the chain along and see how far we get. NFacePair bdryFaces = NFacePair(baseFace, dest(baseTet, baseFace).facet).complement(); unsigned bdryTet = baseTet; followChain(bdryTet, bdryFaces); // Here's where we must diverge and create the double handle. NTetFace dest1 = dest(bdryTet, bdryFaces.lower()); NTetFace dest2 = dest(bdryTet, bdryFaces.upper()); // These two faces must be joined to two distinct tetrahedra. if (dest1.simp == dest2.simp) return false; // They also cannot be boundary. if (dest1.isBoundary(size_) || dest2.isBoundary(size_)) return false; // Since they're joined to two distinct tetrahedra, they cannot be // joined to each other. So we can start hunting for the double handle. int handle = 0; for (int i = 0; i < 4; i++) if (dest(dest1.simp, i).simp == dest2.simp) handle++; // Did we find our double handle? return (handle >= 2); } bool NFacePairing::hasWedgedDoubleEndedChain() const { // Search for the end edge of the first chain. unsigned baseTet; unsigned baseFace; // Skip the last tetrahedron -- any of the two ends will do. for (baseTet = 0; baseTet + 1 < size_; baseTet++) for (baseFace = 0; baseFace < 3; baseFace++) if (dest(baseTet, baseFace).simp == static_cast(baseTet)) { // Here's a face that matches to the same tetrahedron. if (hasWedgedDoubleEndedChain(baseTet, baseFace)) return true; // There's no sense in looking for more // self-identifications in this tetrahedron, since if // there's another (different) one it must be a // one-tetrahedron component (and so not applicable). break; } // Nothing found. Boring. return false; } bool NFacePairing::hasWedgedDoubleEndedChain(unsigned baseTet, unsigned baseFace) const { // Follow the chain along and see how far we get. NFacePair bdryFaces = NFacePair(baseFace, dest(baseTet, baseFace).facet).complement(); unsigned bdryTet = baseTet; followChain(bdryTet, bdryFaces); // Here we expect to find the wedge. NTetFace dest1 = dest(bdryTet, bdryFaces.lower()); NTetFace dest2 = dest(bdryTet, bdryFaces.upper()); if (dest1.isBoundary(size_) || dest2.isBoundary(size_) || dest1.simp == dest2.simp) return false; // We are joined to two new and distinct graph vertices. // Hunt for the edge joining them, and also see where they follow // through to beyond these two new vertices. // Drawing a diagram whilst reading this code will certainly help. :) NTetFace throughFace[2][3]; int nThroughFaces[2]; nThroughFaces[0] = nThroughFaces[1] = 0; int i, j; NTetFace nextDest; bool foundCrossEdge = false; for (i = 0; i < 4; i++) { if (i != dest1.facet) { nextDest = dest(dest1.simp, i); if (nextDest.simp == dest2.simp) foundCrossEdge = true; else if (nextDest.simp != dest1.simp && ! nextDest.isBoundary(size_)) throughFace[0][nThroughFaces[0]++] = nextDest; } if (i != dest2.facet) { nextDest = dest(dest2.simp, i); if (nextDest.simp != dest1.simp && nextDest.simp != dest2.simp && ! nextDest.isBoundary(size_)) throughFace[1][nThroughFaces[1]++] = nextDest; } } if (! foundCrossEdge) return false; // We have our cross edge. // Moreover, all of the faces in throughFace[] belong to previously // unseen tetrahedra. // Hunt for the other half of the double-ended chain. NFacePair chainFaces; unsigned chainTet; for (i = 0; i < nThroughFaces[0]; i++) for (j = 0; j < nThroughFaces[1]; j++) if (throughFace[0][i].simp == throughFace[1][j].simp) { // Bingo. // Follow the chain and see if it ends in a loop. chainTet = throughFace[0][i].simp; chainFaces = NFacePair(throughFace[0][i].facet, throughFace[1][j].facet).complement(); followChain(chainTet, chainFaces); if (dest(chainTet, chainFaces.lower()).simp == static_cast(chainTet)) return true; } // Nothing found. return false; } bool NFacePairing::hasOneEndedChainWithStrayBigon() const { // Search for the end edge of the chain. unsigned baseTet; unsigned baseFace; for (baseTet = 0; baseTet < size_; baseTet++) for (baseFace = 0; baseFace < 3; baseFace++) if (dest(baseTet, baseFace).simp == static_cast(baseTet)) { // Here's a face that matches to the same tetrahedron. if (hasOneEndedChainWithStrayBigon(baseTet, baseFace)) return true; // There's no sense in looking for more // self-identifications in this tetrahedron, since if // there's another (different) one it must be a // one-tetrahedron component (and so not applicable). break; } // Nothing found. Boring. return false; } bool NFacePairing::hasOneEndedChainWithStrayBigon(unsigned baseTet, unsigned baseFace) const { // Follow the chain along and see how far we get. NFacePair bdryFaces = NFacePair(baseFace, dest(baseTet, baseFace).facet).complement(); unsigned bdryTet = baseTet; followChain(bdryTet, bdryFaces); // Here's where we must diverge and create the stray bigon. // We cannot glue the working pair of faces to each other. if (dest(bdryTet, bdryFaces.lower()).simp == static_cast(bdryTet)) return false; // Try each possible direction away from the working faces into the bigon. NFacePair bigonFaces; int bigonTet, farTet, extraTet; NTetFace destFace; unsigned ignoreFace; int i; for (i = 0; i < 2; i++) { destFace = dest(bdryTet, i == 0 ? bdryFaces.lower() : bdryFaces.upper()); if (destFace.isBoundary(size_)) continue; bigonTet = destFace.simp; for (ignoreFace = 0; ignoreFace < 4; ignoreFace++) { if (destFace.facet == static_cast(ignoreFace)) continue; // Look for a bigon running away from tetrahedron // destFace.simp, using the two faces that are *not* // destFace.facet or ignoreFace. bigonFaces = NFacePair(destFace.facet, ignoreFace).complement(); farTet = dest(bigonTet, bigonFaces.upper()).simp; if (farTet != bigonTet && farTet < static_cast(size_) /* non-bdry */ && farTet == dest(bigonTet, bigonFaces.lower()).simp) { // We have the bigon! // We know that bdryTet != bigonTet != farTet, and we // can prove that bdryTet != farTet using 4-valency. // Ensure that we don't have one of our special exceptions. extraTet = dest(bdryTet, i == 0 ? bdryFaces.upper() : bdryFaces.lower()).simp; // We know extraTet != bigonTet, since otherwise our // one-ended chain would not have stopped when it did. // We also know extraTet != bdryTet by 4-valency. if (extraTet == farTet || extraTet >= static_cast(size_) /* bdry */) return true; if (extraTet == dest(bigonTet, ignoreFace).simp) { // Could be the special case where extraTet joins to // all of bdryTet, bigonTet and farTet. // We already have it joined to bdryTet and bigonTet. // Check farTet. if (extraTet != dest(farTet, 0).simp && extraTet != dest(farTet, 1).simp && extraTet != dest(farTet, 2).simp && extraTet != dest(farTet, 3).simp) return true; } else { // Could be the special case where extraTet joins // twice to farTet. If not, we have the type of // graph we're looking for. bigonFaces = NFacePair( dest(bigonTet, bigonFaces.upper()).facet, dest(bigonTet, bigonFaces.lower()).facet).complement(); if (extraTet != dest(farTet, bigonFaces.upper()).simp || extraTet != dest(farTet, bigonFaces.lower()).simp) return true; } } } } // Nup. Nothing found. return false; } bool NFacePairing::hasTripleOneEndedChain() const { // Search for the end edge of the first chain. unsigned baseTet; unsigned baseFace; // Skip the last two tetrahedra -- any of the three chains will do. for (baseTet = 0; baseTet + 2 < size_; baseTet++) for (baseFace = 0; baseFace < 3; baseFace++) if (dest(baseTet, baseFace).simp == static_cast(baseTet)) { // Here's a face that matches to the same tetrahedron. if (hasTripleOneEndedChain(baseTet, baseFace)) return true; // There's no sense in looking for more // self-identifications in this tetrahedron, since if // there's another (different) one it must be a // one-tetrahedron component (and so not applicable). break; } // Nothing found. Boring. return false; } bool NFacePairing::hasTripleOneEndedChain(unsigned baseTet, unsigned baseFace) const { // Follow the chain along and see how far we get. NFacePair bdryFaces = NFacePair(baseFace, dest(baseTet, baseFace).facet).complement(); unsigned bdryTet = baseTet; followChain(bdryTet, bdryFaces); // Here's where we must diverge and hunt for the other two chains. // We cannot glue the working pair of faces to each other. if (dest(bdryTet, bdryFaces.lower()).simp == static_cast(bdryTet)) return false; NTetFace axis1 = dest(bdryTet, bdryFaces.lower()); NTetFace axis2 = dest(bdryTet, bdryFaces.upper()); if (axis1.isBoundary(size_) || axis2.isBoundary(size_)) return false; // We know axis1.simp != axis2.simp because the chain stopped, but // just in case.. if (axis1.simp == axis2.simp) return false; // Count the number of other chains coming from axis1 and axis2. int exit1, exit2; NTetFace arrive1, arrive2; int nChains = 1; unsigned newChainTet; NFacePair newChainFaces; for (exit1 = 0; exit1 < 4; exit1++) { if (exit1 == axis1.facet) continue; arrive1 = dest(axis1.simp, exit1); if (arrive1.simp == static_cast(bdryTet) || arrive1.simp == axis1.simp || arrive1.simp == axis2.simp || arrive1.isBoundary(size_)) continue; for (exit2 = 0; exit2 < 4; exit2++) { if (exit2 == axis2.facet) continue; arrive2 = dest(axis2.simp, exit2); if (arrive2.simp != arrive1.simp) continue; // We have graph edges from axis1 and axis2 to a common vertex, // which is not part of our original chain and is neither axis1 // nor axis2. // See if there's a (possibly zero-length) chain we can // follow to a loop. newChainTet = arrive1.simp; newChainFaces = NFacePair(arrive1.facet, arrive2.facet). complement(); followChain(newChainTet, newChainFaces); if (dest(newChainTet, newChainFaces.lower()).simp == static_cast(newChainTet)) { // Got one! if (++nChains == 3) return true; } } } // Nope. Not enough chains were found. return false; } bool NFacePairing::hasSingleStar() const { int half[4], all[8]; unsigned first, second; unsigned f1, f2; int i; // Skip the last tetrahedron, since we're already testing every // possibility from both sides. for (first = 0; first + 1 < size_; first++) { // All four neighbours must be non-boundary and distinct. for (f1 = 0; f1 < 4; f1++) { half[f1] = dest(first, f1).simp; if (half[f1] >= static_cast(size_) /* bdry */) break; } if (f1 < 4) continue; std::sort(half, half + 4); if (half[0] == half[1] || half[1] == half[2] || half[2] == half[3]) continue; // Look for the adjacent neighbour. for (f1 = 0; f1 < 4; f1++) { second = dest(first, f1).simp; // Now ensure that all eight faces are non-boundary and distinct. for (f2 = 0; f2 < 4; f2++) { all[f2 + 4] = dest(second, f2).simp; if (all[f2 + 4] >= static_cast(size_) /* bdry */) break; } if (f2 < 4) continue; // We have to refresh the first half of the all[] array each // time, since every time we sort all[] we mix the first // tetrahedron's neighbours in with the second tetrahedron's // neighbours. std::copy(half, half + 4, all); std::sort(all, all + 8); for (i = 0; i < 7; i++) if (all[i] == all[i + 1]) break; if (i >= 7) return true; } } return false; } bool NFacePairing::hasDoubleStar() const { int all[7]; unsigned first, second; int f, i; // Skip the last tetrahedron, since we're already testing every // possibility from both sides. for (first = 0; first + 1 < size_; first++) { // All four neighbours must be non-boundary, and three must be // distinct. for (f = 0; f < 4; f++) { all[f] = dest(first, f).simp; if (all[f] >= static_cast(size_) /* bdry */) break; } if (f < 4) continue; std::sort(all, all + 4); // Find the double edge, and move the three distinct tetrahedra // to the beginning of the array. if (all[0] == all[1] && all[1] != all[2] && all[2] != all[3]) { second = all[0]; all[0] = all[3]; } else if (all[0] != all[1] && all[1] == all[2] && all[2] != all[3]) { second = all[1]; all[1] = all[3]; } else if (all[0] != all[1] && all[1] != all[2] && all[2] == all[3]) { second = all[2]; } else continue; // Now look at the edges coming out from the second tetrahedron. for (f = 0; f < 4; f++) { all[f + 3] = dest(second, f).simp; if (all[f + 3] >= static_cast(size_) /* bdry */) break; } if (f < 4) continue; // Look for duplicates. We should only have a single duplicate // pair, this being two copies of first. std::sort(all, all + 7); for (i = 0; i < 6; i++) if (all[i] == all[i + 1]) { if (all[i] != static_cast(first)) break; if (i < 5 && all[i] == all[i + 2]) break; } if (i >= 6) return true; } return false; } bool NFacePairing::hasDoubleSquare() const { unsigned t1; NTetFace t2; int join, fa, fb; int adj1 = 0, adj2 = 0; bool found; // Skip the last three tetrahedra -- any of the four starting points // will do. for (t1 = 0; t1 + 3 < size_; t1++) for (join = 0; join < 4; join++) { t2 = dest(t1, join); if (t2.simp == static_cast(t1) || t2.isBoundary(size_)) continue; // We have distinct t1, t2 adjacent. // Search for double edges leaving t1 and t2 for two new // tetrahedra. found = false; for (fa = 0; fa < 3 && ! found; fa++) { if (fa == join) continue; adj1 = dest(t1, fa).simp; if (adj1 >= static_cast(size_) /* bdry */) continue; if (adj1 == static_cast(t1) || adj1 == t2.simp) continue; for (fb = fa + 1; fb < 4; fb++) { if (fb == join) continue; if (adj1 == dest(t1, fb).simp) { found = true; break; } } } if (! found) continue; found = false; for (fa = 0; fa < 3 && ! found; fa++) { if (fa == t2.facet) continue; adj2 = dest(t2.simp, fa).simp; if (adj2 >= static_cast(size_) /* bdry */) continue; if (adj2 == static_cast(t1) || adj2 == t2.simp || adj2 == adj1) continue; for (fb = fa + 1; fb < 4; fb++) { if (fb == t2.facet) continue; if (adj2 == dest(t2.simp, fb).simp) { found = true; break; } } } if (! found) continue; // All we need now is a link between adj1 and adj2. for (fa = 0; fa < 4; fa++) if (dest(adj1, fa).simp == adj2) return true; } // Nothing found. return false; } } // namespace regina regina-4.95/engine/census/nfacepairing.h000644 000765 000024 00000063641 12234011536 020162 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/nfacepairing.h * \brief Deals with pairing off tetrahedron faces in a census of * 3-manifold triangulations. */ #ifndef __NFACEPAIRING_H #ifndef __DOXYGEN #define __NFACEPAIRING_H #endif #include "regina-core.h" #include "census/ngenericfacetpairing.h" #include "triangulation/nisomorphism.h" namespace regina { class NFacePair; /** * \weakgroup census * @{ */ /** * A legacy typedef provided for backward compatibility only. * * \deprecated As of Regina 4.94, this typedef is now available as * NFacePairing::IsoList. The old typedef NFacePairingIsoList is * provided for backward compatibility, but will be removed in some * future version of Regina. */ typedef std::list NFacePairingIsoList; /** * A legacy typedef provided for backward compatibility only. * * \deprecated As of Regina 4.94, this typedef is now available as * NFacePairing::Use. The old typedef UseFacePairing is * provided for backward compatibility, but will be removed in some * future version of Regina. */ typedef void (*UseFacePairing)(const NFacePairing*, const NFacePairingIsoList*, void*); /** * Represents a specific pairwise matching of tetrahedron faces. * Given a fixed number of tetrahedra, each tetrahedron face is either * paired with some other tetrahedron face (which is in turn paired with * it) or remains unmatched. A tetrahedron face cannot be paired with * itself. * * Such a matching models part of the structure of a triangulation, in * which each tetrahedron face is either glued to some other tetrahedron * face (which is in turn glued to it) or is an unglued boundary face. * * Note that if this pairing is used to construct an actual * triangulation, the individual gluing permutations will still need to * be specified; they are not a part of this structure. * * \testpart */ class REGINA_API NFacePairing : public NGenericFacetPairing<3> { public: /** * Creates a new face pairing that is a clone of the given face * pairing. * * @param cloneMe the face pairing to clone. */ NFacePairing(const NFacePairing& cloneMe); /** * Creates the face pairing of the given 3-manifold triangulation. * This is the face pairing that describes how the tetrahedron faces * of the given triangulation are joined together, as described in the * class notes. * * \pre The given triangulation is not empty. * * @param tri the triangulation whose face pairing should be * constructed. */ NFacePairing(const NTriangulation& tri); /** * A legacy alias for size(), provided for backward * compatibility only. * * This routine returns the number of tetrahedra whose faces are * described by this face pairing. * * \deprecated As of Regina 4.94, this routine has been renamed * to size(). The old name getNumberOfTetrahedra() is provided * for backward compatibility, but will be removed in some future * version of Regina. * * @return the number of tetrahedra under consideration. */ unsigned getNumberOfTetrahedra() const; /** * Follows a chain as far as possible from the given point. * * A chain is the underlying face pairing for a layered chain; * specifically it involves one tetrahedron joined to a second * along two faces, the remaining two faces of the second * tetrahedron joined to a third and so on. A chain can involve * as few as one tetrahedron or as many as we like. Note that * the remaining two faces of the first tetrahedron and the * remaining two faces of the final tetrahedron remain * unaccounted for by this structure. * * This routine begins with two faces of a given tetrahedron, * described by parameters \a tet and \a faces. * If these two faces are both joined to some different * tetrahedron, parameter \a tet will be changed to this new * tetrahedron and parameter \a faces will be changed to the * remaining faces of this new tetrahedron (i.e., the two faces * that were not joined to the original faces of the original * tetrahedron). * * This procedure is repeated as far as possible until either * the two faces in question join to two different tetrahedra, * the two faces join to each other, or at least one of the * two faces is unmatched. * * Thus, given one end of a chain, this procedure can be used to * follow the face pairings through to the other end of the chain. * * \warning You must be sure when calling this routine that you * are not inside a chain that loops back onto itself! * If the face pairing forms a * large loop with each tetrahedron joined by two faces to the * next, this routine will cycle around the loop forever and * never return. * * @param tet the index in the underlying triangulation of the * tetrahedron to begin at. This parameter will be modified * directly by this routine as a way of returning the results. * @param faces the pair of face numbers in the given * tetrahedron at which we begin. This parameter will also be * modified directly by this routine as a way of returning results. */ void followChain(unsigned& tet, NFacePair& faces) const; /** * Determines whether this face pairing contains a triple edge. * A triple edge is where two different tetrahedra are joined * along three of their faces. * * A face pairing containing a triple edge cannot model a closed * minimal irreducible P^2-irreducible 3-manifold triangulation on * more than two tetrahedra. See "Face pairing graphs and 3-manifold * enumeration", Benjamin A. Burton, J. Knot Theory Ramifications * 13 (2004), 1057--1101. * * @return \c true if and only if this face pairing contains a * triple edge. */ bool hasTripleEdge() const; /** * Determines whether this face pairing contains a broken * double-ended chain. * * A chain involves a sequence of tetrahedra, each joined to the * next along two faces, and is described in detail in the * documentation for followChain(). * * A one-ended chain is a chain in which the first tetrahedron * is also joined to itself along one face (i.e., the underlying * face pairing for a layered solid torus). A double-ended * chain is a chain in which the first tetrahedron is joined to * itself along one face and the final tetrahedron is also * joined to itself along one face (i.e., the underlying * face pairing for a layered lens space). * * A broken double-ended chain consists of two one-ended chains * (using distinct sets of tetrahedra) joined together along one * face. The remaining two faces (one from each chain) * that were unaccounted for by the individual one-ended chains * remain unaccounted for by this broken double-ended chain. * * In this routine we are interested specifically in finding a * broken double-ended chain that is not a part of a complete * double-ended chain, i.e., the final two unaccounted faces are * not joined together. * * A face pairing containing a broken double-ended chain cannot * model a closed minimal irreducible P^2-irreducible 3-manifold * triangulation on more than two tetrahedra. See "Face pairing * graphs and 3-manifold enumeration", Benjamin A. Burton, * J. Knot Theory Ramifications 13 (2004), 1057--1101. * * @return \c true if and only if this face pairing contains a * broken double-ended chain that is not part of a complete * double-ended chain. */ bool hasBrokenDoubleEndedChain() const; /** * Determines whether this face pairing contains a one-ended chain * with a double handle. * * A chain involves a sequence of tetrahedra, each joined to the * next along two faces, and is described in detail in the * documentation for followChain(). * * A one-ended chain is a chain in which the first tetrahedron * is also joined to itself along one face (i.e., the underlying * face pairing for a layered solid torus). * * A one-ended chain with a double handle begins with a one-ended * chain. The two faces that are unaccounted for by this * one-ended chain must be joined * to two different tetrahedra, and these two tetrahedra must be * joined to each other along two faces. The remaining two * faces of these two tetrahedra remain unaccounted for by this * structure. * * A face pairing containing a one-ended chain with a double handle * cannot model a closed minimal irreducible P^2-irreducible * 3-manifold triangulation on more than two tetrahedra. See * "Face pairing graphs and 3-manifold enumeration", * Benjamin A. Burton, J. Knot Theory Ramifications 13 (2004), * 1057--1101. * * @return \c true if and only if this face pairing contains a * one-ended chain with a double handle. */ bool hasOneEndedChainWithDoubleHandle() const; /** * Determines whether this face pairing contains a wedged * double-ended chain. * * A chain involves a sequence of tetrahedra, each joined to the * next along two faces, and is described in detail in the * documentation for followChain(). * * A one-ended chain is a chain in which the first tetrahedron * is also joined to itself along one face (i.e., the underlying * face pairing for a layered solid torus). A double-ended * chain is a chain in which the first tetrahedron is joined to * itself along one face and the final tetrahedron is also * joined to itself along one face (i.e., the underlying * face pairing for a layered lens space). * * A wedged double-ended chain is created from two one-ended * chains as follows. Two new tetrahedra are added, and each * one-ended chain is joined to each of the new tetrahedra along * a single face. In addition, the two new tetrahedra are * joined to each other along a single face. The remaining two * faces (one from each of the new tetrahedra) remain * unaccounted for by this structure. * * An alternative way of viewing a wedged double-ended chain is * as an ordinary double-ended chain, where one of the internal * tetrahedra is removed and replaced with a pair of tetrahedra * joined to each other. Whereas the original tetrahedron met its * two neighbouring tetrahedra along two faces each (giving a * total of four face identifications), the two new tetrahedra now * meet each of the two neighbouring tetrahedra along a single * face each (again giving four face identifications). * * Note that if this alternate construction is used to replace * one of the end tetrahedra of the double-ended chain (not an * internal tetrahedron), the resulting structure will either be * a triple edge or a one-ended chain with a double handle * (according to whether the original chain has zero or positive * length). See hasTripleEdge() and * hasOneEndedChainWithDoubleHandle() for further details. * * A face pairing containing a wedged double-ended chain * cannot model a closed minimal irreducible P^2-irreducible * 3-manifold triangulation on more than two tetrahedra. See * "Enumeration of non-orientable 3-manifolds using face-pairing * graphs and union-find", Benjamin A. Burton, * Discrete Comput. Geom. 38 (2007), no. 3, 527--571. * * @return \c true if and only if this face pairing contains a * wedged double-ended chain. */ bool hasWedgedDoubleEndedChain() const; /** * Determines whether this face pairing contains a one-ended * chain with a stray bigon. * * A chain involves a sequence of tetrahedra, each joined to the * next along two faces, and is described in detail in the * documentation for followChain(). * * A one-ended chain is a chain in which the first tetrahedron * is also joined to itself along one face (i.e., the underlying * face pairing for a layered solid torus). * * A one-ended chain with a stray bigon describes the following * structure. We begin with a one-ended chain. Two new * tetrahedra are added; these are joined to each other along * two pairs of faces, and one of the new tetrahedra is joined * to the end of the one-ended chain. We then ensure that: * - This configuration is not part of a longer one-ended chain * that encompasses all of the aforementioned tetrahedra; * - There is no extra tetrahedron that is joined to both the * two new tetrahedra and the end of the chain; * - There is no extra tetrahedron that is joined to the end of * the chain along one face and the far new tetrahedron along * two additional faces (where by "the far new tetrahedron" * we mean the new tetrahedron that was not originally joined to * the chain). * * Aside from these constraints, the remaining four tetrahedron faces * (two faces of the far new tetrahedron, one face of the other new * tetrahedron, and one face at the end of the chain) remain * unaccounted for by this structure. * * A face pairing containing a structure of this type * cannot model a closed minimal irreducible P^2-irreducible * 3-manifold triangulation on more than two tetrahedra. See * "Enumeration of non-orientable 3-manifolds using face-pairing * graphs and union-find", Benjamin A. Burton, * Discrete Comput. Geom. 38 (2007), no. 3, 527--571. * * @return \c true if and only if this face pairing contains a * one-ended chain with a stray bigon. */ bool hasOneEndedChainWithStrayBigon() const; /** * Determines whether this face pairing contains a triple * one-ended chain. * * A chain involves a sequence of tetrahedra, each joined to the * next along two faces, and is described in detail in the * documentation for followChain(). * * A one-ended chain is a chain in which the first tetrahedron * is also joined to itself along one face (i.e., the underlying * face pairing for a layered solid torus). * * A triple one-ended chain is created from three one-ended * chains as follows. Two new tetrahedra are added, and each * one-ended chain is joined to each of the new tetrahedra along * a single face. The remaining two faces (one from each of the * new tetrahedra) remain unaccounted for by this structure. * * A face pairing containing a triple one-ended chain * cannot model a closed minimal irreducible P^2-irreducible * 3-manifold triangulation on more than two tetrahedra. See * "Enumeration of non-orientable 3-manifolds using face-pairing * graphs and union-find", Benjamin A. Burton, * Discrete Comput. Geom. 38 (2007), no. 3, 527--571. * * @return \c true if and only if this face pairing contains a * triple one-ended chain. */ bool hasTripleOneEndedChain() const; /** * Determines whether this face pairing contains a single-edged * star. * * A single-edged star involves two tetrahedra that are adjacent * along a single face, where the six remaining faces of these * tetrahedra are joined to six entirely new tetrahedra (so that * none of the eight tetrahedra described in this structure are * the same). * * @return \c true if and only if this face pairing contains a * single-edged star. */ bool hasSingleStar() const; /** * Determines whether this face pairing contains a double-edged * star. * * A double-edged star involves two tetrahedra that are adjacent * along two separate pairs of faces, where the four remaining * faces of these tetrahedra are joined to four entirely new * tetrahedra (so that none of the six tetrahedra described in * this structure are the same). * * @return \c true if and only if this face pairing contains a * double-edged star. */ bool hasDoubleStar() const; /** * Determines whether this face pairing contains a double-edged * square. * * A double-edged square involves four distinct tetrahedra that * meet each other as follows. Two pairs of tetrahedra are * joined along two pairs of faces each. Then each tetrahedron * is joined along a single face to one tetrahedron of the other * pair. The four tetrahedron faces not yet joined to anything * (one from each tetrahedron) remain unaccounted for by this * structure. * * @return \c true if and only if this face pairing contains a * double-edged square. */ bool hasDoubleSquare() const; private: /** * Creates a new face pairing. All internal arrays will be * allocated but not initialised. * * \pre \a nTetrahedra is at least 1. * * @param nTetrahedra the number of tetrahedra under * consideration in this new face pairing. */ NFacePairing(unsigned nTetrahedra); /** * Internal to hasBrokenDoubleEndedChain(). This routine assumes * that the give face of the given tetrahedron is glued to this * same tetrahedron, and attempts to find a broken double-ended * chain for which this tetrahedron is at the end of one of the * one-ended chains therein. * * \pre The given face of the given tetrahedron is paired with * another face of the same tetrahedron under this face pairing. * * @param tet the index in the triangulation of the given * tetrahedron. * @param face the number of the given face in the tetrahedron; * this must be between 0 and 3 inclusive. * * @return \c true if and only if this face pairing contains a * broken double-ended chain as described above. */ bool hasBrokenDoubleEndedChain(unsigned tet, unsigned face) const; /** * Internal to hasOneEndedChainWithDoubleHandle(). This routine * assumes that the give face of the given tetrahedron is glued * to this same tetrahedron, and attempts to find a one-ended * chain with a double handle for which this tetrahedron is at * the end of the chain contained therein. * * \pre The given face of the given tetrahedron is paired with * another face of the same tetrahedron under this face pairing. * * @param tet the index in the triangulation of the given * tetrahedron. * @param face the number of the given face in the tetrahedron; * this must be between 0 and 3 inclusive. * * @return \c true if and only if this face pairing contains a * one-ended chain with a double handle as described above. */ bool hasOneEndedChainWithDoubleHandle(unsigned tet, unsigned face) const; /** * Internal to hasWedgedDoubleEndedChain(). This routine assumes * that the give face of the given tetrahedron is glued to this * same tetrahedron, and attempts to find a wedged double-ended * chain for which this tetrahedron is at one end of the * double-ended chain. * * \pre The given face of the given tetrahedron is paired with * another face of the same tetrahedron under this face pairing. * * @param tet the index in the triangulation of the given * tetrahedron. * @param face the number of the given face in the tetrahedron; * this must be between 0 and 3 inclusive. * * @return \c true if and only if this face pairing contains a * wedged double-ended chain as described above. */ bool hasWedgedDoubleEndedChain(unsigned tet, unsigned face) const; /** * Internal to hasOneEndedChainWithStrayBigon(). This routine assumes * that the give face of the given tetrahedron is glued to this * same tetrahedron, and attempts to find a one-ended chain with * stray bigon for which this tetrahedron is at the end of the * one-ended chain. * * \pre The given face of the given tetrahedron is paired with * another face of the same tetrahedron under this face pairing. * * @param tet the index in the triangulation of the given * tetrahedron. * @param face the number of the given face in the tetrahedron; * this must be between 0 and 3 inclusive. * * @return \c true if and only if this face pairing contains a * one-ended chain with stray bigon as described above. */ bool hasOneEndedChainWithStrayBigon(unsigned tet, unsigned face) const; /** * Internal to hasTripleOneEndedChain(). This routine assumes * that the give face of the given tetrahedron is glued to this * same tetrahedron, and attempts to find a triple one-ended * chain for which this tetrahedron is at the end of one of the * individual one-ended chains. * * \pre The given face of the given tetrahedron is paired with * another face of the same tetrahedron under this face pairing. * * @param tet the index in the triangulation of the given * tetrahedron. * @param face the number of the given face in the tetrahedron; * this must be between 0 and 3 inclusive. * * @return \c true if and only if this face pairing contains a * triple one-ended chain as described above. */ bool hasTripleOneEndedChain(unsigned tet, unsigned face) const; // Make sure the parent class can call the private constructor. friend class NGenericFacetPairing<3>; }; /*@}*/ // Inline functions for NFacePairing inline NFacePairing::NFacePairing(const NFacePairing& cloneMe) : NGenericFacetPairing<3>(cloneMe) { } inline NFacePairing::NFacePairing(const NTriangulation& tri) : NGenericFacetPairing<3>(tri) { } inline NFacePairing::NFacePairing(unsigned nTetrahedra) : NGenericFacetPairing<3>(nTetrahedra) { } inline unsigned NFacePairing::getNumberOfTetrahedra() const { return size_; } } // namespace regina #endif regina-4.95/engine/census/ngenericfacetpairing-impl.h000644 000765 000024 00000077056 12234011536 022647 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* Template definitions for ngenericfacetpairing.h. */ #include #include #include #include "census/ngenericfacetpairing.h" #include "utilities/memutils.h" #include "utilities/stringutils.h" namespace regina { template NGenericFacetPairing::NGenericFacetPairing( const NGenericFacetPairing& cloneMe) : NThread(), size_(cloneMe.size_), pairs_(new NFacetSpec[cloneMe.size_ * (dim + 1)]) { std::copy(cloneMe.pairs_, cloneMe.pairs_ + (size_ * (dim + 1)), pairs_); } template NGenericFacetPairing::NGenericFacetPairing( const typename NGenericFacetPairing::Triangulation& tri) : size_(tri.getNumberOfSimplices()), pairs_(new NFacetSpec[tri.getNumberOfSimplices() * (dim + 1)]) { unsigned p, f, index; const Simplex *simp, *adj; for (index = 0, p = 0; p < size_; ++p) { simp = tri.getSimplex(p); for (f = 0; f <= dim; ++f) { adj = simp->adjacentSimplex(f); if (adj) { pairs_[index].simp = tri.simplexIndex(adj); pairs_[index].facet = simp->adjacentFacet(f); } else pairs_[index].setBoundary(size_); ++index; } } } template bool NGenericFacetPairing::isClosed() const { for (NFacetSpec f(0, 0); ! f.isPastEnd(size_, true); ++f) if (isUnmatched(f)) return false; return true; } template std::string NGenericFacetPairing::str() const { std::ostringstream ans; for (NFacetSpec f(0, 0); ! f.isPastEnd(size_, true); ++f) { if (f.facet == 0 && f.simp > 0) ans << " | "; else if (f.simp || f.facet) ans << ' '; if (dest(f).isBoundary(size_)) ans << "bdry"; else ans << dest(f).simp << ':' << dest(f).facet; } return ans.str(); } template std::string NGenericFacetPairing::dotHeader(const char* graphName) { std::ostringstream ans; writeDotHeader(ans, graphName); return ans.str(); } template void NGenericFacetPairing::writeDotHeader( std::ostream& out, const char* graphName) { static const char defaultGraphName[] = "G"; if ((! graphName) || (! *graphName)) graphName = defaultGraphName; out << "graph " << graphName << " {" << std::endl; out << "graph [bgcolor=white];" << std::endl; out << "edge [color=black];" << std::endl; out << "node [shape=circle,style=filled,height=0.15,fixedsize=true,label=\"\",fontsize=9,fontcolor=\"#751010\"];" << std::endl; } template std::string NGenericFacetPairing::dot( const char* prefix, bool subgraph, bool labels) const { std::ostringstream ans; writeDot(ans, prefix, subgraph, labels); return ans.str(); } template void NGenericFacetPairing::writeDot(std::ostream& out, const char* prefix, bool subgraph, bool labels) const { static const char defaultPrefix[] = "g"; if ((! prefix) || (! *prefix)) prefix = defaultPrefix; // We are guaranteed that prefix is a non-empty string. if (subgraph) out << "subgraph pairing_" << prefix << " {" << std::endl; else writeDotHeader(out, (prefix + std::string("_graph")).c_str()); // Ancient versions of graphviz seem to ignore the default label="". // Make this explicit for each node. unsigned p; for (p = 0; p < size_; ++p) { out << prefix << '_' << p << " [label=\""; if (labels) out << p; out << "\"]" << std::endl; } int f; NFacetSpec adj; for (p = 0; p < size_; ++p) for (f = 0; f < (dim + 1); ++f) { adj = dest(p, f); if (adj.isBoundary(size_) || adj.simp < static_cast(p) || (adj.simp == static_cast(p) && adj.facet < f)) continue; out << prefix << '_' << p << " -- " << prefix << '_' << adj.simp << ';' << std::endl; } out << '}' << std::endl; } template std::string NGenericFacetPairing::toTextRep() const { std::ostringstream ans; for (NFacetSpec f(0, 0); ! f.isPastEnd(size_, true); ++f) { if (f.simp || f.facet) ans << ' '; ans << dest(f).simp << ' ' << dest(f).facet; } return ans.str(); } template typename NGenericFacetPairing::FacetPairing* NGenericFacetPairing::fromTextRep(const std::string& rep) { std::vector tokens; unsigned nTokens = basicTokenise(back_inserter(tokens), rep); if (nTokens == 0 || nTokens % (2 * (dim + 1)) != 0) return 0; long nSimp = nTokens / (2 * (dim + 1)); FacetPairing* ans = new FacetPairing(nSimp); // Read the raw values. // Check the range of each value while we're at it. long val; for (long i = 0; i < nSimp * (dim + 1); ++i) { if (! valueOf(tokens[2 * i], val)) { delete ans; return 0; } if (val < 0 || val > nSimp) { delete ans; return 0; } ans->pairs_[i].simp = val; if (! valueOf(tokens[2 * i + 1], val)) { delete ans; return 0; } if (val < 0 || val >= (dim + 1)) { delete ans; return 0; } ans->pairs_[i].facet = val; } // Run a sanity check. NFacetSpec destFacet; bool broken = false; for (NFacetSpec f(0, 0); ! f.isPastEnd(nSimp, true); ++f) { destFacet = ans->dest(f); if (destFacet.simp == nSimp && destFacet.facet != 0) broken = true; else if (destFacet.simp < nSimp && ! (ans->dest(destFacet) == f)) broken = true; else continue; break; } if (broken) { delete ans; return 0; } // All is well. return ans; } template bool NGenericFacetPairing::isCanonical() const { // Check the preconditions for isCanonicalInternal(). unsigned simp, facet; for (simp = 0; simp < size_; ++simp) { for (facet = 0; facet < dim; ++facet) if (dest(simp, facet + 1) < dest(simp, facet)) if (! (dest(simp, facet + 1) == NFacetSpec(simp, facet))) return false; if (simp > 0) if (dest(simp, 0).simp >= static_cast(simp)) return false; if (simp > 1) if (dest(simp, 0) <= dest(simp - 1, 0)) return false; } // We've met all the preconditions, so we can now run // isCanonicalInternal(). IsoList list; return isCanonicalInternal(list); } template bool NGenericFacetPairing::isCanonicalInternal( typename NGenericFacetPairing::IsoList& list) const { // Create the automorphisms one simplex at a time, selecting the // preimage of 0 first, then the preimage of 1 and so on. // We want to cycle through all possible first facet gluings, so we'll // special-case the situation in which there are no facet gluings at all. if (isUnmatched(0, 0)) { // We must have just one simplex with no facet gluings at all. Isomorphism* ans; for (int i = 0; i < Perm::nPerms; ++i) { ans = new Isomorphism(1); ans->simpImage(0) = 0; ans->facetPerm(0) = Perm::Sn[i]; list.push_back(ans); } return true; } // Now we know that facet 0 of simplex 0 is glued to something. NFacetSpec* image = new NFacetSpec[size_ * (dim + 1)]; /**< The automorphism currently under construction. */ NFacetSpec* preImage = new NFacetSpec[size_ * (dim + 1)]; /**< The inverse of this automorphism. */ unsigned i, j; for (i = 0; i < size_ * (dim + 1); ++i) { image[i].setBeforeStart(); preImage[i].setBeforeStart(); } // Note that we know size_ >= 1. // For the preimage of facet 0 of simplex 0 we simply cycle // through all possibilities. const NFacetSpec firstFace(0, 0); const NFacetSpec firstFaceDest(dest(firstFace)); NFacetSpec firstDestPre; NFacetSpec trying; NFacetSpec fImg, fPre; bool stepDown; int simp, facet; int permImg[dim + 1]; for (preImage[0] = firstFace ; ! preImage[0].isPastEnd(size_, true); ++preImage[0]) { // Note that we know firstFace is not unmatched. if (isUnmatched(preImage[0])) continue; // If firstFace glues to the same simplex and this facet // doesn't, we can ignore this permutation. firstDestPre = dest(preImage[0]); if (firstFaceDest.simp == 0 && firstDestPre.simp != preImage[0].simp) continue; // If firstFace doesn't glue to the same simplex but this // facet does, we're not in canonical form. if (firstFaceDest.simp != 0 && firstDestPre.simp == preImage[0].simp) { for_each(list.begin(), list.end(), FuncDelete()); list.clear(); delete[] image; delete[] preImage; return false; } // We can use this facet. Set the corresponding reverse mapping // and off we go. image[preImage[0].simp * (dim + 1) + preImage[0].facet] = firstFace; preImage[firstFaceDest.simp * (dim + 1) + firstFaceDest.facet] = firstDestPre; image[firstDestPre.simp * (dim + 1) + firstDestPre.facet] = firstFaceDest; // Step forwards to the next facet whose preimage is undetermined. trying = firstFace; ++trying; if (trying == firstFaceDest) ++trying; while (! (trying == firstFace)) { // INV: We've successfully selected preimages for all facets // before trying. We're currently looking at the last // attempted candidate for the preimage of trying. // Note that if preimage facet A is glued to preimage facet B // and the image of A is earlier than the image of B, then // the image of A will be selected whereas the image of B // will be automatically derived. stepDown = false; NFacetSpec& pre = preImage[trying.simp * (dim + 1) + trying.facet]; if (trying.isPastEnd(size_, true)) { // We have a complete automorphism! Isomorphism* ans = new Isomorphism(size_); for (i = 0; i < size_; i++) { ans->simpImage(i) = image[i * (dim + 1)].simp; for (j = 0; j <= dim; ++j) permImg[j] = image[i * (dim + 1) + j].facet; ans->facetPerm(i) = Perm(permImg); } list.push_back(ans); stepDown = true; } else { // Move to the next candidate. if (pre.simp >= 0 && pre.facet == dim) { // We're all out of candidates. pre.setBeforeStart(); stepDown = true; } else { if (pre.isBeforeStart()) { // Which simplex must we look in? // Note that this simplex will already have been // determined. pre.simp = preImage[trying.simp * (dim + 1)].simp; pre.facet = 0; } else ++pre.facet; // Step forwards until we have a preimage whose image // has not already been set. // If the preimage is unmatched and trying isn't, // we'll also skip it. // If trying is unmatched and the preimage isn't, // we're not in canonical form. for ( ; pre.facet <= dim; ++pre.facet) { if (! image[pre.simp * (dim + 1) + pre.facet]. isBeforeStart()) continue; if ((! isUnmatched(trying)) && isUnmatched(pre)) continue; if (isUnmatched(trying) && (! isUnmatched(pre))) { // We're not in canonical form. for_each(list.begin(), list.end(), FuncDelete()); list.clear(); delete[] image; delete[] preImage; return false; } break; } while (pre.facet <= dim && ! image[pre.simp * (dim + 1) + pre.facet]. isBeforeStart()) ++pre.facet; if (pre.facet == (dim + 1)) { pre.setBeforeStart(); stepDown = true; } } } if (! stepDown) { // We found a candidate. // We also know that trying is unmatched iff the preimage // is unmatched. image[pre.simp * (dim + 1) + pre.facet] = trying; if (! isUnmatched(pre)) { fPre = dest(pre); if (image[fPre.simp * (dim + 1) + fPre.facet]. isBeforeStart()) { // The image of fPre (the partner of the preimage // facet) can be determined at this point. // Specifically, it should go into the next // available slot. // Do we already know which simplex we should // be looking into? for (i = 0; i <= dim; i++) if (! image[fPre.simp * (dim + 1) + i]. isBeforeStart()) { // Here's the simplex! // Find the first available facet. simp = image[fPre.simp * (dim + 1) + i].simp; for (facet = 0; ! preImage[simp * (dim + 1) + facet]. isBeforeStart(); ++facet) ; image[fPre.simp * (dim + 1) + fPre.facet].simp = simp; image[fPre.simp * (dim + 1) + fPre.facet].facet = facet; break; } if (i == (dim + 1)) { // We need to map to a new simplex. // Find the first available simplex. for (simp = trying.simp + 1; ! preImage[simp * (dim + 1)]. isBeforeStart(); ++simp) ; image[fPre.simp * (dim + 1) + fPre.facet].simp = simp; image[fPre.simp * (dim + 1) + fPre.facet].facet = 0; } // Set the corresponding preimage. fImg = image[fPre.simp * (dim + 1) + fPre.facet]; preImage[fImg.simp * (dim + 1) + fImg.facet] = fPre; } } // Do a lexicographical comparison and shunt trying up // if need be. do { fImg = dest(trying); fPre = dest(preImage[trying.simp * (dim + 1) + trying.facet]); if (! fPre.isBoundary(size_)) fPre = image[fPre.simp * (dim + 1) + fPre.facet]; // Currently trying is glued to fImg. // After applying our isomorphism, trying will be // glued to fPre. if (fImg < fPre) { // This isomorphism will lead to a // lexicographically greater representation. // Ignore it. stepDown = true; } else if (fPre < fImg) { // Whapow, we're not in canonical form. for_each(list.begin(), list.end(), FuncDelete()); list.clear(); delete[] image; delete[] preImage; return false; } // What we have so far is consistent with an automorphism. ++trying; } while (! (stepDown || trying.isPastEnd(size_, true) || preImage[trying.simp * (dim + 1) + trying.facet]. isBeforeStart())); } if (stepDown) { // We're shunting trying back down. --trying; while (true) { fPre = preImage[trying.simp * (dim + 1) + trying.facet]; if (! isUnmatched(fPre)) { fPre = dest(fPre); if (image[fPre.simp * (dim + 1) + fPre.facet] < trying) { // This preimage/image was automatically derived. --trying; continue; } } break; } // Note that this resetting of facets that follows will // also take place when trying makes it all the way back // down to firstFace. fPre = preImage[trying.simp * (dim + 1) + trying.facet]; image[fPre.simp * (dim + 1) + fPre.facet].setBeforeStart(); if (! isUnmatched(fPre)) { fPre = dest(fPre); fImg = image[fPre.simp * (dim + 1) + fPre.facet]; preImage[fImg.simp * (dim + 1) + fImg.facet]. setBeforeStart(); image[fPre.simp * (dim + 1) + fPre.facet].setBeforeStart(); } } } } // The pairing is in canonical form and we have all our automorphisms. // Tidy up and return. delete[] image; delete[] preImage; return true; } template bool NGenericFacetPairing::findAllPairings(unsigned nSimplices, NBoolSet boundary, int nBdryFacets, typename NGenericFacetPairing::Use use, void* useArgs, bool newThread) { // Create a set of arguments. Args* args = new Args; args->boundary = boundary; args->nBdryFacets = nBdryFacets; args->use = use; args->useArgs = useArgs; // Start the facet pairing generation. FacetPairing* pairing = new FacetPairing(nSimplices); if (newThread) return pairing->start(args, true); else { pairing->run(args); delete pairing; return true; } } template void* NGenericFacetPairing::run(void* param) { Args* args = static_cast(param); // Bail if it's obvious that nothing will happen. if (args->boundary == NBoolSet::sNone || size_ == 0) { args->use(0, 0, args->useArgs); delete args; return 0; } if (args->boundary.hasTrue() && args->nBdryFacets >= 0 && (args->nBdryFacets % 2 != ((dim+1) * static_cast(size_)) % 2 || args->nBdryFacets > (dim - 1) * static_cast(size_) + 2 || (args->nBdryFacets == 0 && ! args->boundary.hasFalse()))) { args->use(0, 0, args->useArgs); delete args; return 0; } // Initialise the pairings to unspecified (i.e., facet -> itself). for (NFacetSpec f(0,0); f.simp < static_cast(size_); ++f) dest(f) = f; // Note that we have at least one simplex. NFacetSpec trying(0, 0); /**< The facet we're currently trying to match. */ int boundaryFacets = 0; /**< How many (deliberately) unmatched facets do we currently have? */ int usedFacets = 0; /**< How many facets have we already determined matchings for? */ IsoList allAutomorphisms; /**< The set of all automorphisms of the current facet pairing. */ // Run through and find all possible matchings. NFacetSpec oldTrying, tmpFacet; while (true) { // TODO: Check for cancellation, // INVARIANT: Facet trying needs to be joined to something. // dest(trying) represents the last tried destination for the // join, and there is no reciprocal join from dest(trying) back // to trying. // The current value of dest(trying) is >= trying. // Move to the next destination. ++dest(trying); // If we're about to close off the current set of simplices // and it's not all the simplices, we will have something // disconnected! // We will now avoid tying the last two facets in a set together, // and later we will avoid sending the last facet of a set to the // boundary. if (usedFacets % (dim + 1) == (dim - 1) && usedFacets < (dim + 1) * static_cast(size_) - 2 && noDest((usedFacets / (dim + 1)) + 1, 0) && dest(trying).simp <= (usedFacets / (dim + 1))) { // Move to the first unused simplex. dest(trying).simp = (usedFacets / (dim + 1)) + 1; dest(trying).facet = 0; } // We'd better make sure we're not going to glue together so // many facets that there is no room for the required number of // boundary facets. if (args->boundary.hasTrue()) { // We're interested in triangulations with boundary. if (args->nBdryFacets < 0) { // We don't care how many boundary facets. if (! args->boundary.hasFalse()) { // We must have some boundary though. if (boundaryFacets == 0 && usedFacets == (dim + 1) * static_cast(size_) - 2 && dest(trying).simp < static_cast(size_)) dest(trying).setBoundary(size_); } } else { // We're specific about the number of boundary facets. if (usedFacets - boundaryFacets + args->nBdryFacets == (dim + 1) * static_cast(size_) && dest(trying).simp < static_cast(size_)) // We've used our entire quota of non-boundary facets. dest(trying).setBoundary(size_); } } // dest(trying) is now the first remaining candidate destination. // We still don't know whether this destination is valid however. while(true) { // Move onwards to the next free destination. while (dest(trying).simp < static_cast(size_) && ! noDest(dest(trying))) ++dest(trying); // If we are past facet 0 of a simplex and the previous facet // was not used, we can't do anything with this simplex. // Move to the next simplex. if (dest(trying).simp < static_cast(size_) && dest(trying).facet > 0 && noDest(dest(trying).simp, dest(trying).facet - 1)) { ++dest(trying).simp; dest(trying).facet = 0; continue; } break; } // If we're still at an illegitimate destination, it must be // facet 0 of a simplex where the previous simplex is // unused. Note that facet == 0 implies simp > 0. // In this case, we've passed the last sane choice; head // straight to the boundary. if (dest(trying).simp < static_cast(size_) && dest(trying).facet == 0 && noDest(dest(trying).simp - 1, 0)) dest(trying).setBoundary(size_); // Finally, return to the issue of prematurely closing off a // set of simplices. This time we will avoid sending the last // facet of a set of simplices to the boundary. if (usedFacets % (dim + 1) == dim && usedFacets < (dim + 1) * static_cast(size_) - 1 && noDest((usedFacets / (dim + 1)) + 1, 0) && isUnmatched(trying)) { // Can't use the boundary; all we can do is push past the // end. ++dest(trying); } // And so we're finally looking at the next real candidate for // dest(trying) that we know we're actually allowed to use. // Check if after all that we've been pushed past the end. if (dest(trying).isPastEnd(size_, (! args->boundary.hasTrue()) || boundaryFacets == args->nBdryFacets)) { // We can't join trying to anything else. Step back. dest(trying) = trying; --trying; // Keep heading back until we find a facet that joins // forwards or to the boundary. while (! trying.isBeforeStart()) { if (dest(trying) < trying) --trying; else break; } // Is the search over? if (trying.isBeforeStart()) break; // Otherwise undo the previous gluing and prepare to loop // again trying the next option. if (isUnmatched(trying)) { --usedFacets; --boundaryFacets; } else { usedFacets -= 2; dest(dest(trying)) = dest(trying); } continue; } // Let's match it up and head to the next free facet! if (isUnmatched(trying)) { ++usedFacets; ++boundaryFacets; } else { usedFacets += 2; dest(dest(trying)) = trying; } // Now we increment trying to move to the next unmatched facet. oldTrying = trying; ++trying; while (trying.simp < static_cast(size_) && ! noDest(trying)) ++trying; // Have we got a solution? if (trying.simp == static_cast(size_)) { // Deal with the solution! if (isCanonicalInternal(allAutomorphisms)) { args->use(static_cast(this), &allAutomorphisms, args->useArgs); for_each(allAutomorphisms.begin(), allAutomorphisms.end(), FuncDelete()); allAutomorphisms.clear(); } // Head back down to the previous gluing and undo it, ready // for the next loop. trying = oldTrying; if (isUnmatched(trying)) { --usedFacets; --boundaryFacets; } else { usedFacets -= 2; dest(dest(trying)) = dest(trying); } } else { // We're about to start working on a new unmatched facet. // Set dest(trying) to one step *before* the first feasible // destination. // Note that currently the destination is set to trying. // Ensure the destination is at least the // previous forward destination from an earlier facet of this // simplex. if (trying.facet > 0) { tmpFacet = trying; for (--tmpFacet; tmpFacet.simp == trying.simp; --tmpFacet) if (tmpFacet < dest(tmpFacet)) { // Here is the previous forward destination in // this simplex. if (dest(trying) < dest(tmpFacet)) { dest(trying) = dest(tmpFacet); // Remember that dest(trying) will be // incremented before it is used. This // should not happen if we're already on the // boundary, so we need to move back one // step so we will be pushed back onto the // boundary. if (isUnmatched(trying)) --dest(trying); } break; } } // If the first simplex doesn't glue to itself and this // is not the first simplex, it can't glue to itself either. // (Note that we already know there is at least 1 simplex.) if (dest(trying).simp == trying.simp && dest(trying).facet < dim && trying.simp > 0) if (dest(0, 0).simp != 0) dest(trying).facet = dim; } } args->use(0, 0, args->useArgs); delete args; return 0; } } // namespace regina regina-4.95/engine/census/ngenericfacetpairing.h000644 000765 000024 00000105236 12236713375 021714 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngenericfacetpairing.h * \brief Deals with pairing off facets of simplices in a census of * \a n-dimensional triangulations. */ #ifndef __NGENERALFACETPAIRING_H #ifndef __DOXYGEN #define __NGENERALFACETPAIRING_H #endif #include #include #include "regina-core.h" #include "generic/dimtraits.h" #include "generic/nfacetspec.h" #include "utilities/nbooleans.h" #include "utilities/nthread.h" namespace regina { /** * \weakgroup census * @{ */ /** * A dimension-agnostic base class that represents a pairwise * matching of facets of \a dim-dimensional simplices. * Each dimension that Regina works with (2 and 3) offers its own subclass * with richer functionality; users typically do not need to work with this * template base class directly. * * Given a fixed number of \a dim-dimensional simplices, * each facet of each simplex is either paired with some other simplex facet * (which is in turn paired with it) or remains unmatched. * A simplex facet cannot be paired with itself. * * Such a matching models part of the structure of a \a dim-manifold * triangulation, in which each simplex facet is either glued to some * other simplex facet (which is in turn glued to it) or is an unglued * boundary facet. * * Note that if this pairing is used to construct an actual * triangulation, the individual gluing permutations will still need to * be specified; they are not a part of this structure. * * \pre The dimension argument \a dim is either 2 or 3. * * \ifacespython Not present, though the dimension-specific subclasses * (such as NFacePairing) are available for Python users. * * \testpart */ template class REGINA_API NGenericFacetPairing : public NThread { public: typedef typename DimTraits::FacetPairing FacetPairing; /**< The facet pairing class specific to this dimension. This is typically a subclass of NGenericFacetPairing. */ typedef typename DimTraits::Isomorphism Isomorphism; /**< The isomorphism class used for triangulations in this dimension. */ typedef typename DimTraits::Perm Perm; /**< The permutation class used to glue together facets of simplices when building triangulations in this dimension. */ typedef typename DimTraits::Simplex Simplex; /**< The class that represents a top-level simplex of a triangulation in this dimension. */ typedef typename DimTraits::Triangulation Triangulation; /**< The triangulation class specific to this dimension. */ /** * A list of isomorphisms on pairwise matchings of simplex facets. * * Specifically, such an isomorphism can be used to convert one * pairwise matching of simplex facets (as described by class * NGenericFacetPairing) into another. */ typedef std::list IsoList; /** * A routine that can do arbitrary processing upon a facet pairing * and its automorphisms. Such routines are used to process * pairings that are found when running findAllPairings(). * * The first parameter passed should be a facet pairing * (this should not be deallocated by this routine). * The second parameter should be a list of all automorphisms of * this pairing (this should not be deallocated either). * The third parameter may contain arbitrary data as passed to * findAllPairings(). * * It may be assumed that the pairing is of the appropriate * dimension-specific subclass (such as NFacePairing for * dimension three, or Dim2EdgePairing for dimension two). * * Note that the first two parameters passed might be \c null to * signal that facet pairing generation has finished. */ typedef void (*Use)(const FacetPairing*, const IsoList*, void*); protected: unsigned size_; /**< The number of simplices under consideration. */ NFacetSpec* pairs_; /**< The other facet to which each simplex facet is paired. If a simplex facet is left unmatched, the corresponding element of this array will be boundary (as returned by NFacetSpec::isBoundary()). If the destination for a particular facet has not yet been decided, the facet will be paired to itself. */ public: /** * \name Constructors and Destructors */ /*@{*/ /** * Creates a new facet pairing that is a clone of the given * facet pairing. * * @param cloneMe the facet pairing to clone. */ NGenericFacetPairing(const NGenericFacetPairing& cloneMe); /** * Creates the facet pairing of given triangulation. This * is the facet pairing that describes how the facets of * simplices in the given triangulation are joined together, as * described in the class notes. * * \pre The given triangulation is not empty. * * @param tri the triangulation whose facet pairing should * be constructed. */ NGenericFacetPairing(const Triangulation& tri); /** * Deallocates any memory used by this structure. */ virtual ~NGenericFacetPairing(); /*@}*/ /** * \name Basic Queries */ /*@{*/ /** * Returns the number of simplices whose facets are described by * this facet pairing. * * @return the number of simplices under consideration. */ unsigned size() const; /** * Returns the other facet to which the given simplex facet is * paired. If the given facet is left deliberately unmatched, * the value returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return the other facet to which the given facet is paired. */ const NFacetSpec& dest(const NFacetSpec& source) const; /** * Returns the other facet to which the given simplex facet is * paired. If the given facet is left deliberately unmatched, * the value returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return the other facet to which the given facet is paired. */ const NFacetSpec& dest(unsigned simp, unsigned facet) const; /** * Returns the other facet to which the given simplex facet is * paired. This is a convenience operator whose behaviour is * identical to that of dest(const NFacetSpec&). * * If the given facet is left deliberately unmatched, the value * returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return the other facet to which the given facet is paired. */ const NFacetSpec& operator [](const NFacetSpec& source) const; /** * Determines whether the given simplex facet has been left * deliberately unmatched. * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return \c true if the given facet has been left unmatched, or * \c false if the given facet is paired with some other facet. */ bool isUnmatched(const NFacetSpec& source) const; /** * Determines whether the given simplex facet has been left * deliberately unmatched. * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return \c true if the given facet has been left unmatched, or * \c false if the given facet is paired with some other facet. */ bool isUnmatched(unsigned simp, unsigned facet) const; /** * Determines whether this facet pairing is closed. * A closed facet pairing has no unmatched facets. */ bool isClosed() const; /*@}*/ /** * \name Isomorphic Representations */ /*@{*/ /** * Determines whether this facet pairing is in canonical form, * i.e., is a lexicographically minimal representative of its * isomorphism class. * * Isomorphisms of facet pairings correspond to relabellings of * simplices and relabellings of the (\a dim + 1) facets within each * simplex. * * Facet pairings are ordered by lexicographical comparison of * dest(0,0), dest(0,1), ..., * dest(size()-1,\a dim). * * \pre This facet pairing is connected, i.e., it is possible * to reach any simplex from any other simplex via a * series of matched facet pairs. * * @return \c true if and only if this facet pairing is in * canonical form. */ bool isCanonical() const; /** * Fills the given list with the set of all combinatorial * automorphisms of this facet pairing. * * An automorphism is a relabelling of the simplices and/or a * renumbering of the (\a dim + 1) facets of each simplex resulting * in precisely the same facet pairing. * * This routine uses optimisations that can cause unpredictable * breakages if this facet pairing is not in canonical form. * * The automorphisms placed in the given list will be newly * created; it is the responsibility of the caller of this * routine to deallocate them. * * \pre The given list is empty. * \pre This facet pairing is connected, i.e., it is possible * to reach any simplex from any other simplex via a * series of matched facet pairs. * \pre This facet pairing is in canonical form as described by * isCanonical(). * * \ifacespython Not present, even in the dimension-specific * subclasses. * * @param list the list into which the newly created automorphisms * will be placed. */ void findAutomorphisms(IsoList& list) const; /*@}*/ /** * \name Input and Output */ /*@{*/ /** * A deprecated alias for str(), which returns a human-readable * representation of this facet pairing. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type str() instead. * * @return a string representation of this pairing. */ std::string toString() const; /** * Returns a human-readable representation of this facet pairing. * * The string returned will contain no newlines. * * \ifacespython This implements the __str__() function. * * @return a string representation of this pairing. */ std::string str() const; /** * Returns a text-based representation of this facet pairing that can be * used to reconstruct the facet pairing. This reconstruction is * done through routine fromTextRep(). * * The text produced is not particularly readable; for a * human-readable text representation, see routine str() instead. * * The string returned will contain no newlines. * * @return a text-based representation of this facet pairing. */ std::string toTextRep() const; /** * Writes the graph corresponding to this facet pairing in * the Graphviz DOT language. Every vertex of this graph * represents a simplex, and every edge represents a pair of * simplex facets that are joined together. Note that for a * closed triangulation this graph will be entirely (\a dim + 1)-valent; * for triangulations with boundary facets, some graph vertices * will have degree \a dim or less. * * The graph can either be written as a complete DOT graph, or * as a clustered subgraph within some larger DOT graph * (according to whether the argument \a subgraph is passed as * \c false or \c true). * * If a complete DOT graph is being written, the output may be * used as a standalone DOT file ready for use with Graphviz. * * If a subgraph is being written, the output will contain a * single \c subgraph section that should be inserted into some * larger DOT file. Note that the output generated by * writeDotHeader(), followed by one or more subgraphs and then * a closing curly brace will suffice. The subgraph name will * begin with the string \c pairing_. * * The argument \a prefix will be prepended to the name of each * graph vertex, and will also be used in the name of the graph * or subgraph. Using unique prefixes becomes important if you * are calling writeDot() several times to generate several * subgraphs for use in a single DOT file. If the \a prefix * argument is null or empty then a default prefix will be used. * * Note that this routine generates undirected graphs, not * directed graphs. The final DOT file should be used with * either the \a neato or \a fdp programs shipped with Graphviz. * * \ifacespython The \a out argument is not present; instead * standard output is assumed. * * @param out the output stream to which to write. * @param prefix a string to prepend to the name of each graph * vertex, and to include in the graph or subgraph name; see * above for details. * @param subgraph \c false if a complete standalone DOT graph * should be output, or \c true if a clustered subgraph should * be output for use in some larger DOT file. * @param labels indicates whether graph vertices will be * labelled with the corresponding simplex numbers. * This feature is currently experimental, and the default is * \c false. * * @see http://www.graphviz.org/ */ void writeDot(std::ostream& out, const char* prefix = 0, bool subgraph = false, bool labels = false) const; /** * Returns a Graphviz DOT representation of the graph that * describes this facet pairing. * * This routine simply returns the output of writeDot() as a * string, instead of dumping it to an output stream. * * All arguments are the same as for writeDot(); see the * writeDot() notes for further details. * * @return the output of writeDot(), as outlined above. */ std::string dot(const char* prefix = 0, bool subgraph = false, bool labels = false) const; /*@}*/ /** * \name Internal Routines */ /*@{*/ /** * Internal to findAllPairings(). This routine should never be * called directly. * * Performs the actual generation of facet pairings, possibly as a * separate thread. At most one copy of this routine should be * running at any given time for a particular NGenericFacetPairing * instance. * * \ifacespython Not present, even in the dimension-specific * subclasses. * * \pre This object is known to be of the dimension-specific subclass * FacetPairing, not an instance of the parent class * NGenericFacetPairing. * * @param param a structure containing the parameters that were * passed to findAllPairings(). * @return the value 0. */ void* run(void* param); /*@}*/ /** * Reconstructs a facet pairing from a text-based representation. * This text-based representation must be in the format produced * by routine toTextRep(). * * The facet pairing returned will be newly constructed; it is the * responsibility of the caller of this routine to deallocate it. * * \pre The facet pairing to be reconstructed involves at least * one simplex. * * @param rep a text-based representation of a facet pairing, as * produced by routine toTextRep(). * @return the corresponding newly constructed facet pairing, or * \c null if the given text-based representation was invalid. */ static FacetPairing* fromTextRep(const std::string& rep); /** * Writes header information for a Graphviz DOT file that will * describe the graphs for one or more facet pairings. See * the writeDot() documentation for further information on such * graphs. * * The output will be in the Graphviz DOT language, and will * include appropriate display settings for graphs, edges and * nodes. The opening brace for a \c graph section of the DOT * file is included. * * This routine may be used with writeDot() to generate a single * DOT file containing the graphs for several different facet * pairings. A complete DOT file can be produced by calling * this routine, then calling writeDot() in subgraph mode for * each facet pairing, then outputting a final closing curly brace. * * Note that if you require a DOT file containing the graph for * only a single facet pairing, this routine is unnecessary; you * may simply call writeDot() in full graph mode instead. * * This routine is suitable for generating undirected graphs, not * directed graphs. The final DOT file should be used with * either the \a neato or \a fdp programs shipped with Graphviz. * * \ifacespython The \a out argument is not present; instead * standard output is assumed. * * @param out the output stream to which to write. * @param graphName the name of the graph in the DOT file. * If this is null or empty then a default graph name will be used. * * @see http://www.graphviz.org/ */ static void writeDotHeader(std::ostream& out, const char* graphName = 0); /** * Returns header information for a Graphviz DOT file that will * describe the graphs for one or more facet pairings. * * This routine simply returns the output of writeDotHeader() as * a string, instead of dumping it to an output stream. * * All arguments are the same as for writeDotHeader(); see the * writeDotHeader() notes for further details. * * @return the output of writeDotHeader(), as outlined above. */ static std::string dotHeader(const char* graphName = 0); /** * Generates all possible facet pairings satisfying the given * constraints. Only connected facet pairings (pairings in which * each simplex can be reached from each other via a series of * individual matched facets) will be produced. * * Each facet pairing will be produced precisely once up to * isomorphism. Facet pairings are considered isomorphic if they * are related by a relabelling of the simplices and/or a * renumbering of the (\a dim + 1) facets of each simplex. Each facet * pairing that is generated will be a lexicographically minimal * representative of its isomorphism class, i.e., will be in * canonical form as described by isCanonical(). * * For each facet pairing that is generated, routine \a use (as * passed to this function) will be called with that pairing and * its automorphisms as arguments. Each pairing will be of the * appropriate dimension-specific subclass (for instance, * NFacePairing for dimension three, or Dim2EdgePairing for * dimension two). * * Once the generation of facet pairings has finished, routine * \a use will be called once more, this time with \c null as its * first two arguments (for the facet pairing and its automorphisms). * * The facet pairing generation may be run in the current thread * or as a separate thread. * * Because this class cannot represent an empty facet pairing, * if the argument \a nSimplices is zero then no facet pairings * will be generated at all. * * \todo \optlong When generating facet pairings, do some checking to * eliminate cases in which simplex (\a k > 0) can be swapped * with simplex 0 to produce a smaller representation of the same * pairing. * \todo \feature Allow cancellation of facet pairing generation. * * \ifacespython Not present, even in the dimension-specific * subclasses. * * @param nSimplices the number of simplices whose facets should * be (potentially) matched. * @param boundary determines whether any facets may be left * unmatched. This set should contain \c true if pairings with at * least one unmatched facet are to be generated, and should contain * \c false if pairings with no unmatched facets are to be generated. * @param nBdryFacets specifies the precise number of facets that * should be left unmatched. If this parameter is negative, it * is ignored and no additional restriction is imposed. If * parameter \a boundary does not contain \c true, this parameter * is likewise ignored. If parameter \a boundary does contain * true and this parameter is non-negative, only pairings with * precisely this many unmatched facets will be generated. * In particular, if this parameter is positive then pairings * with no unmatched facets will not be produced irrespective of * whether \c false is contained in parameter \a boundary. * Note that, in order to produce any pairings at all, this parameter * must be of the same parity as nSimplices * (dim+1), * and can be at most (dim-1) * nSimplices + 2. * @param use the function to call upon each facet pairing that is * found. The first parameter passed to this function will be a * facet pairing. The second parameter will be a list of all its * automorphisms (relabellings of simplices and individual * simplex facets that produce the exact same pairing). * The third parameter will be parameter \a useArgs as was passed * to this routine. * @param useArgs the pointer to pass as the final parameter for * the function \a use which will be called upon each pairing found. * @param newThread \c true if facet pairing generation should be * performed in a separate thread, or \c false if generation * should take place in the current thread. If this parameter is * \c true, this routine will exit immediately (after spawning * the new thread). * @return \c true if the new thread was successfully started (or * if facet pairing generation has taken place in the current thread), * or \c false if the new thread could not be started. */ static bool findAllPairings(unsigned nSimplices, NBoolSet boundary, int nBdryFacets, Use use, void* useArgs = 0, bool newThread = false); protected: /** * Creates a new facet pairing. All internal arrays will be * allocated but not initialised. * * \pre \a size is at least 1. * * @param size the number of simplices under * consideration in this new facet pairing. */ NGenericFacetPairing(unsigned size); /** * Returns the other facet to which the given simplex facet is * paired. If the given facet is left deliberately unmatched, the * value returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return the other facet to which the given facet is paired. */ NFacetSpec& dest(const NFacetSpec& source); /** * Returns the other facet to which the given simplex facet is * paired. If the given facet is left deliberately unmatched, the * value returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return the other facet to which the given facet is paired. */ NFacetSpec& dest(unsigned simp, unsigned facet); /** * Returns the other facet to which the given simplex facet is * paired. This is a convenience operator whose behaviour is * identical to that of dest(const NFacetSpec&). * * If the given facet is left deliberately unmatched, the value * returned will be boundary (as returned by * NFacetSpec::isBoundary()). * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return the other facet to which the given facet is paired. */ NFacetSpec& operator [](const NFacetSpec& source); /** * Determines whether the matching for the given simplex facet * has not yet been determined. This is signalled by a facet * matched to itself. * * \pre The given facet is a real simplex facet (not boundary, * before-the-start or past-the-end). * * @param source the facet under investigation. * @return \c true if the matching for the given facet has not yet * been determined, or \c false otherwise. */ bool noDest(const NFacetSpec& source) const; /** * Determines whether the matching for the given simplex facet * has not yet been determined. This is signalled by a facet * matched to itself. * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return \c true if the matching for the given facet has not yet * been determined, or \c false otherwise. */ bool noDest(unsigned simp, unsigned facet) const; /** * Determines whether this facet pairing is in canonical * (smallest lexicographical) form, given a small set of * assumptions. * * If this facet pairing is in canonical form, the given list * will be filled with the set of all combinatorial automorphisms * of this facet pairing. If not, the given list will be left empty. * * \pre The given list is empty. * \pre For each simplex \a t, the only case in which * dest(t,i) is greater than dest(t,i+1) is where * facets (t,i) and (t,i+1) are paired together. * \pre For each simplex \a t > 0, it is true that * dest(t,0).simp < t. * \pre The sequence dest(1,0), dest(2,0), * ..., dest(n-1,0) is strictly increasing, where * \a n is the total number of simplices under investigation. * * @param list the list into which automorphisms will be placed * if appropriate. * @return \c true if and only if this facet pairing is in * canonical form. */ bool isCanonicalInternal(IsoList& list) const; private: /** * Holds the arguments passed to findAllPairings(). */ struct Args { NBoolSet boundary; int nBdryFacets; Use use; void* useArgs; }; }; /*@}*/ // Inline functions for NGenericFacetPairing template inline NGenericFacetPairing::NGenericFacetPairing(unsigned size) : size_(size), pairs_(new NFacetSpec[size * (dim + 1)]) { } template inline NGenericFacetPairing::~NGenericFacetPairing() { delete[] pairs_; } template inline unsigned NGenericFacetPairing::size() const { return size_; } template inline const NFacetSpec& NGenericFacetPairing::dest( const NFacetSpec& source) const { return pairs_[(dim + 1) * source.simp + source.facet]; } template inline const NFacetSpec& NGenericFacetPairing::dest( unsigned simp, unsigned facet) const { return pairs_[(dim + 1) * simp + facet]; } template inline const NFacetSpec& NGenericFacetPairing::operator []( const NFacetSpec& source) const { return pairs_[(dim + 1) * source.simp + source.facet]; } template inline bool NGenericFacetPairing::isUnmatched( const NFacetSpec& source) const { return pairs_[(dim + 1) * source.simp + source.facet].isBoundary(size_); } template inline bool NGenericFacetPairing::isUnmatched( unsigned simp, unsigned facet) const { return pairs_[(dim + 1) * simp + facet].isBoundary(size_); } template inline NFacetSpec& NGenericFacetPairing::dest( const NFacetSpec& source) { return pairs_[(dim + 1) * source.simp + source.facet]; } template inline NFacetSpec& NGenericFacetPairing::dest( unsigned simp, unsigned facet) { return pairs_[(dim + 1) * simp + facet]; } template inline NFacetSpec& NGenericFacetPairing::operator []( const NFacetSpec& source) { return pairs_[(dim + 1) * source.simp + source.facet]; } template inline bool NGenericFacetPairing::noDest( const NFacetSpec& source) const { return dest(source) == source; } template inline bool NGenericFacetPairing::noDest( unsigned simp, unsigned facet) const { NFacetSpec& f = pairs_[(dim + 1) * simp + facet]; return (f.simp == static_cast(simp) && f.facet == static_cast(facet)); } template inline void NGenericFacetPairing::findAutomorphisms( typename NGenericFacetPairing::IsoList& list) const { isCanonicalInternal(list); } template inline std::string NGenericFacetPairing::toString() const { return str(); } } // namespace regina #endif regina-4.95/engine/census/ngenericfacetpairing.tcc000644 000765 000024 00000004714 12234011536 022221 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngenericfacetpairing.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use ngenericfacetpairing-impl.h instead. #include "census/ngenericfacetpairing-impl.h" regina-4.95/engine/census/ngenericgluingperms-impl.h000644 000765 000024 00000013326 12234011536 022535 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* Template definitions for ngenericgluingperms.h. */ #include #include #include "census/ngenericgluingperms.h" #include "utilities/stringutils.h" namespace regina { template NGenericGluingPerms::NGenericGluingPerms( const NGenericGluingPerms& cloneMe) : pairing_(cloneMe.pairing_), inputError_(false) { unsigned nSimp = cloneMe.size(); permIndices_ = new int[nSimp * (dim + 1)]; std::copy(cloneMe.permIndices_, cloneMe.permIndices_ + nSimp * (dim + 1), permIndices_); } template typename NGenericGluingPerms::Triangulation* NGenericGluingPerms::triangulate() const { unsigned nSimp = size(); Triangulation* ans = new Triangulation; Simplex** simp = new Simplex*[nSimp]; unsigned t, facet; for (t = 0; t < nSimp; ++t) simp[t] = ans->newSimplex(); for (t = 0; t < nSimp; ++t) for (facet = 0; facet <= dim; ++facet) if ((! pairing_->isUnmatched(t, facet)) && (! simp[t]->adjacentSimplex(facet))) simp[t]->joinTo(facet, simp[pairing_->dest(t, facet).simp], gluingPerm(t, facet)); delete[] simp; return ans; } template int NGenericGluingPerms::gluingToIndex(const NFacetSpec& source, const Perm& gluing) const { Perm permSn_1 = Perm(pairing_->dest(source).facet, dim) * gluing * Perm(source.facet, dim); return (std::find(Perm::Sn_1, Perm::Sn_1 + Perm::nPerms_1, permSn_1) - Perm::Sn_1); } template int NGenericGluingPerms::gluingToIndex(unsigned simp, unsigned facet, const Perm& gluing) const { Perm permSn_1 = Perm(pairing_->dest(simp, facet).facet, dim) * gluing * Perm(facet, dim); return (std::find(Perm::Sn_1, Perm::Sn_1 + Perm::nPerms_1, permSn_1) - Perm::Sn_1); } template void NGenericGluingPerms::dumpData(std::ostream& out) const { out << pairing_->toTextRep() << std::endl; unsigned simp, facet; for (simp = 0; simp < size(); ++simp) for (facet = 0; facet <= dim; ++facet) { if (simp || facet) out << ' '; out << permIndex(simp, facet); } out << std::endl; } template NGenericGluingPerms::NGenericGluingPerms(std::istream& in) : pairing_(0), permIndices_(0), inputError_(false) { // Remember that we can safely abort before allocating arrays, since C++ // delete tests for nullness. std::string line; // Skip initial whitespace to find the facet pairing. while (true) { std::getline(in, line); if (in.eof()) { inputError_ = true; return; } line = regina::stripWhitespace(line); if (line.length() > 0) break; } pairing_ = FacetPairing::fromTextRep(line); if (! pairing_) { inputError_ = true; return; } unsigned nSimps = pairing_->size(); if (nSimps == 0) { inputError_ = true; return; } permIndices_ = new int[nSimps * (dim + 1)]; unsigned simp, facet; for (simp = 0; simp < nSimps; ++simp) for (facet = 0; facet <= dim; ++facet) { in >> permIndex(simp, facet); // Don't test the range of permIndex(simp, facet) since the // gluing permutation set could still be under construction. } // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } } // namespace regina regina-4.95/engine/census/ngenericgluingperms.h000644 000765 000024 00000053412 12234011536 021576 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngenericgluingperms.h * \brief Deals with selecting gluing permutations to complement a * particular pairing of facets of simplices in an n-manifold triangulation. */ #ifndef __NGENERICGLUINGPERMS_H #ifndef __DOXYGEN #define __NGENERICGLUINGPERMS_H #endif #include "regina-core.h" #include "generic/dimtraits.h" #include "generic/nfacetspec.h" namespace regina { /** * \weakgroup census * @{ */ /** * A dimension-agnostic base class that represents a specific set * of gluing permutations to complement a particular pairwise matching of * simplex facets. Each dimension that Regina works with (2, 3 and 4) * offers its own subclass, in some cases with richer functionality. * Users should not need to work with this template base class directly. * * Given a pairwise * matching of facets (as described by class NGenericFacetPairing), * each facet that is matched with some other facet will have an associated * permutation of (\a dim + 1) elements. * * If a facet is matched with some other facet, the two associated * permutations in this set will be inverses. If a facet is left * deliberately unmatched, it will have no associated permutation in this set. * * Such a set of permutations models part of the structure of a * triangulation, in which each simplex facet that is glued to another * facet has a corresponding gluing permutation (and the matched facet has * the inverse gluing permutation). * * \pre The dimension argument \a dim is either 2, 3 or 4. * * \ifacespython Not present. */ template class REGINA_API NGenericGluingPerms { public: typedef typename DimTraits::FacetPairing FacetPairing; typedef typename DimTraits::Perm Perm; typedef typename DimTraits::Simplex Simplex; typedef typename DimTraits::Triangulation Triangulation; protected: const FacetPairing* pairing_; /**< The facet pairing that this permutation set complements. This is guaranteed to be the minimal representative of its facet pairing isomorphism class. */ int* permIndices_; /**< The index into array Perm::Sn_1 describing how each simplex facet is glued to its partner. Note that this is not a gluing permutation as such but rather a permutation of 0,...,\a dim-1 only (see the routines gluingToIndex() and indexToGluing() for conversions). If a permutation has not yet been selected (e.g., if this permutation set is still under construction) then this index is -1. */ bool inputError_; /**< Has an error occurred during construction from an input stream? */ public: /** * Creates a new set of gluing permutations that is a clone of * the given permutation set. * * @param cloneMe the gluing permutations to clone. */ NGenericGluingPerms(const NGenericGluingPerms& cloneMe); /** * Reads a new set of gluing permutations from the given input * stream. This routine reads data in the format written by * dumpData(). * * If the data found in the input stream is invalid or * incorrectly formatted, the routine inputError() will return * \c true but the contents of this object will be otherwise * undefined. * * \warning The data format is liable to change between * Regina releases. Data in this format should be used on a * short-term temporary basis only. * * @param in the input stream from which to read. */ NGenericGluingPerms(std::istream& in); /** * Deallocates any memory used by this structure. */ virtual ~NGenericGluingPerms(); /** * Was an error found during construction from an input stream? * * This routine returns \c true if an input stream constructor was * used to create this object but the data in the input stream * was invalid or incorrectly formatted. * * If a different constructor was called (i.e., no input stream * was used), then this routine will always return \c false. * * @return \c true if an error occurred during construction from * an input stream, or \c false otherwise. */ bool inputError() const; /** * Returns the total number of simplices under consideration. * * @return the number of simplices under consideration. */ unsigned size() const; /** * Returns the specific pairing of simplex facets that this * set of gluing permutations complements. * * @return the corresponding simplex facet pairing. */ const FacetPairing* getFacetPairing() const; /** * Returns the gluing permutation associated with the given * simplex facet. * * \pre The given facet is actually paired with some other facet in * the underlying pairwise matching (see routine getFacetPairing()). * \pre The given facet is a real simplex * facet (not boundary, before-the-start or past-the-end). * * @param source the simplex facet under investigation. * @return the associated gluing permutation. */ Perm gluingPerm(const NFacetSpec& source) const; /** * Returns the gluing permutation associated with the given * simplex facet. * * \pre The given facet is actually paired with some other facet in * the underlying pairwise matching (see routine getFacetPairing()). * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return the associated gluing permutation. */ Perm gluingPerm(unsigned simp, unsigned facet) const; /** * Returns a newly created triangulation as modelled by this set * of gluing permutations and the associated simplex facet * pairing. * * Each matched pair of facets and their associated permutations * will be realised as two simplex facets in the triangulation glued * together with the corresponding gluing permutation. Each * unmatched facet will be realised as a boundary facet in the * triangulation. * * It is the responsibility of the caller of this routine to * delete this triangulation once it is no longer required. * * @return a newly created triangulation modelled by this structure. */ Triangulation* triangulate() const; /** * Dumps all internal data in a plain text format to the given * output stream. This object can be recreated from this text * data by calling the input stream constructor for this class. * * This routine may be useful for transferring objects from * one processor to another. * * Note that subclass data is written after superclass data, so * it is safe to dump data from a subclass and then recreate a * new superclass object from that data (though subclass-specific * information will of course be lost). * * \warning The data format is liable to change between * Regina releases. Data in this format should be used on a * short-term temporary basis only. * * @param out the output stream to which the data should be * written. */ virtual void dumpData(std::ostream& out) const; protected: /** * Creates a new permutation set. All internal arrays will be * allocated but not initialised. * * \pre The given facet pairing is connected, i.e., it is possible * to reach any simplex from any other simplex via a * series of matched facet pairs. * \pre The given facet pairing is in canonical form as described * by NGenericFacetPairing::isCanonical(). Note that all facet pairings * constructed by NGenericFacetPairing::findAllPairings() are of this * form. * * @param pairing the specific pairing of simplex facets * that this permutation set will complement. */ NGenericGluingPerms(const FacetPairing* pairing); /** * Returns the index into array Perm::Sn_1 describing how the * the given facet is joined to its partner. * * Note that this permutation is not a gluing permutation as such, * but rather a permutation of 0,...,\a dim-1 only. For a real facet * gluing permutation, see routine gluingPerm(). * * \pre The given facet is a real simplex * facet (not boundary, before-the-start or past-the-end). * * @param source the simplex facet under investigation. * @return a reference to the corresponding array index. */ int& permIndex(const NFacetSpec& source); /** * Returns the index into array Perm::Sn_1 describing how the * the given facet is joined to its partner. * * Note that this permutation is not a gluing permutation as such, * but rather a permutation of 0,...,\a dim-1 only. For a real facet * gluing permutation, see routine gluingPerm(). * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return a reference to the corresponding array index. */ int& permIndex(unsigned simp, unsigned facet); /** * Returns the index into array Perm::Sn_1 describing how the * the given facet is joined to its partner. * * Note that this permutation is not a gluing permutation as such, * but rather a permutation of 0,...,\a dim-1 only. For a real facet * gluing permutation, see routine gluingPerm(). * * \pre The given facet is a real simplex * facet (not boundary, before-the-start or past-the-end). * * @param source the simplex facet under investigation. * @return a reference to the corresponding array index. */ const int& permIndex(const NFacetSpec& source) const; /** * Returns the index into array Perm::Sn_1 describing how the * the given facet is joined to its partner. * * Note that this permutation is not a gluing permutation as such, * but rather a permutation of 0,...,\a dim-1 only. For a real facet * gluing permutation, see routine gluingPerm(). * * @param simp the simplex under investigation (this must be * strictly less than the total number of simplices under * consideration). * @param facet the facet of the given simplex under * investigation (between 0 and \a dim inclusive). * @return a reference to the corresponding array index. */ const int& permIndex(unsigned simp, unsigned facet) const; /** * Returns the index into array Perm::Sn_1 corresponding to * the given gluing permutation from the given facet to its * partner. This need not be the index into Perm::Sn_1 that * is currently stored for the given facet. * * Indices into array Perm::Sn_1 are stored internally in the * array \a permIndices_. Full gluing permutations on the other * hand are used in constructing triangulations. * * \pre The given simplex facet has a partner according to * the underlying facet pairing, i.e., is not a boundary facet. * \pre If the given simplex facet and its partner are facets * \a x and \a y of their respective simplices, then the * given gluing permutation maps \a x to \a y. * * @param source the simplex facet under investigation. * @param gluing a possible gluing permutation from the given * simplex facet to its partner according to the underlying * facet pairing. * @return the index into Perm::Sn_1 corresponding to the * given gluing permutation; this will be between 0 and \a dim!-1 * inclusive. */ int gluingToIndex(const NFacetSpec& source, const Perm& gluing) const; /** * Returns the index into array Perm::Sn_1 corresponding to * the given gluing permutation from the given facet to its * partner. This need not be the index into Perm::Sn_1 that * is currently stored for the given facet. * * Indices into array Perm::Sn_1 are stored internally in the * array \a permIndices_. Full gluing permutations on the other * hand are used in constructing triangulations. * * \pre The given simplex facet has a partner according to * the underlying facet pairing, i.e., is not a boundary facet. * \pre If the given simplex facet and its partner are facets * \a x and \a y of their respective simplices, then the * given gluing permutation maps \a x to \a y. * * @param simp the simplex under investigation; this must be * strictly less than the total number of simplices under * consideration. * @param facet the facet of the given simplex under * investigation; this must be between 0 and \a dim inclusive. * @param gluing a possible gluing permutation from the given * simplex facet to its partner according to the underlying * facet pairing. * @return the index into Perm::Sn_1 corresponding to the * given gluing permutation; this will be between 0 and \a dim!-1 * inclusive. */ int gluingToIndex(unsigned simp, unsigned facet, const Perm& gluing) const; /** * Returns the gluing permutation from the given facet to its * partner that corresponds to the given index into array * Perm::Sn_1. This index into Perm::Sn_1 need not * be the index that is currently stored for the given facet. * * Indices into array Perm::Sn_1 are stored internally in the * array \a permIndices_. Full gluing permutations on the other * hand are used in constructing triangulations. * * If the given simplex facet and its partner according to * the underlying facet pairing are facets \a x and \a y of their * respective simplices, then the resulting gluing permutation * will map \a x to \a y. * * \pre The given simplex facet has a partner according to * the underlying facet pairing, i.e., is not a boundary facet. * * @param source the simplex facet under investigation. * @param index an index into Perm::Sn_1; this must be * between 0 and \a dim!-1 inclusive. * @return the gluing permutation corresponding to the given * index into Perm::Sn_1. */ Perm indexToGluing(const NFacetSpec& source, int index) const; /** * Returns the gluing permutation from the given facet to its * partner that corresponds to the given index into array * Perm::Sn_1. This index into Perm::Sn_1 need not * be the index that is currently stored for the given facet. * * Indices into array Perm::Sn_1 are stored internally in the * array \a permIndices_. Full gluing permutations on the other * hand are used in constructing triangulations. * * If the given simplex facet and its partner according to * the underlying facet pairing are facets \a x and \a y of their * respective simplices, then the resulting gluing permutation * will map \a x to \a y. * * \pre The given simplex facet has a partner according to * the underlying facet pairing, i.e., is not a boundary facet. * * @param simp the simplex under investigation; this must be * strictly less than the total number of simplices under * consideration. * @param facet the facet of the given simplex under * investigation; this must be between 0 and \a dim inclusive. * @param index an index into Perm::Sn_1; this must be * between 0 and \a dim!-1 inclusive. * @return the gluing permutation corresponding to the given * index into Perm::Sn_1. */ Perm indexToGluing(unsigned simp, unsigned facet, int index) const; }; /*@}*/ // Inline functions for NGenericGluingPerms template inline NGenericGluingPerms::NGenericGluingPerms( const FacetPairing* pairing) : pairing_(pairing), permIndices_(new int[pairing->size() * (dim + 1)]), inputError_(false) { } template inline NGenericGluingPerms::~NGenericGluingPerms() { delete[] permIndices_; } template inline bool NGenericGluingPerms::inputError() const { return inputError_; } template inline unsigned NGenericGluingPerms::size() const { return pairing_->size(); } template inline const typename NGenericGluingPerms::FacetPairing* NGenericGluingPerms::getFacetPairing() const { return pairing_; } template inline typename NGenericGluingPerms::Perm NGenericGluingPerms::gluingPerm(const NFacetSpec& source) const { return indexToGluing(source, permIndex(source)); } template inline typename NGenericGluingPerms::Perm NGenericGluingPerms::gluingPerm( unsigned simp, unsigned facet) const { return indexToGluing(simp, facet, permIndex(simp, facet)); } template inline int& NGenericGluingPerms::permIndex(const NFacetSpec& source) { return permIndices_[(dim + 1) * source.simp + source.facet]; } template inline int& NGenericGluingPerms::permIndex(unsigned simp, unsigned facet) { return permIndices_[(dim + 1) * simp + facet]; } template inline const int& NGenericGluingPerms::permIndex( const NFacetSpec& source) const { return permIndices_[(dim + 1) * source.simp + source.facet]; } template inline const int& NGenericGluingPerms::permIndex( unsigned simp, unsigned facet) const { return permIndices_[(dim + 1) * simp + facet]; } template inline typename NGenericGluingPerms::Perm NGenericGluingPerms::indexToGluing( const NFacetSpec& source, int index) const { return Perm(pairing_->dest(source).facet, dim) * Perm::Sn_1[index] * Perm(source.facet, dim); } template inline typename NGenericGluingPerms::Perm NGenericGluingPerms::indexToGluing( unsigned simp, unsigned facet, int index) const { return Perm(pairing_->dest(simp, facet).facet, dim) * Perm::Sn_1[index] * Perm(facet, dim); } } // namespace regina #endif regina-4.95/engine/census/ngenericgluingperms.tcc000644 000765 000024 00000004711 12234011536 022116 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngenericgluingperms.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use ngenericgluingperms-impl.h instead. #include "census/ngenericgluingperms-impl.h" regina-4.95/engine/census/ngluingperms.cpp000644 000765 000024 00000005713 12234011536 020575 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "census/ngluingperms.h" #include "census/ngenericgluingperms-impl.h" #include "triangulation/ntriangulation.h" namespace regina { // Instantiate all templates from the -impl.h file. template NGenericGluingPerms<3>::NGenericGluingPerms( const NGenericGluingPerms<3>&); template NGenericGluingPerms<3>::NGenericGluingPerms(std::istream&); template NTriangulation* NGenericGluingPerms<3>::triangulate() const; template int NGenericGluingPerms<3>::gluingToIndex( const NTetFace&, const NPerm4&) const; template int NGenericGluingPerms<3>::gluingToIndex( unsigned, unsigned, const NPerm4&) const; template void NGenericGluingPerms<3>::dumpData(std::ostream&) const; } // namespace regina regina-4.95/engine/census/ngluingperms.h000644 000765 000024 00000015267 12234011536 020247 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngluingperms.h * \brief Deals with selecting gluing permutations to complement a * particular pairing of tetrahedron faces. */ #ifndef __NGLUINGPERMS_H #ifndef __DOXYGEN #define __NGLUINGPERMS_H #endif #include "regina-core.h" #include "census/nfacepairing.h" #include "census/ngenericgluingperms.h" namespace regina { /** * \weakgroup census * @{ */ /** * Represents a specific set of gluing permutations to complement a * particular pairwise matching of tetrahedron faces. Given a * pairwise matching of faces (as described by class NFacePairing), each * face that is matched with some other face will have an associated * permutation of four elements (as described by class NPerm4). * * If a face is matched with some other face, the two associated * permutations in this set will be inverses. If a face is left * deliberately unmatched, it will have no associated permutation in this set. * * Such a set of permutations models part of the structure of a * triangulation, in which each tetrahedron face that is glued to another * face has a corresponding gluing permutation (and the matched face has * the inverse gluing permutation). * * \ifacespython Not present. */ class REGINA_API NGluingPerms : public NGenericGluingPerms<3> { public: /** * Creates a new set of gluing permutations that is a clone of * the given permutation set. * * @param cloneMe the gluing permutations to clone. */ NGluingPerms(const NGluingPerms& cloneMe); /** * Reads a new set of gluing permutations from the given input * stream. This routine reads data in the format written by * dumpData(). * * If the data found in the input stream is invalid or * incorrectly formatted, the routine inputError() will return * \c true but the contents of this object will be otherwise * undefined. * * \warning The data format is liable to change between * Regina releases. Data in this format should be used on a * short-term temporary basis only. * * @param in the input stream from which to read. */ NGluingPerms(std::istream& in); /** * Returns the total number of tetrahedra under consideration. * * \deprecated This routine has been renamed to size(). * This old name has been kept for backward compatibility, but * will be removed in some future version of Regina. * * @return the number of tetrahedra under consideration. */ unsigned getNumberOfTetrahedra() const; /** * Returns the specific pairing of tetrahedron faces that this * set of gluing permutations complements. * * \deprecated This routine has been renamed to getFacetPairing(). * This old name has been kept for backward compatibility, but * will be removed in some future version of Regina. * * @return the corresponding tetrahedron face pairing. */ const NFacePairing* getFacePairing() const; protected: /** * Creates a new permutation set. All internal arrays will be * allocated but not initialised. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * * @param pairing the specific pairing of tetrahedron faces * that this permutation set will complement. */ NGluingPerms(const NFacePairing* pairing); }; /*@}*/ // Inline functions for NGluingPerms inline NGluingPerms::NGluingPerms(const NGluingPerms& cloneMe) : NGenericGluingPerms<3>(cloneMe) { } inline NGluingPerms::NGluingPerms(std::istream& in) : NGenericGluingPerms<3>(in) { } inline NGluingPerms::NGluingPerms(const NFacePairing* pairing) : NGenericGluingPerms<3>(pairing) { } inline unsigned NGluingPerms::getNumberOfTetrahedra() const { return pairing_->getNumberOfTetrahedra(); } inline const NFacePairing* NGluingPerms::getFacePairing() const { return pairing_; } } // namespace regina #endif regina-4.95/engine/census/ngluingpermsearcher.cpp000644 000765 000024 00000047550 12235723624 022145 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "census/ncensus.h" #include "census/ngluingpermsearcher.h" #include "triangulation/ntriangulation.h" #include "utilities/memutils.h" namespace regina { const char NGluingPermSearcher::dataTag_ = 'g'; NGluingPermSearcher::NGluingPermSearcher( const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs) : NGluingPerms(pairing), autos_(autos), autosNew(autos == 0), orientableOnly_(orientableOnly), finiteOnly_(finiteOnly), whichPurge_(whichPurge), use_(use), useArgs_(useArgs), started(false), orientation(new int[pairing->getNumberOfTetrahedra()]) { // Generate the list of face pairing automorphisms if necessary. // This will require us to remove the const for a wee moment. if (autosNew) { const_cast(this)->autos_ = new NFacePairing::IsoList(); pairing->findAutomorphisms(const_cast(*autos_)); } // Initialise arrays. unsigned nTets = getNumberOfTetrahedra(); std::fill(orientation, orientation + nTets, 0); std::fill(permIndices_, permIndices_ + nTets* 4, -1); // Just fill the order[] array in a default left-to-right fashion. // Subclasses can rearrange things if they choose. order = new NTetFace[nTets * 2]; orderElt = orderSize = 0; NTetFace face, adj; for (face.setFirst(); ! face.isPastEnd(nTets, true); face++) if (! pairing->isUnmatched(face)) if (face < pairing->dest(face)) order[orderSize++] = face; } NGluingPermSearcher::~NGluingPermSearcher() { delete[] orientation; delete[] order; if (autosNew) { // We made them, so we'd better remove the const again and // delete them. NFacePairing::IsoList* autos = const_cast(autos_); std::for_each(autos->begin(), autos->end(), FuncDelete()); delete autos; } } NGluingPermSearcher* NGluingPermSearcher::bestSearcher( const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs) { // Use an optimised algorithm if possible. if (pairing->getNumberOfTetrahedra() >= 3) { if (finiteOnly && pairing->isClosed() && (whichPurge & NCensus::PURGE_NON_MINIMAL) && (whichPurge & NCensus::PURGE_NON_PRIME) && (orientableOnly || (whichPurge & NCensus::PURGE_P2_REDUCIBLE))) { // Closed prime minimal P2-irreducible triangulations with >= 3 // tetrahedra. return new NClosedPrimeMinSearcher(pairing, autos, orientableOnly, use, useArgs); } } if (finiteOnly) return new NCompactSearcher(pairing, autos, orientableOnly, whichPurge, use, useArgs); return new NGluingPermSearcher(pairing, autos, orientableOnly, finiteOnly, whichPurge, use, useArgs); } void NGluingPermSearcher::findAllPerms(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs) { NGluingPermSearcher* searcher = bestSearcher(pairing, autos, orientableOnly, finiteOnly, whichPurge, use, useArgs); searcher->runSearch(); delete searcher; } void NGluingPermSearcher::runSearch(long maxDepth) { // In this generation algorithm, each orientation is simply +/-1. unsigned nTetrahedra = getNumberOfTetrahedra(); if (maxDepth < 0) { // Larger than we will ever see (and in fact grossly so). maxDepth = nTetrahedra * 4 + 1; } if (! started) { // Search initialisation. started = true; // Do we in fact have no permutation at all to choose? if (maxDepth == 0 || pairing_->dest(0, 0).isBoundary(nTetrahedra)) { use_(this, useArgs_); use_(0, useArgs_); return; } orderElt = 0; orientation[0] = 1; } // Is it a partial search that has already finished? if (orderElt == orderSize) { if (isCanonical()) use_(this, useArgs_); use_(0, useArgs_); return; } // ---------- Selecting the individual gluing permutations ---------- int minOrder = orderElt; int maxOrder = orderElt + maxDepth; NTetFace face, adj; while (orderElt >= minOrder) { face = order[orderElt]; adj = (*pairing_)[face]; // TODO: Check for cancellation. // Move to the next permutation. // Be sure to preserve the orientation of the permutation if necessary. if ((! orientableOnly_) || adj.facet == 0) permIndex(face)++; else permIndex(face) += 2; // Are we out of ideas for this face? if (permIndex(face) >= 6) { // Yep. Head back down to the previous face. permIndex(face) = -1; permIndex(adj) = -1; orderElt--; continue; } // We are sitting on a new permutation to try. permIndex(adj) = NPerm4::invS3[permIndex(face)]; // Is this going to lead to an unwanted triangulation? if (mayPurge(face)) continue; if (! orientableOnly_) if (badEdgeLink(face)) continue; // Fix the orientation if appropriate. if (adj.facet == 0 && orientableOnly_) { // It's the first time we've hit this tetrahedron. if ((permIndex(face) + (face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1)) % 2 == 0) orientation[adj.simp] = -orientation[face.simp]; else orientation[adj.simp] = orientation[face.simp]; } // Move on to the next face. orderElt++; // If we're at the end, try the solution and step back. if (orderElt == orderSize) { // We in fact have an entire triangulation. // Run through the automorphisms and check whether our // permutations are in canonical form. if (isCanonical()) use_(this, useArgs_); // Back to the previous face. orderElt--; } else { // Not a full triangulation; just one level deeper. // We've moved onto a new face. // Be sure to get the orientation right. face = order[orderElt]; if (orientableOnly_ && pairing_->dest(face).facet > 0) { // permIndex(face) will be set to -1 or -2 as appropriate. adj = (*pairing_)[face]; if (orientation[face.simp] == orientation[adj.simp]) permIndex(face) = 1; else permIndex(face) = 0; if ((face.facet == 3 ? 0 : 1) + (adj.facet == 3 ? 0 : 1) == 1) permIndex(face) = (permIndex(face) + 1) % 2; permIndex(face) -= 2; } if (orderElt == maxOrder) { // We haven't found an entire triangulation, but we've // gone as far as we need to. // Process it, then step back. use_(this, useArgs_); // Back to the previous face. permIndex(face) = -1; orderElt--; } } } // And the search is over. use_(0, useArgs_); } void NGluingPermSearcher::dumpTaggedData(std::ostream& out) const { out << dataTag() << std::endl; dumpData(out); } NGluingPermSearcher* NGluingPermSearcher::readTaggedData(std::istream& in, UseGluingPerms use, void* useArgs) { // Read the class marker. char c; in >> c; if (in.eof()) return 0; NGluingPermSearcher* ans; if (c == NGluingPermSearcher::dataTag_) ans = new NGluingPermSearcher(in, use, useArgs); else if (c == NCompactSearcher::dataTag_) ans = new NCompactSearcher(in, use, useArgs); else if (c == NClosedPrimeMinSearcher::dataTag_) ans = new NClosedPrimeMinSearcher(in, use, useArgs); else return 0; if (ans->inputError()) { delete ans; return 0; } return ans; } void NGluingPermSearcher::dumpData(std::ostream& out) const { NGluingPerms::dumpData(out); out << (orientableOnly_ ? 'o' : '.'); out << (finiteOnly_ ? 'f' : '.'); out << (started ? 's' : '.'); out << ' ' << whichPurge_ << std::endl; int nTets = getNumberOfTetrahedra(); int i; for (i = 0; i < nTets; i++) { if (i) out << ' '; out << orientation[i]; } out << std::endl; out << orderElt << ' ' << orderSize << std::endl; for (i = 0; i < orderSize; i++) { if (i) out << ' '; out << order[i].simp << ' ' << order[i].facet; } out << std::endl; } NGluingPermSearcher::NGluingPermSearcher(std::istream& in, UseGluingPerms use, void* useArgs) : NGluingPerms(in), autos_(0), autosNew(false), use_(use), useArgs_(useArgs), orientation(0), order(0), orderSize(0), orderElt(0) { if (inputError_) return; // Recontruct the face pairing automorphisms. const_cast(this)->autos_ = new NFacePairing::IsoList(); pairing_->findAutomorphisms(const_cast(*autos_)); autosNew = true; // Keep reading. char c; in >> c; if (c == 'o') orientableOnly_ = true; else if (c == '.') orientableOnly_ = false; else { inputError_ = true; return; } in >> c; if (c == 'f') finiteOnly_ = true; else if (c == '.') finiteOnly_ = false; else { inputError_ = true; return; } in >> c; if (c == 's') started = true; else if (c == '.') started = false; else { inputError_ = true; return; } in >> whichPurge_; int nTets = pairing_->getNumberOfTetrahedra(); int t; orientation = new int[nTets]; for (t = 0; t < nTets; t++) in >> orientation[t]; order = new NTetFace[2 * nTets]; in >> orderElt >> orderSize; for (t = 0; t < orderSize; t++) { in >> order[t].simp >> order[t].facet; if (order[t].simp >= nTets || order[t].simp < 0 || order[t].facet >= 4 || order[t].facet < 0) { inputError_ = true; return; } } // Did we hit an unexpected EOF? if (in.eof()) inputError_ = true; } bool NGluingPermSearcher::isCanonical() const { NTetFace face, faceDest, faceImage; int ordering; for (NFacePairing::IsoList::const_iterator it = autos_->begin(); it != autos_->end(); it++) { // Compare the current set of gluing permutations with its // preimage under each face pairing automorphism, to see whether // our current permutation set is closest to canonical form. for (face.setFirst(); face.simp < static_cast(pairing_->getNumberOfTetrahedra()); face++) { faceDest = pairing_->dest(face); if (pairing_->isUnmatched(face) || faceDest < face) continue; faceImage = (**it)[face]; ordering = gluingPerm(face).compareWith( (*it)->facePerm(faceDest.simp).inverse() * gluingPerm(faceImage) * (*it)->facePerm(face.simp)); if (ordering < 0) { // This permutation set is closer. break; } else if (ordering > 0) { // The transformed permutation set is closer. return false; } // So far it's an automorphism of gluing permutations also. // Keep running through faces. } // Nothing broke with this automorphism. On to the next one. } // Nothing broke at all. return true; } bool NGluingPermSearcher::badEdgeLink(const NTetFace& face) const { // Run around all three edges bounding the face. NTetFace adj; unsigned tet; NPerm4 current; NPerm4 start(face.facet, 3); bool started, incomplete; for (unsigned permIdx = 0; permIdx < 3; permIdx++) { start = start * NPerm4(1, 2, 0, 3); // start maps (0,1,2) to the three vertices of face, with // (0,1) mapped to the edge that we wish to examine. // Continue to push through a tetrahedron and then across a // face, until either we hit a boundary or we return to the // original face. current = start; tet = face.simp; started = false; incomplete = false; while ((! started) || (static_cast(tet) != face.simp) || (start[2] != current[2]) || (start[3] != current[3])) { // Test for a return to the original tetrahedron with the // orientation reversed; this either means a bad edge link // or a bad vertex link. if (started && finiteOnly_ && static_cast(tet) == face.simp) if (start[3] == current[3] && start.sign() != current.sign()) return true; // Push through the current tetrahedron. started = true; current = current * NPerm4(2, 3); // Push across a face. if (pairing_->isUnmatched(tet, current[3])) { incomplete = true; break; } adj = pairing_->dest(tet, current[3]); if (permIndex(tet, current[3]) >= 0) { current = gluingPerm(tet, current[3]) * current; } else if (permIndex(adj) >= 0) { current = gluingPerm(adj).inverse() * current; } else { incomplete = true; break; } tet = adj.simp; } // Did we meet the original edge in reverse? if ((! incomplete) && (start != current)) return true; } // No bad edge links were found. return false; } bool NGluingPermSearcher::lowDegreeEdge(const NTetFace& face, bool testDegree12, bool testDegree3) const { // Run around all three edges bounding the face. NTetFace adj; unsigned tet; NPerm4 current; NPerm4 start(face.facet, 3); bool started, incomplete; unsigned size; for (unsigned permIdx = 0; permIdx < 3; permIdx++) { start = start * NPerm4(1, 2, 0, 3); // start maps (0,1,2) to the three vertices of face, with // (0,1) mapped to the edge that we wish to examine. // Continue to push through a tetrahedron and then across a // face, until either we hit a boundary or we return to the // original face. current = start; tet = face.simp; started = false; incomplete = false; size = 0; while ((! started) || (static_cast(tet) != face.simp) || (start[2] != current[2]) || (start[3] != current[3])) { started = true; // We're about to push through the current tetrahedron; see // if we've already exceeded the size of edge links that we // care about. if (size >= 3) { incomplete = true; break; } // Push through the current tetrahedron. current = current * NPerm4(2, 3); // Push across a face. if (pairing_->isUnmatched(tet, current[3])) { incomplete = true; break; } adj = pairing_->dest(tet, current[3]); if (permIndex(tet, current[3]) >= 0) { current = gluingPerm(tet, current[3]) * current; } else if (permIndex(adj) >= 0) { current = gluingPerm(adj).inverse() * current; } else { incomplete = true; break; } tet = adj.simp; size++; } if (! incomplete) { if (testDegree12 && size < 3) return true; if (testDegree3 && size == 3) { // Only throw away a degree three edge if it involves // three distinct tetrahedra. int tet1 = pairing_->dest(face.simp, start[2]).simp; int tet2 = pairing_->dest(face.simp, start[3]).simp; if (face.simp != tet1 && tet1 != tet2 && tet2 != face.simp) return true; } } } // No bad low-degree edges were found. return false; } bool NGluingPermSearcher::mayPurge(const NTetFace& face) const { // Are we allowed to purge on edges of degree 3? bool mayPurgeDeg3 = (whichPurge_ & NCensus::PURGE_NON_MINIMAL); // Are we allowed to purge on edges of degree 1 or 2? // // A 2-0 edge move or a 2-1 edge move can result in one or more of // the following topological changes. // // Bigon squashing: // - Disc reduction; // - Sphere decomposition or reduction; // - Crushing embedded RP2 to an invalid edge. // // Pillow squashing: // - Loss of 3-ball; // - Loss of 3-sphere; // - Loss of L(3,1). // bool mayPurgeDeg12 = (whichPurge_ & NCensus::PURGE_NON_MINIMAL) && (whichPurge_ & NCensus::PURGE_NON_PRIME) && ((whichPurge_ & NCensus::PURGE_P2_REDUCIBLE) || orientableOnly_) && finiteOnly_ && (getNumberOfTetrahedra() > 2); if (mayPurgeDeg12 || mayPurgeDeg3) return lowDegreeEdge(face, mayPurgeDeg12, mayPurgeDeg3); else return false; } } // namespace regina regina-4.95/engine/census/ngluingpermsearcher.h000644 000765 000024 00000405777 12235500316 021612 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file census/ngluingpermsearcher.h * \brief Supports searching through all possible sets of tetrahedron * gluing permutations for a given tetrahedron face pairing. */ #ifndef __NGLUINGPERMSEARCHER_H #ifndef __DOXYGEN #define __NGLUINGPERMSEARCHER_H #endif #include "regina-core.h" #include "census/ngluingperms.h" #include "utilities/nqitmask.h" /** * Specifies whether the NClosedPrimeMinSearcher census generation code * should prune on high-degree edges. * * It is well known that a closed prime minimal P^2-irreducible triangulation * formed from at least three tetrahedra can never have an edge of degree * one or two. Combining this with the fact that such a triangulation * must always have one vertex, a simple Euler characteristic * calculation shows that there must be precisely n+1 edges, where \a n * is the number of tetrahedra. * * A little arithmetic then shows that, for any \a k edges, the sum of * their edge degrees can be no more than 3(n+k-1); otherwise one of the * remaining edges will be forced to have degree one or two. This * observation is the basis behind the high-degree edge pruning that * this option controls. * * To enable pruning on high-degree edges, set this macro to 1 (the default * for Regina's main source distribution); to disable it, set it to 0. */ #define PRUNE_HIGH_DEG_EDGE_SET 1 namespace regina { /** * \weakgroup census * @{ */ class NGluingPermSearcher; /** * A routine used to do arbitrary processing upon a particular set of * tetrahedron gluing permutations. Such routines are used to process * permutation sets found when running NGluingPermSearcher::findAllPerms(). * * The first parameter passed will be a set of gluing permutations * (in fact it will be of the subclass NGluingPermSearcher in order to * support partial searches as well as full searches). This set of * gluing permutations must not be deallocated by this routine, since it * may be used again later by the caller. The second parameter may contain * arbitrary data as passed to either NGluingPerms::findAllPerms() or * the NGluingPermSearcher class constructor. * * Note that the first parameter passed might be \c null to signal that * gluing permutation generation has finished. */ typedef void (*UseGluingPerms)(const NGluingPermSearcher*, void*); /** * A utility class for searching through all possible gluing permutation * sets that correspond to a given tetrahedron face pairing. Subclasses of * NGluingPermSearcher correspond to specialised (and heavily optimised) * search algorithms that may be used in sufficiently constrained scenarios. * The main class NGluingPermSearcher offers a default (but slower) search * algorithm that may be used in more general contexts. * * The simplest way of performing a search through all possible gluing * permutations is by calling the static method findAllPerms(). This will * examine the search parameters and ensure that the best possible algorithm * is used. For finer control over the program flow, the static method * bestSearcher() can be used to create a search manager of the most * suitable class and then runSearch() can be called on this object directly. * For absolute control, a specific algorithm can be forced by explicitly * constructing an object of the corresponding class (and again * calling runSearch() on that object directly). * * Note that this class derives from NGluingPerms. The search will * involve building and repeatedly modifying the inherited NGluingPerms * data in-place. * * \ifacespython Not present. */ class REGINA_API NGluingPermSearcher : public NGluingPerms { public: static const char dataTag_; /**< A character used to identify this class when reading and writing tagged data in text format. */ protected: const NFacePairing::IsoList* autos_; /**< The set of isomorphisms that define equivalence of gluing permutation sets. Generally this is the set of all automorphisms of the underlying face pairing. */ bool autosNew; /**< Did we create the isomorphism list autos_ ourselves (in which case we must destroy it also)? */ bool orientableOnly_; /**< Are we only searching for gluing permutations that correspond to orientable triangulations? */ bool finiteOnly_; /**< Are we only searching for gluing permutations that correspond to finite triangulations? */ int whichPurge_; /**< Are there any types of triangulation that we may optionally avoid constructing? See the constructor documentation for further details on this search parameter. */ UseGluingPerms use_; /**< A routine to call each time a gluing permutation set is found during the search. */ void* useArgs_; /**< Additional user-supplied data to be passed as the second argument to the \a use_ routine. */ bool started; /**< Has the search started yet? This helps distinguish between a new search and the resumption of a partially completed search. */ int* orientation; /**< Keeps track of the orientation of each tetrahedron in the underlying triangulation. Orientation is positive/negative, or 0 if unknown. Note that in some algorithms the orientation is simply +/-1, and in some algorithms the orientation counts forwards or backwards from 0 according to how many times the orientation has been set or verified. */ protected: NTetFace* order; /**< Describes the order in which gluing permutations are assigned to faces. Specifically, this order is order[0], order[1], ..., order[orderSize-1]. Note that each element of this array corresponds to a single edge of the underlying face pairing graph, which in turn represents a tetrahedron face and its image under the given face pairing. The specific tetrahedron face stored in this array for each edge of the underlying face pairing graph will be the smaller of the two identified tetrahedron faces (unless otherwise specified for a particular edge type; see NClosedPrimeMinSearcher for examples). */ int orderSize; /**< The total number of edges in the face pairing graph, i.e., the number of elements of interest in the order[] array. */ int orderElt; /**< Marks which element of order[] we are currently examining at this stage of the search. */ public: /** * Initialises a new search for gluing permutation sets. The * search is started by calling runSearch(). Note that the * static method findAllPerms() handles both construction and * searching, and is the preferred entry point for end users. * * The arguments to this constructor describe the search * parameters in detail, as well as what should be done with * each gluing permutation set that is found. * * Parameter \a whichPurge may be used to avoid constructing * permutation sets that correspond to triangulations satisfying * certain constraints (such as non-minimality). The use of * this parameter, combined with parameters \a orientableOnly * and \a finiteOnly, can significantly speed up the permutation * set generation. For some combinations of these parameters * entirely different algorithms are used. * * Note that not all permutation sets described by parameter * \a whichPurge will be avoided (i.e., you may get gluing * permutation sets that you did not want). It is guaranteed however * that every permutation set whose corresonding triangulation does * \e not satisfy the \a whichPurge constraints will be generated. * * Similarly, even if \a finiteOnly is set to \c true, some * non-finite triangulations might still slip through the net * (since the full vertex links are not always constructed). * However, like \a whichPurge, setting \a finiteOnly to \c true * allow the census algorithm to take shortcuts and therefore * run faster. The resulting triangulations may be tested for * finiteness (and other properties) by calling triangulate(). * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * * @param pairing the specific pairing of tetrahedron faces * that the generated permutation sets will complement. * @param autos the collection of isomorphisms that define equivalence * of permutation sets. These are used by runSearch(), which produces * each permutation set precisely once up to equivalence. These * isomorphisms must all be automorphisms of the given face pairing, * and will generally be the set of all such automorphisms. This * parameter may be 0, in which case the set of all automorphisms * of the given face pairing will be generated and used. * @param orientableOnly \c true if only gluing permutations * corresponding to orientable triangulations should be * generated, or \c false if no such restriction should be imposed. * @param finiteOnly \c true if only gluing permutations * corresponding to finite triangulations are required, or * \c false if there is no such requirement. Note that * regardless of this value, some non-finite triangulations * might still be produced; see the notes above for details. * @param whichPurge specifies which permutation sets we may * avoid constructing (see the function notes above for details). * This should be a bitwise OR of purge constants from class NCensus, * or 0 if we should simply generate every possible permutation set. * If a variety of purge constants are bitwise ORed together, a * permutation set whose triangulation satisfies \e any of these * constraints may be avoided. Note that not all such * permutation sets will be avoided, but enough are avoided that * the performance increase is noticeable. * @param use the function to call upon each permutation set that * is found. The first parameter passed to this function will be * a gluing permutation set. The second parameter will be * parameter \a useArgs as was passed to this routine. * @param useArgs the pointer to pass as the final parameter for * the function \a use which will be called upon each permutation * set found. */ NGluingPermSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs = 0); /** * Initialises a new search manager based on data read from the * given input stream. This may be a new search or a partially * completed search. * * This routine reads data in the format written by dumpData(). * If you wish to read data whose precise class is unknown, * consider using dumpTaggedData() and readTaggedData() instead. * * If the data found in the input stream is invalid or incorrectly * formatted, the routine inputError() will return \c true but * the contents of this object will be otherwise undefined. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. * @param use as for the main NGluingPermSearcher constructor. * @param useArgs as for the main NGluingPermSearcher constructor. */ NGluingPermSearcher(std::istream& in, UseGluingPerms use, void* useArgs = 0); /** * Destroys this search manager and all supporting data * structures. */ virtual ~NGluingPermSearcher(); /** * Generates all possible gluing permutation sets that satisfy * the current search criteria. The search criteria are * specified in the class constructor, or through the static * method findAllPerms(). * * Each set of gluing permutations will be produced precisely * once up to equivalence, where equivalence is defined by the * given set of automorphisms of the given face pairing. * * For each permutation set that is generated, routine \a use_ (as * passed to the class constructor) will be called with that * permutation set as an argument. * * Once the generation of permutation sets has finished, routine * \a use_ will be called once more, this time with \c null as its * first (permutation set) argument. * * Subclasses corresponding to more specialised search criteria * should override this routine to use a better optimised algorithm * where possible. * * It is possible to run only a partial search, branching to a * given depth but no further. In this case, rather than * producing complete gluing permutation sets, the search will * produce a series of partially-complete NGluingPermSearcher * objects. These partial searches may then be restarted by * calling runSearch() once more (usually after being frozen or * passed on to a different processor). If necessary, the \a use_ * routine may call completePermSet() to distinguish between * a complete set of gluing permutations and a partial search state. * * Note that a restarted search will never drop below its * initial depth. That is, calling runSearch() with a fixed * depth can be used to subdivide the overall search space into * many branches, and then calling runSearch() on each resulting * partial search will complete each of these branches without overlap. * * \todo \feature Allow cancellation of permutation set generation. * * @param maxDepth the depth of the partial search to run, or a * negative number if a full search should be run (the default). */ virtual void runSearch(long maxDepth = -1); /** * Determines whether this search manager holds a complete * gluing permutation set or just a partially completed search * state. * * This may assist the \a use_ routine when running partial * depth-based searches. See runSearch() for further details. * * @return \c true if a complete gluing permutation set is held, * or \c false otherwise. */ bool completePermSet() const; /** * Dumps all internal data in a plain text format, along with a * marker to signify which precise class the data belongs to. * This routine can be used with readTaggedData() to transport * objects from place to place whose precise class is unknown. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param out the output stream to which the data should be * written. */ void dumpTaggedData(std::ostream& out) const; // Overridden methods: virtual void dumpData(std::ostream& out) const; /** * The main entry routine for running a search for all gluing * permutation sets that complement a given face pairing. * * This routine examines the search parameters, chooses the best * possible search algorithm, constructs an object of the * corresponding subclass of NGluingPermSearcher and then calls * runSearch(). * * See the NGluingPermSearcher constructor for documentation on * the arguments to this routine. See the runSearch() method * for documentation on how the search runs and returns its * results. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. */ static void findAllPerms(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs = 0); /** * Constructs a search manager of the best possible class for the * given search parameters. Different subclasses of * NGluingPermSearcher provide optimised search algorithms for * different types of search. * * Calling this routine and then calling runSearch() on the * result has the same effect as the all-in-one routine * findAllPerms(). Unless you have specialised requirements * (such as partial searching), you are probably better calling * findAllPerms() instead. * * The resulting object is newly created, and must be destroyed * by the caller of this routine. * * See the NGluingPermSearcher constructor for documentation on * the arguments to this routine. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * * @return the newly created search manager. */ static NGluingPermSearcher* bestSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, bool finiteOnly, int whichPurge, UseGluingPerms use, void* useArgs = 0); /** * Creates a new search manager based on tagged data read from * the given input stream. This may be a new search or a * partially completed search. * * The tagged data should be in the format written by * dumpTaggedData(). The precise class of the search manager * will be determined from the tagged data, and does not need to * be known in advance. This is in contrast to dumpData() and * the input stream constructors, where the class of the data being * read must be known at compile time. * * If the data found in the input stream is invalid or * incorrectly formatted, a null pointer will be returned. * Otherwise a newly constructed search manager will be returned, * and it is the responsibility of the caller of this routine to * destroy it after use. * * The arguments \a use and \a useArgs are the same as for the * NGluingPermSearcher constructor. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. */ static NGluingPermSearcher* readTaggedData(std::istream& in, UseGluingPerms use, void* useArgs = 0); protected: /** * Compares the current set of gluing permutations with its * preimage under each automorphism of the underlying face pairing, * in order to see whether the current set is in canonical form * (i.e., is lexicographically smallest). * * @return \c true if the current set is in canonical form, * or \c false otherwise. */ bool isCanonical() const; /** * Determines whether the permutations already constructed model * a triangulation with an edge identified with itself in reverse. * Note that such edges can only occur in non-orientable * triangulations. * * Tests that do not refer to the gluing permutation for the * given face will not be run. * * This routine is not fussy about the order in which gluing * permutations are selected, as long as permutations not yet * selected have the corresponding element of permIndices[] set * to -1. * * If \a finiteOnly_ is \c true in the search criteria, additional * tests will be run that can eliminate triangulations with * non-orientable vertex links. Although these tests are not * searching for bad edge links per se, they can be performed within * this routine with very little additional work needing to be done. * * @param face the specific tetrahedron face upon which tests * will be based. * @return \c true if the permutations under construction will * lead to an edge identified with itself in reverse, or \c false * if no such edge is found. */ bool badEdgeLink(const NTetFace& face) const; /** * Determines whether the permutations already constructed model * a triangulation with a low degree edge. Precisely which * types of low degree edges are identified must be specified * through parameters \a testDegree12 and \a testDegree3. * * Tests that do not refer to the gluing permutation for the * given face will not be run. * * This routine is not fussy about the order in which gluing * permutations are selected, as long as permutations not yet * selected have the corresponding element of permIndices[] set * to -1. * * @param face the specific tetrahedron face upon which tests * will be based. * @param testDegree12 \c true if we should test for * non-boundary edges of degree 1 or 2. * @param testDegree3 \c true if we should test for non-boundary * edges of degree 3 involving three distinct tetrahedra. * @return \c true if the permutations under construction will * lead to a low-degree edge as specified by parameters * \a testDegree12 and \a testDegree3, or \c false if no such * edge is found. */ bool lowDegreeEdge(const NTetFace& face, bool testDegree12, bool testDegree3) const; /** * Determines whether the permutations under construction are * doomed to model a triangulation that can be purged from the * census. The conditions under which a triangulation may be * purged are specified by the search parameter \a whichPurge_. * * Note that this routine will not identify all triangulations * that satisfy the given conditions; however, whenever this * routine \e does return \c true it is guaranteed that the * permutations under construction will only lead to * triangulations that do meet the given conditions. * * Tests that do not refer to the gluing permutation for the * given face will not be run. * * This routine is not fussy about the order in which gluing * permutations are selected, as long as permutations not yet * selected have the corresponding element of permIndices[] set * to -1. * * @param face the specific tetrahedron face upon which tests * will be based. * @return \c true if the permutations under construction will only * lead to triangulations that may be purged, or \c false if the * results are inconclusive. */ bool mayPurge(const NTetFace& face) const; /** * Returns the character used to identify this class when * storing tagged data in text format. * * @return the class tag. */ virtual char dataTag() const; }; /** * A gluing permutation search class that offers a specialised search algorithm * for when all vertex links must all have a given fixed Euler characteristic. * Examples might be Euler characteristic 2 (for closed manifolds), * or Euler characteristic 0 (for manifolds with torus and/or Klein * bottle cusps). In addition, we require that every edge must be valid * (i.e., not identified with itself in reverse). * * Vertices on boundary triangles are treated a little differently. * If the underlying face pairing includes boundary triangles and the * given Euler characteristic is \a E, then boundary vertex links must have * Euler characteristic \a E-1, and must have exactly one puncture. * For instance, if \a E is 2 and the face pairing includes boundary faces, * then all vertex links must be either spheres (for internal vertices) * or discs (for boundary vertices). * * The search algorithm uses modified union-find structures on both * edge and vertex equivalence classes to prune searches that are * guaranteed to lead to bad edge or vertex links. For details see * "Enumeration of non-orientable 3-manifolds using face-pairing graphs and * union-find", Benjamin A. Burton, Discrete Comput. Geom. 38 (2007), no. 3, * 527--571; and "Detecting genus in vertex links for the fast enumeration * of 3-manifold triangulations", Benjamin A. Burton, in "ISSAC 2011: * Proceedings of the 36th International Symposium on Symbolic and * Algebraic Computation", ACM, 2011, pp. 59-66. * * No additional unwanted triangulations will be produced by this search * (in contrast to other search classes, such as NClosedPrimeMinSearcher). * That is, \e only 3-manifolds with the required vertex links will be produced. * * \ifacespython Not present. */ class REGINA_API NEulerSearcher : public NGluingPermSearcher { protected: static const char VLINK_CLOSED; /**< Signifies that a vertex link has been closed off (i.e., the link has no remaining boundary edges). */ static const char VLINK_BAD_EULER; /**< Signifies that a vertex link has either (i) accumulated too high a genus (so when its punctures are filled the Euler characteristic will be too low), or has (ii) been closed off with too low a genus (so its final Euler characteristic is too high). */ /** * Constants used in the \a vertexStateChanged array to indicate * how a partial vertex link becomes glued to itself along two * of its boundary edges. See \a vertexStateChanged for details. */ enum { VLINK_JOIN_INIT = -1, /**< A placeholder value for a join that has not yet been made. */ VLINK_JOIN_HANDLE = -2, /**< Signifies that two distinct boundary components of a vertex link have been connected, forming a handle (either orientable or non-orientable). */ VLINK_JOIN_BRIDGE = -3, /**< Signifies that a single boundary component of the vertex link has had two edges joined together in an orientation-preserving fashion. */ VLINK_JOIN_TWIST = -4 /**< Signifies that a single boundary component of the vertex link has had two edges joined together in an orientation-reversing fashion. */ }; protected: static const int vertexLinkNextFace[4][4]; /**< Maintains an ordering of the three tetrahedron faces surrounding a vertex in a tetrahedron. This ordering is consistent with the orientations of triangles in the vertex link used by TetVertexState::twistUp. For vertex v (0..3), the tetrahedron face that follows f (0..3) in this ordering is \a vertexLinkNextFace[v][f]. The remaining array elements \a vertexLinkNextFace[v][v] are all -1. */ static const int vertexLinkPrevFace[4][4]; /**< Provides backwards links for the ordering described by \a vertexLinkNextFace. For vertex v (0..3), the tetrahedron face that precedes f (0..3) in this ordering is \a vertexLinkPrevFace[v][f]. The remaining array elements \a vertexLinkPrevFace[v][v] are all -1. */ protected: /** * A structure used to track equivalence classes of tetrahedron * vertices as the gluing permutation set is constructed. Two * vertices are considered equivalent if they are identified * within the triangulation. * * Tetrahedron vertices are indexed linearly by tetrahedron and * then vertex number. Specifically, vertex v (0..3) of * tetrahedron t (0..nTets-1) has index 4t+v. * * Each equivalence class of vertices corresponds to a tree of * TetVertexState objects, arranged to form a modified union-find * structure. * * Note that a single tetrahedron vertex (as described by this * structure) provides a single triangular piece of the overall * vertex link. This triangle piece is referred to in several * of the data members below. */ struct TetVertexState { int parent; /**< The index of the parent object in the current tree, or -1 if this object is the root of the tree. */ unsigned rank; /**< The depth of the subtree beneath this object (where a leaf node has depth zero). */ unsigned bdry; /**< The number of boundary edges in the vertex link for this equivalence class of vertices. Any face whose gluing permutation has not yet been decided is treated as a boundary face. This value is only maintained correctly for the root of the corresponding object tree; other objects in the tree will have older values to facilitate backtracking. */ int euler; /**< The Euler characteristic that the vertex link would have if its punctures were all filled. As above, this value is only maintained correctly for the root of the corresponding object tree. */ char twistUp; /**< The identification of this object and its parent in the tree corresponds to a gluing of two triangles in the vertex link. Each of these triangles in the vertex link can be labelled with its own vertices 0, 1 and 2 and thereby be assigned a clockwise or anticlockwise orientation. The parameter \a twistUp is 0 if these two triangles in the vertex link are joined in a way that preserves orientation, or 1 if the gluing does not preserve orientation. If this object has no parent, the value of \a twistUp is undefined. */ bool hadEqualRank; /**< Did this tree have rank equal to its parent immediately before it was grafted beneath its parent? This information is used to maintain the ranks correctly when grafting operations are undone. If this object is still the root of its tree, this value is set to false. */ unsigned char bdryEdges; /**< The number of edges of the triangular piece of vertex link that are in fact boundary edges of the vertex link. Equivalently, this measures the number of faces of this tetrahedron meeting this vertex that are not yet joined to their partner faces. This always takes the value 0, 1, 2 or 3. */ int bdryNext[2]; /**< If the corresponding triangular piece of vertex link has any boundary edges, \a bdryNext stores the indices of the tetrahedron vertices that provide the boundary edges following on from either end of this boundary segment. Note that in most cases (see below) this is not the present vertex. For instance, if this vertex provides two boundary edges, then this array describes the boundary before the first edge and after the second. The boundary segment described by \a bdryNext[1] follows on from this segment in the direction described by the \a vertexLinkNextFace array. The boundary segment in the other direction is described by \a bdryNext[0]. If the vertex link is just this one triangle (i.e., all three faces of this tetrahedron surrounding this vertex are boundary faces, or one is a boundary and the other two are joined together), then both elements of \a bdryNext refer to this vertex itself. These are the only situations in which \a bdryNext refers back to this vertex. If the triangle is internal to the vertex link (i.e., \a bdryEdges is zero), then this array maintains the last values it had when there was at least one boundary edge earlier in the search. Each element of this array lies between 0 and 4t-1 inclusive, where \a t is the total number of tetrahedra. */ char bdryTwist[2]; /**< Describes whether the orientation of this boundary segment of the vertex link is consistent with the orientation of the adjacent segments on either side. See \a bdryNext for further discussion of boundary segments. The \a bdryNext array defines an orientation for this section of vertex link, pointing from the end described by \a bdryNext[0] to the end described by \a bdryNext[1]. For each \a i, the value \a bdryTwist[i] is 0 if the orientation of the adjacent segment described by \a bdryNext[i] is the same as this segment (as defined by the \a bdryNext values stored with the adjacent vertex), or 1 if the orientations differ. If the triangle supplied by this vertex is internal to the vertex link, this array maintains the last values it had when there was at least one boundary edge earlier in the search (just like the \a bdryNext array). */ int bdryNextOld[2]; /**< Stores a snapshot of the values in the \a bdryNext array from the last point in the search when \a bdryEdges was precisely two. If \a bdryEdges is still two or three, then this array is undefined. */ char bdryTwistOld[2]; /**< Stores a snapshot of the values in the \a bdryTwist array from the last point in the search when \a bdryEdges was precisely two. If \a bdryEdges is still two or three, then this array is undefined. */ /** * Constructor for a standalone tetrahedron vertex in an * equivalence class all of its own. Note that the vertex * link will be a single triangle with three boundary edges. */ TetVertexState(); /** * Dumps all internal data in a plain text format to the * given output stream. This state can be recreated from * this text data by calling readData(). * * This routine may be useful for transferring objects from * one processor to another. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param out the output stream to which the data should be * written. */ void dumpData(std::ostream& out) const; /** * Fills this state with data read from the given input stream. * This routine reads data in the format written by dumpData(). * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * This routine does test for bad input data, but it * does \e not test for end-of-file. * * @param in the input stream from which to read. * @param nStates the total number of vertex states under * consideration (this must be four times the number of * tetrahedra). * @return \c false if any errors were encountered during * reading, or \c true otherwise. */ bool readData(std::istream& in, unsigned long nStates); }; /** * A structure used to track equivalence classes of tetrahedron * edges as the gluing permutation set is constructed. Two edges * are considered equivalent if they are identified within the * triangulation. * * Tetrahedron edges are indexed linearly by tetrahedron and * then edge number. Specifically, edge e (0..5) of * tetrahedron t (0..nTets-1) has index 6t+e. * * Each equivalence class of edges corresponds to a tree of * TetEdgeState objects, arranged to form a modified union-find * structure. */ struct TetEdgeState { int parent; /**< The index of the parent object in the current tree, or -1 if this object is the root of the tree. */ unsigned rank; /**< The depth of the subtree beneath this object (where a leaf node has depth zero). */ unsigned size; /**< The total number of objects in the subtree descending from this object (where this object is counted also). */ bool bounded; /**< Does this equivalence class of tetrahedron edges represent a boundary edge? If this equivalence class describes a complete loop of tetrahedron edges then the value of \a bounded is \c false. If this equivalence class describes a string of tetrahedron edges with two endpoints, the value of \a bounded is \c true. Here we treat any face whose gluing permutation has not yet been decided as a boundary face. This value is only maintained correctly for the root of the corresponding object tree; other objects in the tree will have older values to facilitate backtracking. */ char twistUp; /**< Each tetrahedron edge can be assigned an orientation pointing from the lower numbered tetrahedron vertex to the higher. The parameter \a twistUp is 0 if the identification of this object and its parent in the tree preserves this orientation, or 1 if it does not. If this object has no parent, the value of \a twistUp is undefined. */ bool hadEqualRank; /**< Did this tree have rank equal to its parent immediately before it was grafted beneath its parent? This information is used to maintain the ranks correctly when grafting operations are undone. If this object is still the root of its tree, this value is set to false. */ NQitmaskLen64 facesPos; /**< Indicates how many times this edge runs along the boundary of each tetrahedron face in the positive direction. Specifically, the (4t+i)th trit counts how many times it runs in the positive direction around the boundary of face \a i of tetrahedron \a t. Which direction is "positive" is chosen arbitrarily for each face; for details see the implementation of the NEulerSearcher constructor. Because of the fixed-size data type, this only stores information for the faces of the first 16 tetrahedra. Currently this data member is initialised by the NEulerSearcher constructors (since it belongs to TetEdgeState), but it is only used and updated in the subclass NClosedPrimeMinSearcher (where it allows us to optimise the census algorithm). */ NQitmaskLen64 facesNeg; /**< Indicates how many times this edge runs along the boundary of each tetrahedron face in the negative direction. Specifically, the (4t+i)th trit counts how many times it runs in the negative direction around the boundary of face \a i of tetrahedron \a t. Which direction is "negative" is chosen arbitrarily for each face; for details see the implementation of the NEulerSearcher constructor. Because of the fixed-size data type, this only stores information for the faces of the first 16 tetrahedra. Currently this data member is initialised by the NEulerSearcher constructors (since it belongs to TetEdgeState), but it is only used and updated in the subclass NClosedPrimeMinSearcher (where it allows us to optimise the census algorithm). */ /** * Constructor for a standalone tetrahedron edge in an * equivalence class all of its own. */ TetEdgeState(); /** * Dumps all internal data in a plain text format to the * given output stream. This state can be recreated from * this text data by calling readData(). * * This routine may be useful for transferring objects from * one processor to another. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param nTets the number of tetrahedra under consideration * in the census. * @param out the output stream to which the data should be * written. */ void dumpData(std::ostream& out, unsigned nTets) const; /** * Fills this state with data read from the given input stream. * This routine reads data in the format written by dumpData(). * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * This routine does test for bad input data, but it * does \e not test for end-of-file. * * @param in the input stream from which to read. * @param nTets the number of tetrahedra under consideration * in the census. * @return \c false if any errors were encountered during * reading, or \c true otherwise. */ bool readData(std::istream& in, unsigned nTets); }; public: static const char dataTag_; /**< A character used to identify this class when reading and writing tagged data in text format. */ protected: int euler_; /**< The Euler characteristic that vertex links must have. For boundary vertices, this is the Euler characteristic of the closed surface that would be obtained if the puncture in the vertex link were filled. */ unsigned nVertexClasses; /**< The number of equivalence classes of identified tetrahedron vertices. */ TetVertexState* vertexState; /**< Used for tracking equivalence classes of identified tetrahedron vertices. See the TetVertexState description for details. This array has size 4n, where vertex v of tetrahedron t has index 4t+v. */ int* vertexStateChanged; /**< Tracks the way in which the vertexState[] array has been updated over time. This array has size 8n, where element 4i+v describes how the gluing for order[i] affects vertex v of the corresponding tetrahedron (thus a quarter of this array will remain unused, since only three vertices are affected for each gluing). If this identification of vertices results in the tree with root vertexState[p] being grafted beneath the tree with root vertexState[q] (so two distinct vertex links become joined together), this array will store the value p. Otherwise it will store one of the values \a VLINK_JOIN_HANDLE, \a VLINK_JOIN_BRIDGE or \a VLINK_JOIN_TWIST, indicating how the corresponding partial link is glued to itself. The value \a VLINK_JOIN_INIT will be stored for positions in the array that correspond to gluings that have not yet been made. */ unsigned nEdgeClasses; /**< The number of equivalence classes of identified tetrahedron edges. */ TetEdgeState* edgeState; /**< Used for tracking equivalence classes of identified tetrahedron edges. See the TetEdgeState description for details. This array has size 6n, where edge e of tetrahedron t has index 6t+e. */ int* edgeStateChanged; /**< Tracks the way in which the edgeState[] array has been updated over time. This array has size 8n. Suppose the gluing for order[i] affects face f of tetrahedron t. Then element 4i+v of this array describes how the gluing for order[i] affects the edge of tetrahedron t opposite vertices f and v (note that a quarter of this array will remain unused, since f and v are never equal). If this identification of edges results in the tree with root edgeState[p] being grafted beneath the tree with root edgeState[q], this array will store the value p. Otherwise it will store the value -1. */ public: /** * Creates a new search manager that restricts Euler characteristic * on the vertex links, as described in the class overview. * * For details on how a search manager is used, see the * NGluingPermSearcher documentation. Note in particular that * this class will be automatically used by * NGluingPermSearcher::findAllPerms() if possible, so there is * often no need for an end user to instantiate this class * directly. * * All constructor arguments except for \a euler are the same as for * the NGluingPermSearcher constructor, though some arguments (such as * \a finiteOnly) are not needed here since they are already implied * by the specialised search context. * * \pre The given Euler characteristic is at most 2. * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * * @param useEuler the Euler characteristic that vertex links must * have. For boundary vertices, this is the Euler characteristic * of the closed surface that would be obtained if the puncture in * the vertex link were filled. */ NEulerSearcher(int useEuler, const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, int whichPurge, UseGluingPerms use, void* useArgs = 0); /** * Initialises a new search manager based on data read from the * given input stream. This may be a new search or a partially * completed search. * * This routine reads data in the format written by dumpData(). * If you wish to read data whose precise class is unknown, * consider using dumpTaggedData() and readTaggedData() instead. * * If the data found in the input stream is invalid or incorrectly * formatted, the routine inputError() will return \c true but * the contents of this object will be otherwise undefined. * * The arguments \a use and \a useArgs are the same as for the * NGluingPermSearcher constructor. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. */ NEulerSearcher(std::istream& in, UseGluingPerms use, void* useArgs = 0); /** * Destroys this search manager and all supporting data * structures. */ virtual ~NEulerSearcher(); // Overridden methods: virtual void dumpData(std::ostream& out) const; virtual void runSearch(long maxDepth = -1); protected: // Overridden methods: virtual char dataTag() const; protected: /** * Returns the representative of the equivalence class containing * the given tetrahedron edge. The class representative is * defined to be the root of the corresponding union-find tree. * * See the TetEdgeState class for further details. See also the * other variant of findEdgeClass(), which is slightly slower * but which also tracks edge orientation. * * @param edgeID the index of a single tetrahedron edge; this * must be between 0 and 6t-1 inclusive, where \a t is the * number of tetrahedra. See the TetEdgeState class notes for * details on edge indexing. * @return the index of the tetrahedron edge at the root of the * union-find tree, i.e., the representative of the equivalence * class. */ int findEdgeClass(int edgeID) const; /** * Returns the representative of the equivalence class containing * the given tetrahedron edge. The class representative is * defined to be the root of the corresponding union-find tree. * * The argument \a twisted is also modified to indicate whether * or not the identification of the given edge with the class * representative preserves orientation. Note that this arugment * is \e not initialised. Instead, if the identification * is orientation-preserving then \a twisted will be left * unmodified, and if it is orientation-reversing then \a twisted * will be changed from 0 to 1 or vice-versa. * * See the TetEdgeState class for further details. See also the * other variant of findEdgeClass(), which is slightly faster * but which does not track edge orientation. * * @param edgeID the index of a single tetrahedron edge; this * must be between 0 and 6t-1 inclusive, where \a t is the * number of tetrahedra. See the TetEdgeState class notes for * details on edge indexing. * @param twisted used to track edge orientation, as described * above. This must be either 0 or 1 as it is passed into the * function, and it will also be either 0 or 1 upon returning * from the function. * @return the index of the tetrahedron edge at the root of the * union-find tree, i.e., the representative of the equivalence * class. */ int findEdgeClass(int edgeID, char& twisted) const; /** * Merge the classes of tetrahedron vertices as required by the * new gluing made at stage \a orderElt of the search. * * See the TetVertexState class for details. * * This routine returns a bitwise (OR) combination of the * VLINK_... flags defined earlier in this class. These * flags describe what happened to the vertex links during * this particular merge. In particular, they note when a * vertex link is closed off, or enters a state where it will be * forced to have the wrong Euler characteristic. * * @return a combination of VLINK_... flags describing how * the vertex links were changed, or 0 if none of the changes * described by these flags were observed. */ int mergeVertexClasses(); /** * Merge the classes of tetrahedron edges as required by the * new gluing made at stage \a orderElt of the search. * * See the TetEdgeState class for details. * * This routine returns a boolean that indicates whether this * merge creates an invalid edge (i.e., an edge identified with * itself in reverse). * * @return \c true if this merge creates an invalid edge, or * \c false if not. */ bool mergeEdgeClasses(); /** * Split the classes of tetrahedron vertices to mirror the * undoing of the gluing at stage \a orderElt of the search. * * See the TetVertexState class for details. */ void splitVertexClasses(); /** * Split the classes of tetrahedron edges to mirror the undoing * of the gluing at stage \a orderElt of the search. * * See the TetEdgeState class for details. */ void splitEdgeClasses(); /** * Signifies that the boundary edges supplied by the vertex * linking triangles for the two given tetrahedron vertices * should be marked as adjacent. The \a bdryNext and \a bdryTwist * arrays for each vertex will be adjusted to point to the other. * * See the TetVertexState class for details. * * @param vertexID the first tetrahedron vertex on which to operate; * this must be between 0 and 4n-1 inclusive, where \a n is the number * of tetrahedra. * @param end specifies in which direction the adjacent boundary * edges lie. This must be either 0 or 1, and its value should * correspond to the relevant index in the \a bdryNext and \a bdryTwist * arrays for vertex \a vertexID. * @param adjVertexID the tetrahedron vertex whose boundary edges are * adjacent to the boundary edges supplied by \a vertexID; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param twist 0 if the orientations of the two boundary segments of * vertex link are oriented in the same direction, or 1 if they are * oriented in opposite directions; see the \a bdryTwist * documentation for details. */ void vtxBdryJoin(int vertexID, char end, int adjVertexID, char twist); /** * Adjusts the \a bdryNext and \a bdryTwist arrays for * nearby tetrahedron vertices, to ensure that these arrays * are consistent with the \a bdryNext and \a bdryTwist arrays * stored with the given vertex. * * It is assumed that the vertex linking triangle for the given * tetrahedron vertex contributes at least one boundary edge to * the vertex link. Recall from the TetVertexState class notes * that the \a bdryNext and \a bdryTwist arrays for the given * vertex describe the boundary edges that follow on in either * direction from the boundary edges supplied by this triangle. * * This routine locates the tetrahedron vertices that provide * the neighbouring boundary edges, and adjusts the \a bdryNext * and \a bdryTwist arrays for these neighbouring vertices to * point back to the given vertex. * * This routine is intended to assist with backtracking. This * routine is safe to use if the given tetrahedron vertex points * to itself (i.e., it provides a complete boundary cycle of * three edges in the vertex link). * * See the TetVertexState class for further information. * * \pre The vertex linking triangle for the given tetrahedron * vertex contributes at least one boundary edge to the vertex link. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryFixAdj(int vertexID); /** * Copies the \a bdryNext and \a bdryTwist arrays to the * \a bdryNextOld and \a bdryTwistOld arrays for the given * tetrahedron vertex. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex on which to operate; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryBackup(int vertexID); /** * Copies the \a bdryNextOld and \a bdryTwistOld arrays to the * \a bdryNext and \a bdryTwist arrays for the given tetrahedron * vertex. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex on which to operate; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryRestore(int vertexID); /** * Assuming the given edge of the vertex linking triangle for the * given tetrahedron vertex lies on the boundary of the vertex link, * this routine identifies the adjacent boundary edges of the vertex * link in each direction. The given edge of the vertex linking * triangle must belong to one of the two tetrahedron faces * currently being joined. * * The tetrahedron vertex to examine is passed in \a vertexID, * \a tet and \a vertex, and the particular edge of the vertex * linking triangle to examine is specified by \a bdryFace. * Details of the adjacent boundary edges are returned in the * arrays \a next and \a twist. * * Note that the values returned might or might not correspond * to the \a bdryNext and \a bdryTwist arrays of the * TetVertexState class, since the TetVertexState arrays skip * over adjacent edges belonging to the same vertex linking triangle. * * If the given edge of the vertex linking triangle is not a * boundary edge of the vertex link, the behaviour of this * routine is undefined. * * See the TetVertexState class for further information. * * \pre The tetrahedron face (\a tet, \a bdryFace) is one of the * two faces that are currently being joined together. That is, * this face is either order[orderElt] or its partner in the * underlying face pairing. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param tet the tetrahedron described by \a vertexID; this * must be (vertexID / 4). It is passed separately to avoid a * slow division operation. * @param vertex the tetrahedron vertex number described by \a vertexID; * this must be (vertexID % 4). It is passed separately to * avoid a slow modulus operation. * @param bdryFace the face number of the given tetrahedron * containing the edge of the vertex linking triangle that is * under consideration. This must be between 0 and 3 inclusive, * and it may not be equal to \a vertex. * @param next returns the tetrahedron vertex supplying each * adjacent boundary edge; see the TetVertexState::bdryNext * notes for details on which directions correspond to array * indices 0 and 1. * @param twist returns whether the orientations of the adjacent * boundary edges are consistent with the orientation of this * boundary edge; see the TetVertexState::bdryTwist notes for * further information on orientations in the vertex link. */ void vtxBdryNext(int vertexID, int tet, int vertex, int bdryFace, int next[2], char twist[2]); /** * Determines whether one of the edges of the vertex linking * triangle for the given tetrahedron vertex in fact forms an * entire one-edge boundary component of the overall vertex link. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @return \c true if a one-edge boundary component is formed as * described above, or \c false otherwise. */ bool vtxBdryLength1(int vertexID); /** * Determines whether edges of the vertex linking triangles for each * of the given tetrahedron vertices combine to form an entire * two-edge boundary component of the overall vertex link, with one * edge from each triangle. * * See the TetVertexState class for further information. * * @param vertexID1 the first tetrahedron vertex to examine; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param vertexID2 the second tetrahedron vertex to examine; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @return \c true if a two-edge boundary component is formed as * described above, or \c false otherwise. */ bool vtxBdryLength2(int vertexID1, int vertexID2); /** * Runs a number of tests on all tetrahedron vertices to locate * consistency errors in the \a bdryEdges, \a bdryNext and * \a bdryTwist members of the TetVertexState class. * * Any errors that are identified will be written to standard error. * Note that some errors might be harmless (for instance, when * a call to mergeVertexClasses() leaves processing incomplete * because it has located a bad vertex link and expects the * merge to be immediately undone). */ void vtxBdryConsistencyCheck(); /** * Dumps a summary of \a bdryNext, \a bdryTwist and \a bdryEdges * for every vertex of every tetrahedron to the given output stream. * The output format is relatively compact, and is subject to change * in future versions of Regina. The output uses one line only, and * a final newline is written. * * See the TetVertexState class for further information. * * @param out the output stream to which to write. */ void vtxBdryDump(std::ostream& out); }; /** * A gluing permutation search class that offers a specialised search * algorithm for when only compact (finite) 3-manifold triangulations are * required. The only constraints placed upon a triangulation are that * every edge must be valid (i.e., not identified with itself in reverse), * and that the link of every vertex must be a disk or a sphere. * * The search algorithm uses modified union-find structures on both * edge and vertex equivalence classes to prune searches that are * guaranteed to lead to bad edge or vertex links. For details see * "Enumeration of non-orientable 3-manifolds using face-pairing graphs and * union-find", Benjamin A. Burton, Discrete Comput. Geom. 38 (2007), no. 3, * 527--571; and "Detecting genus in vertex links for the fast enumeration * of 3-manifold triangulations", Benjamin A. Burton, in "ISSAC 2011: * Proceedings of the 36th International Symposium on Symbolic and * Algebraic Computation", ACM, 2011, pp. 59-66. * * No additional unwanted triangulations will be produced by this search * (in contrast to other search classes, such as NClosedPrimeMinSearcher). * That is, \e only compact 3-manifolds will be produced. * * \ifacespython Not present. */ class REGINA_API NCompactSearcher : public NGluingPermSearcher { protected: static const char VLINK_CLOSED; /**< Signifies that a vertex link has been closed off (i.e., the link has no remaining boundary edges). */ static const char VLINK_NON_SPHERE; /**< Signifies that a vertex link has been made into something other than a 2-sphere with zero or more punctures. */ protected: static const int vertexLinkNextFace[4][4]; /**< Maintains an ordering of the three tetrahedron faces surrounding a vertex in a tetrahedron. This ordering is consistent with the orientations of triangles in the vertex link used by TetVertexState::twistUp. For vertex v (0..3), the tetrahedron face that follows f (0..3) in this ordering is \a vertexLinkNextFace[v][f]. The remaining array elements \a vertexLinkNextFace[v][v] are all -1. */ static const int vertexLinkPrevFace[4][4]; /**< Provides backwards links for the ordering described by \a vertexLinkNextFace. For vertex v (0..3), the tetrahedron face that precedes f (0..3) in this ordering is \a vertexLinkPrevFace[v][f]. The remaining array elements \a vertexLinkPrevFace[v][v] are all -1. */ protected: /** * A structure used to track equivalence classes of tetrahedron * vertices as the gluing permutation set is constructed. Two * vertices are considered equivalent if they are identified * within the triangulation. * * Tetrahedron vertices are indexed linearly by tetrahedron and * then vertex number. Specifically, vertex v (0..3) of * tetrahedron t (0..nTets-1) has index 4t+v. * * Each equivalence class of vertices corresponds to a tree of * TetVertexState objects, arranged to form a modified union-find * structure. * * Note that a single tetrahedron vertex (as described by this * structure) provides a single triangular piece of the overall * vertex link. This triangle piece is referred to in several * of the data members below. */ struct TetVertexState { int parent; /**< The index of the parent object in the current tree, or -1 if this object is the root of the tree. */ unsigned rank; /**< The depth of the subtree beneath this object (where a leaf node has depth zero). */ unsigned bdry; /**< The number of boundary edges in the vertex link for this equivalence class of vertices. Any face whose gluing permutation has not yet been decided is treated as a boundary face. This value is only maintained correctly for the root of the corresponding object tree; other objects in the tree will have older values to facilitate backtracking. */ char twistUp; /**< The identification of this object and its parent in the tree corresponds to a gluing of two triangles in the vertex link. Each of these triangles in the vertex link can be labelled with its own vertices 0, 1 and 2 and thereby be assigned a clockwise or anticlockwise orientation. The parameter \a twistUp is 0 if these two triangles in the vertex link are joined in a way that preserves orientation, or 1 if the gluing does not preserve orientation. If this object has no parent, the value of \a twistUp is undefined. */ bool hadEqualRank; /**< Did this tree have rank equal to its parent immediately before it was grafted beneath its parent? This information is used to maintain the ranks correctly when grafting operations are undone. If this object is still the root of its tree, this value is set to false. */ unsigned char bdryEdges; /**< The number of edges of the triangular piece of vertex link that are in fact boundary edges of the vertex link. Equivalently, this measures the number of faces of this tetrahedron meeting this vertex that are not yet joined to their partner faces. This always takes the value 0, 1, 2 or 3. */ int bdryNext[2]; /**< If the corresponding triangular piece of vertex link has any boundary edges, \a bdryNext stores the indices of the tetrahedron vertices that provide the boundary edges following on from either end of this boundary segment. Note that in most cases (see below) this is not the present vertex. For instance, if this vertex provides two boundary edges, then this array describes the boundary before the first edge and after the second. The boundary segment described by \a bdryNext[1] follows on from this segment in the direction described by the \a vertexLinkNextFace array. The boundary segment in the other direction is described by \a bdryNext[0]. If the vertex link is just this one triangle (i.e., all three faces of this tetrahedron surrounding this vertex are boundary faces, or one is a boundary and the other two are joined together), then both elements of \a bdryNext refer to this vertex itself. These are the only situations in which \a bdryNext refers back to this vertex. If the triangle is internal to the vertex link (i.e., \a bdryEdges is zero), then this array maintains the last values it had when there was at least one boundary edge earlier in the search. Each element of this array lies between 0 and 4t-1 inclusive, where \a t is the total number of tetrahedra. */ char bdryTwist[2]; /**< Describes whether the orientation of this boundary segment of the vertex link is consistent with the orientation of the adjacent segments on either side. See \a bdryNext for further discussion of boundary segments. The \a bdryNext array defines an orientation for this section of vertex link, pointing from the end described by \a bdryNext[0] to the end described by \a bdryNext[1]. For each \a i, the value \a bdryTwist[i] is 0 if the orientation of the adjacent segment described by \a bdryNext[i] is the same as this segment (as defined by the \a bdryNext values stored with the adjacent vertex), or 1 if the orientations differ. If the triangle supplied by this vertex is internal to the vertex link, this array maintains the last values it had when there was at least one boundary edge earlier in the search (just like the \a bdryNext array). */ int bdryNextOld[2]; /**< Stores a snapshot of the values in the \a bdryNext array from the last point in the search when \a bdryEdges was precisely two. If \a bdryEdges is still two or three, then this array is undefined. */ char bdryTwistOld[2]; /**< Stores a snapshot of the values in the \a bdryTwist array from the last point in the search when \a bdryEdges was precisely two. If \a bdryEdges is still two or three, then this array is undefined. */ /** * Constructor for a standalone tetrahedron vertex in an * equivalence class all of its own. Note that the vertex * link will be a single triangle with three boundary edges. */ TetVertexState(); /** * Dumps all internal data in a plain text format to the * given output stream. This state can be recreated from * this text data by calling readData(). * * This routine may be useful for transferring objects from * one processor to another. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param out the output stream to which the data should be * written. */ void dumpData(std::ostream& out) const; /** * Fills this state with data read from the given input stream. * This routine reads data in the format written by dumpData(). * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * This routine does test for bad input data, but it * does \e not test for end-of-file. * * @param in the input stream from which to read. * @param nStates the total number of vertex states under * consideration (this must be four times the number of * tetrahedra). * @return \c false if any errors were encountered during * reading, or \c true otherwise. */ bool readData(std::istream& in, unsigned long nStates); }; /** * A structure used to track equivalence classes of tetrahedron * edges as the gluing permutation set is constructed. Two edges * are considered equivalent if they are identified within the * triangulation. * * Tetrahedron edges are indexed linearly by tetrahedron and * then edge number. Specifically, edge e (0..5) of * tetrahedron t (0..nTets-1) has index 6t+e. * * Each equivalence class of edges corresponds to a tree of * TetEdgeState objects, arranged to form a modified union-find * structure. */ struct TetEdgeState { int parent; /**< The index of the parent object in the current tree, or -1 if this object is the root of the tree. */ unsigned rank; /**< The depth of the subtree beneath this object (where a leaf node has depth zero). */ unsigned size; /**< The total number of objects in the subtree descending from this object (where this object is counted also). */ bool bounded; /**< Does this equivalence class of tetrahedron edges represent a boundary edge? If this equivalence class describes a complete loop of tetrahedron edges then the value of \a bounded is \c false. If this equivalence class describes a string of tetrahedron edges with two endpoints, the value of \a bounded is \c true. Here we treat any face whose gluing permutation has not yet been decided as a boundary face. This value is only maintained correctly for the root of the corresponding object tree; other objects in the tree will have older values to facilitate backtracking. */ char twistUp; /**< Each tetrahedron edge can be assigned an orientation pointing from the lower numbered tetrahedron vertex to the higher. The parameter \a twistUp is 0 if the identification of this object and its parent in the tree preserves this orientation, or 1 if it does not. If this object has no parent, the value of \a twistUp is undefined. */ bool hadEqualRank; /**< Did this tree have rank equal to its parent immediately before it was grafted beneath its parent? This information is used to maintain the ranks correctly when grafting operations are undone. If this object is still the root of its tree, this value is set to false. */ NQitmaskLen64 facesPos; /**< Indicates how many times this edge runs along the boundary of each tetrahedron face in the positive direction. Specifically, the (4t+i)th trit counts how many times it runs in the positive direction around the boundary of face \a i of tetrahedron \a t. Which direction is "positive" is chosen arbitrarily for each face; for details see the implementation of the NCompactSearcher constructor. Because of the fixed-size data type, this only stores information for the faces of the first 16 tetrahedra. Currently this data member is initialised by the NCompactSearcher constructors (since it belongs to TetEdgeState), but it is only used and updated in the subclass NClosedPrimeMinSearcher (where it allows us to optimise the census algorithm). */ NQitmaskLen64 facesNeg; /**< Indicates how many times this edge runs along the boundary of each tetrahedron face in the negative direction. Specifically, the (4t+i)th trit counts how many times it runs in the negative direction around the boundary of face \a i of tetrahedron \a t. Which direction is "negative" is chosen arbitrarily for each face; for details see the implementation of the NCompactSearcher constructor. Because of the fixed-size data type, this only stores information for the faces of the first 16 tetrahedra. Currently this data member is initialised by the NCompactSearcher constructors (since it belongs to TetEdgeState), but it is only used and updated in the subclass NClosedPrimeMinSearcher (where it allows us to optimise the census algorithm). */ /** * Constructor for a standalone tetrahedron edge in an * equivalence class all of its own. */ TetEdgeState(); /** * Dumps all internal data in a plain text format to the * given output stream. This state can be recreated from * this text data by calling readData(). * * This routine may be useful for transferring objects from * one processor to another. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param nTets the number of tetrahedra under consideration * in the census. * @param out the output stream to which the data should be * written. */ void dumpData(std::ostream& out, unsigned nTets) const; /** * Fills this state with data read from the given input stream. * This routine reads data in the format written by dumpData(). * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * This routine does test for bad input data, but it * does \e not test for end-of-file. * * @param in the input stream from which to read. * @param nTets the number of tetrahedra under consideration * in the census. * @return \c false if any errors were encountered during * reading, or \c true otherwise. */ bool readData(std::istream& in, unsigned nTets); }; public: static const char dataTag_; /**< A character used to identify this class when reading and writing tagged data in text format. */ protected: unsigned nVertexClasses; /**< The number of equivalence classes of identified tetrahedron vertices. */ TetVertexState* vertexState; /**< Used for tracking equivalence classes of identified tetrahedron vertices. See the TetVertexState description for details. This array has size 4n, where vertex v of tetrahedron t has index 4t+v. */ int* vertexStateChanged; /**< Tracks the way in which the vertexState[] array has been updated over time. This array has size 8n, where element 4i+v describes how the gluing for order[i] affects vertex v of the corresponding tetrahedron (thus a quarter of this array will remain unused, since only three vertices are affected for each gluing). If this identification of vertices results in the tree with root vertexState[p] being grafted beneath the tree with root vertexState[q], this array will store the value p. Otherwise it will store the value -1. */ unsigned nEdgeClasses; /**< The number of equivalence classes of identified tetrahedron edges. */ TetEdgeState* edgeState; /**< Used for tracking equivalence classes of identified tetrahedron edges. See the TetEdgeState description for details. This array has size 6n, where edge e of tetrahedron t has index 6t+e. */ int* edgeStateChanged; /**< Tracks the way in which the edgeState[] array has been updated over time. This array has size 8n. Suppose the gluing for order[i] affects face f of tetrahedron t. Then element 4i+v of this array describes how the gluing for order[i] affects the edge of tetrahedron t opposite vertices f and v (note that a quarter of this array will remain unused, since f and v are never equal). If this identification of edges results in the tree with root edgeState[p] being grafted beneath the tree with root edgeState[q], this array will store the value p. Otherwise it will store the value -1. */ public: /** * Creates a new search manager for use when only compact 3-manifold * triangulations are required. * * For details on how a search manager is used, see the * NGluingPermSearcher documentation. Note in particular that * this class will be automatically used by * NGluingPermSearcher::findAllPerms() if possible, so there is * often no need for an end user to instantiate this class * directly. * * All constructor arguments are the same as for the * NGluingPermSearcher constructor, though some arguments (such as * \a finiteOnly) are not needed here since they are already implied * by the specialised search context. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. */ NCompactSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, int whichPurge, UseGluingPerms use, void* useArgs = 0); /** * Initialises a new search manager based on data read from the * given input stream. This may be a new search or a partially * completed search. * * This routine reads data in the format written by dumpData(). * If you wish to read data whose precise class is unknown, * consider using dumpTaggedData() and readTaggedData() instead. * * If the data found in the input stream is invalid or incorrectly * formatted, the routine inputError() will return \c true but * the contents of this object will be otherwise undefined. * * The arguments \a use and \a useArgs are the same as for the * NGluingPermSearcher constructor. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. */ NCompactSearcher(std::istream& in, UseGluingPerms use, void* useArgs = 0); /** * Destroys this search manager and all supporting data * structures. */ virtual ~NCompactSearcher(); // Overridden methods: virtual void dumpData(std::ostream& out) const; virtual void runSearch(long maxDepth = -1); protected: // Overridden methods: virtual char dataTag() const; protected: /** * Returns the representative of the equivalence class containing * the given tetrahedron edge. The class representative is * defined to be the root of the corresponding union-find tree. * * See the TetEdgeState class for further details. See also the * other variant of findEdgeClass(), which is slightly slower * but which also tracks edge orientation. * * @param edgeID the index of a single tetrahedron edge; this * must be between 0 and 6t-1 inclusive, where \a t is the * number of tetrahedra. See the TetEdgeState class notes for * details on edge indexing. * @return the index of the tetrahedron edge at the root of the * union-find tree, i.e., the representative of the equivalence * class. */ int findEdgeClass(int edgeID) const; /** * Returns the representative of the equivalence class containing * the given tetrahedron edge. The class representative is * defined to be the root of the corresponding union-find tree. * * The argument \a twisted is also modified to indicate whether * or not the identification of the given edge with the class * representative preserves orientation. Note that this arugment * is \e not initialised. Instead, if the identification * is orientation-preserving then \a twisted will be left * unmodified, and if it is orientation-reversing then \a twisted * will be changed from 0 to 1 or vice-versa. * * See the TetEdgeState class for further details. See also the * other variant of findEdgeClass(), which is slightly faster * but which does not track edge orientation. * * @param edgeID the index of a single tetrahedron edge; this * must be between 0 and 6t-1 inclusive, where \a t is the * number of tetrahedra. See the TetEdgeState class notes for * details on edge indexing. * @param twisted used to track edge orientation, as described * above. This must be either 0 or 1 as it is passed into the * function, and it will also be either 0 or 1 upon returning * from the function. * @return the index of the tetrahedron edge at the root of the * union-find tree, i.e., the representative of the equivalence * class. */ int findEdgeClass(int edgeID, char& twisted) const; /** * Merge the classes of tetrahedron vertices as required by the * new gluing made at stage \a orderElt of the search. * * See the TetVertexState class for details. * * This routine returns a bitwise (OR) combination of the * VLINK_... flags defined earlier in this class. These * flags describe what happened to the vertex links during * this particular merge. In particular, they note when a * vertex link is closed off, or is made into something other * than a punctured 2-sphere. * * @return a combination of VLINK_... flags describing how * the vertex links were changed, or 0 if none of the changes * described by these flags were observed. */ int mergeVertexClasses(); /** * Merge the classes of tetrahedron edges as required by the * new gluing made at stage \a orderElt of the search. * * See the TetEdgeState class for details. * * This routine returns a boolean that indicates whether this * merge creates an invalid edge (i.e., an edge identified with * itself in reverse). * * @return \c true if this merge creates an invalid edge, or * \c false if not. */ bool mergeEdgeClasses(); /** * Split the classes of tetrahedron vertices to mirror the * undoing of the gluing at stage \a orderElt of the search. * * See the TetVertexState class for details. */ void splitVertexClasses(); /** * Split the classes of tetrahedron edges to mirror the undoing * of the gluing at stage \a orderElt of the search. * * See the TetEdgeState class for details. */ void splitEdgeClasses(); /** * Signifies that the boundary edges supplied by the vertex * linking triangles for the two given tetrahedron vertices * should be marked as adjacent. The \a bdryNext and \a bdryTwist * arrays for each vertex will be adjusted to point to the other. * * See the TetVertexState class for details. * * @param vertexID the first tetrahedron vertex on which to operate; * this must be between 0 and 4n-1 inclusive, where \a n is the number * of tetrahedra. * @param end specifies in which direction the adjacent boundary * edges lie. This must be either 0 or 1, and its value should * correspond to the relevant index in the \a bdryNext and \a bdryTwist * arrays for vertex \a vertexID. * @param adjVertexID the tetrahedron vertex whose boundary edges are * adjacent to the boundary edges supplied by \a vertexID; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param twist 0 if the orientations of the two boundary segments of * vertex link are oriented in the same direction, or 1 if they are * oriented in opposite directions; see the \a bdryTwist * documentation for details. */ void vtxBdryJoin(int vertexID, char end, int adjVertexID, char twist); /** * Adjusts the \a bdryNext and \a bdryTwist arrays for * nearby tetrahedron vertices, to ensure that these arrays * are consistent with the \a bdryNext and \a bdryTwist arrays * stored with the given vertex. * * It is assumed that the vertex linking triangle for the given * tetrahedron vertex contributes at least one boundary edge to * the vertex link. Recall from the TetVertexState class notes * that the \a bdryNext and \a bdryTwist arrays for the given * vertex describe the boundary edges that follow on in either * direction from the boundary edges supplied by this triangle. * * This routine locates the tetrahedron vertices that provide * the neighbouring boundary edges, and adjusts the \a bdryNext * and \a bdryTwist arrays for these neighbouring vertices to * point back to the given vertex. * * This routine is intended to assist with backtracking. This * routine is safe to use if the given tetrahedron vertex points * to itself (i.e., it provides a complete boundary cycle of * three edges in the vertex link). * * See the TetVertexState class for further information. * * \pre The vertex linking triangle for the given tetrahedron * vertex contributes at least one boundary edge to the vertex link. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryFixAdj(int vertexID); /** * Copies the \a bdryNext and \a bdryTwist arrays to the * \a bdryNextOld and \a bdryTwistOld arrays for the given * tetrahedron vertex. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex on which to operate; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryBackup(int vertexID); /** * Copies the \a bdryNextOld and \a bdryTwistOld arrays to the * \a bdryNext and \a bdryTwist arrays for the given tetrahedron * vertex. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex on which to operate; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. */ void vtxBdryRestore(int vertexID); /** * Assuming the given edge of the vertex linking triangle for the * given tetrahedron vertex lies on the boundary of the vertex link, * this routine identifies the adjacent boundary edges of the vertex * link in each direction. The given edge of the vertex linking * triangle must belong to one of the two tetrahedron faces * currently being joined. * * The tetrahedron vertex to examine is passed in \a vertexID, * \a tet and \a vertex, and the particular edge of the vertex * linking triangle to examine is specified by \a bdryFace. * Details of the adjacent boundary edges are returned in the * arrays \a next and \a twist. * * Note that the values returned might or might not correspond * to the \a bdryNext and \a bdryTwist arrays of the * TetVertexState class, since the TetVertexState arrays skip * over adjacent edges belonging to the same vertex linking triangle. * * If the given edge of the vertex linking triangle is not a * boundary edge of the vertex link, the behaviour of this * routine is undefined. * * See the TetVertexState class for further information. * * \pre The tetrahedron face (\a tet, \a bdryFace) is one of the * two faces that are currently being joined together. That is, * this face is either order[orderElt] or its partner in the * underlying face pairing. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param tet the tetrahedron described by \a vertexID; this * must be (vertexID / 4). It is passed separately to avoid a * slow division operation. * @param vertex the tetrahedron vertex number described by \a vertexID; * this must be (vertexID % 4). It is passed separately to * avoid a slow modulus operation. * @param bdryFace the face number of the given tetrahedron * containing the edge of the vertex linking triangle that is * under consideration. This must be between 0 and 3 inclusive, * and it may not be equal to \a vertex. * @param next returns the tetrahedron vertex supplying each * adjacent boundary edge; see the TetVertexState::bdryNext * notes for details on which directions correspond to array * indices 0 and 1. * @param twist returns whether the orientations of the adjacent * boundary edges are consistent with the orientation of this * boundary edge; see the TetVertexState::bdryTwist notes for * further information on orientations in the vertex link. */ void vtxBdryNext(int vertexID, int tet, int vertex, int bdryFace, int next[2], char twist[2]); /** * Determines whether one of the edges of the vertex linking * triangle for the given tetrahedron vertex in fact forms an * entire one-edge boundary component of the overall vertex link. * * See the TetVertexState class for further information. * * @param vertexID the tetrahedron vertex to examine; this must * be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @return \c true if a one-edge boundary component is formed as * described above, or \c false otherwise. */ bool vtxBdryLength1(int vertexID); /** * Determines whether edges of the vertex linking triangles for each * of the given tetrahedron vertices combine to form an entire * two-edge boundary component of the overall vertex link, with one * edge from each triangle. * * See the TetVertexState class for further information. * * @param vertexID1 the first tetrahedron vertex to examine; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @param vertexID2 the second tetrahedron vertex to examine; this * must be between 0 and 4n-1 inclusive, where \a n is the number of * tetrahedra. * @return \c true if a two-edge boundary component is formed as * described above, or \c false otherwise. */ bool vtxBdryLength2(int vertexID1, int vertexID2); /** * Runs a number of tests on all tetrahedron vertices to locate * consistency errors in the \a bdryEdges, \a bdryNext and * \a bdryTwist members of the TetVertexState class. * * Any errors that are identified will be written to standard error. * Note that some errors might be harmless (for instance, when * a call to mergeVertexClasses() leaves processing incomplete * because it has located a bad vertex link and expects the * merge to be immediately undone). */ void vtxBdryConsistencyCheck(); /** * Dumps a summary of \a bdryNext, \a bdryTwist and \a bdryEdges * for every vertex of every tetrahedron to the given output stream. * The output format is relatively compact, and is subject to change * in future versions of Regina. The output uses one line only, and * a final newline is written. * * See the TetVertexState class for further information. * * @param out the output stream to which to write. */ void vtxBdryDump(std::ostream& out); }; /** * A gluing permutation search class that offers a specialised search * algorithm for when (i) only closed prime minimal P2-irreducible * triangulations are required, and (ii) the given face pairing has * order at least three. * * The search algorithm is significantly different from the default * algorithm provided by NGluingPermSearcher. It is heavily optimised * and takes advantage of a number of results regarding the underlying * face pairing graph. * * Note that additional unwanted triangulations (e.g., non-prime or * non-minimal triangulations) may still be produced by this search. * However, significantly fewer unwanted triangulations will be produced * when using this class instead of NGluingPermSearcher. * * \ifacespython Not present. */ class REGINA_API NClosedPrimeMinSearcher : public NCompactSearcher { private: static const unsigned EDGE_CHAIN_END; /**< Represents the end of a one-ended chain in a face pairing graph. */ static const unsigned EDGE_CHAIN_INTERNAL_FIRST; /**< Represents the first edge of a double edge within a one-ended chain in a face pairing graph. The corresponding element of order[] stores the face closest to the loop at the end of this chain. */ static const unsigned EDGE_CHAIN_INTERNAL_SECOND; /**< Represents the second edge of a double edge within a one-ended chain in a face pairing graph. The corresponding element of order[] stores the face closest to the loop at the end of this chain. */ static const unsigned EDGE_DOUBLE_FIRST; /**< Represents the first edge of a miscellaneous double edge in a face pairing graph. The corresponding element of order[] stores the face belonging to the lower numbered tetrahedron. */ static const unsigned EDGE_DOUBLE_SECOND; /**< Represents the second edge of a miscellaneous double edge in a face pairing graph. The corresponding element of order[] stores the face belonging to the lower numbered tetrahedron. */ static const unsigned EDGE_MISC; /**< Represents a miscellaneous edge in a face pairing graph. */ private: static const char ECLASS_TWISTED; /**< Signifies that an edge has been identified with itself in reverse. */ static const char ECLASS_LOWDEG; /**< Signifies that a set of tetrahedron edges have been identified to form an internal edge of low degree (degree 1 or 2 of any type, or degree 3 with three distinct tetrahedra). */ static const char ECLASS_HIGHDEG; /**< Signifies that a set of tetrahedron edges have been identified to form an edge of such a high degree that either a degree 1 or 2 edge must be formed elsewhere, or else the final number of edges must be too low. */ static const char ECLASS_CONE; /**< Signifies that two edges of a face have been identified to form a cone (with no constraints on any additional identifications that might be taking place). */ static const char ECLASS_L31; /**< Signifies that all three edges of a face have been identified to form an L(3,1) spine. */ private: static const unsigned coneEdge[12][2]; /**< Lists all twelve possible ways in which two edges of a tetrahedron could be identified to create a conical face. For the ith such method, tetrahedron edges coneEdge[i][0] and coneEdge[i][1] are identified. Every element of this array is between 0 and 5 inclusive. */ static const char coneNoTwist[12]; /**< Combines with the \a coneEdge array to list all twelve possible ways in which two edges of a tetrahedron could be identified to create a conical face. For the ith such method, coneNoTwist[i] is 1 if tetrahedron edges coneEdge[i][0,1] should be identified according to their natural orientations, and coneNoTwist[i] is 0 if one of these two edges must be reversed. The natural orientation of a tetrahedron edge is defined to point from the lower-numbered tetrahedron vertex to the higher. This is consistent with the orientation used in the TetEdgeState class. */ public: static const char dataTag_; /**< A character used to identify this class when reading and writing tagged data in text format. */ private: unsigned* orderType; /**< For each edge in the face pairing graph stored in the order[] array, a corresponding category for this edge is stored in the orderType[] array. Categories are described by the EDGE_... constants defined in this class. */ unsigned nChainEdges; /**< The number of edges in the face pairing graph belonging to one-ended chains. */ int* chainPermIndices; /**< Stores the two possible gluing permutations that must be tried for each face in the order[] array of type EDGE_CHAIN_END or EDGE_CHAIN_INTERNAL_FIRST. These two possible permutations can be derived using theoretical results regarding the underlying face pairing graph. Note that for each face of type EDGE_CHAIN_INTERNAL_SECOND, the gluing permutation can be derived from the permutation chosen for the previous face (of type EDGE_CHAIN_INTERNAL_FIRST). In this case we store the two permutations for this face that correspond to the two possible permutations for the previous face. */ #if PRUNE_HIGH_DEG_EDGE_SET int highDegLimit; /**< The lowest allowable edge degree. If the underlying face pairing graph supports a (1,3,4) layered solid torus, this will be 3. Otherwise it will be 4. */ int highDegSum; /**< The sum of (\a degree - \a highDegLimit) over all edges whose degree is \a highDegLimit or higher. This sum is updated throughout the search as part of the high-degree edge pruning code. See the PRUNE_HIGH_DEG_EDGE_SET macro for further details. */ int highDegBound; /**< The maximum allowable value of \a highDegSum. If the sum \a highDegSum exceeds this bound then it can be proven that some edge of the final triangulation must have degree less than \a highDegLimit. This is part of the high-degree edge pruning code; see the PRUNE_HIGH_DEG_EDGE_SET macro for further details. */ #endif public: /** * Creates a new search manager for use when (i) only closed prime * minimal P2-irreducible triangulations are required, and (ii) the * given face pairing has order at least three. Note that other * unwanted triangulations may still be produced (e.g., * non-prime or non-minimal triangulations), but there will be * far fewer of these than when using the NGluingPermSearcher * class directly. * * For details on how a search manager is used, see the * NGluingPermSearcher documentation. Note in particular that * this class will be automatically used by * NGluingPermSearcher::findAllPerms() if possible, so there is * often no need for an end user to instantiate this class * directly. * * All constructor arguments are the same as for the * NGluingPermSearcher constructor, though some arguments (such as * \a finiteOnly and \a whichPurge) are not needed here since they * are already implied by the specialised search context. * * \pre The given face pairing is connected, i.e., it is possible * to reach any tetrahedron from any other tetrahedron via a * series of matched face pairs. * \pre The given face pairing is in canonical form as described * by NFacePairing::isCanonical(). Note that all face pairings * constructed by NFacePairing::findAllPairings() are of this form. * \pre The given face pairing has no boundary faces and has at * least three tetrahedra. */ NClosedPrimeMinSearcher(const NFacePairing* pairing, const NFacePairing::IsoList* autos, bool orientableOnly, UseGluingPerms use, void* useArgs = 0); /** * Initialises a new search manager based on data read from the * given input stream. This may be a new search or a partially * completed search. * * This routine reads data in the format written by dumpData(). * If you wish to read data whose precise class is unknown, * consider using dumpTaggedData() and readTaggedData() instead. * * If the data found in the input stream is invalid or incorrectly * formatted, the routine inputError() will return \c true but * the contents of this object will be otherwise undefined. * * The arguments \a use and \a useArgs are the same as for the * NGluingPermSearcher constructor. * * \warning The data format is liable to change between Regina * releases. Data in this format should be used on a short-term * temporary basis only. * * @param in the input stream from which to read. */ NClosedPrimeMinSearcher(std::istream& in, UseGluingPerms use, void* useArgs = 0); /** * Destroys this search manager and all supporting data * structures. */ virtual ~NClosedPrimeMinSearcher(); // Overridden methods: virtual void dumpData(std::ostream& out) const; virtual void runSearch(long maxDepth = -1); protected: // Overridden methods: virtual char dataTag() const; private: /** * Merge the classes of tetrahedron edges as required by the * new gluing made at stage \a orderElt of the search. * * This overrides NCompactSearcher::mergeEdgeClasses(), and * tests for additional structures that, whilst valid, cannot * appear in a closed prime minimal P2-irreducible triangulation. * * This routine returns a bitwise (OR) combination of the * ECLASS_... flags defined earlier in this class. These * flags describe what happened to the edge classes during * this particular merge. In particular, they note when edge * identifications form a structure that cannot possibly appear * in a closed prime minimal P2-irreducible triangulation. * * Note that, if multiple ECLASS_... flags are appropriate, only * a subset of these flags might be returned. This is because * this routine might exit early after one bad structure has been * detected, without spending time testing for others. It is * guaranteed that if at least one such flag is appropriate then * at least one such flag will be returned. * * @return a combination of ECLASS_... flags describing how * the edge links were changed, or 0 if none of the changes * described by these flags were observed. */ int mergeEdgeClasses(); /** * Split the classes of tetrahedron edges to mirror the undoing * of the gluing at stage \a orderElt of the search. * * This overrides NCompactSearcher::splitEdgeClasses(), so that * we can undo the additional work performed in the overridden * mergeEdgeClasses(). */ void splitEdgeClasses(); }; /*@}*/ // Inline functions for NGluingPermSearcher inline bool NGluingPermSearcher::completePermSet() const { return (orderElt == orderSize); } inline char NGluingPermSearcher::dataTag() const { return NGluingPermSearcher::dataTag_; } // Inline functions for NEulerSearcher inline NEulerSearcher::TetVertexState::TetVertexState() : parent(-1), rank(0), bdry(3), euler(2), twistUp(0), hadEqualRank(false) { } inline NEulerSearcher::TetEdgeState::TetEdgeState() : parent(-1), rank(0), size(1), bounded(true), twistUp(0), hadEqualRank(false) { } inline NEulerSearcher::~NEulerSearcher() { delete[] vertexState; delete[] vertexStateChanged; delete[] edgeState; delete[] edgeStateChanged; } inline char NEulerSearcher::dataTag() const { return NEulerSearcher::dataTag_; } inline int NEulerSearcher::findEdgeClass(int edgeID) const { while (edgeState[edgeID].parent >= 0) edgeID = edgeState[edgeID].parent; return edgeID; } inline int NEulerSearcher::findEdgeClass(int edgeID, char& twisted) const { for ( ; edgeState[edgeID].parent >= 0; edgeID = edgeState[edgeID].parent) twisted ^= edgeState[edgeID].twistUp; return edgeID; } inline void NEulerSearcher::vtxBdryJoin(int vertexID, char end, int adjVertexID, char twist) { vertexState[vertexID].bdryNext[static_cast(end)] = adjVertexID; vertexState[vertexID].bdryTwist[static_cast(end)] = twist; vertexState[adjVertexID].bdryNext[(end ^ 1) ^ twist] = vertexID; vertexState[adjVertexID].bdryTwist[(end ^ 1) ^ twist] = twist; } inline void NEulerSearcher::vtxBdryFixAdj(int vertexID) { if (vertexState[vertexID].bdryNext[0] != vertexID) { vertexState[vertexState[vertexID].bdryNext[0]]. bdryNext[1 ^ vertexState[vertexID].bdryTwist[0]] = vertexID; vertexState[vertexState[vertexID].bdryNext[0]]. bdryTwist[1 ^ vertexState[vertexID].bdryTwist[0]] = vertexState[vertexID].bdryTwist[0]; vertexState[vertexState[vertexID].bdryNext[1]]. bdryNext[0 ^ vertexState[vertexID].bdryTwist[1]] = vertexID; vertexState[vertexState[vertexID].bdryNext[1]]. bdryTwist[0 ^ vertexState[vertexID].bdryTwist[1]] = vertexState[vertexID].bdryTwist[1]; } } inline void NEulerSearcher::vtxBdryBackup(int vertexID) { vertexState[vertexID].bdryNextOld[0] = vertexState[vertexID].bdryNext[0]; vertexState[vertexID].bdryNextOld[1] = vertexState[vertexID].bdryNext[1]; vertexState[vertexID].bdryTwistOld[0] = vertexState[vertexID].bdryTwist[0]; vertexState[vertexID].bdryTwistOld[1] = vertexState[vertexID].bdryTwist[1]; } inline void NEulerSearcher::vtxBdryRestore(int vertexID) { vertexState[vertexID].bdryNext[0] = vertexState[vertexID].bdryNextOld[0]; vertexState[vertexID].bdryNext[1] = vertexState[vertexID].bdryNextOld[1]; vertexState[vertexID].bdryTwist[0] = vertexState[vertexID].bdryTwistOld[0]; vertexState[vertexID].bdryTwist[1] = vertexState[vertexID].bdryTwistOld[1]; } inline void NEulerSearcher::vtxBdryNext(int vertexID, int tet, int vertex, int bdryFace, int next[2], char twist[2]) { switch (vertexState[vertexID].bdryEdges) { case 3: next[0] = next[1] = vertexID; twist[0] = twist[1] = 0; break; case 2: if (permIndex(tet, vertexLinkNextFace[vertex][bdryFace]) < 0) { next[0] = vertexState[vertexID].bdryNext[0]; twist[0] = vertexState[vertexID].bdryTwist[0]; next[1] = vertexID; twist[1] = 0; } else if (permIndex(tet, vertexLinkPrevFace[vertex][bdryFace]) < 0) { next[0] = vertexID; twist[0] = 0; next[1] = vertexState[vertexID].bdryNext[1]; twist[1] = vertexState[vertexID].bdryTwist[1]; } else { // We must be in the process of gluing a tetrahedron // to itself, and one of the gluings hasn't happened // yet (hence bdryEdges == 2 but only one boundary // edge shows up in the gluing permutations). // The boundary that we're not seeing must belong // to either the tetrahedron face we are currently // working with or its adjacent partner. int ghostFace = (bdryFace == order[orderElt].facet ? (*pairing_)[order[orderElt]].facet : order[orderElt].facet); if (vertexLinkNextFace[vertex][bdryFace] == ghostFace) { next[0] = vertexState[vertexID].bdryNext[0]; twist[0] = vertexState[vertexID].bdryTwist[0]; next[1] = vertexID; twist[1] = 0; } else { // Sanity check. if (vertexLinkPrevFace[vertex][bdryFace] != ghostFace) std::cerr << "ERROR: Inconsistent vertex link " "boundary information!" << std::endl; next[0] = vertexID; twist[0] = 0; next[1] = vertexState[vertexID].bdryNext[1]; twist[1] = vertexState[vertexID].bdryTwist[1]; } } break; case 1: next[0] = vertexState[vertexID].bdryNext[0]; next[1] = vertexState[vertexID].bdryNext[1]; twist[0] = vertexState[vertexID].bdryTwist[0]; twist[1] = vertexState[vertexID].bdryTwist[1]; break; } } inline bool NEulerSearcher::vtxBdryLength1(int vertexID) { return (vertexState[vertexID].bdryNext[0] == vertexID && vertexState[vertexID].bdryEdges == 1); } inline bool NEulerSearcher::vtxBdryLength2( int vertexID1, int vertexID2) { return (vertexState[vertexID1].bdryNext[0] == vertexID2 && vertexState[vertexID1].bdryNext[1] == vertexID2 && vertexState[vertexID1].bdryEdges == 1 && vertexState[vertexID2].bdryEdges == 1); } // Inline functions for NCompactSearcher inline NCompactSearcher::TetVertexState::TetVertexState() : parent(-1), rank(0), bdry(3), twistUp(0), hadEqualRank(false) { } inline NCompactSearcher::TetEdgeState::TetEdgeState() : parent(-1), rank(0), size(1), bounded(true), twistUp(0), hadEqualRank(false) { } inline NCompactSearcher::~NCompactSearcher() { delete[] vertexState; delete[] vertexStateChanged; delete[] edgeState; delete[] edgeStateChanged; } inline char NCompactSearcher::dataTag() const { return NCompactSearcher::dataTag_; } inline int NCompactSearcher::findEdgeClass(int edgeID) const { while (edgeState[edgeID].parent >= 0) edgeID = edgeState[edgeID].parent; return edgeID; } inline int NCompactSearcher::findEdgeClass(int edgeID, char& twisted) const { for ( ; edgeState[edgeID].parent >= 0; edgeID = edgeState[edgeID].parent) twisted ^= edgeState[edgeID].twistUp; return edgeID; } inline void NCompactSearcher::vtxBdryJoin(int vertexID, char end, int adjVertexID, char twist) { vertexState[vertexID].bdryNext[static_cast(end)] = adjVertexID; vertexState[vertexID].bdryTwist[static_cast(end)] = twist; vertexState[adjVertexID].bdryNext[(end ^ 1) ^ twist] = vertexID; vertexState[adjVertexID].bdryTwist[(end ^ 1) ^ twist] = twist; } inline void NCompactSearcher::vtxBdryFixAdj(int vertexID) { if (vertexState[vertexID].bdryNext[0] != vertexID) { vertexState[vertexState[vertexID].bdryNext[0]]. bdryNext[1 ^ vertexState[vertexID].bdryTwist[0]] = vertexID; vertexState[vertexState[vertexID].bdryNext[0]]. bdryTwist[1 ^ vertexState[vertexID].bdryTwist[0]] = vertexState[vertexID].bdryTwist[0]; vertexState[vertexState[vertexID].bdryNext[1]]. bdryNext[0 ^ vertexState[vertexID].bdryTwist[1]] = vertexID; vertexState[vertexState[vertexID].bdryNext[1]]. bdryTwist[0 ^ vertexState[vertexID].bdryTwist[1]] = vertexState[vertexID].bdryTwist[1]; } } inline void NCompactSearcher::vtxBdryBackup(int vertexID) { vertexState[vertexID].bdryNextOld[0] = vertexState[vertexID].bdryNext[0]; vertexState[vertexID].bdryNextOld[1] = vertexState[vertexID].bdryNext[1]; vertexState[vertexID].bdryTwistOld[0] = vertexState[vertexID].bdryTwist[0]; vertexState[vertexID].bdryTwistOld[1] = vertexState[vertexID].bdryTwist[1]; } inline void NCompactSearcher::vtxBdryRestore(int vertexID) { vertexState[vertexID].bdryNext[0] = vertexState[vertexID].bdryNextOld[0]; vertexState[vertexID].bdryNext[1] = vertexState[vertexID].bdryNextOld[1]; vertexState[vertexID].bdryTwist[0] = vertexState[vertexID].bdryTwistOld[0]; vertexState[vertexID].bdryTwist[1] = vertexState[vertexID].bdryTwistOld[1]; } inline void NCompactSearcher::vtxBdryNext(int vertexID, int tet, int vertex, int bdryFace, int next[2], char twist[2]) { switch (vertexState[vertexID].bdryEdges) { case 3: next[0] = next[1] = vertexID; twist[0] = twist[1] = 0; break; case 2: if (permIndex(tet, vertexLinkNextFace[vertex][bdryFace]) < 0) { next[0] = vertexState[vertexID].bdryNext[0]; twist[0] = vertexState[vertexID].bdryTwist[0]; next[1] = vertexID; twist[1] = 0; } else if (permIndex(tet, vertexLinkPrevFace[vertex][bdryFace]) < 0) { next[0] = vertexID; twist[0] = 0; next[1] = vertexState[vertexID].bdryNext[1]; twist[1] = vertexState[vertexID].bdryTwist[1]; } else { // We must be in the process of gluing a tetrahedron // to itself, and one of the gluings hasn't happened // yet (hence bdryEdges == 2 but only one boundary // edge shows up in the gluing permutations). // The boundary that we're not seeing must belong // to either the tetrahedron face we are currently // working with or its adjacent partner. int ghostFace = (bdryFace == order[orderElt].facet ? (*pairing_)[order[orderElt]].facet : order[orderElt].facet); if (vertexLinkNextFace[vertex][bdryFace] == ghostFace) { next[0] = vertexState[vertexID].bdryNext[0]; twist[0] = vertexState[vertexID].bdryTwist[0]; next[1] = vertexID; twist[1] = 0; } else { // Sanity check. if (vertexLinkPrevFace[vertex][bdryFace] != ghostFace) std::cerr << "ERROR: Inconsistent vertex link " "boundary information!" << std::endl; next[0] = vertexID; twist[0] = 0; next[1] = vertexState[vertexID].bdryNext[1]; twist[1] = vertexState[vertexID].bdryTwist[1]; } } break; case 1: next[0] = vertexState[vertexID].bdryNext[0]; next[1] = vertexState[vertexID].bdryNext[1]; twist[0] = vertexState[vertexID].bdryTwist[0]; twist[1] = vertexState[vertexID].bdryTwist[1]; break; } } inline bool NCompactSearcher::vtxBdryLength1(int vertexID) { return (vertexState[vertexID].bdryNext[0] == vertexID && vertexState[vertexID].bdryEdges == 1); } inline bool NCompactSearcher::vtxBdryLength2( int vertexID1, int vertexID2) { return (vertexState[vertexID1].bdryNext[0] == vertexID2 && vertexState[vertexID1].bdryNext[1] == vertexID2 && vertexState[vertexID1].bdryEdges == 1 && vertexState[vertexID2].bdryEdges == 1); } // Inline functions for NClosedPrimeMinSearcher inline NClosedPrimeMinSearcher::~NClosedPrimeMinSearcher() { delete[] orderType; if (chainPermIndices) delete[] chainPermIndices; } inline char NClosedPrimeMinSearcher::dataTag() const { return NClosedPrimeMinSearcher::dataTag_; } } // namespace regina #endif regina-4.95/engine/checkdocs000755 000765 000024 00000000453 12234011536 015727 0ustar00babstaff000000 000000 #!/bin/sh for i in `find . -name "*.h"`; do if ! grep -r "@{\$" $i > /dev/null; then case "$i" in *registry.h ) ;; * ) echo "$i : No group opening tag." ;; esac elif ! grep -r "@}\*/\$" $i > /dev/null; then echo "$i : No group closing tag." fi done regina-4.95/engine/CMakeLists.txt000644 000765 000024 00000004753 12236713375 016636 0ustar00babstaff000000 000000 # Engine SET ( SOURCES engine shareableobject ) # Each of these subdirectories modifies the SOURCE variable, # adding more source files ADD_SUBDIRECTORY("algebra") ADD_SUBDIRECTORY("angle") ADD_SUBDIRECTORY("census") ADD_SUBDIRECTORY("data") ADD_SUBDIRECTORY("dim2") ADD_SUBDIRECTORY("enumerate") ADD_SUBDIRECTORY("file") ADD_SUBDIRECTORY("foreign") ADD_SUBDIRECTORY("generic") ADD_SUBDIRECTORY("manifold") ADD_SUBDIRECTORY("maths") ADD_SUBDIRECTORY("packet") ADD_SUBDIRECTORY("progress") IF (NOT EXCLUDE_SNAPPEA) ADD_SUBDIRECTORY("snappea") ENDIF (NOT EXCLUDE_SNAPPEA) ADD_SUBDIRECTORY("split") ADD_SUBDIRECTORY("subcomplex") ADD_SUBDIRECTORY("surfaces") ADD_SUBDIRECTORY("triangulation") ADD_SUBDIRECTORY("utilities") ADD_DEFINITIONS(-DREGINA_DLL_EXPORTS=1) INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/engine) INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/engine) ADD_LIBRARY("regina-engine" SHARED ${SOURCES} ) SET(ENGINE_LINK_LIBRARIES ${LIBXML2_LIBRARIES} ${GMP_LIBRARIES} ${GMPXX_LIBRARIES} ${ZLIB_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) TARGET_LINK_LIBRARIES(regina-engine ${ENGINE_LINK_LIBRARIES} ${ICONV_LIBRARY}) SET_TARGET_PROPERTIES(regina-engine PROPERTIES VERSION ${PACKAGE_VERSION}) SET_TARGET_PROPERTIES(regina-engine PROPERTIES SOVERSION ${PACKAGE_VERSION}) SET( HEADERS ${HEADERS} docs.h engine.h regina-core.h shareableobject.h ) SET( MANS ${MANS} regina-engine-config.1 ) # Build space-separated flag lists for regine-engine-config: GET_DIRECTORY_PROPERTY(ENGINE_INCLUDE_DIRECTORIES DIRECTORY .. INCLUDE_DIRECTORIES) foreach(arg ${ENGINE_INCLUDE_DIRECTORIES}) SET(RECONFIG_INCLUDE_FLAGS "${RECONFIG_INCLUDE_FLAGS} -I${arg}") endforeach(arg ${ENGINE_INCLUDE_DIRECTORIES}) foreach(arg ${ENGINE_LINK_LIBRARIES}) SET(RECONFIG_LINK_FLAGS "${RECONFIG_LINK_FLAGS} ${arg}") endforeach(arg ${ENGINE_LINK_LIBRARIES}) CONFIGURE_FILE ( regina-engine-config.in "${PROJECT_BINARY_DIR}/engine/regina-engine-config" @ONLY ) INSTALL(TARGETS regina-engine LIBRARY DESTINATION ${LIBDIR} RUNTIME DESTINATION ${BINDIR} COMPONENT Runtime) if (${REGINA_INSTALL_DEV}) INSTALL(FILES ${HEADERS} DESTINATION ${INCLUDEDIR} COMPONENT Development) INSTALL(FILES ${CMAKE_BINARY_DIR}/engine/regina-config.h DESTINATION ${INCLUDEDIR} COMPONENT Development) INSTALL(PROGRAMS ${PROJECT_BINARY_DIR}/engine/regina-engine-config DESTINATION bin COMPONENT Development) INSTALL(FILES regina-engine-config.1 DESTINATION ${MANDIR}/man1 COMPONENT Development) endif (${REGINA_INSTALL_DEV}) # Miscellaneous extras: ADD_SUBDIRECTORY("doxygen") regina-4.95/engine/data/CMakeLists.txt000644 000765 000024 00000000440 12235500316 017520 0ustar00babstaff000000 000000 SET(INTERNALDATADIR ${PKGDATADIR}/data) SET(internaldata_DATA snappea-census-sec5.dat snappea-census-sec6o.dat snappea-census-sec6n.dat snappea-census-sec7o.dat snappea-census-sec7n.dat ) INSTALL(FILES ${internaldata_DATA} DESTINATION ${INTERNALDATADIR} COMPONENT Runtime) regina-4.95/engine/data/create-snappea-census-data.py000644 000765 000024 00000003114 12234011536 022430 0ustar00babstaff000000 000000 #!/usr/bin/regina-python --nolibs # # Reads the given file and outputs a line for each triangulation. # Each line contains two alphabetical strings, encoding the face gluings # and the first homology group respectively. # # The face gluings are encoded using the Callahan-Hildebrand-Weeks # dehydration format. The first homology group is encoded by a string # of letters, where a..z represent 0..25 and A..Z represent 26..51. # The first letter gives the rank of the group, and the remaining # letters give the invariant factors. import sys if len(sys.argv) != 2: print "Usage: " + sys.argv[0] + " " sys.exit(1) tree = regina.readFileMagic(sys.argv[1]) if not tree: print "E: Could not open file " + sys.argv[1] + "." print print "Usage: " + sys.argv[0] + " " sys.exit(1) def encode(i): if i < 0: print 'ERROR: Negative integer:', i sys.exit(1) if i < 26: return chr(ord('a') + i) if i < 52: return chr(ord('A') + i - 26) print 'ERROR: Integer out of range:', i sys.exit(1) def grouprep(g): ans = encode(g.getRank()) for i in range(g.getNumberOfInvariantFactors()): ans = ans + encode(g.getInvariantFactor(i).longValue()) return ans def process(tri): dehydration = tri.dehydrate() if dehydration == '': print 'ERROR: No dehydration' sys.exit(1) print dehydration + ' ' + grouprep(tri.getHomologyH1()) sys.stdout.flush() p = tree while p != None: if p.getPacketType() == regina.NTriangulation.packetType: process(p) p = p.nextTreePacket() regina-4.95/engine/data/README.txt000644 000765 000024 00000000313 12234011536 016455 0ustar00babstaff000000 000000 Regina Internal Data Directory ------------------------------ This directory contains data files for internal use by Regina's calculation engine. Users should not need to access these files directly. regina-4.95/engine/data/snappea-census-sec5.dat000644 000765 000024 00000017553 12234011536 021251 0ustar00babstaff000000 000000 baaaade b cabbbbabw bc cabbbbecs bc cabbbbmlq bf cabbbbdam b dafbcccaadl bd dagacccffon bf dafbcccauwg bd dafbcccekms b dadbcccaqgw bc dadbcccaqrb bg dadbbccaunk b dadbcccbbcv b dafbbcccwoo be ebdbcdddaaafj be dadbcccemjg b dadbbccptnk b dafbbccltcg bh dadbcccccdm b dajbbccmlwn b dajbbcctvik bc ealcbdddjbntr bd ebdcbdddqhhpe bh ebdcbdddqhhit bd ffdaabcdeeeaaaadl bf dadbcccaqhx bcc ecjbbcddapnhg b ecdbbcddlqbaf b ebjabcddghikt bc eanbddcdhwbog bf ebgaccddkbgqg bh ebgacdddggsfu b eanbcddddsnno b eanbdcddmnjno bj ebfbcdddlhqpt b eanbddcdhwbgg be ealccdddeonob bd ecgaccddffohf bi eahbcdddlgkrb b fffaabcdeeehhqhgw be fffaabcdeeeahqhkv bi eahcdbddbokpv bc fbhaabcedeeukxnjo bf ecfbbcddhlfhg bf ecfbbcddepwab b eahccdcddbfof bc ealbcdcdlgbgk bk fkdaabbcdeeqpbaab b eanadccdphfpk bc fkdaabbcdeelqbaab b fapaaccddeegmgegb be fddaabcceeeqqotlf be ecdbbcddlqghf b eahbccddigvnb b eaoaccddfkoab bl ecdbbcddqpghf b ebjabcddgqmnt bc fjkaaabddeenaunxe bf fbhaadcedeehfmclp bl fapaacedceegsgosg bd ecdbbcddihohf b fdkaaabddeefankak bj ffkaaabdeeekqghaq bh ffjaabaddeeafblqm b ecdbbcddptchf b ecfbbcdddjmqg be fdjaaabddeenqofhf bl fihaaccdceeqjofqn bf fehaacddceeqbtoqt bd eahdcccdaokrb b eahdcccdakrob b fkdaacccdeeienhhk b fkdaacccdeehtfhhk b fenaabdcedepffhng bn ffkaaabdeeeghfdad b fbhaabedddeqqgtlo bc fenaabdcedepfoqnn bn fbhaadceddelganwf bd fffaabcdeeeaatasf b fenaabdcedeewwafn bo fcoaaacdeeefkamfk bc ealcdbddhrfwb b ebgaccddbbkqg b fkjaabbcdeemlgqqg b fjfaaaccdeebbgqqb bm fdjaaabdeeenakhaq bi fihaaccdceebpfoan bc fkjaabbcdeehdoqqg b fkjaabbcdeetviaan bc fehaabdcedehofaon bn ffkaaabdeeefhghaq bh ealcddcdkejdm b fjfaaaccdeepdgqqb bd fchaabbddeehkbfhk bp ffjaaabdeeenqgeth b fkjaabbcdeelakqhk b fkjaabbcdeeapkqhk b fgjaaabceeefqafle be fkjaabbcdeeeqbqhk bf fkjaabbcdeepisqhk b ecgaccddnfohg b fgkaaabceeefaqgmi bc fkdaabbcdeeihoqak b fkdaacccdeeienhhf b fdjaaabddeegqngaf br fchaacdeceeablkau bd fkdaabbcdeeptcqak b fkgaaabcdeettbhan bc fkdaacccdeehtfhhf b fgkaaabcdeeohhpou be fjfaaaccdeenjgqqb bs fdkaaabdeeefhopih bc fkdaacccdeeienhqb b fkdaacccdeehtfhqb b fihaacdddeeqbuehf bc fclaaccdeeemfneam b eanbcdcdlcngn b ealcbdddhfgwb b ecdcccdddenan b ealcdddcqvbfb b fbnaaacdeeenklirb b finaabbddeeabnkxf b fbnaabedddeaawonf bh fihaadccceeaokraf b eahbcdddjxxxj cc ealcddcdicwcc c ebdbcdddccvca be ebdbcdddaccax bi eahbcdddjsssh c eahbcdddhsssj c ealccbddhffof bi ebdbcdddbbbbx c ebdbcdddaabbx be eahbdcddnxxxk bc eahbcdddhhhhx bcc ebfbcdddhhqha bce ebfbcdddhhqxq bcc ealccdddevbrj b ealbcdddkhdwh b fcnaabbddeeabonan bg fbhaabdeedeuoxibu bc eanbcdcdlcnon bc ealcdbddhrkwb b fcoaaacdeeefkaufb b fjgaaaccdeeoochab b ecfaccddbbgxk b eahdcbddhnkcn bc ealbdccdiofkg bc ealbcdddqvnkg bc ealbdcddannkg bg fjfaaaccdeebnkaab bd finaabbddeeabfohg b ebdbcdddaahkc bg ebdbcdddaajfa bc ealcbcddmnjof b feoaaacdeeebsqqgb b ealbdcddieojp b finaabbddeeabfohf b eahbdcddkebob bc ecfaccddnbgxk bh eanbdcddhoobg bd fjfaabccdeeaawqhg bc fbnaabecdeehhogit b fkjaabbcdeepdsqhk b fjdaaccdceemthoab b fihaadccceeakroak b fbnaabedddeaanwok b ffkaaabddeefhgbqn b fapaaeddeedsnksgs bc fdjaaabddeenaknab bd eanbdccdmwbbn b finaabdcceepffnhg b fihaadccceeakroqg bh fihaadccceeaokrqg b fapaaecdedefiifed b fjdaacbddeeqqqlgo b fchaacdedeeuiaiee b eahbdcddsoqtq b ecfaccddnbnan b fehaadccedeaokhbf bc ebfaccddnbjan b fenaabcdedelcnhnk bd fjkaaabddeebanwak b fchaadceddehnacnb b fihaadbddeedgvnhg bh ecfaccddnbnaf b fihaacbcdeeunjoan bd eahdccddakfhq b fihaacdcdeeeofwhf bc fjfaaaccdeenbjxaf b fjkaaabddeefabsan b fbhaadeddcedqvnkk b fbhaaccddeeqkkolk b finaabbddeehgwohg b fkfaaaccdeebbgqqk b fdfaabcddeehhlmgo b filaacbcdeeqbgbaf b filaabcdceelbgbhf b fkfaaaccdeenbgqqk b fdfaabcedeeaaembc b filaabdddeeawgrab bc fapaaccddeevkfnbg b ebfbbddddilph c ebfbcdddddiix c ebgaccddixidd bc eahbcdddtuutu bc ebdcbdddqhamu bf ebfbcdddahqmd bdd fehaabdcedeukkabf bk fehaabbdeeeqbchaq b fdfaabcddeehhlmwn b fcnaabbedeeabaoqh b filaacbcdeeqngbaf b fkfaaaccdeenbgqhf b fkfaaaccdeebbgqhf b fehaadbdededfvqkf b finaabbddeeabwohf bf finaabdcceeegwnhf bg fclaacceddehfhofg b fehaabdcedetknanb b fchaaccdeeeengtmi b fbhaaccdeeeekgelp b fcoaaadddeefaqhxn b fchaabcddeepfnwdb b fapaacdeceegononc b filaabdcceedoskhf b fbhaabededehhgkck bc fdkaaabdeeegqgahq bc fcnaabbdeeehfwhaq b ffjaabaddeehfocxg b fehaadccedehofxgn b filaaccbdeehfffhf be fbnaabeededaaifpf b filaaccbdeehfkfhf b ffdaacbdeeeqhhqfo bk ffdaacbdeeeqqaacv bg fbhaacdeceeagakhq b fffaabcdeeeaaafdv b finaabbddeeabwohg b ffjaabaddeehfcbxo b filaabdcceedofkhk b fdkaaabddeefhbfxo b finaabbddeeabwkhg b fblaacceddemjqofo b fkfaaaccdeebbnqqb b fffaabcdeeeaaaqhx bce filaaccbdeednkvhf b fbhaadcdeeeurgpeh bd filaabcdceeqggghf b fbhaadceedeiremfe bd fehaabbddeehkcfqb b fihaacbcdeemfbghk b fehaadccedehkfagk b fjfaaaccdeenbjaab bj fffaabcdeeeaaarec b fblaacceddeqgqbbg be fjjaaabddeegqsnqg b fclaacceddeinhnbg b filaabcdceeqggohf b fclaacddceeqfbbqn bj fbhaaddedeedkandu b fblaacdeedeqouunh bc fenaabcddeehoooxk bk fkfaaaccdeebbnqhf bc fapaaedeeddllilti bc fapaaeceddecgdobp b fblaacceddeqgqbkg b filaacbcdeemnjfhf bh fclaacdeedegaihdj b fbhaadeddcedqvnfk b fclaacceddeinanbn b filaabdcceeioffhf b finaabbddeehfwohg b finaabcdceelcnoan bc fchaacdeedeqbijap bg fchaabcddeepfnwdk b fkgaaaccdeeogoxqb b finaabdcceemgbbhf b fdkaaabddeefhobqk bc fkfaaaccdeenbnahg b fkfaaaccdeenbnaab bl fkfaaaccdeenbnqhf b filaabdcceedofkhf b ffdaabcdeeebbtbqb b finaabdcceeegbnhf bc fchaadcdeeeubgpeq bf fehaaccdeeeenodte bd fioaaaccdeegbfxqb bl fddaabcedeeqqaabf bd fbhaaceddeedlvoml b fdfaaacdedebbqxok bc fapaabcdedenjjonf b fenaabddeeehvndil c fblaabeecdeaaqknq bk fblaabededeaabqgq bg fblaaccedeeqwqwxq c finaabdcceeegwfhf bd fblaabeecdeaaqkkh bc fehaaddceeejknvep b filaabdcceedosbhf b fcoaaacdedefkhhff b felaacdceeedtkfla b fcnaaaddedenqhqag b fapaabddceefvnkvg bc fbhaabecedehhgqgq bc felaacddeeehrwaqx bc fapaaedceedsfojof bcc fchaacdcdeeannkan bce fddaabcdeeeccccwr b fchaaccddeefbglqg bc fcnaabdeedeebdubx b fehaaddcedejwejlg bc fapaabcdedenjfogf bc fapaadceddenhknhk be fddaabcedeeaaccxa bc ffdaabcdeeeaacccv bc finaabdcceeegbbhf b fbhaadcdedeafhaqk b filaabdcceedoffhf b filaabcddeeqjosqg bc fihaabdcdeeqbbbqk bk fapaacecedetbwlog b fbnaabcedeehoigld b fapaadedeceoitfko bcc fapaadedecemjuffj bc fapaaccddeehgghgk b fapaabdcdeenbggbg b fdfaabcedeeaaknbk bc fehaabcddeeljjvqk c fbnaabcceeehoolhi c fffaabcdeeeaaqqie bj fbhaadcedeeigqfvr bd fclaabdedeeaurnnb bd fchaabcddeekxxkcf b fbhaacecdeenkxnke b fapaacbdedeffjobf b fapaacbdedefkjobf bk fddaabcdedeqqahbo b fapaabdcdeeffksfn b fapaabcdedenjfoof b fbnaabedededinioa b fbhaadeddeeuggxlq b fblaacbeedehfqanx b fapaacedcdeggbonf bc fddaabcdeeeknglje b fehaaddceeetrklpu b fchaabdeddelbpgsg b fapaaceeddetumqde bd fcoaaaddeeegkwkmp b fclaacbeedehgaqnh b fapaabdcdeefvkkkn b fapaacbdedefkfoof b fblaaacdedefnahbo bc fbhaadcdedeafhhqg b fbhaaccedeeaxvava bg fbhaaccedeeaqnhna c felaacdcdeehgfohn b fdfaabbddeedihlfk c fapaacedcdeggbgbg be fbhaacdeeeddoddxo c fapaacbdedefkjobn bc fcoaaacdedefkhafk b fbhaadecdeekfqjnd bc fapaacbdedefkjonf b fapaaebceedrkbbbb b fapaacedceeggbgah be fjfaabbddeedilpff c fffaaacdeeenbldsk c fblaabeecdeaaqffq bi ffdaacbdeeeqhaqcn bm fffaabcdeeeahqhbw bi fblaabeeddeaaqofq be fapaacdceeekfoofo b fapaaeecdderwbobb bdd fapaacbdedeffjobn b fblaabceedebwdsdk bc fclaacdedeedkhndl bc fbhaadcdedeafhhqo bc felaacdcdeehgfoqk b fjdaacbddeewussik bc fbnaaacdeeenkggxl bc fdfaaacedeenbnnpl bc fblaabeeeddvuvuon bc fbhaabddeeejxjhss bc fapaacededeaaxvav bc fapaacbdedefffogn b felaabcddeevtfllt bc fddaabcedeeccccvc bc fgfaaacceeenbnkjg c fbhaadecdeeumorxt b fehaacdceeearkpud b feoaaacdeeefkitfr c fapaabcdedebjfogn b fdfaaacdedenbqxgk b fbhaaceddeenjhjsd b fdfaaacdedenbqagf b fcnaabdedeehepklf bc fdfaaacdedenbaxnk b filaabcddeeddddrn bcc fapaaeecdedixdnib bi felaacdcdeehgfohf bc fdfaaacdedenbaxnb bn fdfaaacdedenbqxgb b felaacdcdeehgfoqb b fcoaaacdeeefkxahq b fapaacdceeedeqdeq c fapaabcddeelnltkt bi ffdaabcdeeeaqhhet bl fapaadcceeebqnbqk bce fapaadcdeeebqkbnq cc fclaabdeedeisldoq bc fapaacdceeedemdeu bg fbhaacdedeedfdnxt cc fapaadcceeebfmbfu bc fapaacdceeedeodew bc regina-4.95/engine/data/snappea-census-sec6n.dat000644 000765 000024 00000013401 12234011536 021414 0ustar00babstaff000000 000000 gfdabbcdefffaaaaabr bg ggjababceeffbqqdkxp bh gkfabbcbdfefaabafdu bc glkaaabdfefffanxftn bh ggjabbacefffhkhfnmn bf ggfabbcbefffaabbcqj b glkaaabdfefffhoxkuo bi ghjaabadfeffhkfrhfd bh gofaabcbeeffaabniae be gkfabbcbdfffaabaekl bc ggjababcefffbqqbjxn bh glkaaabdfefffhorhuo bi gjdabbcdcfefaaakbet bc gkjabbacdfefafahglt bc ggjababceefffhhgdqd bc gokaaabceefffaqodhl bd ggkababceefffaqgdqd be gkjabbacdfffafahiom be gkkababcdffffaqqdnp bc ggjababceeffgqqgdqd bf gjdabbcdceffaaakmnt be gkkababcdefffhqqpou be gokaaabceffffhqnoba bd gkjababcdfffbahqdnp bc ggkababceefffhqbiqi bf gkkababcdfeffaqqbet bc gkjababcdfefbqqafdu bc gkjababcdfefbahqbet be gkkababcdfeffhqqglt bc gkjababcdfffbqqaekl bc gkkababcdfeffhqafdu be gkkababcdffffhqaekl bc gfhaabedfdffkknknan bc gchaccdceeffadtexgg b gfdabbcdefffaaaaffx bc gcpaadedfffejkqbtxk bc gfdabbcdefffaaaaqkv bk gnfaabcdefffaaallbs b gfdabbcdefffaaaarba be gdhaabfefeefkupfxph bd gapabddedfffngqnhxn b gchabcdedfffuoaonse b gbhabbcedfffukanqpe bk gflaaaceefffknameqe bg gkoaaacdfeffnkakdrl b ggoaaaceefffnkiqfmk b gkoaaacdfeffnkahkwm b gkoaaacdfeffnkaknqh bce gapabecdefefsiisvdg bc gnjaabadefffafbqfpg bc gnjaabadefffafbqcrm b gnjaabadefffafbqrba b gkoaaacdfefffkadkrp bc gkoaaacdfefffkakhwe be gkoaaacdfefffkaknqh bk gbpaafcdeeffepmqvka bc gepaaedeffdftftdlfx b ghjaabadfeffhkfrhms b ghjaabadfeffhkfkhot bg ghjaabadfeffhkfjhaf b ghkaaabffeeffhfffhx bc gflaacceefffdkxslae b gflaabefddffofofgfx b gnfaabcdfeffaaahqbk b gflaabefdfefofokseb c gnkaaabdfefffhgkqmb bcc gnkaaabdfefffhgjqrp b gnkaaabdfefffhgrqgh b glkaaabdfefffhorhjl bc glkaaabdfefffhokhpf bg glkaaabdfefffhojhoq be gjlaacedfdffnnnknnx bc gnkaaabdfefffhlxqgi b gbhacdcdedffafhaqak b ghjaabadfeffafiqalf bc gffabbcdefffaaaccax be gnfaabcdfeffaaaaaxa bcc gdlaacdefffeeqamgeb be gflaaaceefffknamqme bg gklaabddefffdffofxp b gcoabacdeffffkaobbk b gcoabacdefffnkaobbk bc gbhabbcedffflfndahq be gnkaaabdeffffasqxha bc gflaabefddffofoonfx b gknaabbdefffhgkhhmd b ghjaabadfeffafkqaqa b gdhaacdeefffaujfxeu b gcoabacdefffnkannok bc gdhaabfefeeflpbkxbr b gkoaaacdeffffkattqi bd gffabbcdefffaaaqhjs bm gffabbcdefffaaaqhof bm gnkaaabdfefffhvxqah b gcoabacdeffffkannok b gbhabbcdeffflfndqha be gbhabbcdeffflfndqqx be ghjaabadfeffafgqaqa b ghkaaabdfefffhdqhek b gflaaacdfefffnnwdhp b gknaabbdefffhgkxahq b gbpaaccfeefffqxfqrr bcc gbpaacdefeffqxqqxqx bcc gbpaacfdefeferoilih bcc gepaaceffdefbdsoomx be glkaaabdfefffanqqei b gjlaaacdeffffnxnkip bj gjlaaacdeffffnxnkld bj gfjabbadefffafbssbn bc gnjaabadfeffafbjjem bc ghkaaabdfefffhfqhqh bc gbpaaeffefedjqgobjn b ghkaaabdfefffhbqhqh b gmlaacdcefffdekxole b glkaaabdeffffanggem bcc glkaaabdeffffanjjxm be gfkababdeffffhgjsim bc gfkababdeffffhgvvam bc gnkaaabdfefffhghhpe b gbhabbcedefflfndkqo b gfnaabddfeffhpkrsps bc gddabbcdefffaaknnna c gddabbcedfffaacvcca bi ghjaabadefffhkfbbxe bc ghjaabadefffhkfvvme bc gbpaacdfdeffiwxdluj bc glkaaabdfefffhoqqld bc gblabacdefffknhhhkv bcc gflaaaceefffknanduk bd gflaaaceefffknanepk bd glkaaabdfefffanaxqh b gkhaabddfeffummhxkv b ghdaabcdfeffqqswphu bc gdkababdeffffhorwmi bc gdkababdeffffhovvai bc gelabbcdeffflhgbkje c gflaabdfdefflgrpjwx bg gglaaccdfeffmfpxcno b gnkaaabdfefffhgqxha b gfkababdeffffhgjsam bcc gfkababdeffffhgvvim bi gdlaacdefffeeqamgek b gblabbddeefflgekoxf bc gdkababdeffffhorwai bc gdkababdeffffhovvmi bc gkoaaacdfefffkanndu bg gkoaaacdfefffkannlm bg gapabdecefffwwxxxww bcc gbpaaccfeeffcxxxcxx bcg gbpaacdfeeffcaxacxx c gbpaaddfeeffacxacxx c gcpaacedeffftetkxje be gddabbcedfffaaccacv be gdhaaceeffefffrvxth c gdhaaceeffefffwlxps c gdhaadffeeefafcisij bd gdnaabfefeefbxthvde b gfdabbcdefffaaccaax bm gfhaabeddfffomqahwp bc glkaaabdfefffhohxqa be gdlaacdefffeeqhmgeg be gcoabacdeffffkhhqkc bc ghdaabcefeffqqjwlnc b gblabacdefffknaxakn bcc gcpaacfeedffbgtigqg b gclabbcdeeffpntfoxo b gflaabceefffiosvfvk bd gglaaccdefffmfqctxe bd gdlaacceefffqqxcqru bc gddabbcedfffaaxxcav be gddabbcedfffaaxxjfa be gipaaecdffeftctqiqs bc gbnabbedeeffaafnkhg b gbnabbedeeffaafnkak bc gapabcdcefffkfooafj bd gdlaacdefffeeqhmgeo b ghdaabceffefqqjnffq b gclacbcdeeffpntfvqn be gfhaadcfdeffccxxawo b gflaabddeeffqunprwf bc gdhaabdeffefsmisxkj b gmlaacdcfeffdlknchr b gflaaacffeeffnnwuti b gjnaabdeefffsfxkbmq bc gddabbcedfffaaaarxr bc gfdabbcdefffaahhhbw bo gbhabbdedfefrehppgt bc gclacccedeffmfkenxn bc gjhaabcedeffjdqnatd be gjhaadcefeffafofkko bd gbnabbedeeffaafnkhf b gapabcdeceffbmmbevo be gapabdeeddffjxjjijd be gbpaacdffeefcxxwjxc bce gbpaadffddefooxmukj be gdhaacedefffddlxxhu cc gdhaacefefefddkxxvh cc gdnaabddefffafqvvbh c gdnaabdfeeffafkvkvb c gbpaaefffdeeorxrfon bc gbpaaddefeffngkcgwp b gbpaacffdeefmonexeq b gldaabcdfeffwwxvckr bc gcoabacdeffffkxaank bc gcpaacfddfefggduxfr b gfhaabdedfffrexlpwx bc gapabcdeedffdoddobg bc gipaacdeedffioidovo bg gdlaaadfedfffhhagah b gbpaaeddffefnttsqnk b ggoaaacdfefffkkwuhe b ghdaabcdefffknodxbi b gdlaaadfedfffahabah b gbnabbcedeffhejkdfk b gbhabcdeedffwoccoln bc gchabdceceffcgsgcuk bc gcnabbcedffflkubqlm bh gdhaadceffeffaifrwa b ggoaaaceeffffkckfoq b gdlaacdfdffeqmmtqnf bc ggoaaacfeefffknmxna b gldaabcdfeffvvvxcph b ggoaaaceffefnkjofdv b ghdaabceffefaafrspb b gddabbcedfffaaaaccx be gfdabbcdefffaahhqfs bq gdlaabddfeffkfhjmmw b ggnaabdddfffnfqgsmt bc gbpaaddfeeffngjgkoq bd gblabbedefefwutxust be gdlaaadfedfffhhaoah bc gcoabacedefffkooioo bi gcoacacedefffkrrirf bcg gdnaabddfeffafillbt bd gdnaabddfeffafqllbh cd gdnaabdfeeffafqvhvb bi gdnaabdffeefafklklb c gdnaabdffeefafolslb bd gdnaabdffeefafqkhkb bcc gdlaaadfedfffahakah b gbpaacedfeffffccfir b ggoaaaceffeffkjofdv b gblabbcdefffiofxctf bd gcoabacedefffkooisn bc gdnaabdceeffafdgjbt bc gbhabbeddfffgiahqsl bc gclabbdedfffkfdhobi b gbhabccddeffqqkxmnt be gbhabccddfffqhkadnp be gapabcedefffaxaxxaa bcc gapabdcdefffrxxxxrx bce gbhabbeeefffoemjfkq c gbpaaccfeeffhxhhxxh bce gbpaaccffeefhqaaqhx cc gbpaacdeffefhhhxxhx d gbpaacdeffefxxxxxxx bccc gddabbcedfffccccccx cc gddabbcedfffccccvvx be gdhaabefeffehxxsxsh be regina-4.95/engine/data/snappea-census-sec6o.dat000644 000765 000024 00000052100 12234011536 021414 0ustar00babstaff000000 000000 gfdabbcdefffaaaaadm bf gfdabbcdefffaaaaalu bj gkfabbcbdfffaabafon b gkfabbcbdfffaabafwo b gbhaccceddffujqjbqb bn gldaacbdefffqhtfgog bf gddacbcceeffqqogbqb bl gokaaabceffffaakhaq bj gehacbcdedffqbgagab bo gkdacbccdeffqqkaqqn bg gkfabbcbdfffaabanko b gkfabbcbdfffaabawkn b gnkaaabdefffoqbxmvb be gokaaabceeffkqqngaf bq gbhaccedddffpqfokqf be gchacccddeffuggcxqo bq gojaaabcefffbaanmmq b ggkacabceeffkqqbsxk br gbpaacfedeefgqvbvbg bf gehacbdcdefflgnwlak br gjdabbcdcfffaaakbgk b gjdabbcdcfffaaakbsg bf gilaccbcdeffmgoohhk bt ggkababcefffkqqbeqp b gelacccddeffeojnuqk bt gapacedceefffowfghk bd gkjabbacdfffafahgbf b gkjabbacdfffafahgvb b gkfacaccdeffffohhhf bq gjfacbccdeffhhnaaak bm gilacaccdeffkknaahg br ggkababceffffaqghaq bl gkjabbacdfffafahfob b gokaaabceefffaqofhf bs gkdaccbcdeffqqohqaf bk gkjabbacdfffafahvof b gkkababcdffffaqqkng b gkkababcdffffaqqsnk b gkfacaccdeffffohaak bt ggkababcefffnaafahq bj gilaccbcdeffdfvvqqg bw ggkababcefffbqqbeae bc gjdabbcdcfffaaakkng b gkkababcdffffhqqfob b gjdabbcdcfffaaaksnk bf gkkababcdffffhqqvof bf gofaabcbeeffhafnkak bt gddabcbcefffqhbfahq bn gchacccddeffpbgoqqb bd gapacedceeffforfbhf bx gkjababcdfffbahqkng b gkgacaccdefffnwaqqb bx ggkababcefffnaafdhl bf gkjababcdfffbahqsnk b ggjababceeffnahgbqb bv gldaabccfeffqakqfah bl gokaaabceefffaqobhk by gchaccdeddffpoansqo be gkkababcdffffaqqbgk b gkkababcdffffaqqbsg b gilacbcdceffpknbaqo bz gilaccccdefftgknaqo bd gkjababcdfffbqqafon b gkjababcdfffbahqbgk bf gjfacaccdeffnjgqqqg bA gokaaabcefffohhopih bc gkjababcdfffbqqafwo b gkjababcdfffbahqbsg b gkkababcdffffhqqgbf b gkkababcdffffhqqgvb bf ginacbdcceffmgwnqak bB gclaccdeddfflkhfcqb bf gkjababcdfffbqqanko b gkkababcdffffhqafon b gkjababcdfffbqqawkn b gkkababcdffffhqafwo b gojaaabceeffnahobhk bD gokaaabcefffgqakiae bd gkkababcdffffhqanko b gkkababcdffffhqawkn b gfdabbcdefffqqqqplu bd gelaccdceeffdgkexgo b gkfabbcbdfffaabanwo b gkfabbcbdfffaabawon b ggjacbaceeffafankxf b gbhabbceefefufxdaub b genacbdcedffpfoqfqo b gkoaaacdfefffkakknf b gofaabcbeeffaabnkaf b gehacbdcdefftngbqqo b gcpaadfeedefoakvkbb b gkjabbacdfffafahfvb b gkjabbacdfffafahvbf b ggjababceeffbqqbgxn b gjdabbcdcfffaaakksg b gjdabbcdcfffaaaksgk b gofaabcbeeffaabgkak b gljaaabdefffnakaufb b gojaaabceeffbqqnkaf b gddabbcceeffaqofohg bd gkkababcdffffaqqksg b gkkababcdffffaqqsgk b ggkababceefffaqgbqn b ggjababceeffgqqgbqn b gkjababcdfffbahqksg b gkjababcdfffbahqsgk b gkkababcdffffhqqfvb b gkkababcdffffhqqvbf b gfdabbcdeeffqqqqkhg b genacbdcedffhfoqnak bd ggkababceefffhqbgqo b gkjababcdfffbqqanwo b gkjababcdfffbqqawon b gkkababcdffffhqanwo b gkkababcdffffhqawon b gflaabedfffeaasqhab bd gdjacbaddeffafnkahg b gioacaccdefffknaqqn b gblabbeedeffqqunitg bc gepaadeffdefrbepsbx bc gbhabcedcfffaqkfqgb b gfjacbaddeffafbgqhk b gioacaccdeffnknaqqn bc gilacaccdeffffraaab b gjkacabddefffhgvxqk b godaabcbfeffqqfhbaq bd ggjabbaceeffafanoho b gclabbbdefffqfnqqgb b gehabbcdefefpbgaqfn b genabadcefffbqkqqah b gjgacaccdeffnwnhhhg b gklaacdefffehrahitb b gilacbbddeffqfbsxqf b ggfabbcbeeffaabskqo bc gghaabbdefffqbfedrg b ggjababceeffgqqfbqb b gdkacabddefffhofhab b ggkacabceeffghhgvhf bi gfkacabddefffhgbqaf b ggkababceefffaqfbqb b gjkacabddefffaknxqg b gokaaabcefffkqqsqha bd ggfacbcbeeffaabksxb b gjkacabddefffhvoxhk b gclabcdecfffqghbhfo b gehacbdcdeffqnoftqo b gelabbcdefeflgbqqnf b gldaabccefffqqooqah bg gjkacabddefffaknxaf bf ggfabbcbeeffaabbgxb bc gokaaabcefffnaawhaq bd gelacccddeffqvojuqf b gokaaabceefffaqbfhf bd gelabbbdefffakbaank b gokaaabceefffaaonxk b gjhaacdefdffqoaqofj bc gjhaabeddfefqqcbqob bc gioacaccdeffnknaaab b ggkababceefffhqkgqg bc gflaabedfffeahsqhag b ggfabbcbeeffaabbgxn bh gfdabacdeffffkhhhaq b ggjababceefffhhfbqb b ggjababceeffgqqvfqn bc gilacbbddeffafksahf b gklaacddfeefqfbqqnf b gnjaaabdfeffgqbuenc b gioacaccdeffnwohhhg bi ggkababceefffaqvfqn b gioacaccdefffknaaab bh gmnaabbdfeffhgfelwg b gokaaabceefffaqvbhk bg gojaaabceeffnqqgkqg b gknaabbeeeffabhqaxf b gokaaabcfeffnaahnqa bg gblabccedfffigafqjf b gdjacabddeffnakoaaf b ggfabbcbefffahbvhaq b ggjabbaceeffhkhfoxf b gokaaabceefffhqgkak bg ggjababceefffhhvfqn b gjlaacdefeefhvqaahn bc gokaaabcfeffghaqkha b gjkacabddefffhfoxqb b gfgabacdefffgchhhaq b gdjacbaddeffafsgaab b ggkababceefffhqskqo b gokaaabcfefffaqqfah bc gnkaaabdeffffhgminv b gclaccdeddffhrhwbhf bd gofaabcbeeffaabnkxb b gokaaabceefffhqsgaf bd gioacaccdefffknaqqb b godaabcbfeffqqgqfah bd glkaaabdeffffhoimkv b gfnaabefdffeaqhnqag b ggjababcefffgqawahq bc gioacaccdeffnknaqqb b gilacaccdefffrnxhhk b gjkacabddefffhfoxhk b gfjacbaddeffafbgqqn b ggkacabceefffaqfohf bc gojaaabcfeffnahhbaq b ggkacabceefffaqfohn b gfkacabddeffbafnxab b gcpaacfeddefnaknbfb b gbhabcdecfffqgaoakn bh ginacbbddeffabfwaqf b gdkacabddeffgqngahg b ggjacbaceeffafawkhg b gioacaccdeffnkoaqqn b ggkacabceefffaqgbqb b gdjacbaddeffafnkaab b gjkacabddefffhgvxhk b ggkacabceefffaqgbqk b ggkacabceeffnaafwab b gfjacbaddeffafbkqhk b gofaabcbeeffaabngqg b ggdaccbbeeffhhkfwak b gojaaabceeffbaangqf b ggkacabceefffhqknaf b gdjacbaddeffafnkaho b ggkacabceefffhqknan b gihacdbddeffdfvnhhf b gjkacabddefffasnxqf b gfjacbaddeffafbgqhf b ggjababceeffbqqbgqg bi gndaabcdffefaaasoff be ggjababceefffhhgbxg b gokaaabceefffaqofxo bc gjgacaccdeffkskqqaf b ggfabbcbeeffaabskaf bc gfhaadcfeeefafleumf b ggjababceeffbqqbgqo b ggkababceefffaqgbxg b gbhabbcdefffunnaqnv bc ggjababceefffhhgbxo b gjdacaccdeffkonhqqo b gcnacbbddeffhgknxhg bl gdhaadcfdeffuomklob b gokaaabceefffaqofxg b gddacbcceeffaqofwak bi ggjababceeffgqqgbxg bi ggkacabceefffhqbgqg b gfoaaacdffefofikfov b ggkababceefffaqgbxo b godaabcbeeffqqgobqk b ggkacabceefffhqbgqf bh ggjacabceeffnqhfwab b genabbdcefefpffahnf b genabbbdefffabwaakn b ggkababceeffnaafnak b gbnabbedefefaafdein bc gjkacabddefffhfoxhf be gnfaabcdefefaaakkkk be gehacdccedffhokxoab b gblabacdefefknhhhok b gdfacbcceeffahobsaf b ggjababceeffgqqgbxo b gkgacaccdefffwkhhhg b genabbbdefffhfwaakn b gokaaabceefffhqnkxn b gjhaabeddfefqagchff b gihacbdccefflfskaqo b gehacbdcedfftknabqo bi gblaccecddffiqvnbak b ggkababceefffhqbgxb b gkhaaddedfefurhgqon b gokaaabceefffhqnkxb bh gihaccbcdeffefvnaan b gddabcbceeffhqkbkan b gdkacabddefffankxqo b gdjacbaddeffafngahg bf gioacaccdeffobcqahg b ggkacabceefffaqvbxf b gkoaaacdefffnkafsnw b ggkababceefffhqbgxn b gchacddecdffiohkshf bc gjkacabddefffhvoxqk b ggjababceeffbqqjgxn b gbhacdedcdffihfbsho b gcnabbbedfffabaoank b gldaacbceeffqhbkohg b gdkacabddefffhvbhhg b gihaccbcdeffefvkhqb b gelacbdcedffdosafho b ggdaccbbeeffqqbbnaf bc gnjaabadefefafbaanf bc gjgacaccdeffffnxhqn b gjkacabddefffasnxak b gioacaccdefffkoaqqn b gjkacabddefffhfohab b gjgacaccdeffffnxhhf b ggfabbcbeeffaabskqg b ggkacabceefffaqfvxg b gblaccecddffeqbwfak b gcpaacdedffffwfbelq bc gghaacceefffmohxiln bg gfdabbcdefffaaaaqha bce gfdabbcdefffaaaaqqx bcg gihacdbddefftnbraak b gfkacabddefffhgbqqo b ggkacabceefffaqvbqb bc gfkacabddefffhvfqqo b gdjacbaddeffafnkaan b gokaaabceeffgqakohf b gokaaabceeffkqhonxk bf gehacbdcedfftknhbqb b gdkacabddefffhofhhg b gdkacabddefffankxaf bj gdkacabddeffoqgkxqg b gjhaadedfdefdqvqkkf b gdfacbcedeffaaembdb b gapabdceefffknfvedd bd ggkacabceefffaqfvhf bf gjhaadcefeefhcqhptf b gelacbbddeffafbkahg b gjjacabddeffnafwhhf b gjkacabddeffnabsxak b ggnaaacdefffgohgwfv b gelabbcdefeflgghhfk b ggjababceeffbqqnjxb b gdkacabddefffhofhan b gfkacabddefffhgfqaf b gfkacabddefffhgbqak b gdjacabddeffnaonaaf b gdjacabddeffghkoaqg b gjdaccbddeffhhhmoan b gjkacabddefffhvoxhf b gclabccedfefinhvhgf b ggkacabceefffhqsgxk b gioacaccdeffnkoaqqb bc gfkacabddeffbanohhf bc gioacaccdefffkohqqf bl ggkacabceefffhqsgqg b gdkacabddefffhobhab b gjkacabddefffasnxqg b gokaaabceefffaqbfhk bd ggfabbcbeeffaabskan b gchabbbdefffhfbhhfo bh gfkacabddefffakgxqg bc ggkacabceefffhqksxb b gokaaabceefffaqvbqk bc ggkababceefffaqvfhn b ggjababceefffhhvfhn b gapabdecdeffnvvnvqf b ggkacabceefffhqksan b ggjababceeffgqqvfhn bc gboacaceddffgcqhfqo bi gknaabbdefefaboahnf bk gelabbcdeffflbgaqjf bc ggkababceefffhqkgqo b ggjababceefffhhfbqn b gjkacabddefffhgvhhg b gbhabddedfeftbqragn b gjkacabddefffasnqqk b ggkacabceefffaqvbqk b gioacaccdefffkoaaan b gfkacabddefffhgbxhk b gcpaacdcffefbkkxubm b gkgacaccdefffwkhhho b ggkababceefffaqvfqb b gdjacabddeffnakoaak b gilacbbddeffakbsqhf bl gokaaabceefffaqvbhf bj gdkacabddefffhofxqn b gokaaabceefffhqsgqf b gdjacbaddeffafngaho b ggkababceefffhqskaf b gchacbbddeffqbobhhg b gehaccddceffajnfqhk b gelaccddceffqvobqaf bl gokaaabceefffhqgkaf bj gjkacabddefffhvoxqb bc gfkacabddefffhgbqqg b ggkacabceefffaqfvhn b ggjababceefffhhvfqb b gapaceedcdffkwwkkpg b gdkacabddefffhofhho be gfkacabddefffhgbxqn b gdkacabddefffangqhk bd gapabbcedfefgbwwanb b gdkacabddeffgqngxaf b gdkacabddefffhofxhk bd gblabacdefefknhhhgk b gknaabbeeeffabhqaxn b gbhacccddeffafjsqqb bg gjnaabedefffaqfxhto bo gbhaccddceffqgjoqhk b ggkababceefffhqskqg b gokaaabceefffhqsgak bd ghjaabafefefhfdixhf bc gioacaccdefffkoaqqb b ggkacabceefffhqsgqf b gdhaacefffdeaqqhxng b gblabcecdfefihvnqff b glkaaabefefffhixpps bc gfjacabddeffnafnxan b gcnacbbddeffhfkoaqg b gjlaabeddfefqhkgxfn b gdhaacefffdeaaqhxnf b gclacbbddeffqfngqqb bn gclaccddceffqvfbhhg b gdlaacceefffhodeeqf b gelaccddceffqgvbhab b gclabccedfefinananf b gfkacabddefffhgfhab b ggkacabceefffhqksaf b gflaacdcfeefqokhxgn bg gdjacabddeffnakoaqg bc ggkababceefffaqvfhf b gdkacabddefffhobxqb b gehacbdcdeffhvncqaf bc gehacdcdedffhofxfhg bo gokaaabceefffaqvbqb b gioacaccdefffkohqqg b ggjababceefffhhvfhf b gepaabeefedfnjoqjnn b gelabbcdefefpcnqann bj gdjacabddeffnakohhk b glfaaacefeefggtoonb bc ggkababceefffhqskan b gokaaabceefffhqsgqg b gapabdcecfefnnkfakb bo ghfaabcfefefaaakkgk bg gdjacbaddeffhfbchho b gapacccddeffbwgrgaf bg gklaabbdfeffqfnaank bk ghjaabaefeefafqqhag b gchaccddceffajkfqqb b gelacbbddeffakskqqb b gfkacabddefffhgbxhf bn gnfaabcdfeffaaannbg bd gfkacabddefffhvfqaf bn gdkacabddefffhofxqb b gkoaaacdfefffkakngr bcc gbpaacfedeefgqbbgbg bci ghgaaacefdffbbecbbv bo gbnacadeddffnaxhqak bc gdkacabddefffhofxhf bm ghfaaaceefffbbqwcvw bg gdfacbbddeffdihlsqo c gnfaabcdefefaaannkn bm gnfaabcdfeffaaaccvc c gblabacdefefknhhhof b gghaacdfdffeerpcabb bd gcpaacfeddefnavnngb b ghjaabafffeeafaqhak b gbpaaedffdfegbxlslg bc gclabccedfefinhnqgg bd gapabdecdeffnvvnvao b gnfaabcdefefhhhfffg b gehabbdcefeflfsahkg b gdhaacdcfeffakfaank bo gapacccddeffbwgrgqf bc gflaacdfdeffenhbtgk bd gcoacacdedffnkhhfqk b gmlaabbdefefqfkxabn bc gfhaaddffeefakhuuin bd gbpaaefceedfbabbbnf b gbpaaefceedfjqoojno b genabbdcefefegwahnf bc gbhabdeddfefdqvnabn b gffabbcdefffaaaqhlu bn gffabbcdefffaaaqhmd bdd genacbbddeffanwnaak b gjkacabddeffgqjnaan b gddacbcdedffqqaabaf b gblabacdefefknaxaff b gddaccbedeffditdoxn b gboabacedfeffnaaabf bc gljaabadfeefafnqqff b gknaabbdefefhgkhhff b ghjaabadfeefafgqafg b gcoacacdedfffkhhfhf b gdkacabddeffnasgqhf bg gdkacabddeffkakohhf b gnjaaabdefefbanxafb bq ghdaabcdefffaaxfofr be gghaacdfefeftreethv be gbpaaefedeffnqkngha be gglaabdeffefdfxtuur bd gboacadeddfffhhaqqn b gboabacedfefbkaahfo bg glkaaabdfeeffagqafg b gnkaaabdfeeffhvxqng bc gapabceceeffgbwrghf bg gapacedceeffofofoan bo gjlaacdeffefqouqthc bl gjhaadcedffftgqbqoj bd gcoacacdedfffkhhghk b gapaccceeeffbgggbqg bci gihacbcddeffqvkcqak bcc gcpaadeffdfeckpabuk bd ghkaaabeefeffhqaqhb b gepaaedffefeonpunag bd gapabedccfefoofkqfn bj ghfaaaceefffnbqwcvw bd gblacacdedfffnhagaf bi gghaacdfdffeerpcabn bg gnfaabcdefefaaaknkn be ghkaaabfffeefhhaqhf bc gapabdecdeffnvvnvag b gnkaaabdfeffoqbhhvr c ghjaabadfeefafkqakg bc ghjaabaefeefafqqhao bc gnkaaabdfefffabhhrc c gapabbcedfefgbwwann b ghjaabadfeefhkfqqbb b gbpaafcdedefqojobnb b gapabcbedfefnfnkabn bc gcoacacdedfffkhhfqk b gboacaeedefffahaqqo bc gblacacdedffknaxbab bh glkaaabeefeffaahxqb b gnkaaabdefeffasqxok b gcpaacfeddefnavnngn b gcnabacedfefngxaabb b gknaabbdefefhgkhafb bc gepaabcdefffgbbrhah bc glkaaabdfeeffhvxhkg bc glkaaabefeeffhqxahf bc gnkaaabdefefghnxakk b ggnaaacdeffffohoowr bc gcoacacdedfffwhagak bc gbpaaefceedfjqgojno b gbpaaefffedebpxlcgg b gfnaaacdeeffbkuobsb bh gfhaacceefffqruiqib b gblabacdefefknhxhnk bc gknaabbdefefhgkxhfg bc gblacacdedffknhxgab b gblabacdefeffraaabf bf gblacacdedffkrxafaf b gapabbcedfeffbwwann b ghjaaabdfeefnqbxhko bh gnkaaabdfeeffhghhnn b gbpaaecfdeefoohfvcg b gbpaaefceedfjqoojng bc gblabacdefefknaxanf b gblacacdedffknahbqn b gblacacdedffknhhoqn b gcoacacdedfffkhafaf b ghgaaacdeeffggionjf b gljaaabdfeefgqgqxng b gflaacdcfeefqokhxgf bd gddabbcdefefaahhqbo b gnfaabcdefefhhhfofn b gkhaaccefffeufhhmtg c gbhaccdeeeffdoddxqn c ghjaabadfeefafgqakg bc ghjaabafffeeafaqhaf bc gknaabbdefefhgkxhkg b gcoacacdedfffkhhfqb bc gdhaacdeefffawhqxew bc gnkaaabdfeefnhfhqnf bh gnfaabcdefefhhhoofn bd gddacbcdedffqqaabqo bf gghaacdffefednapaun b glkaaabdfeeffhoqqkk b gboacadeddfffhhaqhk b gboabacedfeffnhaqoo b gapabbcedfeffbwbqng b glkaaabefeeffhhxaqg b gnkaaabdfeeffhvxqnf b gcoacacdedfffkhafqf b gldaabcdfeffqqhffkn b gboabacedfefnnhaqgo b gddaccbeeeffiixibak c gelabcddcfffqvobcfb c gepaadbdfeffokruotu bc gapaccedcdffggbgnhk b gelacccdeeffmfvppcg bf gnfaabcdefefhhhoffg b gcoabacdefeffkhhqfk b gnkaaabdfeeffhghhff b glkaaabdeffffhoaarj c gdkababddffffhvbgvf c gfjababdefffghfdufr c gjkababddfffghfwnor c gnfaabcdefefaaakkbb b gnfaabcdefffaaakkrw bd gboabacedfefnohaqoo b gcoacacdedffnkxafqf b gbpaaefceedfjqgojng bc glkaaabdfeeffhoqqbb b gddacbcdedffqqaabak b ghjaaabdfeefnqfhxff bd ghkaaabdfeeffhfqhfb b ghkaaabeefeffhqaqhn b gcoacacdedffnkxhfqk bf gcoacacdedfffkxafaf bh gblacacdedfffnhxgab b gkoaaacefdffbnmbfwn b gbpaadfcfeeffmghwgm bce gddabbcdefffaaqqhhx bcc gddabbcedfffaaxxaax cc gblabacdefefkrxaabb b gboacadeddfffhhaqqb b gboacadeedffkltdean b gknaabbdefefhgkxhff b gdjacbbddefftuapnhg c gbnacbeeedfftuxtgqf c glkaaabdfeeffanqqfg b gblacacdedffknahkqn bc gblacacdedfffnhxghg b ghkaaabdfeeffhbqhfb b ghkaaabfffeefhhaqhk b gblabacdefefknaxafk b ghjaabadfeefhkfhqbn bc gboacaeedefffahaqaf bc gcnacaceddffnnxafaf b gblacacdedfffnaxbab b gbhacdcdedffafkakaf b ghjaabadfeefhkfqxgn bk ggoaaacefefffkakjoo b gcoabacdefeffkahaff b ghkaaabdfeeffhfqhkn b gboabacedfefnnhaqoo b ghkaaabdfeeffhfqxon b ghkaaabdfeeffhbxhkg bc gdfacbcddeffdiidnak c gjhaaccefffemgqleqg c gghaacdcefefqboqxob b ghjaabafffeeafaqhxb b ghkaaabfffeefhhaqxo bc gflaacdeefefhbduqhb bc gghaabbdfeefhfbqhkb bo ghkaaabdfeeffhbqhkn bc ghjaaabdfeefnqchhkn bc ghjaabadfeefafgqqgg b glkaaabefeeffhhhaqf bc gipaacedfdefggbhngf bd gcoacacdedfffkxhfhk b gbnacaceddffgfqxoqg b gmnaabbdefefhgfxhkf b gblacacdedffkraabaf b gjjabbaddfffhfgocfb c gddaccbeeeffdixibak c gffabbcdefffaaaqhie bp gffabbcdefffaaaqhpt bl gblacacdedfffnahbqn b gblacacdedffknaxkab b gcpaadfdcfefbtnfxfp bn gjnaabcdefffakfhdnf bf gdlaabfedfefqmeopan be gdhaabcfffeeuktxemo be gdnaabfdfeefpxrlpxv c gdlaacdfefeflfpehmo b gcpaacefffeeoolhxcf bp gapaceededffvovvohn b ghkaaabdfeeffhbqqkb bc gapabdcceeffrfrrnan b gblacacdedffknaxban b gboacaeedefffahaqqg b ghjaaabdfeefbanhxko bo gnkaaabdfeeffhgqhfk bc gdnaabddfeffafwddbg b glkaaabdfeeffanqxnf b gbhacdcdedffafkhkhk bc gclabcddcfffqgfbovb c gdjababdefffnakitfr c ghjaabadfeefhkfhqbf b gdnaabefdeefmxtgdln bf gflaacdcfeefqobhxgf bj gipaabeefdefnnkagcn b gcoacacdedfffkhhgqk b gcpaadceffefnokxxkx b gnfaabcdefefaaaknbf b gnkaaabdfeeffhghxon bc gnkaaabdfeeffhvqqnb bc gblabacdefefkraaabf bc glkaaabdfeeffhvhhkn b gipaabeefffennkaqhk be ghkaaabdfeefgqgqxbg bq gblacacdedffknahbqb b gnfaabcdefefaaannff bj gnfaabcdefffaaannof bf gcoacacdedfffkxhkhk b gboabacedfeffohaqoo bc gcnabacedfefnnaxafk b ghkaaabdfeeffhfhhff bi gdnaabddfeffafwddbk b ghkaaabeefeffhqaxho be gnkaaabdfeeffhghhnf b ghkaaabdfeeffhbxqkg bo gcpaadeffdfeknlpspn b gcoabacdefeffkahqfg bn gbpaaefeffdebarduog b gcoacacdedfffkhhghf b gbpaaefdceffjurfwpq bc gepaadcedfffngobtpu b gcoacacdedfffkhagaf bh glkaaabdefefkakxafk b glkaaabdfeeffhohqbn bc gbnabacedfefgbqqabf b glkaaabefeeffhhhaqn b gboabacedfefnohaqog bc gipaacbeefffnknjmee bc gfnaabcffeefmghixes bc glkaaabdfeeffhoqxgk bc gblacacdedffknhhoqb bc gfhaabcdeeffqnfltgf bc gbpaaedcefffjgooxpm bd gcpaacdeffefnwnexgq b gbpaaefdedffwejbkth b gnkaaabdfeeffhvqqnk b gepaacecfeffgbglgmq bg gcnacbcedeffhjmkujn bg ggoaaacefefffkawjoo b glkaaabdfeeffanaqff bl gnkaaabdfeeffhghxgn b ghkaaabfffeefhhaqxg b glkaaabdfeeffhoqqkb bc ghkaaabdfeeffhfqxgn b ghkaaabdfeeffhbqqbb b ghkaaabdfeeffhbxhko bc gcoabacdefeffkhaqkk b ghfaaacdefffbbhvnvb be gehabbdceeffuskumjk bc gcoabacdefeffkhhhfk b gghaacddfffeabniqbn bj gapacdcceeffocvfohg bd gcoacadeedffopdtmxf b ghjaabadfeefhkfqxgb b gklaabcdefffpnbptsc br gknaabdeefffpotxqdg b gcoacacdedfffkxakaf b gnkaaabdfeeffhgqhfb b gbhabcddefffangedkv b gbhabcedefffqmkeudk b gnkaaabdfeeffhvxhnf b ghfaabcefeffahlgnkw bp gghaabdfdefftnakxbr bh ghdaabceefffaaqfrsj bc ghjaabadfeefafgqako b gghaabbdfeefhkfhxff b gdfabacdefefbjaqhgf b gnkaaabdfeeffhgqhnk b gblacacdedfffnaxbhg b gcoacacdedfffkxafqf b gfhaabcffeefucqiluw b glkaaabdfeeffhoqxok b gboabacedfefnohaqgo b gnkaaabdfeeffhvxqbf b gcpaadeffdfeckpabub bd ghdaabcefeffqqhwknc bc gcoacacdedfffkxhfqk b glkaaabdfeeffhohqbf b gbpaaefcedefbqbbogg br gghaadcfeeefhohxqag bf gglaacdedeffevxbtgk bf gnkaaabdfeeffhgqxgb bd gdoaaaedfdeffxxhfkg bc gnkaaabdfeeffhghxof b gchacbdeedffqcthfwn bc glkaaabdfeeffanaqfn b ginacbdcdeffakkfaqf b gchaccdceefftjoqlff bc gcoabacdefeffkxhafb b glkaaabdfeeffanqxng b ghgaaacefeffffangor b gcoabacdefeffkxaakb b gcpaabcffdefbbxxnkx bj gcpaaccffdefgbxxnkx bf ghjaaabdfeffbaohhbg bc gboabacedfeffohaaok b glkaaabdfeeffhohqkn b gcoabacdefeffkhhqfb b gcoacacdedfffkxafak b ghkaaabdfeeffhfqxob b gblabacdefefkraaanf b gbpaabfcdfeffxffxfx bm gbpaabfeedfffxoffxx bce gbpaaccfeeffcvxvcxx cd gbpaaccfeefffoxfoxx be gbpaacdfeeffccxccxx d gbpaacefdeffffxofxx be gbpaaddfeeffnkxnkxx bm gbpaaddfeeffofxofxx be gbpaaddfeeffvcxvcxx c gddabbcdefffaahhqah bce gddabbcedfffaaaaaax cc gdhaaceeffefqemtxth bd gdhaadeffefehmipuxd bdd gdnaabfefeefhxthpde c gfdabbcdefffaahhqah bee gfdabbcdefffaahhqqx bcg gbpaafcfdfeeqbhbabg bm gkhaaddceeffannaxkk c glkaaabdfeeffhoqxgb bl gbhacdcdedffafkakak bc ghjaabadfeefhffhxff be gghaacceefefaglxetk bc ghkaaabdfeeffhbxhfg bh gboabacedfeffohaqog b gblacacdedfffnaxban b ghkaaabdfeeffhfhxof bq gcoacaddeeffneiidub be gcoacacdedfffkxhfhf b gnkaaabdfeeffhgqhnb b gflaaccfeeffdkhuiuf bd gjlaacddefffhbfmtog bn gcoacacdedfffkxhkqk b gnkaaabdfeeffhghxgf b gdfacbcedeffaaqqbaf b gchabcddefffqnkahjs b ghkaaabdfeeffhfqhkb b ghkaaabdfeeffhbxqbg b gblacacdedfffnahbqb b ghkaaabdfefffhfhhnk b ghjaabadfeefafgqqgo b ghkaaabdfeeffhbqqkn bo gcpaabcfdffefgxoxxf bd gboabacedfeffohaqgo b gnkaaabdfeeffhvxhff b ghkaaabdfeeffhbqhfn bh gflaabedefffaawxalr be gmhaadcdfeffdgkqegb bm gjlaacdeffefhouhahs be gbpaacefdeeffnakfob bd gcoacaddeefffmiidun bc ghkaaabdfeeffhbxqko bc gblabcddeefftbfqmbb bc gnkaaabdfefffhvqqgb b gnkaaabdfeeffhvqqbb bc gdoaaafeddefgqqqnbb b gbnabbedeeffaafhhhg b gbnabacedfefgbqqanf br glkaaabdfeeffhohqkf b gnkaaabdfeeffhvqhfb bc ghjaabbeeffftulbfko c gepaaccfdffebgxoxxg bc glkaaabdfeeffhoqxob b ghkaaabdfefffhbqqnk b gdhaabfcffeeuikxemg b gjnaaaceefffbkqongs b gehabcdceeffqbgqqgn bd ghfaaaceefffnbqbrwg bp gdlaaadfedeffhhagnf b gdnaaaefddefnqxqggb b gdhaadcdffefuoredkk b gcoacacdedfffkxhkhf b ghfaaacefffebbxsngk c gkoaaacefeffnwqjjko b gdnaabefedefexitfuf b gdlaaadfedeffhhagng b gepaadeffefejjetfqs bd ghkaaabdfeeffhfhhkf b gjnaaacdfeffffagwrc b gdhaacdfdfefqkpblgf b ghkaaabdfeeffhfqxgb b gnkaaabdfeeffhvqqbk b gcoabacdefeffkhaqkb b gghaabddfefftnnqxck bc gbpaadededffofgobxg bc ghkaaabdfeeffhbxhfo b ghkaaabdfeeffhbqqbn b gdlaaadfedeffahabnf b gdgacacdeeffggaarhb cc gcpaaecdfeeffrohfwo bd gepaaebdefffrfkbied bf gmlaabdceeffdfctugk b gcoacaddeeffneiidun b gjnaaadddffffqahovb c gdlaacfdfeefdhndhqo b ghkaaabdfeeffhfhxgf b gnkaaabdfeeffhvqhfk b ghkaaabdfeeffhbxqbo b gapabdecdfffnnonuem b gnfaaacdfeffnblsgsw b gmhaacdcffefabjqtkk b gdlaaadfedeffahabbf b gclabcdceeffhgouirf b gbhabcddefffqgjxqvb bd gapabeccdeffffrowan b gldaaacefffekohfkrf c gdfacbcedeffaaqqban b gipaaceeffefgbfhegt bj ghdaabceefffqaigfkv b gmlaacbdfffelnnxlof cc gbpaabfcdfeffxfnxnx bp gbpaaccfeeffcvxjsqq bd gapabcdedfefvvkvhbb c gdfacbcedeffaaqqbqg bc gdhaacfdfeefuijhlds b gjnaaaddefffgtuukof b gknaaacefffennqbnjf c gdlaacedefffdmnedlb b gfhaacbeefefekeahmf bf gdhaadefeffeimphtqt b gffabbcdefffaaqqhet br gffabbcdefffaaqqhip bn gboacaededffoiidhan bf gdlaaadfedeffhhagbg b gdhaabfdfdefixkifgg c gclabbcedfffqbpnhbr b gchabcddefffqnkqxsj b gbhabcdeefffmremxec c gmlaacdcfeffdkkluco bdd gfnaabcfefefmghllqn bh gdhaacdfdfefqkpblof b gdlaaadfedeffhhaonf b gapabdcdeeffngknbqg bg gdlaaadfedeffahabbg b ggoaaaceefffnkakfon bc gelabbdcdfffqggksng c gglaaccefeffibllmpn b gbhabbcdeeffhogidog b gdlaaadfedeffahaknf b gfnaaacddfffnjafsng c gbpaabcffdeffbaabfx bn gbpaaddefeffvcwxrxx b gepaabeedfefbnvvqvf c gffabacdeeffbbldkaf c gcpaadefedffckpwbud bc gmnaabcdfefflknhqgk bc gbnacbcedeffhflkpog b gflaaacefefffnhwnrv bc ggoaaaceeffffkakfon bc ghfaabbdefefdilfsfb c ghdaabceefffqqhjknj b gdlaaadfedeffahabng b ggoaaaceffefnkhofnv bc ghdaabceffefaaqrsbb bc gdlaaadfedeffhhaong b gdlaaadfedeffhhagbf b gdlaaadfeffefhhafon bc gjhaadccfeffafrdmwn b gcpaabcffdeffgxqnkq b gfnaaacddfffffxgovb c gdlaaadfeeeffahankf b gdhaaddedfefakakabb bf gdlaaadfedeffhhxgfg bl gdlaaadfedeffahakbf bf gdlaaadfedeffhhaobg b gdlaaadfedeffahakbg bc gdhaacedefffddnxmun cd gdnaabddfeffafollbs bd gdnaabdffeefafilulb bd gdhaacedefffddnxdln c gjhaacdeedffdfdxngn c gbpaacdefefffgbefae bc ggoaaaceffeffkhofnv bc gdnaabddefffafwtpbs b gjoaaacefffefohwfng c gdlaaadfedeffahakng bc gdlaaadfedeffaxabnn b gdnaabcdeeffdogxdnb c gdhaabffdeeftdmcluc bd gdlaaadfedeffhhaobf bf gdlaaadfeeeffahankg b gdlaaadfeffefhhafob bc gdlaaadfedeffhhxgff b gdhaaddedfefakakabn bc gbpaabcfdffefohfxhf bu gbpaabefedffffhofxh bce gbpaaddefeffoffhoxh be gdhaabefffeehpilpet bd gdlaabffeefeixdxxxd cd gfdabbcdefffaqhhqax bck gfdabbcdefffaqhhqqh bee regina-4.95/engine/data/snappea-census-sec7n.dat000644 000765 000024 00000052655 12234011536 021433 0ustar00babstaff000000 000000 hfdafbcdefgggaaaaaadu bh hjdafbcdcffggqqqgdkxp bj hkfagbcbdegggaabaabpd be hkfajbcbdffggaabadkxt bg hkkadabcdfgggfaqqnggl bk hkfadbcbdffgghhghkmad b hgjadbacefggghkhfbxfe bj hbpaccefdgeggknxnqfld bl hkfadbcbdfgggaabakgaf bf hkfagbcbdegggaabaadnp bc hofacbcbegfggaabnratn bj hgkadabcegfggfaqgxkgi bl hgjadabcefgggbqqbxfig bm hfdadbcdeegggaaaanbpd bc hgkadabcefgggfhqbfxbd bn hjdadbcdcfgggaaaknggl bj hkfagbcbdegfgaabahglt be hkkajabcdffggfaqqbmxu bk hkfagbcbdefggaabahpou bc hgkadabcegfggfaqgjqtg bl hjdadbcdcffggqqqgmkae bc hkjafbacdfgggafahgjcq be hgjadabcefgggbqqbgqbx bm hkjadbacdffggafahodhl bf hkkagabcdegggfaqqqiom be hkjagbacdegggafahhekl bc hjdadbcdcffggaaakniae bh hkkafabcdfgggfhqqgffd bl hfdadbcdeegggaaaandnp be hkkadabcdfgggfaqqnoba bd hkkagabcdegggfhqqqdnp bc hgkadabcefgggfhqbqcub bn hkfagbcbdegggaabqhekl bc hkkafabcdffggfaqqbiqi bh hkjagabcdefggbahqqpou bc hkkadabcdfgggfhqqongh bd hkjadabcdffggbahqniae be hkjagbacdegfgafahhfdu be hkjadabcdffggfqahodhl bh hkkagabcdegfgfaqqqglt bc hkkafabcdfgggfhqqgjcq bi hkfagbcbdegggaabqhfle bc hkjadabcdffggfhhqniae bf hjdagbcdcegggaaakqiom bc hkjadabcdffggbqqakmad bh hkjagabcdegfgbqqaabet be hkkagabcdegfgfhqqqbet bc hjdagbcdcegfgaaakqglt bc hkkagabcdegfgfaqqafdu be hkjagabcdegfgbahqqglt be hkkagabcdegfgfhqaabet bc hkkadabcdffggnaqhkmad bf hkkadabcdfgggfhqakgaf bi hkjagabcdegfgbahqafdu bc hkkagabcdegggfaqqaekl bc hkjagabcdegfgbqqahglt be hkkagabcdegggfhqqhekl bc hkjagabcdefggbqqaamnt bc hkjagabcdegggbahqaekl bc hkkagabcdegfgfhqqhfdu be hkkagabcdegggfhqaadnp bc hkjagabcdefggbqqahpou bc hkkagabcdegfgfhqahglt bc hkkagabcdefggfhqahpou be hfdafbcdefgggaaaaafdv b hfdafbcdefgggaaaaaqhx bcg hfdafbcdefgggaaaaarec b hflacbefdgffgofofqfxk b hfdadbcdefgggaaaajjbs bc hbhakdcdedfggafhaqaab b hofabbcbgfgfgaabkkkax b hbpaccefdgffgkhxnqmjg b hgjadabcegfggfhhgxjbm bo hbhafbcedfgggukxnqvrl b hgjadbacefggghkhfbxji b hgjadbacefggghkhfjxaf b hgjadbacefggghkhfcxlo bk hgfadbcbefgggaabbqfpg bc hkoaeacdfegggnkakkfsq bc hgfadbcbefgggaabbqcrm be hokacabcegfggfaqoxkdj be hgfadbcbefgggaabbqrba bc hlkacabdffgggfanxkaxn bo hfnacbedfgfgghhkknjso b hkoaeacdfegggfkahkqkx b hofacbcbegfggaabnrace b hofacbcbegfggaabnfamk bce hlkababdeffggfhoddrct b hlkacabdffgggfhoxfqao bci hofacbcbegfggaabncanq b hhjabbadefggghkfhfacl b hhjabbadefggghkfhfuvt b hhjabbadefggghkfhfoxh bo hgjadabcefgggbqqbxfre bc hnkababdegfggfhgqagiv be hgjadabcefgggbqqbxclb bo hgkadabcefgggfhqbfxrm b hlkababdegfggfhohoave b hlkababdegfggfhohfoqa bci hgjadabcegfggfhhgkqmb bcc hpjaabagfegfgafbbaodi b hgjadabcegfggfhhgjqrp b hgjadabcegfggfhhgrqgh b hokacabcegfggfaqokhpf bg hokacabcegfggfaqorhjl be hokacabcegfggfaqojhoq bc hgkadabcegfggfaqgjqrp b hgkadabcegfggfaqgkqmb bce hpfaabceffgggaabcuvms b hgkadabcegfggfaqgrqgh b hgjadabcefgggbqqbcqer bc hgjadabcefgggbqqbfqbl bk hgjadabcefgggbqqbrqag be hfdadbcdegfggaaaaknbk bc hbpaccfedgffgknxnqokg b hokacabcegfggfhqnfamk bg hokacabcegfggfhqnrace b hokacabcegfggfhqncanq b hgjadbacefgggafadhxfe bc hgkadabcefgggfhqbqcrm b hgkadabcefgggfhqbqfpg bk hgkadabcefgggfhqbqrba b hpjaabafeggfgafqnnllf b hbpaeecfdffgganxankxf b hojababcfgfggbqqbxggx bc hflacbefdgffgofofqfxb b hlkababdefgggfhohttla bc hofabbcbegfggaabiqalf b hdhaidcedefggkqxsinaf b hbhakdcdedfggafhaqaan b hfjafbadefgggafbbgbbq b hnkababdefgggfhgquuap b hnjacbadefgggafbqtteh b hdkafabdefgggfannnnka b hhjacbadfggfghkfrgbdf bc hnkababdfgfggnabxeihl b hbpabdcfdgfggfoxfevhl b hpkaaabgefgfgfhhvvrdi bc hhjaebadfeggghkfbhklp b hbhakdcdedfggafhhqhhg b hgjadbacefgggafawhxqa b hpkaaabgeffggfhqvvmmj b hlkababdegfggfhojqqct b hnjaebadefgggafbqkobo b hgkadabcegfggfaqvxqah b hfdadbcdegfggaaaaccxa bc hfdafbcdefgggaaaacccv bc hkoabacdegfggfkakbulx bc hbpabdcfdggfgfoxfuefh bd hdkafabdefgggfhoooofh bc hgjadabcegfggfhhlxqgi b hdkafabdefgggfannnnnx b hnjaebadefgggafbqggkb b hofabbcbegfggaabgqaqa bc hgkadabcefgggfhqsqxha bc hlkacabdfgfggfhorgubo b hhjabbadegfgghkfhiiad b hlkacabdffgggfhoxepad bg hokacabcegfggfaqvxhaq b hhjaebadfeggghkfohfhi b hfkafabdefgggfhgggggx b hgfadbcbefgggaabbaalm be hokababcegfggfaqdqhek b hojababcegfggfhhdqhek b hlkaeabdfegggfhobhkok bc hnkababdefgggfhgquula b hnkaeabdfegggfhgfqngn b hdkafabdefgggfhooooox bc hgjadabcegfggfhhvxqah b hlkababdefgggfhohttap bc hnkaeabdfegggfhgbqbgf b hokacabcegfggfhqsxahq bc hojababcegfggfhhfqhqh bc hokababcegfggfhqiqalf bc hokababcegfggfaqbqhqh b hlkaeabdfegggfhofhfob bc hokababcegfggfaqfqhqh bc hojababcegfggfhhbqhqh b hgfadbcbefgggaabbxqah bc hokababcegfggfhqgqaqa b hokababcegfggfhqkqaqa b hgfadbcbegfggaabbkkxe bc hgfafbcbefgggaabbcvnj bc hgjadbacefggghkhfaapi b hfdafbcdefgggaaaaqqie bp hfdafbcdefgggaaaaqqlu bp hdpaaegfdgfegkriebxkq b hgjadbacegfgghkhfnnid bc hgjadbacegfgghkhfrrdx bc hbpaccefdefggkkxnakkk bc hbhafbcdefggglfndqqgb bf hbhafbcdefggglfndqqrw bf hofacbcbefgggaabnggxm be hofacbcbefgggaabnjjem bcc hgjadabcefgggfhhgccxl bc hgjafabcefgggfhhgvvam bc hdlaeceeffgfgmometquk b hgjadabcegfggfhhghhpe b hokacabcegfggfaqoqqld be hgjadbacefggghkhfxhaq b hokacabcefgggfaqobblp bc hokaeabcefgggfaqorwmi bc hgkadabcegfggfaqghhpe b hcpabcdffegfggggxbabf b hgkafabcefgggfaqgjsim bc hgkafabcefgggfaqgvvam bc hgjadabcefgggbqqbaape bc hgjadabcegfggfhhgqxha b hokacabcefgggfhqnggem bcc hokacabcefgggfhqnjjxm be hokacabcegfggfhqnqqei b hgjafabcefgggfhhgjsam bcc hgjafabcefgggfhhgvvim bi hnkababdfgfggnabxdgmg bp hnkababdfgfggnabxegig bp hokacabcegfggfaqohxqa bc hokacabcefgggfaqocclp bc hokaeabcefgggfaqovvmi bc hgjafabcefgggbqqbjjsk bc hgjafabcefgggbqqbvcsb bc hgkadabcefgggfhqbaalm b hgkafabcefgggfaqgjsam bcc hgkafabcefgggfaqgvvim be hknabbbdefggghgkktfhu b hgkadabcegfggfaqgqxha b hgjafabcefgggbqqbjjbk bc hgjafabcefgggbqqbvckb bc hgkafabcefgggfhqbcvbj be hgkafabcefgggfhqbssbj bcc hokacabcegfggfhqnaxqh b hokacabcefgggfhqnggxm bc hokacabcefgggfhqnjjem bc hgjadabcefgggbqqbqxha be hffafbcdefgggaaaqhhaq bci hgkafabcefgggfhqbcvnj bc hgkafabcefgggfhqbssbn bc hgkadabcefgggfhqbxqah b hkoaeacdfegggnkaknacv bm hkoaeacdfegggnkaknank bm hnfaebcdefgggaaahqkxn c hnfaebcdfegggaaaaxcca bk hdlaeaffefgggffhaegpc c hdoabaedfgfggfhaqhbww b hnjacbadegfggafbqgget bd hnjacbadegfggafbqggld bd hdlaeaefeegggfnhamgol c hhjabbadffggghkffpqev b hlkacabdffgggfhoxfmlf bp hlkacabdffgggfhoxfpef bp hjlabcedgggffnnnhxhab b hnfabbcdffggghhhilhtt bd hhfabbceegfggnkikkslw b hbpabcdfgegfgkvxjmfle bcc hbpacfdfegfggkdxcwmdg c hbpacfdfegfggkfxcwmfg bi hdpaacfegfgfgvoixiksp cc hjpaaccgefgfgqnckqtqw bc hdpaaeffdgfggqbgkxtge c hdpaadeeggffgkhnxxbtk bk hnlaaacffegfgfnwehjnf be hbnajbedeefggaafnkaaf b hhjabbadefggghkfhooie bj hhjabbadefggghkfhoolu bj hjlaeacdefgggfnxnkaoj bm hjlaeacdefgggfnxnkasf bm hblafacdefgggknhhhqpt bc hcnadbbdefggghgkkftlp b hepabedegdfggtotaoxjo bc hbpaecdffefggfhxdfoab bc hjpaaefegedggjqoqjnah b hpdaacbfgfgfgqqwtptpr bd hllaabeegfffgqxoxsqfn b hndabbcdfgfggqqqdmonm b hblafacdefgggfnxjgodv c hjlaeacdfegggfnxdljoa bk hflacaceeffggknammebn b hblajbddeefgglgekoxab b hdhaicdfdffggqunixmab b hbpabdcedgfggooqguclh bf hjpaacfddggfgknnbcgrw bf hbhajbcedefgglfndkqqn b hdpaaegfdgfegkriebxbq b hnkababdegfggfhgqbbei bg hnkababdegfggfhgqbblu bg hdlaicdefffggeqhmgehk b hknaebbdefggghgkhhqkc bc hdhaedeeffgggarxqaand b hlkababdegfggfhohffei bj hlkababdegfggfhohfflu bj hbhafcddefgggqnkxqbgx b hnkababdffgggfhggdlhc b hdlaeceeffgfgmometqub b hblafacdefgggknbagomv c hflabaceegfggknalrtxw bi hdlaeceeffgfgmomethuk b hnlaaacdfggfgkngbfinq b hcoadacdefgggfkaosuhe b hkoabacdffgggfkaddkwu b hlkababdffgggfhooepqc bi hcpabcdffgeggavnxqkxn bc hflacaceegfggknamaerg be hmpaaecfgegfgflogmkxx bc hnfaebcdfefggaaahqkak be hclajbcdeefggpntfoxqn b hcoadacdefgggnkaosuhe bc hkoabacdffgggnkaddkwu bc hflacaceegfggknanakbw be hflacaceegfggknanakrg be hcoafacdefgggfkhhqhmd b hffafbcdefgggaaaccacv bc hnfaebcdfegggaaaaaacv bc hbhajbcedefgglfndkqqb bc hfnaibeedefggaannkaqo bc hfjagbadeegfgafbfqneu bc hbhajcedeffggqkkxvqhn bc hhjaebaffegggafdqpbth b hdlaicdfdffggqmmtqnhg b hdpaadedggffgglqgxgnu bcc hcoafacdefgggfkabobcp c hkoaeacdefgggfkaeirqr bk hkoaeacdeffggfkattibf b hboafacedfgggfnhaqqah b hhjaebadfegggafkqahof bc hndabbcdfgfggqqqieomg bc hlkababeefgggfhippucp be hkoaeacdefgggfkattawb bcc hklaccdeeggfgkkhnkcgq b hdjadabdefgggbakkftlp b hooaaacefffggfkqfmkbg b hlkaeabdfegggfanqqhnc bc hhjacbaffefggafcqaemm bg hjlabacdggffgfnxbiaec bj hldacbceegggfqqcdetgt b hjlabcdeggffgknqbtwqh bd hcoadacdefgggnkanwuhm be hkoabacdfggfgnkahkblp be hdpaaedffgfggqnfoxetb c hkoaeacdfegggfkannagr bce hkoaeacdfegggfkannawb bce hfnacbefdgfggaafhblvv b hfkadabdegfggfhggouid b hhkababdffgggfhfvmxdo b hdlaccfeffgggetxmeqdl bd hdlacadfegeggfxhhhoof bcc hepabedegdfggefeafxjo bc hhkababefegggnapdahun b hepacdddfggfgngnobewp b hfnacbedfgfggbgghbrqb bc hkoabacdfggfgfkahkblp bf hkoabacdgffggfkanqdrp bf hhkababdffgggfhbvmxdo bc hbnajbedeefggaafnkaqf b hpkaaabgfeggffhxxhaqo be hflaccceeffggdkxsaekk bc hflaebddeffgglgobopwf bc hhkaeabfefgggfhimqgua be hnfabbcdffggghhhilmtt bg hnfaibcdfefggaaahqban b hflacaceeffggknammewn b hbnajbedeefggaafnkaak bc hdkadabdegfggfhoogtem bc hdhabbffedggglpbxdrfj bc hkoaeacdefgggnkaeiabw bc hhhaacefgfeggmxhhpvaj bc hnlaaacffegfgfnwehjng b hhkababefefggfhrqhlll be hdpaacfgeefggnxfqkwvk bg hflacccefefggdkxlkacg be hdpaaefdgdffghxnlnodo b hdpaafgeggefffrowqoqf b hcpaceedffgggqnckxkxa bc hhjaebadfegggafgqahof bc hcpabdedeffggcncaxnnf b hnkaeabdfegggfhghhagr bc hhkababffefggfhjqhlpp bg hkoaeacdeffggfkattqbf b hknabbbdegfgghgkkwuhe b hdpaacfdfegggnxwkkrfd b hkoaeacdefgggfkaeiabw bcc hglabcceeffggmfenaacf b hdhaicdfdffggqunixmhg b hlkacabdfgfggfhpxgobp b hcpabecdgfgfgaoapduen bc hapajcdedfegguuntjnaf bc hblajcededfggswnsnlaf bc hfpaafegfggefftwnfwds bd hflacaceeffggknammebb bc hdlabcefegegghfhoxxfc b hlkaeabdfegggfhoqqaoj bcc hboafacedfgggfohaqqah bc hcnadbbdegfgghgkkanlx b hlkaeabdfegggfanaxakn bc hflaibddeefggqunprwho bg hcoafacdefgggfkxaaahq b hfpaaceffegggifriioox be hfpaafgdfegfgkrnriouo c hnkaeabdfegggfhvxqqbg b hdgadacdefgggnfhjsxfb bc hflaeaceeffggknamqebb bcc hfhabbcdefggguknowpgs b hgoacadfefgfgdgdmrbak bd hflaibddeefggqunprwab b hpjaabafgeffgafqqaqaf bc hbpabddfefgfgaexawrwg b hlkababdegfggfhphomcv b hfhabbcfefgfglfcbwsmb b hhkaeabdfegggfhfqhank b hdnabbfdeggfghxkhaxbq bc hflacaceeffggknamqebn bc hfhaicdefeeggofxjcvqf b hnlaaacdfggfgknobfinq bc hglacccfeefggmfrcuxtg b hkoaeacdeffggfkattirf b hnkaeabdfegggfhgqxqgb bc hdlabbedfegggowcnughg b hnfaebcdfegggaaavvrba bf hghaidceeefgggbcvxbhf b hpkaaabgfeeggfhxqxgah b hdlabaefegfggfnhmjmfi bc hpkaaabgfeggffhxxhaqg b hkpaaedfggffgstesbeju bc hgpaadgfeefggpqnodnxf bf hcoafacdeffggnkannkak bcc hfpaadgefegfgmqasamer b hhkaeabdfegggfhbqhank b hhjaebaffegggaffqjbgj b hflabccefefggdkxakank b hlkaeabdeffggfanggmgo bc hlkaeabdeffggfanjjmjk bc hpjaabafgeffgafqaaqaf bc hkoabacdfgfggnkaupxcc bc hnjabbadffgggafbrbnkh bg hflacaceeffggknammewf b hnfaebcdfegggaaaccjxj bd hkoaiacdeffggfkattqbg be hlkaeabdfegggfhohxhof bcc hdlacadfegffgfhhahahk bd hfkafabdeffggfhgjsmjk bc hfkafabdeffggfhgvvmvg bc hapadcdeegffgdoddqbgn bc hbhadcdeegffgwoccqugn bc hbpabeddeffggwgiajggg b hbpabeffdfgggaoxajsqs bcc hffafbcdefgggaaaqhqdu bt hbhafbcdefggglfndqacn bd hbhafbcdefggglfndqakv bd hnkababdegfggfhlqgicv be hlkaeabdeffggfanggmwf bg hlkaeabdeffggfanjjmok bc hcoafacdeffggfkannkak bc hbpabfdfgfeggeuxgeect bc hglacbddegfggkrhlmihi b hflaccceeffggdkxsaenf b hdpaaefdgdffghxnlnodg b hkoabacdfggfgnkaivghu bd hkoabacdgffggnkafpxcu bd hkoaeacdeffggfkattibg b hhfaibceeffgghhggbxxb bc hglaccceegfggmfeiswnd bc hnjabbadfggfgafbcknmw bc hlkababdegfggfhojqocv b hhjacbadfggfghkfjgbfq bc hdlacadfegffgfahaaahf bg hcoadacdefgggnkajvtgk b hgpaaeegffdggevogxnid bc hcoadacdefgggfkacpttg b hhdacbcdfgfggqqrwqtrc bc hooaaacefffggfkqfmkbo b hlkababdfgfggfannppqg b hipabecdfegggriifdnan bc hnfaebcdfegggaaavvcav bc hdlaicdfdffggqmmtqnho bc hdlabadfeggfgkxhlmfli bc hdlabadfgegfgkxhdufmt bc hdpaacfdfegggnxtkkxkd bc hfpaaedggeffgwnckweot bf hbpacfcdeegggksisswib bc hblafccdefgggdknxhajo bn hbpabcdfegfggcaxccaca bc hbpabcdfegfggjhxjhjhj bc heoajacdeefggfkjsraaf bcc heoakacdeefggfkwwkaqo be hcoadacdefgggfkabcuxd bg hkoabacdfggfgfkaejvau bg hflacccefefggdkxlkaco b hdpaacfeegfggsbsbhcik bce hcpaccdefggfgggflswes bcc hhdabbceeffggqqjlncxk bi hhhaacgegfefgemeumsqw be hcpacecdfegggrnclkksl b hdlaicdfdffggqmmtqnab b hfhacdcfdfgfgscxcwcom b hhdabbcefefggqqjffqxk b hdkafabdeffggfhorwirn be hdkafabdeffggfhovvivf be hflaeacffefggfnnwuikk b hbpacdedfggfgajrcbesj cc hbpabecfgegfgxqxxqqha bce hbpacfefdgeggeixfjfwd bk hdpaaefdgfeggixfsiftn bg hepacdefegfggcurmsktu c hfpaacfeeggfgexdjinhp be hgpaadeffgeggcuavvavt c hgpaadgffeggfcwavxcvs c hpdaabceefgggbbbxgbxg be hpdaabceefgggbbhwaawh c hpfaabcegffggaaboeots bd hfkafabdeffggfhgjsmon bc hfkafabdeffggfhgvvmnb bc hpkaaabgfeffgfhqqhqhk b hdkadabdefgggfannaklx b hflaeacffefggfnnwuikb bc hpkaaabgfeffgfhhqhqhn b hcoadacdefgggfkajvtgk bc hkoaeacdeffggfkattirk bc hcpacdfefgeggctraskux bi hnkababdfgfggfhggiihf b hpkaaabgfeeggfhxqxoah bc hdkafabdeffggfhorwigk be hdkafabdeffggfhovvikf bm hhkababefegggnaojajkn b hhjacbadfgfgghkfrgmbj bc hflabcceegfggdkxdcjcx b hflaeaceeffggknamqebk bc hbpabcdeegfggjhsjeuig bq hbpabeddefgggacccimbu c hbpabeddefgggvacaimbv bcc hbpabfddefggglacaipmb c hbpacfefdgeggkixfxfwd bc hfpaadfdegfggbxgmmsse b holaaccffggfgmfxfxrri b hhkaeabfefgggfhkcqgbc be hhfabbcegefgghhjbxsdo b hbpaccefdgfgggnkbbpfe bd hnfabbcdfggfgaaaepule bc hlkababdefgggfhokdjcv b hnkababdffgggnabrbnkh bd hfkadabdefgggfhggqbdx b hfnacbedfggfghhkkncvk bc hndabbcdggffgknefxbfj b hbpabecdfggfgpuolxocc b hpjaabafgeffgafqqaqag b hhdabbcfeegggknrnxnxo b hbpabeddeffggwgiajggo b hlkacabdfgfggfhojgoba b hlkababdfgfggfhoommqb bi hdpaaceegfffgmecxbood b hdpaacfdgegfgxeqxggjf bc hcladbddegfggittdcmwi bc hdpaacdffgegguddmknkr bcc hpjaabafgeffgafqaaqan b hbpabfdegfggfiwfboxim b hfpaaefgdefggpgixplol be hdpaacgdgfgffmdsxbcwd bc hfdafbcdefgggaqhhqaet bv hcoafacdeffggnkannkab bc hnkababdfggfgnabcknmw b hdlabadfeggfgfxhlmfli bcc hdlabadfgegfgfxhdufmt bcc hpdaabcfdgfggknwevthq bc hdkadabdefgggfhoohfex bc hdoaiaedfeeggfhahhaqk b hkoaiacdeffggfkattqbo b hdoaiaedfeeggfhaahaqf bc hdpaadfegeggfhhhxwitg b hhlaaafegegfgfhawlxll bc hjlaeacdfegggfnxcvsoa bc hdnacbdeegfggfqkmaoca b hlkaeabdeffggfanggmgg bc hlkaeabdeffggfanjjmjb bc hhfacbcdeffggaawbawqf b hlkacabdfggfgfhorgbls b hflaccceegfggdkxmsslk bf hnfaebcdfegggaaaccccx bc hlhaadcfeggfghnhsswtu bd hlkaeabdeffggfanggmwk bcg hlkaeabdeffggfanjjmof be hjpaafdfdefgghgeutkjb bc hcoafacdeffggfkannkab b hgpaacefeggfgbqbssrxc c hbhajbdecffggnixlnlkg bd hkpaaecdfgfggxxkxcgdb b hdlacadfegffgfhhahahf bg hnlaaccffegfgdkxsjwpl bc hfkafabdeffggfhgjsmjf bcc hfkafabdeffggfhgvvmvf bi hohaacdgefgfgwbbxflgc bc hdpaacefegfggbgpdncgk bcc hdpaacfefgeggnxpkblwd bcc hghacccfegfggendxtmop bg hjhabcdedgfggfofoxhqa bcc hpdaabceefgggaafqxxfq cc hpdaabceefgggbbnuxxni bc hpdaabceefgggbbpswwjm bg hpdaabcegffggaafqxqxf cc hpdaabcgffgfgaaqcxvqx bce hepaedefeffggcurmokwb b hcpabdfegfeggpkekxwkx b hbpacededffggbwuhbvgf bc hdfadbcedgffgaaknaaff b hcnadbddefgggebbsnpsk b hgpaaecfgfggfcibsbkxs b hdlacadfegffgfahaaahk bd hhfaibceeffgghhggbxaf b hflabbeeegffgaaknxafk bc hdpaacegfegfgnekikwon bc hjhabbddegffgpbmkxhgf b hdlacadfegggffxxaankn bc hbpabccffgfggjsxhrhxr b hflacaceegfggknanhkkc bc hdhabceffdfggnpnxnmct bd hblafbcedfgggioxohhwr br hhdacbcdeffggknodbiff b hfkafabdeffggfhgjsmob bc hfkafabdeffggfhgvvmnn bc hdpaagdfeffggfmimikwn b hbpabfdfefgggeuxlekwl b hhdabbcefefggqqjffqhf bc hfpaafgeefgfgsxvcsxxx b hbpabeedfefggxjcxxigb be hcoadacdefgggnkacpggk b hpkaaabgfeffgfhqqhqhb b hdlabadfeggfgkxhudfgw bg hdlabadfgegfgkxhmlfgw bc hpkaaabgfeggffhhqxqab b hbpabededgggfviuuqmpf bc hhlaaafegfeggfhawiutd bg hhlaaafegfeggfhawplpl bc hpkaaabgfeffgfhhqhqhf bc hfhabceeffgggtixsrblo bg hpkaaabgfeggffhhqxqan b hdkafabdeffggfhorwirb bc hdkafabdeffggfhovvivg bc hcoadacdefgggfkacpggk bc hdlaibefdfeggqxrqwlqu b hfpaacegegffgctjjissw bc hdlaccdefgfggkbxnklpu bc hfnaebeefeggghhfoofko b hmlaccdcffgfgdlknhdrb b hipabdeeffgfgpbfofqrn bc hbpabfedfggegtnvuxvnt be hlkaeabdeffggfhobbpwf bg hlkaeabdeffggfhoccpnk bc hlhaacdfgggffqkvaowob bf hdhabcfefegggvcxcaxac bc hlhaabegfgffgrxjrjmae bc hcpacedefgggfuduaxffi bc hkoaeacdfegggfkannhkc bh hlnaabeefgggfsxkqbmqb bd hkoaeacdefgggfkakkrxr bc hpdaabceefgggaafkxxfk ce hpdaabceegfggaafirrfu be hpdaabcegffggaafkxnxf bce hpdaabceggffgaafirtrf be hpdaabceggffgaafkqnqf bce hpdaabceggffgaafqrqrf cc hpdaabcffgggfaalcrver be hpdaabcffgggfaaxcrvxr bci hpdaabcgffgfgaafctvod bc hpdaabcgffgfgaafcxvox bce hpdaabcgfggffaadcrvir c hpdaabcgfggffaafcmvoe c hpdaabcgfggffaafcqvoq cc hpdaabcgfggffaaqcrvqr cc hhdabbcefefggqqjffqhg b hbpabcedgffggcemekvsl be hdhaecdfeefggcjlcinld be hflacaceegfggknanakfj bi hnhaacdfgggffuaoktwsp bg hbhadbcedgffglfndxqok b hcoadacdegfggnkajjakg b hblafcdeefgggipuxbkqu bd hepacceffgegghvfienuo bc hbpabcfdgegfgxaxaaxcc bc hpdaabcdfgeggaaicvtel be hpdaabcedffggaafdmpfu be hpdaabcefegggaaeclvel cc hpdaabcffgggfaaiclvtm bm hpdaabcgfggffaaeclvpm cc hbpabddeggffgngsjbbut bc hbpabeedfgfggbfcwcimb bc hdlabbefggfgfcxaxcxcc bc hdpaadfegegfghxhhhxjs bc hfhacbcdffgggsxxswxsd bc hllaacdfeegggkhuslwin c hcpaecfddfgggfgfuxtvu bc hdpaafffggeegccxxxvcx bc hohaaccegfggfcdxeqndd bc hfhaicdfedfggwoxjowwd bc hndabbcdgfgfgkneffcbm b hfpaaceggffgfcejbaxxo bc hfhaecdfedfggcodcgcci be hapaddbedfgggoflpxgev b hkhabdcegefggahfxjohf bc hkoaeacdefgggfkaccrxr bc hdpaadfegeggfkanjwwtb b hdpaaefggefegkxpehdak be hgpaacgfffeggprexmhsp b hdhacdfeegfggeexlsxhg c hldaibcdeffggqqjpcnak bi hjhaedbeffgfgeexidvfe bc hdnabbdfeggfgafmmhtsi b hkoaeacdefgggnkaccccx bc hlhaadcfeggfgkqgexwcm b hhdabbceeffggqqjlncxf b hipacecedffggkmpeejvg b hfhabdcfgffggueecufnk b hbpabcdegfgfgcxepaboj bcc hdpaacgefgefgpqomjbtr bcc hhdacbcdeffggknodbiof b hcnafbcedfggglkubqhbr bl hcnafbcedfggglkubqhgw bl hnhaabcdffgggjdnhoxds b hcoadacdegfggfkajjakg bc hbpabdfegggffqwqffhrx b hdnacbdfeefggafvclacl c hepacedffggfgvordbgnq be hmpaaecdfgfggujksvrvh bc hkoaeacdefgggnkakkjjx be hfdafbcdefgggaahhqqdl bx hkoaeacdefgggfkaccccx b hlnaabdgfefggfqwrreww b hbpabcdegfffgctqtarfb b hfhacbcefgfgglfditqdv bg hfhacbcefggfglfdituav bg hkoaeacdfegggfkannafj bn hbhafdceefgggkgakltlb b hcpabcdegffggdtdvsilt bcc hbhadbcedgffglfndqqof bi hcpaccceffgggabaxkurn b hnnaabcggeffgmgtwrsha c hnnaabcggfffgmgtwbshq c hjpaacfdeegggmuexevpk b hgpaadfgeffggpwifxpkv bcc hpdaabcefegggknmfbolb b hdhabcfefgeggctwxatdu bm hdpaaggfegefftwgaxxti be hdlacadfegeggfxhxhfof bcc hipabcdegfeggaakvnkah bcc hjpaacdefggfgiipevudw bc hlhaabffeggfgixixmelt c hpdaabcfeegggknwqcwvq b hbhadbcedgfgglfkuhxhx bg hehadbdcegfgglqfbvkqa c hkoaeacdefgggfkakkjjx bf hhnaabdgeefgghkjpdgsh bi hhnaabdgeefgghkspdgsx bcc hnhaabcfggefgjdgxdouv bc hjhabcedgffggnihsuwdv b hgoaeacfeffggfkswflwd bc hdlaeadfedgggfhhagqbg bc hbpacdeffgeggmmxluefb be hcpabeffdggfgluremetj b hhlaaafegefggfhawkind bc hhlaaafegefggfhawkpnl bc hdfagbcdeegggaankbmtg bc hdfagbcedefggaacvbdgl bc hipaeecdffgfgtktqebsg bc hnlaaacffegggfnwqtfqi bcc hdlacadfegeggfxhxhnof bc hgoabacdfggfgfkrfjatr bc hbhafbcdefggglfndqqjs bi hbhafbcdefggglfndqqof bi hhdabbceeffggqqjlncab b hnlaaacffegggfnwevfuc b hnlaaacffegggfnwktfki c hnlaaacffegggfnwqvfqc bcc hnlaaacfgegfgfnwmvfpc c hnlaaacfgegfgfnwntfni c hnlaaacfgegfgfnwnufnd c hnlaaacfgfgfgfnwnmfnm bf hnlaaacfgfgfgfnwnqfnq bck hdpaaefdggfgfbhbjxajq bc hkpaadedggffgjqoudgeb b hnnaabcgfffggmgtdbskq bcc hnnaabcgfffggmgtjasur bd hnnaabcggeffgmgtwqshg bq hnnaabcggfffgmgtkaspr bd hnnaabcggfffgmgtubsnq c hnnaabcggfffgmgtwashr cd hnlaabcggeffgiouwkwii bc hlhaadcfegfgghnxjjhoh b hpdaabcegefggknrsurxk b hbpabfcfeggfghbrtkgkj bc hbpabcedggffgmqqrtewi bd hhlaaafegggfffhawkoah c hdpaaedgggfffnjhssdud bc hdpaacgdeffggcpxxdwsk bcc hjpaaccgfegfgcxsagcfw bcc hkpaaedgegffgetbokhwi cc hjhabbddegffgpbmkaxnb b hnlaaacfdggfgfnwefltm bm hnlaaacfefgfgfnwluftu bce hnlaaacfeggfgfnwldftm cc hnlaaacfgegfgfnwdlftm cc hffafbcdefgggaaqqhqax bcm hdlaeadfedgggfahabqbg bc hhlaaafegefggfhawlopf bc hhlaaafegefggfhawuotf bc hdlabbeefgeggdxfkcipg b hdhabbdegfgfghgvhkjos bg hdlaebeefegggqxldurwq bc hghacdcfffgggkbknjsmt bcc hcpacecfdegggofkwadsc b hdhabcddeffggwwwxslxt bc hbpabfddefggggkmqgqjm be hohaadcgfegfgthbwscdd be hdfafbcdefgggaaknknsk b hdhabbdfegfgghgxpokrm bc hjlabceddgfggnnkkxaqh bcc hhdacbcdeffggknodbijk b hfpaaedffgfggjcdxsijx b hdhabdcffefggvxcvjdpm be hjpaaccgfegfgcxsamcdw bcc hjpaaecfggefgwuqkplsw be hlnaabdfefgfgcsrxxacm bcc hbpabcfeeggfghbshkmtk c hbpabfddefgggfacawuru bcc hjpaacedggffgkhhlsmjw bd hhhaabdgdgffgknkbaqhx bc hbhadbcedgffglfndqxkf b hhlaaacgegffgllwqmlse bc hfhacbcefgfgglfdiemmg bc hhlaabddegfggkhnjwbir bc hhdacbcdeffggknodbijb bc hbpabcefegfggedxdwwlu b hbpabfefdegggeixwxwqu bc hdpaacfgefggfsxhxjjhj bc helagadceegggfknnwler c hohaacdfgegfgtptfsagc bc hjhabbddegffgpbmkaxnk b hbpabedefggfgvaltmenl bc hgpaadfgdffggdpgbbfsj bd hlhaacdfffgegnhtplwel bc hdlaeadfedgggfhhaoqbg b hdhabdcfgffggvuqmnruc b helafacdeegggffofrwdt c hpdaabcfgefggaavcacca bc hpdaabcfggffgaavcaacc bc hblafccedegggdkotsiao bc helafcdceefggdeksalxl bc hcpacecdfefggtsnlltsn b hbhadbcedgffglfndqxkg bc hddadbcdegfggaaknxahq c hddadbcedgfggaacvxaax bk hjhaiccdffeggqmkrjeoe bg hdlaeadfedgggfahakqbg b hflaeaedffgggeeqswmeq b hdhacbfedefggtnxhumsp b heoadacdegfggfkknxaqh bc hbpabcfffgegghbicshlc bd honaabcfefggglkearres bc honaabcfeggfglkeabrps bc hdpaadfegeggfkhnjwwtg b hcpabecfgegfgtavuwbdw b hhlaacfgegefgenomkjmu bcc hjpaadfceefggfmepelwe bcg hfhabdefdgfggsefkgett bc hchaddccegfggkhqabebf c hcladbcdegfggpnioxaax bk hboadadedfgggnghaaoni b hdlacbedegfggqlnpjfiw bc hgnabbdegfgfgmwebtppi be heoagacdeegggfkknfmop c hhhaadgefffggcxxxxcwr b hbpacfeedggfgeefpnpmh bc hboadadedfgggfghaaoni bc hdpaadfegeggfkhnjwwto bi hghacbdfefgggudetcsno c hjpaaedgdfgfghflxflfw c hbpaecdefegggxjspxikg bi hbpaeededfggghjshpikg bcc hjpaaccegffggfofhvhcw bc hbpabcdeggffgxfxopwqo be hdhacceefegggffrvtjsh bcc hdhacceefegggffrvtssx c hdhacceefegggqemttjsh bc hdhacceefegggwccftjsh bi hdhaccefeegggddlxtjsh cc hfhabbdcgfggfnjenwuxe bcc hghacbcfeegggmghxmgld c hhlaabfgfefggeurxdewq c hhlaabfgfefggfurvdewq bc hhlaabfgfefgglurpdewq bcc hhlaabfgfefggluwpdewx bi hhlaabfgfefggrurrdewq cc hfdafbcdefgggaqhhqadl bD regina-4.95/engine/data/snappea-census-sec7o.dat000644 000765 000024 00000251101 12234011536 021417 0ustar00babstaff000000 000000 hfdafbcdefgggaaaaaafo bg hfdafbcdefgggaaaaaajs bk hkfagbcbdegggaabaabgk b hkjagabcdegggghhhhgvb bf hjdadbcdcgfggqqqglklq bh hlhaacdgffggfqkhxuojk bp hkjajabcdffggnqqqgbqb bn henakbdddefgghwbgqqqn bl hkfagbcbdegggaabaakng b hfhaebeddfgfgqqofhqfn br hhdabcbfegggfqqqnqhaf bh hkfagbcbdegggaabaasnk bf hfnaebeddfgfghhkoahbg bt hkdadcbbdgfggqqnqefth bf hfnabbedeggfgaqkixgnn bh hnkababdffgggghfxffof bt hkjadbacdfgggafaanmmq b hkdajcbbdffgghhkhvoxg bv hlfaibcdfefgghaucfcun bi hhfabaceegfggnbqgqbml bu hjnabbedfgegghhfuxkfb bv hgkadabcefgggnhhfmxfg bf hfdadbcdeegggaaaanbgk bf hfdadbcdeegggaaaanbsg b hkkafabcdffggghhhfnhg bx hkkadabcdfgggfaaanmda b hgdadbcbegfggaqfgxpbs bx hblakccddefggtfrgthab bh hkfagbcbdegggaabahgbf b hkfagbcbdegggaabahgvb b hapajdedefeggowwfhohk by hokacabcefgggfaakamfk be hkfagbcbdegggaabahfob b hkfagbcbdegggaabahvof b hkkadabcdffggfhhhofhf bu hihakccdcefggqjofhhhf bq hkdafbcbdffggqqfhfoho bw hkkadabcdfgggfaahoqah bo hkjafabcdffggnqqafoho bx hkjafabcdfgggnqqafahq bn hkkagabcdegggfaqqqfob b hkkagabcdegggfaqqqvof b hkjagbacdegggafahhnko b hkjagbacdegggafahhwkn bf hkkadabcdffggohhhknan bz hkfafbcbdfgggaanafahq bl hokacabcefgggnaaktxkg bB hmnacbbdegfgganfhffjs bh hfdadbcdeegggaaaankng b hfdadbcdeegggaaaansnk b hkkagabcdegggfhqqqkng bf hilakcbcdefggqgbbqqaf bx hjdafcbdcfggghqqkghaq br hkkagabcdegggfhqqqsnk b hgdadbcbegfggaqkgxpbs bC hipaicedfeegggsgqsgak bi hkfagbcbdegggaabqhnko b hkfafbcbdffggaanafnhg bD hhdabbcfgeggfqqqefthb bh hkfagbcbdegggaabqhwkn b hjfakaccdefggnnkahhab bB hjdafbcdcfgggaqaobaqh bn hkjadabcdfgggnaaakihi bc hkfajbcbdffggaanawkxf bE hkjagabcdegggbahqqfob b hkjadabcdfgggghaakihi bd hndaibcddffggaahgwkxf bF hkjagabcdegggbahqqvof b hkjadabcdffggghhqngaf bF hkkafabcdfgggfaahglmq bf hkjafabcdffggnahhfoho bz hkjadabcdfggggqaanqha bt hkjagbacdegggafahhfon b hkjagbacdegggafahhfwo bf hjfakaccdefggbbgqqahg bA hjdadcbdcfgggqhaknqha bs hgkadabcefgggkqhfhmwo be hokacabcegfggghanuajk bG hkkadabcdffggkqqakoab bG hkjafabcdfgggghhqbeqp be hkkafabcdffggoqahgbqb bD hkfadbcbdfggghagqoqah bp hkkagabcdegggfaqqqgbf b hkkagabcdegggfaqqqgvb b hkjadabcdffggnahhknan bE hjhaicedfeeggqqkqhaak bo hkfagbcbdegggaabqhfon b hkfagbcbdegggaabqhfwo b hapajdedefeggowwfhohf bH hgjadabcefgggbqqbmqng bf hkjafabcdffggghaafnhg bH hkfakbccdefggdlnahhab bh hkkadabcdffggfhhhobhk bI hkjadabcdfgggnaaakiae bc hkjafabcdffggfhhqbgqg bD hhdaccbfegggfhqhbaqho bt hjdagbcdcegggaaakqfob bf hjdagbcdcegggaaakqvof b hjdafbcdcfgggqaqkblxd bd henakbdcedfggpffhnhan bJ hkjafabcdffggbqqafoho bF hkdakbccdefggaawhahhf br hkjagabcdegggbqqaabgk b hkjagabcdegggbqqaabsg b hkkagabcdegggfhqqqbgk b hkkagabcdegggfhqqqbsg b hjdagbcdcegggaaakqgbf b hkkagabcdegggfaqqafon b hjdagbcdcegggaaakqgvb b hkkagabcdegggfaqqafwo b hkfafbcbdffgghafafnhg bK hjdafcbdcfgggqhhbglmq bi hndacbcddffggaahgkoab bL hkfafbcbdfgggahbhfdhl bd hkjagabcdegggbahqqgbf b hkjagabcdegggbahqqgvb b hapajddeefeggorrfhoqb bf hojacabcegfggnahothcf bL hkkagabcdegggfhqaabgk b hkkagabcdegggfhqaabsg b hjdafbcdcffggqahkfoho bF hkkadabcdfgggfhqakhaq bv hkkafabcdffggnhqafnhg bL hkjadabcdfgggbahqnmda bh hbhafbeddfgfghagbqqbg bI hkdafcbbdfgggqhfafahq bs hkjagabcdegggbahqafon b hkjagabcdegggbahqafwo b hkkagabcdegggfaqqanko b hkkagabcdegggfaqqawkn b hkjadabcdffggfqahobhk bO hjdadcbdcfgggqhaknmda be hkjagabcdegggbqqahgbf b hkjagabcdegggbqqahgvb bf hihakbdccefgglfoghhan bP henakbdddefggdkbgqqhf bd hkkagabcdegggfhqqhnko bf hkkagabcdegggfhqqhwkn b hihakbdccefgglfogaqqb bP hhdabcbfegggfhqhnmdao bh hkjagabcdegggbqqaakng b hkjagabcdegggbqqaasnk b hkjagabcdegggbahqanko bf hkjagabcdegggbahqawkn b hihakcbcdefggebkkaqqb bR hjdadcbdcfggghhafnmda bf hkkagabcdegggfhqqhfon b hkkagabcdegggfhqqhfwo b hkkagabcdegggfhqaakng b hkkagabcdegggfhqaasnk b hkkafabcdffggbqhabkqo bS hihakccdcefggdgosqaho bi hkjagabcdegggbqqahfob b hkkagabcdegggfhqahgbf b hkjagabcdegggbqqahvof bf hkkagabcdegggfhqahgvb bf hkdafbcbdffggaqkhfnhg bV hkjafabcdfgggnahqbeqp bf hkkagabcdegggfhqahfob b hkkagabcdegggfhqahvof bf hfdafbcdefgggqqqqpqnv b hkfagbcbdegggaabaaksg b hkfagbcbdegggaabaasgk b hldabbccegfggqqgfxlkf bd hkfajbcbdffggaabankxf bd hhdabcbfeegfgqqqngafk b hlkacabdfgfggfanxjnkn b hojacabcegfggbaanxpno bd hkfadbcbdffggaabaknab b hlkacabdfgfggfhoxcofo b hkjajabcdffggghhqbgxk bd hlkababdegfggfanannkb b hkfagbcbdegggaabahfvb b hkfagbcbdegggaabahvbf bh hfdadbcdeegggaaaanksg b hfdadbcdeegggaaaansgk b hclakbcddefggpnvohaak bd hjdafbcdcffggqqqgbgxn bd hlkababdegfggfhohoogf b hokacabcefgggkqhkaiok b hkfafbcbdffggaanafohg b hkjafbacdffggafahgbqn b hkjagbacdegggafahhnwo bh hkjagbacdegggafahhwon b hojacabcegfggghantabf b hkjadbacdffggafahofhk b hkkagabcdegggfaqqqfvb b hkkagabcdegggfaqqqvbf b hapakdceddfggngkobaan bd hjdadbcdcffggaaaknkaf b hkfagbcbdegggaabqhnwo b hkfagbcbdegggaabqhwon b hihakbdccefgghfogaahg bd hkkajabcdffggfhqqgbxf b hkkagabcdegggfhqqqksg b hkkagabcdegggfhqqqsgk b hkkafabcdffggfaqqbgqo b hjfajbcdcffggahqogbxf b hkjagabcdegggbahqqfvb b hkjagabcdegggbahqqvbf b hkjadabcdffggnahhknab bd hkjafabcdffggfqahgbqn b hjdagbcdcegggaaakqfvb b hjdagbcdcegggaaakqvbf b hkjadabcdffggfqahofhk b hkkafabcdffggfhqqgbqn b hkkagabcdegggfaqqanwo b hkkagabcdegggfaqqawon b hndaccbddffggqhhonkaf b hkjagabcdegggbqqaaksg b hkjagabcdegggbqqaasgk b hkjadabcdffggbqqaknab b hkjagabcdegggbahqanwo b hkjagabcdegggbahqawon b hkkagabcdegggfhqaaksg b hkkagabcdegggfhqaasgk bh hjdafbcdcffggaqhggbqn b hkkagabcdegggfhqqhnwo bh hkkagabcdegggfhqqhwon b hkjagabcdegggbqqahfvb b hkjagabcdegggbqqahvbf b hkkadabcdffggfhqaknab b hkkagabcdegggfhqahfvb b hkkagabcdegggfhqahvbf b hgkadabcefgggghhgltoj b hkfadbcbdffggaabaonan bc hehafcbdefgggqgvqhqie b hjnaibedfffgghhwahqhg bc hofaibcbeefggaabnkahg b hofaebcbefgggaabnqqgb b hilakaccdefggffraaaaf b hgfajbcbeefggaabbgqhk b hjdadcbdcgfgghhhkqfah b hioakaccdefggfknaqqqo b hjkakabddefggnhfvxqqg b hkfadbcbdffgghhghonan bc hioakaccdefggnknaqqqo b hofaibcbeefggaabgkahg bh hojaiabceefggnqqgkaab b hkfadbcbdffgghhghwoab b hgkafabcefgggfaafaakn b hgkadabcegfggnaaftdkc b hhdaecbfeegfghhhwkhgg b hgjajabceefggnqqbjxqo b hjdadbcdcffggaaakgkak b hlkaeabdfefggfanxewmk b hkjafabcdfgggbaaawahq bc hjhabbedfgegghhgtlgnc b hglaicdffefgghgudltqo b hkdadbcbdgfggqqfqqkha b hkjadbacdffggafahbfhf b hkjafabcdffggnqqqcfqb b hgjakabceefggnqqbnqqk b hbhakdceddfggajqfsqqg b hboajacedefggofemwehf b hgjajabceefgggqqgbqaf b hgkajabceefggfaqgbqaf b hokaiabceefggfaqofhab b hkjafbacdffggafahfbqb b hkkafabcdffggfaqqkgqg bc hgjadabcegfggghabuenc b hgkadabcegfggnhhftdkc b hgjajabceefggfhhgbqaf b hddakbcceefggaqowkxab b hokaeabcefgggnaakhhfo b hgkafabcefgggfaqghhfo b hbnafbedcfgggaabnhadu b hkjafabcdfgggghhqsaqh b hilakaccdefggffraaaak bc hodacbcbefgggaqkopugr b hgjafabcefgggghabaank b hkkafabcdffggfhqqfbqb b hkjadbacdffggafahvbhk bc hgdadbcbefgggqqfgminv b hjfafbcdcfggghahbwahq bc hkfadbcbdffgghhghknxk b hgkajabceefggfhqbgqhk b hkjadabcdffggfqahbfhf b hjnacbedfgeggaafaakkn b hjfadbcdcgfgghhhoaoqh bc hkjafbacdffggafahvfqn b hjdadbcdcffggqqqgngak b hokaiabceefggfhqnkahg b hkjafabcdfgggnqqawahq b hgfakbcbeefggaansnxaf b hjhaecddfegfgqgkqaabb b hflabbedfgeggahnmtgbr b hclafcdecfgggqvhkhhaq bc hjlaicdefffggqnaqtpho b hojaiabceefggnqhkoaaf b hkkadabcdffggfaqqgkak bg hjdadbcdcffggqqqggjaf b hkjafabcdfgggnahhwahq b hkkadabcdgfggghhqaghq bc hkkafabcdffggfaqqskqo b hgfakbcbeefgghafsnxaf b hkfadbcbdffgghhghknxf b hddajcbeeefgghhtpcqqo b hkjadabcdffggbqqaonan bc hgdakcbbeefggqhkbnqqk b hkdadcbbdgfgghhkaaghq b hflacbedfgeggqhnqqfgb b hkkadabcdfgggoqahvqah bd hkjadabcdffggbahqgkak b hkkadabcdgfggfaqqaghq b hkkafabcdffggnhhhgfhn b hioakaccdefggnknaaaaf b hkkadabcdffggfaqqsgaf bd hflaibeddfeggaanganab b hbhakdceeefggarahqaan b hddadcbcegfgghhfftdkc b hkkafabcdffggfhqqvfqn b hfhacdcfegffgafdtagrk b hkjadabcdffggfqahvbhk bc hkkadabcdffggfhqaonan b hkkadabcdffggfhqqbfhf bd hkjadabcdffggfhhqgkak bc hokaiabceefggfhqngaab b hdjakbaddefggafnkahhk b hkdadcbbdfgggqqbqoqah b hjgakaccdefggnwnaaaaf b hokacabcefgggfaqoimkv b hfhaibefdffggqqqcahhk bc hojacabcefgggnahopugr b hjfadbcdcgfggahaohnqa b hkjadabcdffggbahqsgaf b hjdafbcdcfgggqqhgwahq bc hkjadabcdgfggfqahqfah bg hkkadabcdffggfhqqvbhk bg hgkadabcefgggfaqgminv b hioakaccdefggfknaaaaf b hgkajabceefggfhqbkqqn b hkjadabcdffggbqqawoab b hkjadabcdgfggnqhhhbaq b hndaccbddgfggqhabqjha bd hkkadabcdffggnaqhonan b hkkadabcdfgggfhqawhaq bd hkfadbcbdffggaabaknxk b hkjadabcdffggfhhqsgaf b hohaacdgfeffgqbqeuojb b hkdafcbbdfggghhfasaqh bc hkkadabcdfgggfhqqvqah b hgjadabcefgggnahgminv b hkkadabcdffggfhqawoab b hjlaicedfeeggqqfaahhg bd hjdadbcdcgfggqqhgaoqh b hjfadbcdcgfggahaoaoqh bc hkkadabcdffggnaqhwoab bc hclakcdcdefgghgwvmaqg b hndaccbddfgggqhhonqha b hfjakbaddefggafbgqhhg b hkfadbcbdgfgghafqhbaq bd hkkafabcdfgggbqhasaqh bd hkfajbcbdffggaabanwxf bd hkfadbcbdffggaabaknxf b hehafcbdefgggqbkqhqie b hkjadbacdgfgghfqaaoqh bg hkkadabcdffggfaaagkxn bc helakbdcedfggiosqgqqf b hchafbbedfggghkqfqqha b hblafccedfgggigafqaip b hioakaccdefggfknaqqqg b hgkakabceefggfaafwhhf b helafcdbefggghrkhahit b hioakaccdefggnknaqqqg b hjfajbcdcffggaaabwkhg bh hndabbcdgggffqqqqtpkk b hkjajbacdffggafahgbqb b hkjajbacdffggafahgbqk b hkkafabcdffggghhhfnak b hkjajabcdffggghhhfwak b hgjakbaceefggafankxhf b hkkajabcdffggnaaabsaf b hgjakbaceefggafankxab b hkjajbacdffggafahfohf bc hkfadbcbdffggaabakohg b henafbcdefgfgannhahbf b hkjajbacdffggafahfohn b hkkajabcdffggfaqqknaf b hkkajabcdffggfaqqknan b hkkajabcdffggbqqqvoqb b hgkajabceefggfaanohqn b hipacedcfgefgfofhqngn b hofaibcbeefggaabsgaab b hlhaadefgeffghmmqpugn b hjdadbcdcffggqqqgnkxk b hkjafbacdffggafahgbxg b hkkajabcdffggfhqqfohn b hndaecbddffggqhhgjkqo bh hkjadbacdffggafahofxo bc hjdafbcdcffggqqqgbgqg be hkkajabcdffggfhqqfohf bc hjdadbcdcffggqqqgnkxb b hkjajabcdffggbahqknaf b hkkajabcdffggfaqqsnqf b hkjajabcdffggbahqknan b hkdadbcbdffggqqfqgkxn bc hkjafbacdffggafahgbxo b hioakaccdefggfknaaaak b hinakbdccefggmwbnaqqn b hgfajbcbeefggaabbgqqn b hilakaccdefggfrnxhhhg bj hkkafabcdffggnaaabkaf b hkjadbacdffggafahofxg b hjkakabddefggnhfoxqaf b hjdadbcdcffggaaaknkxn be hkkajabcdffggghhqbsaf b hkkajabcdffggfaqqbgqg b hjnaebeddfgfgaafnaakn b hjdajcbdcffgghqqkvoqb b hkkadabcdffggfaqqnkxn b hkkajabcdffggfaqqbgqf b hjdafbcdcffggqaqkjkqo b hioakaccdefggnkoaqqqo b hjdadbcdcffggaaaknkxb b hkjajabcdffggghhqbsan bi hkhacdcefgggfabhaagrf b hehafbcdefgfgpgwhhhgg b hkkadabcdffggfaqqnkxb b hkjajabcdffggbqqafohg b hkjajabcdffggbqqafohf b hkjajabcdffggbahqbgqg bc hjdadbcdcffggqqhokohf b hfhaibedfeegghhgqitqo b hjdafcbdcffgghhakbkaf b hkkafabcdffggfaqqbgxb b hkfakaccdefggnjnqqahg b hkjajabcdffggbahqbgqf b hkkajabcdffggfhqqvoqb be hkkafabcdffggghhqbkan b hjkakabddefggfasnxaab b hkkadabcdffggfhqqofxo bc hkjadabcdffggbahqnkxn b hkjadbacdffggafahobqk bi hkkajabcdffggfhqqgbqb b hgfajbcbeefggaabbkqhk b hjlaicddfeeggqobqqkhk b hojaiabceefggfhhbfhab b hkjajabcdffggbqqankak b hkkafabcdffggfaqqbgxn b hndaebcddffggaahgfnak b hkkajabcdffggfhqafohg b hkkajabcdffggfhqafohf bc hjfajbcdcffggahqgvoqb b hofaibcbeefggaabnkaab b hchafccedfgfgtfhjahfk bh hkkajabcdffggohhhfwab b hkkajabcdffggfhqqgbqk b hkjajabcdffggbqqankab b hkkadabcdffggfhqqofxg b hkjadabcdffggfqahofxo bc hkjadabcdffggbahqnkxb b hkfadbcbdffgghhghwohg b hdkakabddefggfhofhaaf bc hkkajabcdffggnaaasnqg bc hkkafabcdffggfhqqgbxg b hbhakcceddfggtjqfoqqo b hkjadabcdffggnaaakohf b hkjajabcdffggnqhhvoqk b hndaecbddffggqhhojkqo b hkjadabcdffggfhhqnkxn b hkkafabcdffgggqaabkaf be hkjadabcdffggfqahofxg b hhfabbcfeegfghhhbfqfn bc hkkafabcdffgggqaabkan b hkkajabcdffggfhqankak b hfkakabddefggfhgbqaab b hkkafabcdffggfhqqgbxo b hkjadabcdffggbqqaknxk b hjdakaccdefggkonhqqqn b hkkajabcdffggfaqqbsan b hkjadabcdffggbahqngqf b hjlaicddfeeggqobaqbhg b hkkajabcdffggfhqankab b henajbdcefegghffahnqk b hjdajbcdcffggqqqongxk b hhdaebcfeegfgaqqfwabk b hkjadabcdffggfhhqnkxb b hhdaecbfeegfghqabsabf b hkkafabcdffggfaqqbkan b hjfajbcdcffggahqovoqk bh hgjajabceefggbqqjkxqo b hkkadabcdffggnaqhknxk bc hkkadabcdffggghaakohg b hkjadabcdffggbqqaknxf b hmlaicddfeeggqgvahfhg bi hbhajccedfeggujaoafaf b hkkajabcdffggohhqbsaf b hkdajbcbdffggaqkqbsaf bh hkkajabcdffgggqahgvhf be hkkadabcdffggfhqaknxk b hffafbcdeffgghhhhhuon b hofaibcbeefggaabnkaho b hkkadabcdffggnaqhknxf b hkkafabcdffggbahqbkan b hgkakabceefggfaqgbxqk bf hfkakabddefggfaskqqqo b hkjadabcdffggfhhqngqf b hkjadabcdffggfhhqngqg b hehakbcdedfggpbwqfhab bi hkdajcbbdffgghqbhvoqb b hkdajbcbdffggqqghfwak b hgfajbcbeefggaabbgqhf b hkkadabcdffggfhqaknxf b hkjadabcdffgggqaangqg b hkkafabcdffggfhqqgfhn b hkkadabcdffggbqqakohf b hkkajabcdffgggqahvoqk b hfnaiacdfffggnkefksqo b hgkakabceefggfhqknxaf b hkjadabcdffggbqqakohg b hkkafabcdffggbqhabkaf be hjfadbcdcffgghaqngkqg b hjfajbcdcffggahqogvhf b hkgakaccdefggfwkhhhhk b hgjakbaceefggafawkxhg b hhfabbcfeegfghaqbfqgb b hjnaebeddfgfghqfnaabk b hkfadbcbdffgghhghonab b hgkakabceefggnhhwohhk b hfhaidcfeffggafleepqg b hdlaccfdeggfgetjthfbf b hfhacbedfgggfqqfhaojk b hdgakacdeefggkbqtjeqf bc hjhacbedfgggfhhnaqckn bc hjkakabddefggfhfoxqqg b hjkakabddefggfhfoxhhg b hodaicbbeefggqhkngahg b hngacacdefgggfwakbuuq b hgkajabceefggfaafnhhf b hgkakabceefggfaqvoxqk b hofaibcbeefggaabngahg b hfhacbefdgeggqqqcqobg b hkfadbcbdffgghhghwoan b hgjajabceefggbqqbgxqo b hjpaafdgdeffgqfqkgumf b hfjakbaddefggafbgqqqo b hblakbedeefggqqviehho b hgjakabceefggfhhfoxqb b hclafccedfgfgmfhfqqkb bh hjhaedecfdggghhnhohqa b hjgakaccdefggffnxhqqo b hkjajbacdffgghkhaksxb bd hofacbcbffgfgaabqhxab bc hgkakabceefggnaafwhqk b hflaibefdeeggaaabduab b hglabcdcefggghgkqosvb b hkjajbacdffggafahfvhf b hkfakaccdefggbbnqqqqb b hgkajabceefggnhhfnxab b hjdafbcdcffggqqqgjgxn bd hgkakabceefggnhqknxak bc hkjajbacdffggafahvbxf bd hfkakabddefggkafnxhhk b hdjakbaddefggafnkaaaf b hgkakabceefggfhqbgxqf b hdfakbcedefggaautbehk b hbhajddedfeggtbqragaf b hgkakabceefggfhqsnxqf b hddakcbceefgghhkfwxhf b henafbdcefgfghffhaqnb b hgfajbcbeefggaabbgxaf b hkjajbacdffggafahvbqb bc hgkakabceefggfaqgvxhk b hchajccdeefggencqikhk b hgkafabcefgggfaqvhhfo b hfnaiacdeffggnkakkkqg b hfhacdcfegggfafleasff b hioakaccdefggfkoaqqqo b hgjajabceefggfhhgbqqo b hokaiabceefggfaqofhhg b hmhaecddfegfgangaxabf bn hgjakabceefggnqhwkaqf bc hgkajabceefggfaqgbqqo b hjdadbcdcffggaaakgkaf b hgjajabceefgggqqvfqqo b hehafbdcefgfgukcqqqgo bc hjkakabddefggfhfohaaf bc hmlaicddfeeggqvbqqbhk b hgkajabceefggfaqvfqqo b hehafbdcefgfgukkqqqgk b hehafdccefgfgakfxaakb b hgjajabceefgggqqgbqqo b hkkajabcdffggfaqqsgxk b hdkakabddefggfankxqqn b hokaiabceefggfaakoaak b hokaiabceefggfaqvbhhg b hkjadbacdffggafahbfhk b hmnaebbdfeggghfftdhmd bc hndaibcdeefggqqqojsak b hkkafabcdffggfaaakgxn b hioakaccdefggnkoaqqqg bc hgjajbaceefgghkhfoxab bl hgkajabceefggnaafnhqn b hdfakbcceefggaansnxqf b hfoabacdfgfgggfhfmjit b hjkakabddefggfhfohhhk b helafbbdefgggafsaaahq bc hnkaeabdefgggkqgmihlu b hdkakabddefggghkoaqqn b hdfafacedfgggbbeuqaoj b hklaecddfegfghvfhqqoo b hjjakabddefgggqsgxaho b hilakaccdefggfrnxhhho bd hkjafbacdffggafahvfhn b hjlaccdefgggfqvqaqbgf b hojaiabceefggnqhkoaqo b hkkafabcdffggkqhhfbxg bc hkjadbacdffggafahvbqk bc hhfabbcfeegfgahqgkxnn b hkjafbacdffggafahfbqn b hokaeabcefggggqawhhfo b hgjakabceefggnahgvqaf b hlkababdefgggnhohgwfv b hdjakbaddefggafnkahhf b hjhaidecfeeggaqfhldhf b hjdadbcdcffggqqqggjqo bf hkjajabcdffggbahqsgqg b hkjajabcdffgggqaaksan bc hioakaccdefggnkoaqqaf b hgkakabceefggfaqgbxqb b hgkakabceefggghasnxak b hjdakaccdefggkonhqqqb bk hokaiabceefggfhqnkaab b hgkakabceefggfhqbsxaf b hgjajabceefggnqhfnhqn bf hgkajabceefggfaqgbqak b hokaiabceefggfaqofhan b hgkajabceefggfaafnaaf b hkkajabcdffggfaqhwoxn b hgjajabceefgggqqgbqak b hgjajabceefggghhgfqak be hkkafabcdffggfaqqkgqo b hgjajabceefggfhhgbqak b hfjakbaddefggafbgqhho b hioakaccdefggfkohqqqk b hgkajabceefggfhqbgqqn b hgkakabceefggfhqknxak b hkkajabcdffggfhqqvbxf b hkkajabcdffggfaqqksxb b hokaiabceefggghangxqo bf hflacbefdgeggahqkqggb b hdkakabddefggfankxaab b hgjajabceefgggqqgfqaf b hgkajabceefggfaafnaqf b hgkajabceefggfhqskqqn b hblakcceddfggdkqnbhho bc hkjajabcdffggbahqksan bk hkfadbcbdffgghhghwohf b hjjakabddefggnafwxaaf b hfdafbcdefgggaaaaaqkc bk hfdafbcdefgggaaaaaqnv bo hkjadabcdffggnahhonxk b hgfakbcbeefggaanbsxak b hkjajabcdffggbqqanwak b hkkadabcdffggfaqqsgqf b hnjaeabdfegggnafelaud b hgjafabcefgggfhhvhhfo b hblafcecdfgfgiqvnqqgf be hgkajabceefgggqafnhqn b hfjakbaddefggafbgqhab b hdjakbaddefggafnkahqn b hkkafabcdffggfaqqskaf b hokaiabceefggfhqsgaab bh hkkafabcdffggfhhakgxb bc hkkajabcdffggfaqhnwxf b hojaeabcfegggnqhhnhfo b hkkajabcdffggfhqqfvhf b hdhaidcfdffggtopbdnho b hjdadbcdcffggqqqgngaf b hkjajabcdffggbqqanwxf bd hkjajabcdffggbqqawohf b hkgakaccdefggfwkhhhhf b hkjadbacdffggafahvbhf b hkkafabcdffggfhqqfbqn b hmlaecddfegfghvoahanb b hgdakbcbeefggaaffwaqg b hndaibcdeefggqqqojoqk b hkjadabcdffggfqahbfhk b hkjafbacdffggafahvfqb b hgkakabceefggohhvoxhf b hehafdccefgfgakfhahkf b hgdajcbbeefgghqbfnhqn b hkkajabcdffggfhqanwak bc hblajacdefeggknhhhohg bc hkjadabcdffggbahqsgqf b hnkaeabdfeggggqbeuqld bc hkkajabcdffggfhqqfvxg b hflacbefdgeggqqqkqbgb b hkjadabcdffggfqahvbqk bc hgkajabceefggfhqbgqhf b hkkadabcdffggfhqqvbqk bk hokaiabceefggfhqnkaho b hkjajabcdffggbqqawoxn bj hjkakabddefggfasnxqqk b hokaeabcefgggfhqsqqgb b hddajcbceefggqhbfnxab b hkkafabcdffggfhqqvfhn bf hodaecbbfeggghqbaoank b hdhabcdcefgggqkoqcksj bg hbhafddedfgfgtbqrahnk b hkkadabcdffggfaqqgkaf bd hnjacabdefggggqbqnvsg b hjdadbcdcffggqqqggjak b hkkajabcdffggfhqawohf b hkkajabcdffggfhqanwxf b hgjakabceefggnahvoxhf b hgjakbaceefggafawkxhf b hkjadabcdffggfhhqsgqf b hcnakbbddefgghgkohhhg b hkjadabcdffggbqqawohg b hgkajabceefggfaqgfqqg b hkkafabcdffggfaqqskqg b hkjadabcdffggbqqaonab b hgkakabceefggfaqgbqaf b hkjadabcdffggbahqgkaf b hgjajabceefggbqqbgxqg be hkkajabcdffggfhqawoxn b hldaebccefgggqakoqqbg b hgkajabceefggfhhfnhhf b henafbcdefgfglckhqhfg b hkkadabcdffggfaqqsgak bj hkkadabcdffggnaqhwohg bc hkkadabcdffggfhqawohg b hgkakabceefggfaqgbqqf b hkkadabcdffggfhqqbfhk bj hehajbdcefeggukcqabho b hkjadabcdffggfhhqgkaf b hfhacdcfegeggafltafgr b hkkadabcdffggfhqaonab b hhfaiacdfefggggqgggqb be hkjadabcdffggfqahvbhf b hkkafabcdffggfhqqvfqb b hndaibcdeefggqqqojsqg b hgfadbcbefgfgaabbaanf bg hkjadabcdffggbahqsgak b hkjajbacdffggafahfvhn b hjlaecddfegfghobahhno b hkkadabcdffggfhqqvbhf bd hioakaccdefggfkoaaaak b hfnaibeddfeggaakoafqg b hkkadabcdffggnaqhonab b hgkakabceefggfhqbgxqg b hkjadabcdffggbqqawoan b hdjakbaddefggafnkahqb b hgfakbcbeefgghhfgvqak b hehajbcdefeggpbbaqfqb b hkjadabcdffggfhhqsgak b hgkakabceefggfhqknahg b hdkakabddefgggqngaaaf b hioakaccdefggfkoaqhhg b hjkakabddefggghfwhqqf bn hipacbcdffgggngkluqlf b hkpaadcegffggnboippir b hmlaecddfegfghgvhhhog b hgkakabceefggghhvoqqf b hkkadabcdffggfhqawoan b henafbdcefgfgmgwhqhkf b hblafcedcfgggqhbbhhqa bi hgjajabceefggbqqbgqhk b hkkadabcdffggnaqhwoan b hfkakabddefggfhgbqqqn b hdjakbaddefggafngahhk b hjdafcbdcffggqqaknoxk b hgkakabceefggfaqvbqaf b hdkakabddefggfhofhhhk b henajbcdefegglnnhhfqb b hgdakcbbeefgghhkfwaqf b hgjajabceefggbqqbgqqn b hgnaiadedefgggelhochk bc hkkajabcdffggfaqqsgqf b helajbdcefeggissaqkhk b hdkakabddefggghwoaaan b hjhaededfdgfgihfashfg b hinakbbddefgghfwkxaak b hokaiabceefggfaqofhho bc hgjajabceefggfhhgbqqg b hhdabbcfeegfgaahonxgf b hgkajabceefggfaqgbqqg b hcnakbbddefggabkoxhhk b henajbdcefeggpffahnqk b hjkakabddefggfhvoxqqf bh hgpaacdffeeggvrexbfbg b hldaibcceefggaafkohhk b hgjajabceefgggqqgbqqg be hlfacbcdeffggaatbvgek be hioakaccdefggfkoaqqqg b hokaiabceefggfaqofxqn b hgjajabceefggfhhgbxhk b henajbdcefeggpsfaanab bl hgkakabceefggfhqbgqhk b hblajbeddfeggahbsagab b hndaibcdeefggqqqojoqb b hgjakabceefggnqhnwaqf b hgkajabceefggfaqgbxhk b hjhaededdfgfgiqffqqbg b hgkakabceefggfhqbgqqk b hkjajabcdffggbahqsgqf b hkkajabcdffggfhqqvbqk b hjkakabddefggfasnqqqf b hehafdccefgfgakfhaqkb b hgjajabceefgggqqgbxhk b hioakaccdefggfkoaqqaf bl hchakcddcefggajgkxqqb bc hgjajabceefggfhhgbxqn b hgfajbcbeefggaabbgxak b hokaiabceefggfaqofxhk bj hjdadcbdcffggqhhbbfxg b hgkakabceefggfaqgvqak b hgkakabceefggfaqvoqaf b hkjadbacdffggafahvbqb bl hjkakabddefggfasnxaan b hkjafbacdffggafahvfhf b hgkajabceefggfaqgbxqn b hgdajbcbeefggqaffnhqb be helajbdcefeggioohhfab b hdkakabddefggfhofxqqo bc hioakaccdefggfkohqqqb b hgkakabceefggfaqvbxqk b hgoaeacedffggfkxfskeg bc hepaedecdffggnbkkpurf bc hokaiabceefggfhqnkaan b hhdaebcfeegggaaafwhep b hehafdccefgfghokhhqfb bl hilakbbddefggakbsahhk b hfdadbcdefgfgaaaahhkk bg hgkajabceefggfhqbgqqb b hjdadbcdcffggqqqggjqg b hgjajabceefgggqqgbxqn b hkjajabcdffggbahqksaf b hjjakabddefggnafwxaqo b hgdakbcbeefggaqkgvxhf b hjkakabddefggghfwaqqk b hgkajabceefggfaqgfqak b hkjajabcdffggbqqanwab b hehafbdcefgfglffaaanf b hgkajabceefggfhqkgxaf b hddakcbceefgghqkbsaab b hgkakabceefggfhqsnqqk b hfkakabddefggfhgfqaab b hdkakabddefggfhofhaak b hehajdccefeggaokxqnhk b hkkafabcdffggohhqkgxn bl hfkakabddefggfhgbqaan b hfkakabddefggfhgbxqqo b hkkajabcdffggfhqqfvhn b hgkakabceefgggqqgoqak bc hldaicbceefgghhfkohhk b hkjajabcdffggbqqawohg b hjnaibeddfegghqnohkqb b hgkakabceefgggqanwaqf b hkkafabcdffggfaqqskan b hojaiabceefggbqqgkqhk b hokaiabceefggfhqnkxqo b hkkadabcdffggfaqqsgqg b hdgafacdefgfgfnhaqqbf b hndacbcddffggqqhfbfxg b hdkakabddefggfhofxhhg b hddakbcceefggaqowkhqk b hbhafdeddfgfgiqfoaank b hkkajabcdffggfhqanwab b hgkakabceefggfhqsnqhk b hofaibcbeefgghhfobxqb b hgkajabceefggfhqbgxaf b hddakcbceefggqhbfwhqb b hokaiabceefggfhqnkxaf bd hokaiabceefggfaqobqaf bd hgkajabceefggghabkaab b hfkakabddefggfhgbqahg b hdkakabddefggfhofhaqo bc hojaiabceefggghagkaab bn hgkakabceefggfaqfvxhk b hfjakabddefggghfnaqqk b hkkajabcdffggfhqawohg bl hkjadabcdffggfqahvbqb b henajbcdefegglnkhafho b hkjadabcdffggbahqsgqg b hkkafabcdffggfhqqvfhf b hgkajabceefggfhqbgxqo b hgkakabceefggnaawkhhk b hkkadabcdffggfhqqvbqb b hgdajbcbeefggaqkgfxhf b hhgabacdfggfgnflkhdce b hdkakabddefggfhobhaaf b hdfajbcceefggaabbkaan b hfkakabddefggghfnaaab b hgfajbcbeefggaabbkahf be hgkajabceefggfaqgfhab b hokaiabceefggghangqqk b hbhafccedfgfgujafhakn bl hlkababefeggggqqlakwf b hkhaecddfegfganjaaanb b hkjadabcdffggbqqawohf b hdjakabddefggnakoxhhk b hkjadabcdffggfhhqsgqg b hndacbcdefgggqqhjgeex bk hndaccbdefggghhacfpxp bs helakbbddefggqfbkqqaf b hddajbcceefggqakgchhg b hjjakabddefggnqgoxqqg b hofaibcbeefgghhfobqaf b hokacabcegffgkqqnaxbf bc hkkadabcdffggfhqawohf b hnkaeabdfegfgfhvhqqfo b hkkadabcdffggnaqhwohf b hldaicbceefggqhbkohhk b hojaiabceefggnaangqqk b hokaiabceefggfhqngqhk bj hjkakabddefggfhvoxhho b hjjakabddefggnafwxaak b hddajbcceefggqakgchab b hgkajabceefggnhhwnhqb b hjkakabddefggfasnahqk b hgdajcbbeefggqhkjgxqg bc hokaiabceefggkqhwohhk b hkoaeacdeffggfkafswhg b hokaiabceefggghasgqqk b hgkakabceefggfaqgbqak b hdkakabddefggfhofhhqn b hojaiabceefggbqqgjqqn b hlfaibcceefgghhobcqqo b hgkakabceefggfhqsgxqf b hblajccedfeggmjqohoqb b hojaiabceefgggqakohhk b hgjajabceefggbqqnjxaf b hehajbdcefeggtncqhbqb b hjkakabddefggfhvoqaab bo hdkakabddefggnhobhaqo bf hdkakabddefgggqgkqqqo b hjnaibeddfegghhwnakak b hdkakabddefggfhofhaqg b hmnaibbdeffgghffligxk b hfkakabddefggfhgbqaho b hblajcecdfeggeqbbqbhk b hfjakabddefggbqcfhaak b hblakcddcefggqobbhhhf b hfkakabddefggkqgfxhab bp hchakddcdefggabrwhqqo bc hojacabcegfggbaanxxkn bs hbhafdccefgfghkrxqann b hdlabccffefggqomqenvk b hnjacabdfggfgghfdfsfr bc hgkajabceefggnaanohhk b hdkakabddefgggqgkaaqo b hlkacabdfgfggnhoqgsbo bc hfkakabddefggfhgbqqqb b hokaiabceefggfhqsgqqk b hjlaecddfegfghvbaqqnf b hbhakdcdedfgghkcakqqk bce hapakedceefggfofofqqo bcg hgnaeacdfegggfohoohxh bce hldaecbdeffggqqhbwwhg bck hdkakabddefggfhofhhhf be hknaibbeeefggabhqaxab be hnkaeabdefgfggqbxaanf bc hojacbacfffggafaaqhxn b hgjakabceefggnahvbxqk b hojababcfgfggbqquxllc bg hclakcddcefggqvfbqqqb b hblajacdefeggknhhhghg b hofacbcbffgfgaabqhxak b hdhaicefffdggaqqhxnqb b hfjakabddefgggqskqhhg b hchakdccedfgghoraoaan bn henajbdcefeggpsohafab b hlfacbccffgggaandmxmv bg hgjakabceefggnahfvhhg b hchakdccedfgghoraoqqn b henakbbddefgghffnhhho b hdkakabddefggfhobqqqk bc hehakdccedfgghkfxohhf b hdjakbaddefggafngahhf b hjjakabddefggnawkaqhk b hlgaiacdeffggkbqnvsan b hbpacdcefgffgfosqhaig bd hdjakabddefggnakoaahg bc hofabbcbfgffgaabqqhag bc helajbcdefegglbwaakqo b hjkakabddefggkafwaaan b hgkakabceefggfhqsgxqg b hihakbbddefggqbcohaaf b hdjakbaddefgghfbfxhab b hokaiabceefggfaqvbhho b hgkakabceefggfhqbgqhf b hjpaadefeefgggflnrqgf bd hdkakabddefggbqobxqaf b hklaecddfegfgqgfqhhff b hchakdccedfgghonaoqhk b hgkakabceefggfhqbgqqb b hgjajabceefggfhhvfqqg b hcpaedfcdegfgnhkkvqgk b hbhajbeddfeggqacoqfaf b hjkakabddefggghfwaaan b hokaiabceefggfaqofxqb b hjjakabddefggnawkxhhf bc hgkajabceefggghaskqhk b hgjajabceefggfhhgbxhf b henajbdcefeggmwbaakqo be hchakcdcdefggannkaaaf bs hojacabcegfggghhoqhjf bc hbnajbcdeefgghvfeevho bk hhfabaceegfggnbqgqbha bw hgkajabceefggfaqgbxhf b hokaiabceefggfaqofxhf bg hdkakabddefggfhofxqqg b helakbbddefggakskqqaf b hojaiabceefggbqqgjahg b hgkajabceefggfaqgbxqb b hnnaabedgfefghhohakbo bc hbnakadeddfggnaxhqqhk bc helajbcdefegglbgqqgak bo hnjaeabdefgfgfqbqahfk bk hknaibbdefeggaboahnqk bo hgjajabceefgggqqgbxhf b hgjajabceefgggqqgbxqb be hgjakabceefggnahvbqqf b hmnacbbdegfgghgfhofwb bw hkpaacdgfeffgbgqxgmuf bg hnkababdfgfgggqbaswjb bc hdkakabddefggkakoaqhk b hcpaiededdfggvvvofqak bc hofabbcbgggffaabaqhak b hblakacdedfggfnhagqqn b hdkakabddefggnangxqhk bd hblakacdedfggknhhgqqo bc hgjajabceefggfhhfbqqg b hdkakabddefgggqgkaaqg b hojaiabceefggnahvbhho b hcnakaceddfgggvqqbqqb b hgnaiadedefggfelhochf b hnfaibcdefegghhhfffqk b hbhajbeddfegghacoqnqg b hgjakabceefggnahgvhhf b hgkakabceefggfaqgvhhf b hddakcbceefggqhbnwxan bh hclafccedfgggmfaoaahq bd hchajccedfeggufhohohf br hgfakbcbeefggahbvoqak b hgkakabceefggfhqsnqhf b hdkakabddefgggqgkahhf b hgkajabceefggnaafnaak b henafbdcefgfgpsoqaqkg b hchakdccedfggaonhbqaf b hfnaibeddfeggaqwoagqo b henajbdcefeggpsohqkhk bd hfdadbcdegfggaaaaqqbg bg hokaiabceefggfhqnkxqg b hgjajabceefgggqqvfhab b hgkajabceefgggqawnhqn b hokaiabceefggfhqnkxak bd hgkajabceefggfhqbgxak b hodaibcbeefggqqgvbqaf b hfnabbedfgefghhwaxkbn b hgkajabceefggfhqbgxqg b hdpaafedfggeguvkmxhgf b hblakacdedfggfnhhgqqo bi hodaicbbeefggqhkgkqhf bm hdfakbcceefgghabgohhf b hblafcecdfgfgdqvnqagk b hfdadbcdefgfgaaaaqqkn bo hfdadbcdegfggaaaaaavc c hgfagbcbeegggaabbskjg c hokacabcefgfgfaakxafb bc hojabbacfgffghkqqxhab b hjjakabddefggnawkaqhf bc hknaibbdefegghgkhhfhk b hljacabdegfggfhkankwg bcm hojacabcegfggnaanxqkc bcc hbnajbeddfeggaawkhkhf bc hokacabcffgfgfhqahxqk b hgkakabceefggnaawkhqb b hlfaibcceefgghabbfqak b hgkajabceefggfaqgfhhf b hfjakabddefgggqbkqhho bc hgjadabcegffgfhhgqxok bc hldaicbceefggqhbonxho b hepaibeefedggnjoqjnqo bj hgdakbcbeefggaqkfvhan b hehafbdcefgfglosqhhfk b hfkakabddefgggqbjaaak bc hclafccedfgfgdnavqqbo bc hgkakabceefgggqawkhhf b hgkakabceefgggqqgohan bc hnkababdfgfgggqbqfgsg bg hapakcecdefggbnrnnaak bA hcpaidfdceeggrhrfbgqn bc hgkakabceefgggqqgohhf bn hmlaecddfegfghvoaqank b hjhaecddfegfgqgkhqqfb b hokaiabceefggfhqngqhf bd hklaecddfegfghvfhqaok b hokababcegffgfaaohabo b hgkajabceefggfhqskahg b hokaiabceefggfhqsgqhk bd hlkacabdeggfgbaklkgnr b hclakcdcdefggqvcnqqqb bk hgkadabcefgggnaafhhfo bs hodabcbbegfggqhkkaank bck hojacabcefgggnqhkahcn bce hokacabcegffgfaqohxgn bc hchafdccefgfghorhqhnf b hodaibcbeefggqafwohhf br hihakbbddefggqbcohaqg b hgkajabceefggfhqbkaan b hapakedceefggfosjohab bg hgkadabcefgggkqhfhhfo bw hldaicbceefgghhfwohhf b hdkakabddefgggqgkqhho b hokacabcfffggfhhhqaxf bc hihakbbddefggqbgoqaho b hnhaabedgfegghhfqhoos bcc hddadcbcefggghhffhhfo bcm hmlaebbdefgfgafbqqqfn bs hclakcddcefggqgfbxhan bs hblajacdefeggfnxhaoaf b holaacdgfeefgqgaqxokf bg hljacabdefgggghkavvjs bd hofacbcbegffghafsxafk b hgdajbcbeefggaqkvfqak b hokacabcegffgfhqnaxbf bc hcpaiedeedfggrgvjwqhk bd hlkacabdfgffgnhotgcfn b hodaibcbeefggaqkvbhan bh hapakdceddfggkknnkhqb bw hgjadabcegfggnahghqrb bk hhjaibadfeegghkfqqbqo b hokacabcffgfgfhqqhxab b hokacabcegffgohhohxgn bc hgkadabcefgggfhqbqarg bg hfnabbedfgeggaqkaagkn bA hclakcddcefggqgfbhaqo b hokaiabceefggkqhwohqb bs hbpaicfeddeggkqvnbfqn b hfhaidcdeffgguwrmukxb b hchajccedfeggufafqnqg bc henafbdcefgfgmgbqhqfb b hojababcgggffbqqaqhak b hokababcgggfffaqhaqhf bc hfkakabddefggfhgfhhqb b hgkajabceefggfhqskaan b hokacabcefgfggqakxafb bg hokababcffgfgfaqqaqhb b hlgacacdegfggffhbubti b hblafacdefgfgknhhhqkn be hojababcgggfffhhhaqhf bc hnnaabedgfefgaanhakng b hgkadabcefgfgnaawhxgf bc hokababcegffgfaqbqhkb bc hapajdecdefggnvvnvqab bd hddakbcdedfggqqaabaab bc hljacabdeffggnakebfrn bc hmnacbdcfgfggpooabojj bd hnkaiabdefeggfakxxbqg bs hooaaacgfefggfkkffwrb bd hlkababdefgggfhohsssj bg hldaccbcefgfgqhbwaxbn b hokababcegffgfaqfqhfn b hlhaaddefefgghfiidfrb bd hokaiabceefgggqawohqb b hdfajbcceefggahoskahf bd hokababcgggfffhqaqhak b hdlaibecfeeggplfhhphg bd hblajacdefeggknaxafab bc hokababcfgffgfhqqqhag b hojacabcegffgbqqnqakf bc hdpaadgffgeeggdepxgkc bc hlkacabdfgeggfanmbkwn b hmnaibbdefegghgnhaoak bo hghaibbdfeegghkbqhfak bq hcoakacdedfggfkhhfhho b hgnaebbdfegfghgohxaob b hjpaaefegedfgjaoqjnfn bh hnkaeabdefgfgoqbxxqko bu hofabbcbegffgaabkqakg b hofabbcbfgffgaabqqhao b hgfadbcbefgfgaabbaann bd hokababcegffggqanhang b hcnakaceddfggngqqghhg b hihakbcddefgguvvvhqqk c hhfacacefgfggbbqfebde bg hgjafabceegggghhgbovb c hdnacbeffefgglutaiaob b hlgacaceefgggggqbnupt bd hjpaaefegedfgjaohjnfk b hipaeedcfdgfgofohfhkk b hbpaiefceedggjqoojnqn b hboakadeddfggfhhaqqqo bc hllaacdgfeefghbaqhkbn b hofacbcbegffgaabnqqff bd hkpaadcegeggfnnrikxkb b hcoafacdefgfgfkhhqhkk b hfdadbcdefgfgqqqqaabn bd hcoakacdedfggfkhhfqqf b hgjadbacefgfgafawhxgn bc hdhaecdcfegfgakfaaafb bw hojacbacfffggafaaqhxf bc hapakedccdfggsffkfaab bc hofacbcbegffgaabnqqgg bd hboajacedfeggnohxaoaf b hlkababdefgggnhohssrw bd hljacabdefgggbakavvrs bd hcoakacdedfggfkhafaab bc hcoakacdedfggfkhhfhab bc hodaecbbeeggghhkkowfn c hjhaccedfgggfqlkqiskn c hfhabbdeeffggpbetqwkk bc hblajacdefeggknhhhgho bc hojababcfgffgbqqaxhqb bc hljacabdefgggghkacccv b hepaeedffegfgfoetfqxn b hlkaiabdfeeggfhoxxghk bw hhfacacefgfggnbqfebde bd hapafdcecfgfgnckoaqbf bc hlgacaceefgggogqbnupt bg hofabbcbegffgaabgqakg b hofabbcbgggffaabaqhaf b hokacabcegffgkqqnaxbn b hdlacbeefgffgupqqhhcg b hboakaeedefggfahaqqqn b hepaedbfdegfgrfabgqff b hgkadabcefgfgnaanhxof b hblakacdedfggknhxgaaf bc hfdadbcdefgfgaaaahqkn bc hboajacedfeggfnhaqgqn bc hblajacdefeggfraaabab b hlkababdefgggnhohoowr b hblajacdefeggfnxxafaf b hdnaeaeeffgfgfiqifakb b hkhabdcdefgfguggxbbfg b hnkaeabdfegfgfhgqqqko by hgfadbcbefgfgaabbxanf bc hjpaacedgfffggrbqahdn b hofabbcbegffgaabgqafo b hgkadabcegffgfaqvxqnf b hokacabcfgffgfaqhxaqg b hblajacdefeggknhxhnhg b hghaicdfdffggarpcabak bd hnjacabdefgggnafhssvr bg hmpaacegefefgbnqneobg bc hcoakacdedfggfkhhfhan b hknaibbdefegghgkxafaf b hkpaadfegeffgoaoqkuun bg hblakacdedfggknhagqqb b hklaebbdfegggqfnlalxp b hdhacdcefgefgaohaqono bf hcoakacdedfggfkhhghhg b hblakacdedfggfnhagaab b hblakacdedfggknaxbaaf bc hokacabcegffgfaqvxhff b hlkaiabeefeggfaahxqqf be hblajacdefeggknaxanab b hlkaiabdfeeggnhoxqbhk be hbnadbeddegggaafwknor c hjnaibedfeeggdibaxdan c hddajbcdefeggaahhqbqn b hblakacdedfggknhhgqqg b hkpaaefegdefgfhohnfgf bA hblakacdedfggknhhgqaf bc hljacabdefgggnakavvjs bg hclajbdedefgglricprak bd hnkaiabdfeeggfhghhfhg b hknaibbdefegghgkhhghk b hnfaibcdefegghhhoofab b hgdafcbbefgggqqnbldsk c hgjagabceegggghhvocfb c hokacabcegffgfaqvxhkg bc hokacabcfgffgfaqqxahf bc hofabbcbgggffaabaqhxn b hbpacbecffgfgfffimpmk b hboakadeddfggfhhaqhhg b hojababcegffgbqqkqqoo bd hipaiedcfdeggsfohfbab b hgkadabcefgfgfhqsqxok b hokacabcffgfgfhqahxqb b hbpaccfedgffgkdjkaalb b hblakacdedfggkrxafaab bm hfdafbcdefgggaaaaqqgb bm hfdafbcdefgggaaaaqqrw bq hbpaiefceedggjqgojnqn b hapajdcecfeggnckghkhk b hokacabcegffgfaqoqqkk b hlkaiabefeeggfhqxahhk b hfpaaefgdeefgnhaknogb bc hblakacdedfggknahbqqo bc hojaeabceegggghhobgvf c hfdakbcddefggpplxnaaf c hlkaiabdfeeggfhoqqbqf b hokacabcegffgfaqvxqkf b hfdakbbddefggemxqbahg c hfnabbedgfegghqkqqkcj c hgjadabcegffgfhhvxqbg bc hgjagabceegggghabskjg c hfdakcbddefggllllgqaf c hojababcefgggbqqkhhrv c hddadcbcegfgghhffqqjc c hnjacabdefgggnafhjjjs b hblakacdedfggfraabaab b hepaideeddfggbbbsjqak b hojababcegffgfhhfqhfb b hojababcgggffbqqaqhaf b hnkaeabdfegfgnhgqxqkn b hcoafacdefgfgfwahahff b hlkababdefgggfhohjjsj bc hgjadabcegffgfhhvxqnf b hojababcfgffgbqqqxhan b hblakacdedfggfnhagqqb b hgkadabcegffgfaqghhnn b hdlaicddeefggqnwlxnak bc hfdadbcdefgfgqqqqhhgo b hhjaibaefeeggafqqhahk b hokababcegffgfaqbqhfb b hokababcgggfffaqhaqhk b hofabbcbegffgaabgqqfg b hhfabbcfgfefgaaaqnbnf b hokacabcegffgfhqsxakk b hipaecddfegggbngagacv bf hhdabbcfgefggqqqdfbhp bd hblakacdedfggfnaabaab b hokababcegffgfaqfqhfb b hokababcffgfgfaqqaqhn b hhjaebadfegfghkfqhhng bc hblakacdedfggknhagqhf bc hboajacedfeggfnhaqoqn bl hgfadbcbefgfgaabsqqog b hblakacdedfggfnhagqhk b hblakacdedfggknhagaan b hcpaedfdcffggbtnfxeqf b hgfagbcbeeggghafbskjg c hjhaccedfgggfqlkhibkf c hokacabcegffgfhqsxafb b hokacabcffgfgfhqqhxak b hblakacdedfggfnhhgqqg b hgoaiadfeffggnaaankqf b hknaibbdefeggabwaxbab be hcoakacdedfggfkhafaan b hgdafcbbefgggqhkbldsk c hgdagbcbeegggqafwknor c hojababcegffgfhhbqhfb b hojababcgggfffhhhaqhk b hokababcegffgfaqbqhkn bc hnkaeabdefgggkafhpihe bc hgjadabcefgfgbqqbaaff bj hgkadabcefgfgfhqsaxfb b hgkadabcefgfgnaawaxbf bc hgjadabcegffgfhhghhff bd hokacabcegffgfaqoqqbb b hddadbccefgggqakgaars c hodacbcbefgggaqkoaarj c hnfaibcdefegghhhfffhk b hokababcegffgfaqfqhkn b hojaeabceegggbqqgkbnj c hldaebccfegggqqgefdvf c hpkaaabfegfggfaqhhbww b hboajacedfeggnnhaqgqn b hlkaeabdfegggfhoehemx b hblakacdedfggknhhoqqo b hgjadabcefgfgnqqnaxfb b hgkadabcegffgfaqghhff b hhjaebadfegfgafkqahog b hokacabcegffgfhqnqqff b hojababcegffgfhhbqhkn bc hojacabcegffgbaanaxnf b hokababcegffgfhqgqakg bc hokababcgggfffhqaqhaf bc hddakbcdedfggqqaabaan b hlkaiabdefeggkakhxfhf b hfdadbcdefgfgqqqqahbo b hnhaabedeffggqqfhhofn bm hbpaecfddeggggqobbhcn be hblakacdedfggfnhxgaaf bi hljaeabdefgfgbakahqfo b hgfafbcbeegggahbgfovb c hgjafabcefgggghabetnj c hddafbcdefgfgaahhqqof bo hnfaibcdefegghhhfffqb b hblakacdedfggfnhhgqaf bi hokacabcefgfgfaakxafk b hnkaeabdefgfgnabaahfg b hjnabbedfgefgaafxhfff bc hgjafabceegggbqqbjsng c hjhaidecfffgglqbllxqg c hgjadabcefgfgbqqbaann bd hlkaeabdfegfgfhoqqakf bh hokababcegffgfhqkqakg bc hokababcfgffgfhqqqhao bc hddajacedfeggkohhqfqo b hnnaabedgfefghhohhfgo b hojababcegffgbqqkaxkf b hofabbcbegffgaabgxafn b hokacabcegffgfhqgqakg bc hgjafabceegggnqhfnwko c hokaeabcefgggghanmtjn c hgjadbacefgfgafanhxgf b hboakadeddfggfhhaqqqg b hokababcgggfffaqhaqxo bc hokababcegffgfhqgqafo b hcoajacdefeggfkahqfqn b hapafedccfgfgfofkqhnf bd hcnajbdedefggewpomwhg b hcnakaceddfggnnxakqqk bc hhjaibafffeggafaqhahg b hboajacedfeggnohaqoqn b hblakacdedfggfnhagahg b hhkaeabdfegfgghohaaof b hokababcegffgfhqkqafo b hojaeabcefgggnahopugo c hgkagabceegggfhqbskjg c hgkadabcefgfgfhqbaaff b hnfabbcdefggghhhvgttd bg hofacbcbegffgaabnaqgf bc hgkagabceeggggqqgocfb c hfhaibefdeeggmmakmean c hokacabcegffgfhqsxqfk b hlkaiabdfeeggfhoxqbhk bc hgjafabceegggnahgfovb c hldaebccfegggqakefdvf c hokacabcfgffgfaqhhaqf bc hofacbcbegffgaabnqxbf bc hgfadbcbefgfgaabbxann b hnjaebadfegfghkghhafb b hpjaaabfgegfgbahqqrrb bc hknaibbdefegghgkhhfhf b hdnaibfedefggpxpoldhn b hboakaeedefggfahaqaab b hfnabbedfgefgaawaakbk b hjpaaefegedfgjqoqjnon b hokababcegffgfaqbqqkb bc hcoajacedfeggobqxqbaf b hokababcegffgfaqbxhkg bc hokababcegffgfaqfqxon bf hokababcffgfgfaqqaxhg bf hjpaacedggfgfkkndmuuo b hokacabcegffgfhqnqqgg b hlkaiabdfeeggnhoxqbhf b hlkaiabefeeggfhhxaqqk b hojababcegffgfhhfqxon b hfdadbcdefgfgqqqqhagn bh hnhaabedgfefgqafhxffn b hlkaiabdefeggohoahnqk bh hofabbcbgggffaabaqhxb bc hojababcegffgfhhbxhkg bc hddafcbceegggqhbfnwko c hfhacbefdgegghaefdnvf c hjnabbedfgefgaafxhkkf b hcnakaceddfggbnxakqhk b hokacabcfgffgfaqqqahb b hipaiedcfdeggsfohfkhg bc hgfadbcbefgfgaabsqqof b hnfaibcdefegghhhoffqk b hgkadabcefgfgfhqbaann b hofabbcbegffgaabgqqgg bc hblakacdedfggfnhagqhf be hokacabcegffgfaqvxqbf b hbpacfcdegefghofohffk b hgkadabcegffgfaqvqqnb bc hgjadbacefgfghkhfaabk bd hokababcgggfffhqaqhxn b hokababcegffgkqhnaanb b hojababcgggfffhhhaqxo bc hgfadbcbegffgahbgqxob b hghaebbdfegfgqnbqxqbb b hblafacdefgfgknaxaafb bc hapajdecdefggnvvnvqan bg hojababcfgffgbqqqqhag bc hbpaccfedgefgkavnqgkg b hgjadabcefgfgnqhfxhfg bj hknaibbdefegghgkxhfhk b hbhakdcdedfggafkakaab bd hklaibbdfeeggafsaafhg b hcoakacdedfggfkxafaab bc hkhaecceeffggufhxxoon bc hdfafacdefgfgnjaqhqgb bi hodabcbbegffgqhkjaafn bf hokacabcffgfgfhqahaqn b hgkadabcefgfgfhqsaxnb b hnfaibcdefegghhhfofhg b hgkadabcefgfggqafxhfg b hipaebdceffggffgkuujf bd hapafedccfgfgfosbqhng b hlfabbccegffgahokaakf b hblakacdedfggfnhagaan b hblafacdefgfgknhhhqkb b holaacdgfeefgqgaqxobf bd hapajbcedfegggbbbakan b hmpaacegefefggnhneokf b hokababcegffgfhqgxafb b hokababcegffgfhqkqxno b hcoakaceddfgggohxfaak bc hdnaiaeddefggftthfbqk b hapakedccdfggffffnhhk b hlkaiabdfeeggfanxxnaf b hghaebdfdegggtnqkxqul bdd hhgacaceegfggnfxfhoul br hjlabbedfgefgqqbxqbbf b hgkadabcefgfgfhqsqqog b hokababcfgffgfhqqxhab b hojababcegffgbqqjaafn bc hgjadabcefgfggqafxhkf b hflacaceffgggfnxwraxa b hglaicdcfeeggqvkxhnhf b hnjacabdeffggfqbponfg b hgjadabcegffgfhhgqhfk bc hlkacabdfgffgfantwbfn b hokacabcffgfgfhqqhqag b hokacabcegffgohhohxgf b hboajacedfeggnnhaqoqn bn hgjadabcegffgfhhghxon bc hknaibbdefegghgwaxbhg be hcoajacdefeggfkhhqfqf bn hgkadabcefgfgnaawaxkf bc hboakaeedefggfahaqqqb b hcoakacdedfggfkxhfhhg bc hlkaeabdfegfgfhoqhhkg bc hklaecdedfgggqkhbphep bd hknaebbdefgfghgkxhagb b hhjaibadfeeggafgqafhk b hhjaebadfegfgafkaahng b hnfaibcdefegghhhfffhf be hblakacdedfggknahkqqo b hojacabcefgfggqakxakb bd hknaibbdefeggabwaabaf b hblakacdedfggkraabaab b hodabbcbegffgaqkfhhfn b hgjadabcefgfgbqqjqqof b hblajacdefeggkrxaabaf bc hdnaibfddefgghxonhgqn b hokacabcegffgfhqgqakf be hgjadbacefgfghkhfxafb b hblakacdedfggfnaxbaaf bi hokacabcegffgfaqohqbn bc hlfacbccegffgahonaxbn bj hokacabcegffgfhqsaafn b hokacabcegffgfaqoqxgk bc hofacbcbegffgaabnaqgn b hnhaacefggffgtitexlkw b hojababcgggffbqqaqhxb bi hokacabcfgffgfaqhhaqn b hojababcegffgfhhfhhff be hooaaacegffggnkffbbwk b hfdadbcdefgfgaaaahhbb c hfdadbcdefgggaaaahhrw be hjpaaefegedfgjaoqjnkn b hbhakdcdedfggafkhkhhg bd hknaebbdefgfghgkhaqbk b hbpaifcdedegghofogfan bc hmnaibbdefeggannhaoaf b hgkadabcegffgfaqvqqnk b hlkaiabdfeeggfanxqfaf b hpkaaabefgfggfhaqagww b hokacabcegffgfhqsxqgk b hmhaebbdfegfgqngxqqfo b hgkadabcegffgfaqgqhfk bc hepabccdggefgoknqaffn b hdoacaefdggfgktxaixcl b hmpaadegfeffgjjqhfaxg bd hblakacdedfggknhxoaaf b hapafbcedfgfggbwbqaon bc hokababcgggfffaqhaqxg b hgkadabcegffgfaqghxon bk hnkaeabdfegfgfhvxqqgo bc hokababcegffgfaqbqqbb b hblakacdedfggknaxbaak b hojacbacegffgafqbxhkf b hhjaebadfegfghkfhhhfg b hblakacdedfggfnahbqqo bq hhjaibaefeeggafqqhaqn b hapafdcecfgfgnckghqgg b hnkaeabdefgfgghfxhhgg b hokacabcfgffgfaqqqahk b hblakacdedfggknaxkaaf b hokababcegffgfaqbxqkg bc hhjaebadfegfgafkqahgg b hcoajacdefeggfkaaqkqn b hflaicdcfeeggqobhxghg b hlkaiabdfeeggnhvhqkhg b hblafacdefgfgfraaaaff b hklacbbdfegggqgnmnrwn b hfdafbcdefgggaaaaqqjs bo hfdafbcdefgggaaaaqqof bdg hokacabcegffgfaqvhhkn b hblafacdefgfgknhhhhkf b hcoakacdedfggfkhagaab b hokababcffgfgfaqqaxho bc hknaibbdefegghgkxhkhk b hokacabcegffgfhqnaqgf bc hhjacabefgggfbahxhfok b hbnakaceddfggbkxxbqqb b hnkaiabdefegggqbaqnhk b hgjadabcefgfgbqqbqaon bc hknaebbdefgfghgkhhqfk b hokababcegffgfaqbxhko bi hokacabcegffgfhqnqxbf bc hcoakacdedfggfkhhgqqf b hokababcegffgfaqfhhff bc hgjadabcefgfgbqqbaxfk bc hhkaiabeefeggfhqaqhaf bc hblajacdefeggkraaabab b hokababcegffgfaqfqxgn b hojababcegffgfhhfqxgn b hblakacdedfggknahbqqg b hhjaibadfeegghkfqqbqg b hdhaibcfffegguoexemqb b hokababcegffgfaqfhxon b hddafbcdefgfgqqaaaqfo bc hgkadabcegffgfaqvqhnb bc hlhaacegdfeggqaqoqfbg b hojababcegffgfhhbxhko bc hokacabcffgfgfhqahaqf bc hboajacedfeggfnhaaoab b hgjadabcegffgfhhghhnf bd hgoaiadfeffggnhhhfoqk b hghaecdcefgfgqnoxxakk b hojababcfgffgbqqqqhao b hddafacedfgfgfkaaqqof b hlkaiabdfeeggfanqqfqk bl hgkadabcefgfgfhqsqqof bc hhjaibadfeegghkfqqbaf b hlkaiabdfeeggfhoxxohk b hojababcgggfffhhhaqxg b hofacbcbegffgaabnqxbg b hhjaibadfeeggafkqakhk b hokacabcegffgfaqoqqkb be hpkaaabefgfggfhaqacck b hokacabcegffgfaqvhqkf b hgjadabcegffgfhhvqqnk b hhkaiabfffeggfhhaqhab b hnfaibcdefegghhhoofhg b hnjaebadfegfghkgxqaff b hcnafacedfgfgbnaahaob b hgpaadcgefgfgooxkuquf b hokababcgggfffhqaqhxb b hjpaacfdgeegggqbqbgoj bd hdhaedcdffgfghnoaiakf bp hojababcegffgfhhbqqbb b hokababcegffgfhqgqqgg b hgkadabcefgfgnhhfxhff b hgkadabcefgfgfhqkqxob b hjpaaefegedfgjqohjnok b hlkaiabeefeggfaahxqaf be hndaibcdefeggqqqgbgak b hlgabacdgfgfgffxhwhrx b hgkadabcefgfgfhqbaqnf bc hgkadabcefgfgfhqbxanf bc hojababcegffgfhhbxqkg bc hlgabacdgfgfgffhhfhkh bc hcoajacdefeggfkahafab b hbpaccfdegefgjqjoqfog b hgjadabcegffgfhhghxgn b hgkadabcegffgfaqghhnf b hcoakacdedfggfkxhkhhg b hboajacedfeggnohaqoqb bc hhjaebadfegfghkfqhhbg b hgnaebbdfegfghgohhqok bc hokacabcffgfgfhqqhqaf bc hhkaeabdfegfgfhfqhanb b hokababcegffgfhqgxafn b hboajacedfeggfohaqoqn b hnkaeabdefgfgfasqxqkn b hjpaaefegedfgjaohjnkk b hokababcegffgfhqkqxbo b hokababcegffgfhqgxqfb b hokacabcegffgfhqsaaff bc hokababcfgffgfhqqxhan b hjpaaefegedfgjqoqjngn bo hflaecdcfegfghbfqhhbg b hcoakacdedfggfkhhghho b hnfaibcdefegghhhoffhk bd hlkababdefgggfanxsssg b hokababcegffgfhqkaakn b hfdadbcdefgfgaaaaqqff bk hfdadbcdefgggaaaaqqof bg hcoafacdefgfgfkhhhhkk b hhkababfgeeggfhhhhffo be hblakacdedfggfnaabahg b hofabbcbegffgaabgqako b hokacabcegffgfaqoqxok bl hfnabbefgfeggaapdaxro bc hnkaeabdfegfgfhgqqqbo bd hknacaceeffgggvhrbbxg b hboajacedfeggfnhaqoqb b hgjadabcegffgfhhgqhfb b hbnajacedfegggbqqabab bc hhdacbcfegfggqqqrdrul bf hdnabafeggegffxhgofof bf hdlacccdfggfghsshgofo bd hboajacedfeggnohxagqo b hglaccdcfegggqgblgjgs bc hcoajacdefeggfkaaakab b hlkaiabdfeeggfhohxghg b hblakacdedfggknhaoqqb bc hgkadabcefgfgnaanhxgf b hgjadabcefgfgbqqbaanf bg hnjacabdegggfgqbdbfog bc hhjaibadfeegghkfhqbhg b hcoajacedfeggoohhafak b hfdadbcdefgfgaaaahqbf bf hgkadabcefgfgfhqsaqfg b hlkaeabdfegggfanxqhmd b hepaececefgfggnoneakk bl hnjaebadfegfghkgxhafb bd hipabcedgfggfkgshqemn b hhkaeabdfegfgfhfhhanb b hhkaiabdfeeggfhfqhfqo bc hboafacedfgfgfnhaqqon bd hcoafacdefgfgnkaaqqgf bc hokacabcegffgfaqohqbf b hcoafacdefgfgfkhhqhbk b hokababcegffgfhqkaxnf b hhkaiabdfeeggfhbqhkaf bc hokacabcegffgfhqnqqfg b hdhaecdcfegfgakkaxaff b hnfaibcdefegghhhoofan b hgkadabcegffgfaqghxgn b hgkadabcegffgfaqvxqbf b hcoafacdefgfgfkhhqhkf b hokacabcegffgfhqsaqfn b hcnafacedfgfgfvqqaakf b hgkadabcefgfgnaafxakk bl hokacabcegffgfaqohqkn b hlkaiabeefeggfaahxqqg b hnlaacdgfeefgqoahxoff bc hjlaccdedffggdnqgwrcn bd hcoakacdedfggfkxafqqk b hgkadabcegffgfaqgqhfb b hlkaeabdfegfgfhohhhfg b hlkaeabdfegfgfanqqhgk b hblakacdedfggfnaabaan b hepaecddffgfgonbqhahb bd hblajadedefggfuddofaf b hcoafacdefgfgfkhaqakf b hbpaeddfdegggnghkfqkc b hhjaibaefeeggafqqhahf bc hdnabbfdegeggppsuxowk b hdhaedcefdgfgaohxbqfb b hcoakacdedfggfkxhfqqf b hokacabcegffgfhqgxafk b hdhaicdcfeeggagkqafhk bt hipaidcefffggcknqtphg bd hipabbeefggfgnnkmemmj b hgkadabcefgfgfhqbaafn b hhjaibafffeggafaqhaab b hlkaeabdfegfgfanqxaff b hlkaeabdfegfgfhvxhhgg b hcoakacdedfggfkxakaab bn hokacabcegffgfaqoqxgb b hblakacdedfggknhhoqqg bc hknaebbdefgfghgkxaakb b hcoafacdefgfgfkhhqqkk b hbpaefceddgfghvknnakn bc hokacabcegffgfhqnqxnf b hgjadabcefgfgbqqbaxfb b henakbdcdefggakkoaqqn bg hnkaiabdfeeggfhghhnhg b hipaeedcfdgfgsofaoanf b hgkadabcegffgfaqvxhnf b hblakacdedfggfnaabqqb b hgoaiadfeffggfaaxnkab b hddajacedfeggkohhqgqo b hipaiedcfdeggofohffab b hgkadabcegffgfaqgqhnk b hgjadabcefgfgbqqbqagn b hlhaacddggefgmjbmwfob b hgjadabcefgfgnqhfxhff bd hhjaibadfeeggafgqakhk b hofabbcbegfggaabgqqof bd hblakacdedfggknhaoaan b hdhabbefgeeggpxuhhprv b hboajacedfeggnohaqgqn b hboajacedfeggnnhaaoab b hgjadbacefgfghkhfxafk b hmnabbbdffgfghgftkvgk b hokacabcegffgfhqnaqgn b hknaebbdefgfghgkxaabn b hddafbcdefgfgaahhqqgf b hokacabcegffgfaqvxhfg b hapafedccfgfgfoffhafb bh hgkadabcegffgfaqghxof b hbpabbeffegggkfhakddo b hipaibddfeeggnknhknqb b hokacabcegffgfhqnaqff b hnkaiabdefeggfasqxoqf be hldaibcdeffggaaefwbpn bg hghaccdeeffggabldqggn bs hokacabcegffgfaqvxqkg bc hlkaeabdfegfggqgxaabb bc hcoafacdefgfgfkxhahbk b hapafbcedfgfggvknqagb b hcoajacdefeggfkhaqkqf br hdlabadfdgfggfxhkfcnn b hblajacdefeggkrxaanaf b hdfafacdefgfgbbaqaqnb b hokacabcegffgohhohxof be hljaeabdefgfgghkaaakk bv hddajcbedefgghhaaohqn bf hbpaicfeddeggkqnnbkqb b hgjadabcefgfgbqqbqaof b hblakacdedfggknhaoqhf b hgkadabcefgfgfhqbxabf bl hhjaibadfeegghkfhqbab b hgkadabcefgfgfhqsqxgk b hhkaeabdfegfgfagqqqgk bc hdpaadeceffggjffjmmwn b hboafacedfgfgfohaaqfn bh hhjaibadfeegghkfqqbak bc hipaeedcfdgfgsffafanf b hdgajacdefeggfwhhhfan b hdlaecdcfegfgqbkxhagb b hgjadabcefgfgbqqbaxnk b hlkaiabefeeggfhqxahhf bc hnfaebcdfegggaaavvqha bc hbnafacedfgfgnjqaahbf b hfoaiadefffggoqxqbghf b hlkacabdffgggfanarngg bd hgkadabcefgfgfhqbaqng b hdnaibfddefgghxkohchn b hnfaibcdefegghhhoffqb b hnkaiabdfeeggfhgqhfqf b hblakacdedfggknaakaan be hhjaiabffefggbaahxqab bk hcoajacdefeggfkxhafaf b hhkaiabdfeeggfhbqqkaf bc hhjaibaefeeggafqqhaqb b hhjaibadfeegghkfqxgqo b hnfaibcdefegghhhoofhf be hchakdcddefggaokkaaan bg hboajacedfeggnnaxakqg bd hlkaiabdfeeggfhvxhkhk b hhkaeabdfegfgghohaaok bc hhfaebcfdfgfghhhgfhog b hnkaiabdfeeggfhghhfab b hlkaeabdfegfgfhvhhhfg b hepaidcfffeggrkxpugaf b hgjadabcegffgfhhvxhnf b hhjaibadfeeggafgqqghk b hlkaeabdfegfgfanqxqfo bf hgkadabcefgfgfhqbaqff b hblakacdedfggfnaabaho be hjpaaefegedfgjqoqjnob b hgjadabcegffgfhhvxqbf b hokacabcegffgfhqgqafg b hlkaeabdfegfgfanaqhfk b hlkaeabdfegfgfhoqhhbg b hcoakacdedfggfkxafaan b hblafacdefgfgkrxaaabf b hhfaeacefefggnbaswbqg bd hbpabbcfegfggkghklalo bf hnkaiabdfeeggfhghhfhf b hnjaiabdefeggghfaxbhf b hhkaeabdfegfgfhfqxqnn b hgkadabcefgfgfhqbxann b hblakacdedfggfnaabqhf be hdfafacdefgfgnjxqqhgk bc hknaebbdefgfghgkxhafb b hhkaiabfffeggfhhaqxqn bh hcoajacdefeggfkahqfhk b hkhaibddeefggucctxchk b hkoacacdegfggfkarxktl b hhjaebadfegfgafgqahgg b hboafacedfgggofqhaqos bcc hdnabbeffgeggmxiqmlvg bcc hcoafacdefgfgfkxaaabf b hlkaiabefeeggfhhxaqhk b honaaadffgeggnqunnkvk bp hklabccdggffgmfnhksof bh hlkababdefgggfhoxvvvb b hgkadabcefgfgfhqsaxfk b hcoajacdefeggfkahqfqb b hipaedcefdgfgcknqoagb br hkpaaefegeffgfaohjetk b hhjaeabdfegfgnqchhhng bj hcoakacdedfggfkxhfhho b hokababcegffgfaqbxhfg b hofacbcbegfggahbvhhof b hojababcegffgfhhfqhkb b hhjaibafffeggafaqhaho bc hnjabbadfgfggafbqsckk b hhkaiabdfeeggfhbxhkhk bc hlkaiabdfeeggfhoqqkqf b hhkaiabdfeeggnhfhhfhf b hojababcegffgfhhfqxob b hodabbcbegfggqqgbqqnk bd hcoakacdedfggfkxafahg bc hflacbedffgggqqnldbgs bg hokababcegffgfaqfqxob b hboajacedfeggnnhaqoqb b hokababcegffgfaqbqhfn b hnjaeabdfegggnqgxxqha bh hbpaccfedgefgkannakkf bt hgkadabcefgfgnaanxxkf b hknaebbdefgfghgkxahbo bc hnkaeabdfegfgfhvxqqfo b hblajacdefeggkraaanab b hblakacdedfggkraabaan b hfoabacdffggggfhfketd bc hloaaadegeffgnttfnonk bk hhjaebadfegfgafgxaanb b hgnaibbdfeeggabnaxkak b hipaedbdefgggokoohhkc bn hepabedegdfggfffhnhck bv hnfaibcdefegghhhoffhf bm hbnafacedfgfgnkqxaqfb bc hljacabdegfggghkannfo bh hchakbcdeefgghjgetjhk bdd hcoajacdefeggfkxaakaf b hokacabcegffgfhqsxakb b hokababcegffgfaqfqhkb b hhjaebadfegfgafgqaaob b hcoafacdefgfgfkhhhhfk b hojababcegffgfhhbxhfg b hcoakacdedfggfkxhfhab bc hboajacedfeggfohxaoqg b hgjadabcefgfgbqqjqaof b hlfacbccegfgghabohhnk b hlkaiabdfeeggfhoqqbaf b hnkaeabdfegfgfhvqqqbo bg hknabbbdfgfgghgkakjoo b hcoajacdefeggfkahafhg b hgkadabcegffgfaqvqhnk b hokacabcegffgfhqsxqfb b hhjaibadfeeggafkqakhf b hojababcegffgfhhbqhfn b hokacabcegffgfaqohqkf b hokacabcegffgfhqgqxbg b hnjacabdegfggfqbqggkn bn hljacabdegfggbakannjs bd hlkaeabdfegfgfhoqqqkf b hnkaiabdfeeggfhghxohg b hfpaaefgdeffgoaxfkmmn bc hmpaaedffdgggffxhnumo b hokacabcegfggfaqvhhof bc hokacabcegffgfaqoqxob b hnkaeabdfegfgfhghxqnn b hcoafacdefgfgfkhhqqkb b hlkaiabdfeeggfhoqqbqg b hokababcegffgfaqfhhkn b hpkaaabgfeefgnhxhhfng b hhjaiabdfeeggnqchhkhg bf hdhabdefdggfgtmpbqogk bd hklabccdfggfgmfnqgckj bt hlkababdeggfgfhomonnk b hblakacdedfggfnaxbaak b hlkaiabdfeeggfhohqbhg b hlkacabdffgggfanarsgg bd hgfadbcbefgfgaabsqqgf b hokacabcegffgfaqvhhff b hokacabcegffgfaqvhqkn b hnfaibcdefegghhhofkhk bd hnfaibcdefegghhhookhg b hojababcegffgfhhfhxof be hghacdcfegffghkaaqgbn b hhkaiabeefeggfhqaqhqo bc hboafacedfgfgfnhaqhof b hokababcegffgfaqbxqbg b hgkadabcefgggfhqsqqbg b hhkaeabdfegfgfhbxqqgf b hnkaeabdefgfgnabaahff b hokababcegffgfaqbxqko bq hofabbcbegffgaabgqqgo b hhkaeabdfegfgfhfqhabb b hblakacdedfggfnahbqqg b hokababcegffgfhqgqako b hhjaeabdfegfgbaohhqok bc hgkadabcegffgfaqgqhnb bn hcpaeefeedgggsasgsank bc hlkababefgggfnaaxaknb b hgkadabcegffgfaqghxgf b hokababcegffgfhqgqqfo b hokababcegffgfaqfhxof bc hojababcegfggbqqjaank bd hojababcegffgfhhbqqkn bc hokababcegffgfhqgxakb bc hlkaiabefeeggfhhxaqqb b hnkaeabdfegfgfhgqhakb b hbnafacedfgfgffhhaabb b hokababcegffgfhqkqafg b hgkadabcegffgfaqvqhfb bi hgjadabcegffgfhhvqqbb bc hcoajacdefeggfkhhqfqg b hblafacdefgfgkraaaaff b hokacabcegffgfhqgxxbk b hkhaedcceffgghonxhrab b hokababcegffgfhqkqxng bc hofabbcbegfggaangqqof bd hclajbddeefggissdxoqk bm hllaacdgfeffghfahhnkn bc hgkadabcefgfgfhqsqqgg b hboajacedfeggfohaqoqb b hnkaiabdfeeggfhvxqnqk b hnkaeabdfegfgfhghhafb b hokababcegffgfaqfhxgn b hhkaeabdfegfgfhfhhafb b hnkaiabdfeeggfhgqhfaf b hhjacabefgggfnahhhfog b hokacabcegffgfhqnaqfn b hlkaeabdfegfgfhoqqakk b hojababcegffgfhhbxqbg b hhjaebadfegfghkfqhhbo b hboafacedfgfgfnhaqqob bs hokacabcegffgfhqnqxng b hnkacabdffgggnhghnfbc b hgjadabcefgfgbqqbqagf b hbhajccdeefggakkxanqo b hmhaiddceefgghjfhqoab bp hgkadabcegffgfaqvxhff b hlkaeabdfegfgfhoqxhkk b hhjaibafffeggafaqhaan b hgjadabcefgfgbqqbaxnb b hhkaiabfffeggfhhaqhhg b hboafacedfgfgfohaqqon bd hhkaeabdfegfgfhbqqqbf b hflaicdcfeeggqvbhhkab b hlkaiabdfeeggfanaxnab b hlkaeabdfegfgfanaxqfo b hljaiabdefeggfhoxhfhk bx hmnaebbdefggghgfhhhaq bh hokacabcegffgfaqvhqbf b hhkaiabdfeeggfhfqhfaf bc hhjaibadfeegghkfhxgab b hcpabedfegffgrwmomidf be hlkaiabdfeeggfhvhhkhg bc hcoajacdefeggfkahafan b hlhaacdgfeffgqjhaankf b hcnajbdedefggavhkqcaf bv hehajcdceefggqbgqqbqk bj hcoajacdefeggfkaaqkhk b hcoajacdefeggfkahqfhf b hhkaiabdfeeggfhbqhfaf b hblafacdefgfgknhhqqnb b hpfaaacgfffegggsgkcfo b hhkaiabeefeggfhqaxhqn b hnjacabdefgggnafakkwo b hokababcegffgfhqkaaff b hgkadabcefgfgfhqsaqff bc hdnaibefedeggexetfuqf b hhkaeabdfegfgfhfqhhng b hldabcbcegfggqhbnaagb bc hokacabcegffgfhqsaakn b hjpaaefgedeggbqqbggbg bce hlkaiabdfeeggfanqxnqk b hlkaeabdfegfgfhoqqabf bl hlhaacfdeggfgmtglljbj bf hjhaccdcefgggtfolkwfg bl hnkaiabdfeeggfhgqxgqf b hojababcegffgfhhbxqko bc hlkaiabdfeeggfhvxqkqb bc hcoajacdefeggfkaaakhg b hlhaacddggefgmjbmwkob b hboajacedfeggfohaqgqn b hlkaiabdfeeggfhoqxgqf b hlkaeabdfegfgfhvhhhng b hfhacbdedfgggpblswjns b hbpabefdgefggnqotndur b hnkaeabdfegfgfhghxhnk b hgjadabcegffgfhhvqhfb bc hhjababdgfgfgnqfdwrsv bd hokababcegffgfhqgxqfn b hokababcegffgfhqgxqgb b hllaabedgfffgqxkxqbfn b hnkaiabdfeeggfhghhfan bc hgkadabcefgfgfhqbaqfg b hlnaaaeffdgfggiifkokk bf hgkadabcefgfgfhqbxabn b hhoaaaedfgeggghqsofob b hokacabcegffgfaqvxqbg bn hlkaeabdfegfgfanaxanf b hokacabcegffgfhqsaqff bc hlkaeabdfegggfhoxxhaq bi hknaibbdefegghfoxxfhf bu hhkaiabdfeeggfhbqhkqo bc hfnacaefdgffgghhxqfob b hbnajacedfegggbqqanab b hhkababfegeggkaaaabkn b hhhaabgfffeeguqtxemof bd hgkadabcefgfgfhqsaqng b hcoajacdefeggfkaaqkqb b hlkaeabdfegfgfhohqanf bn hcpaidffdefggrixsothg bh hgnaiadefffggnqqqgbhf b hhdaccbfefgfghhhwbhro bc hlkaiabdfeeggfanaqfab b hnkaeabdfegfgfhgqxhkk b hokababcegffgfhqkaxnn b hhkaiabdfeeggfhfqhkqo b hlkaiabdfeeggfanqqfhk b hnkaiabdfeeggfhgqxoaf b hhkaeabdfegfgfhbqhabb b hlhaabedgffggqlfxpnww b hcoakacdedfggfkxhkhho b hlkababdegfggfanqbbks b hlkaeabdfegfgfanaqhnk b hokacabcegffgfhqgqaff b hhkaiabdfeeggfhfhhfab b hhkaiabffeeggfadqpalg bg hbnakaeeddfggndideqqg bg hjlabacdgffggfnxhnwdl bc hlkaiabdfeeggfhohqbab b hhkaiabfffeggfhhaqxhk b hhjaibadfeegghkfqxgaf bf hboafacedfgfgfnhaaqfn b hfpaaefdgefggbxgeotdo bc hepabedegdfggsosaoxjo bcc hgnabaddgfgfggipgcrog bk hcoafacdefgfgnwahahkf bx hnjaeabdefgggnafhaqdl bd hnjacabdegfggnqcaossg b hokababcegffgfhqkaxbf b hhjaiabdfeeggghnhanhf bc hhkaiabdfeeggfhbqqbaf b hhkaeabdfegfgfhfqxhnk b hknaebbdefgfghgkhhqfb b hokacabcegffgfhqsaqgn b hlhaacefffgeguapxptfg bc hnlaacdefgggfhbmqtgjb bc hgkadabcegffgfaqvqqbk b hgkadabcefgfgfhqsaxnk b hnkaiabdefeggfasqxoqg b hgjadabcegffgfhhvxhff b hcoajacdefeggfkaaakan b hhkaiabeefeggfhqaqhak b hnkacabdegfggnhgaossg bf hnkaeabdefgfgnabaaafn bc hhkaeabdfegfgfhbqhhng b hlkaiabefeeggfhhxaqhf bc hnfaebcdefgggaaakkqqx bg hnfaebcdfegggaaavvaax c hhjaibadfeegghkfhxghf b hlkaeabdfegfgfhoqxqkn b hfpaafecgegfghffpoixo b hhkaiabdfeeggfhbxhkqn bc hdhaecdcfegfgakkqahgo bw hipacedcfgffgfosqhdlf bc hnkaeabdfegfgfhvqqqko bd hlkaiabdfeeggfhoqqbak bc hlkaeabdfegfgfanqqqgk br hlkacabdffgggfhoxfsjf by hcpaideeedfggnkknkxak bm hhjaibadfeeggafkqafhk b hcoakacdedfggfkxakaan b hnkaiabdfeeggfhghxghg b hnkaiabdfeeggfhvqqnqf b hgpaaeecfggfgvfkxildn b hlkaeabdfegfgfanaxafk bl hlkaeabdfegfgfanqxagf b hnkaiabdfeeggfhgqhnqf b hhkaiabdfeeggfhfqxgqo b hhkacabdffgggnhfhrbcb bd hepaeedfefgfgfoqoahhk bv hnkababdffgggoqbqbbgg bf hokababcegffgfaqbqqbn b hlkaiabdfeeggfhvxhkhf b hapafdcecfgfgnnkkaqng bB hbpacfcedgggfhbbbqahf bh hnkaeabdfegfgfhgqxqbn b hokacabcegffgfaqvhhfn b hhkaiabfffeggfhhaqhan b hokababcegffgfaqbxhfo b hojababcegffgfhhfqxgb b hhkaiabffeeggnhtttmtb b hcpabdfdgfggfbtnhxaqp bk hcoafacdefgfgfkxahagf b hhkababfgeeggfhhhxgfo b hhkaiabeffegggqmxpaek b hnkaiabdfeeggfhgqxoqg b hfnacbcffefgghoqtdcfk b hjhabbecfgffgqqkqqbgb bj hokababcegffgfaqfqxgb b hhhaadfggfefghahixtfj b hnkaiabdfeeggfhghxoab bd hcpaeecedffggkfbgxunf bd hcoadadedgffgnpdaubgg bd hhkaeabdfegfgfhbxhhog b hlkaiabdfeeggfhohxohg b hhkaeabdfegfgfhbqqabf b hfhaebefdffggqqifhwbg b hljababdegfgggqnakkbg bl hbpaeefedfgfgnqkntqig bdd hboafacedfgfgfnhaqqgn bd hnkaiabdfeeggfhfhqnan b hlkaeabdfegfgfhoqqqkg bo hlkaeabdefgfgkakxahbg bn hokababcegffgfaqfhhkf bn hokacabcegffgfhqsxqgb b hlkababdegfggfhohffgb bo hlkacabdegfggkakannjs bg hlkaiabdfeeggfhohxgab b hhjaebadfegfgafgqahgo b hhkaeabdfegfgfhfqhann b hcoajacdefeggfkaaqkhf b hojababcegffgfhhbxhfo b hklaecdeffgfghguhlcnk bc hblafacdefgfgkrxaaanf bt hhjacabdffgggnqfqnvbv b hljaiabdfeegggqsxafqg b hfhaedcdfffggikomhohk bd hjnaeaceeffggbjarggqg bd hnkababdfgfggnabqsckk b hhjaiabefefggghlhaeen bd hgkadabcefgfgfhqsqqgf b hlkacabdffgggfhohrobb bd hgjadabcegffgfhhvqqbk b hfpaacgdgfgffgtbteqaq b hfhaccedegfggtmfxhfvk b hepacedfedfggsolgoisk bc hgpaadfgdfgfgnpdbdxma bc hmhaibbdfeeggqbgqqbqg bC hmnaebbdefggghffhhhaq bi hlkaiabdfeeggfanqqfqb b hepaebddfegfgnbgqbhbf b hdnacbefegegglpmtadcv c hdpaacdeggffgvncxhaqk bc hpdaabceegfggaafwbbfo be hhjaibadfeeggafkqxbhk bt hlkacabdffgggfhohrvbb bd hokacabcegffgfhqgqxbf b hojababcegffgfhhbqqbn b hnkaiabdfeeggfhghhnab b hmnacbbdefggghffakkwo b hjhacbedfefgghhgddfob b hpdaabcffegggqqnffvco b hghaedcfedgfghohaoafk b hhkaiabdfeeggfhfqxoaf bc hflabbcdegfgglcflgwgf b hlkaiabdfeeggfanaqfhg b hcoafacdefgfgfkxaaqbf b hnkaiabdefeggfasqxgqf b hhkaiabeefeggfhqaqhqg b hnkaiabdefegggqbaqnqb b hnkababdfgfggnabqbckk b hddafbcdefgfgaahhqqgg bq hddafbcdefgggaahhqqbg be hfdafbcdefgggaahhqqbg bu hfdafbcdefgggaahhqqrw bq hokababcegffgfhqgqqgo b hhkaiabdfeeggfhbqhkak bc hfhaidcefdegghfhxoohg b hhkaeabdfegfgfhfhxqfn b hcoajacdefeggfkhaqkqg b hchakcdceefggtjoqpoqn bh hdnaeaefefgggbxqhwahq be hhkaiabdfeeggfhfhxoab b hdhaibfddefggqlofmkaf b hokababcegffgfhqgxakn b hfpaafgdfdgfghhnmbxda b hdhaeceeffegguemeqqbo bc hnkaeabdfegfgfhgqhabb b hflaccdcefgggqobavvok bh hokacabcegffgfhqsaakf b hhjaibadfeegghkfhxgan bc hokababcegffgfhqkqxbg b hgkadabcegffgfaqvqhfk b hhkaeabdfegfgfhbxhagb b hlkaiabdfeeggfhoqxoqf bc hcnakaddeefggbdiuiuhg bc hhjaibaffeeggafdqpadn b hkhabbdefeggghbuidsvf b hlkaeabdfegfgfanaxqfg bc hokababcegffgfhqkaafn b hipaieecdfegggbbbqbak b hljababdegfgggqnakkvc bv hhjaibadfeegghkfqxgqg b hboafacedfgfgfohaaqkn b hhkaiabfffeggfhhaqhho bc hchakcdceefggtjoqlfab b hhkaeabdfegfgfhfqxqbn bt hlkaiabdfeeggfhoqxgaf b hpdaacbeefgggqqkngbng b hlkaeabdfegfgfanqxqgo b hcoafacdefgfgfkhaqabf b hlkaeabdfegfgfhohqqnf b hmhaebbdfegfgqbgxqqfg bl hlkaiabdfeeggfhohqkhg bc hhkaiabeffeggnheiulhf be hdpaaedfegeggoolcxrob b hhjaeabdfegfgbaohhqob b hgpaaeffgeggfgdlanlil bc hnkaeabdfegfgfhghxqfn b hcoajacdefeggfkxaakak b hcoafacdefgfgfkhhhhkf b hlkaiabdfeegggqnqakqb b hepaeeefdfgfgcsunaqpo bd hokacabcegffgfaqvhqbn b hchakbcdeefggqgnipwhg bc hlkaeabdfegfgfanaqhff b hbnafacedfgfgnkqxaqkb b hgpaaddgefeggsoafxgon b hfoabacdegfggofhbdwlp bx hlkaiabdfeeggfhohqbhf b hlfaiacdeffggnbawbkxn bc hjpaaefgfdeggbaimogig bc hhkaiabfffeggfhhaqxqb b hmpaadcgefffgknhsplpg b hokababcegffgfaqbxqbo b hnkaiabdfeeggfhvxqnqb bc hflaicdfdefggenhbtgxk bf hnkaeabdfegfgfhghxqnb b hjpaafeegdefghoohnfff b hhfacaceegfggnbqwqwqx be hlfaibcdeffgghhhgrwho be hgnaeacdfeggggohffqah bi hbnafacedfgfgffhhaabn b hlkaiabdfeeggfanaxnhg b hlkaeabdfegfgfanqqhgf b hdoaiaeefdfggktddnnmf b hlhaabfgffeeguihxemog bd hdhaicddeefggaknxanak be hlkacabdegfggkakannvc by hnkaiabdfeeggfhgqxoak bc hflaebdfefggglgaatnvg b hbpabecfefeggnbxniogb be hfhabdcedgfggafanwgok b hblafacdefgfgkraaaakf b hcoafacdefgfgfkxhahbf b hbpaefdcdegfgqvkkkabn b hcoadacdegfggfkapxewj bc hhhaadgfdgfegtlebtnfn bw hnfaibcdfefggaaannbaf b hnkaeabdfegfgfhghhhfg b hokababcegffgfaqfhxgf b hlkaiabdfeeggfanqxnhk b hlkaeabdfegfgfhohqaff bo hnoaaacgffefggfrrfgvb bc hmlaibbdefeggqgsqqoqb b hnoaaacgffefggfrkfovb be hboafacedfgfgfohaqqgn bv hlkaeabdfegfgfanaxqno b hnkaiabdfeeggfhgqhnaf b hlkaiabdfeeggfhoqqkaf b hnkaeabdfegfgfhghxhnf bc hgjadabcegffgfhhvqhfk b hhjaebadfegfgafgqahog b hlkaiabdfeeggfhohqban bc hnkaeabdfegfgfhgqxhkf bc hnkaiabdfeeggfhghhnhf b hhkaiabdfeeggfhfhhfhf be hbpaeedfedgggbjxsbxnk b hbpaeeefddgggbfxobxnk b hhjaibadfeegghkfqxgak bc hcoafacdefgfgfkxaaanf b hlkaiabdfeeggfanqqfhf b hboafacedfggggfhhhhfo bk hnkaiabdfeeggfhghxohf b hboafacedfgfgfohaqhok b hcoafacdefgfgfkxaaabk b hlhaadfgdegfgthiblwwg b hjpaacfeffggggakmlhah b hldaecbeeffgghhxjosxf bce hbpaedcfedgggfohofafs bce hcpaidfeffeggoafqhohf bcc hgkadabcefgfgfhqsaqnf b hjpaaefedgggfnqcbxmpg b hojababcegffgfhhbxqbo b hcoafacdefgfgfkhhqqbk b hlkaeabdfegfgfanaqqfb bc hhkaiabfffeggfhhaqxhf bo hnfaebcdefgggaaannhaq bk hnfaebcdfegggaaaccaax c hepaidcefeeggnrkhwoqb bd hlkaeabdfegfgfhoqxhbk bc hgpaaddedgffgcccbhxon b hnfaibcdfefggaaannbqf b hhjaiabdfeeggnqchhfhg b hkoabacdfgfggnkablcxu bz hdnaibdfdefggtnaopgqg b hlkaeabdfegfgfhoqxhkf b hnkaiabdfeeggfhgqxgaf b hjhaibcdeffgglfkmqslg bc hbpaedceeffgggrfcpmng bi hcoafacdefgfgfkhhqhbf bf hhgabaceeffggfwhcnnan bj hcpaeedcdffggvvbkxenn b hnkacabdefgggghfakkwk b hklabcdeffggghgaquvcg b hdlaicdcfeeggqfbqhfak bc hokababcegffgfhqgxqgn b hhjaibadfeeggafkqafhf b hdfakbcedefggaaqqbaab bc hlkaeabdfegfgfhoqqqbf b hhjaiabdfeeggnanaanqg b hlkababdegfggnhvhffgb b hokacabcegffgfhqsaqgf br hjlaccecfefggehbplkgg b hdlaibfddefggqdngdcqk be hnkaeabdfegfgfhgqhakn b hlkaeabdfegfgfhoqxqkb bc hdhabddegfeggubaaqbnk b hnkaiabdfeeggfhghxgab bd hlkaeabdfegfggqgxahbo b hnkaiabdfeeggfhghxoan bs hipacceefeggggbfqgqrw bp hohaacdgfffggqbqdqbbg bh hokababcegffgfhqkaxbn b hlkaiabdfeeggfhohxgan bc hglaebcfeegggakqptcbg b honaaacgdfggfgogkkkon b hlkaeabdfegfgfanqqqgb b hdnaebfddefgghxkohbqn b hnkaiabdfeeggfhvqqbaf b hhjaeabdfegfgbaohhqgk b hcoafacdefgfgfkxahaof b hohaacdfegeggqnlaengc bu hhfabacefggfgbbqvumbq be hfoacaddefgfgkuttnmfo b hhhaacffeggfgaqhmmqrn bc hooaaacgfffegknrnoogg b hfpaaeedgdggfvknpntrk b hbnafacedfgfgffhhaanb b hnjababdefgggnqghnvov bd hpkaaabgfeefgfhqxxgok bg hhkaeabdfegfgfhbqqqbg b hblafacdefgfgkraaaafk b hlkaeabdfegfgfhohxqnb bc hljaiabeffeggnqlqthin b hcpaeeddcffggswrfiqsk bc hjlaccecfefggehbplkgo b hdoaeafeddgfggqqhfqbo bd hkpaadcefgggfnrkaeelf bA hdhaibddeffggiobhpnab bc hhdabcbeeffggqqhcnnan bcc hnkaiabdfeeggfhvxqbqk b hhkaeabdfegfgfhbqqqnf b hlnaaaeddfgggbqxkvnjn bl hdfafacdefgfgbbaxhafk bc hfhabcdefgggfqnxxqobb bc holaacdfgeeggqvuthnoj b hbnakadedefggntpllian bd hboafacedfgfgfnhaqqgb bg hlkaiabdfeeggfhohqkab bc hlkaiabdfeeggfhvhhfhg b hdhabcfeegefgmhxiemob b hbnajbedeefggaafhhhhk b hbpaefecefggghgoghadv b hlkaiabdfeeggfhoqxgqg b hhkaeabdfegfgfhfqhhbg b hlkaiabdfeeggfhoqqkqg bc hboafacedfgfgfohaqqob bj hhkaiabefefggkalhaeen b hnkaiabdfeeggfhghhnan b hfpaaefggedfgbaadbnhb b hloaaaefgfggfnhxgvfgf bd hpjaabafgeefgafqqakof b hhjaebadfegfgafgqaaon b hhnaabfgeffegpqhlpaff bc hhdabcbdffgfghhigoegk b hhjaeabdfegfgnaohhqob bc hhfaibbdefeggdilfokhk c hljacabdefgggnakhfffw b hbpaedccfegggoskigaoj b hgpaabegffgfgbwpxpiqh bc hlkaiabdfeeggfhohxoab b hlkaeabdfegfgfhohqank b hnkaiabdfeeggfhvxhfqk b hmnabbbdfgfgganfangrr b hhkaeabdfegfgfhfhhhfg b hgnabacdfggfggohoiqre bf hmpaadbfefgggoktopetn bt hjoabadeggffgkqtbgocn bf hbnakaededfggbueedtqg b hmpaaccggeffgokimbtpj b hhkaiabdfeeggfhbqhfqo b hcoakaddeefggfeiixuak bd hgoaeaceeffggnkhvrkab bg hhkababdffggggqkhrkrg bh hbpabecfgeffgboxhoxxf b hlkaiabdfeeggfanqxnqb bt hlkaeabdfegfgfhoqxqbn bc hfhaebcffefggukqeawnf b hhkaiabdfeeggfhfqhkaf b hjnaebedffggghhfthgwf bm hbpabefdgeeggnananobw bu hhhaacggefefgqhaammgf be hjoacaceefgggnohbndma bc hglaibbffeeggqfdqpaun b hlkaeabdfegfgfhohxqfn b hfpaaeedfdfggvkndnuof be hgoaeaceeffggfkhvfkak bj hnkaeabdfegfgfhghhafn b hlkaiabdfeeggfhoqxoaf bc hlkaiabdfeeggfhoqxgak bc hcoakadeedfggoldththk bd hlkaiabefefggbaldexhn bc hflaicddfffggivkptrab b hgpaaccfgffeggoxetdfb be hcoafacdefgfgfkhhhhff bf hbpaeecfdegfgjohsvqof b hhkaeabdfegfgfhfqxhbk b hhkaeabdfegfgfhfhhafn b hohaadcggffgfubauunjo b hlkaeabdfegfgfanaxank b hcpaeeefdfgfgcrindatf b hnjacabdfgfgggqbhccbk bi hhkaeabdfegfgfhfqhabn b hdpaaedggffegnnptienn b hlfaebbdefgfgdihffhfg c hpjaabafgeefgafqqakgf be hdnaiaefddeggbxqqgbqk b hnnaaadffgfggnupfjgnb bc hohaabbgfeefgqnqhxgnk bd hjhaedcefegggaftqqsgb b hcoafacdefgfgfkhhqqbb bf hlkaeabdfegfgfhohqqng bc hjnacbeddgfggaafnjswf b honaabbfegefghgxaxbkf b hlkacabdffgggfhoqkbcc bd honaabdffefggebpidwbk bk hdhabbdfeefgghgimukof b hlkaeabdfegfgfhohqqff bc hlkaeabdfegfgfanaqhnf b hfpaaedceffggvnfvmmfg b hkpaaccgfegfgkopdnuen b hdlaiccdeefggecbatkhf bd hbpaieddeefggwvrgkhqf b hkoacacdefgggfkakoiqi bf hhkaeabdfegfgfhbqhanb b hhkaiabdfeeggfhbxhfqn b hlkaeabdfegfgfhoqqabk bc hcpaeccdfffggbrnxtmqf b hhkaiabdfeeggfhbqqbqo bt hgpaaegefffggslgilpii b hdhaicecffeggaeklpuxo bg hnkaiabdfeeggfhvqqbqf b henajbdceefggmcgmdnqk b hpjaabafgeefgafqxxbob b hhkaiabdfeeggfhfhhkab b hpkaaabfefgggfaqttvkb bc hhjaiabffeeggnamitlxf b hnkaiabdfeeggfhghxghf b hnfabbcdffgggaaanrpuu bc hnkaeabdfegfgfhvqqakf b hooaaadeffgggnadnkbjg b hlkaiabdfeeggfhoqqkak b honaabbfegffghghhxbgf bc hnlaaacffggfgknwrcfvo c hjnacbdeegfgglkptqcbg be hpdaabcffggfgqqwrcnvo c hpfaabcegefggaabogoks bc hhkaiabdfeeggfhbqhfak b hcnakadeedfggfeplhdab bp hhjaebadfegfgafgqahoo b hhjabbaefegggafpdajkn bc hbnakaeeddfggbdidahab b hhkaiabdfeeggfhfqxgaf b hcpaebfcdfgfgfhffhahb b hdpaafcggdegfaoqxnbxf b hmpaadcgefgfgknaspipw b hlkaeabdfegfgfanaxqng b hnkaeabdfegfgfhghhhff b hdlacadfegefgfhhahngk bm hbpaifefdfegghjhsxohg bc hljaiabefefggnapdailk bd hhjaeabdeffggnqfaasqf c hdlaiceceffgghtfuiupb b hghabcdcefgggqjghvnvb b hdoacafedfggggqphnxpq b hflabbeffggfgqqpixhkj b hhkaiabdfeeggfhbqqbak b hlkaeabdfegfgfanaqqnb b hdpaaedefggfgkoguxmxo b hjhaedcdfegggurgahhep bf hkhabbcefgggftnahhsjf b hdoacaedfgefgfxhhqffb b hpkaaabgfeefgfhxxhkof b hhkaiabdfeeggfhfqhkqg b hhkaiabdfeeggfhfhxgab bx hjnabbedeggfghhshafoo bcc hcpaibcfdffggfoxoxxqk b hnkaeabdfegfgfhgqxqbb b hhkaeabdfegfgfhbxhaob b hhkababdefgggnhfhnvgb bc hdhabbedgfffgqxbqqsgb bc hjnaeaceeffggnjarggqf bd hbhaddeceggfghenphcnf bc hhkaeabdfegfgfhbqqanf b hjpaaecfdefggbrpggawf bf hdnaeaefddgggbqqqgqha bi hipacbeedgfggnnkcqhck bce hlhaadcfeefgganaaxnkk bee hjlabbedfefggqqbqargb bcc hhkacabdfggfgfakabgfs b hnkaeabdfegfgfhvqqqkg bd hcnakadeedfggnmuqpaab b hdgadacdegggfnfmxthok b hlkaiabdfeeggfhohxohf be hepaedcdefgfgsssoqhfg c hlfabacdgfgfgnbaakdnl b hcpaecdedffggbvsoxmnb b hepabdcfefgggojqwdahv bc hepabbdcfgffgffklmtlk b hhfacaceffgggnjqbkdmq bl hepabceceffggbgkcppbn b hcpaedcfefeggjoqolnoo b hljababdggffggqntjcjk bd hlkaiabdfeeggfhohqkhf b hnnaaacdgffggnkbrfjkn bc hllaabegfeefgplahdpfb bp hkpaabfffeggggheqcema b hhkaiabdfeeggfhfqxgqg b hhoaaaedfgeggohqsofov b hepaicdefffggnovdpihk b hlkaeabdfegfgfhoqqqbg b hdnaibefeedggmxtudghg bd hdhaeddedfgfgakxkxafb b hhjaebbdefgfgtuaknakf c hpjaabafgeefgafqaakgf be hdhaibfcffeggtikxedhg bc hcpabccefgfggbcsphmqs bc hfnacaceeffggnkhrrgho bce hpjaabafgeefgafqaafgf b hnkaeabdfegfgfhghxqfb b hnkaiabdfeeggfhvxqbqb bk hgpaaeegfdfggkkxxfaxk b hbpaieddcefggccnfcqhg c hhkaeabdfegfgfhbqhhno b hhkaeabdfegfgfhbxqaof b hdjadabdegfggnakifnvj bc hnkaeabdfegfgfhgqhabn b hcpaeedcdfgggvvbkxhcn bc hdnaeafeddgfgghhxghfg bf hffajacdeefggggltgmqo c hgnacbbdfgggfaboxwfnf c hdnabafeegggfgxhrbcbf b hkhabbddefgfguccxckgb b hdlabcdcffggghffarcwb b hlkaiabdfeeggfhoqxoqg b hhhaabefegfgghehxpesc b hdpaafgfegfgeaaeoxthn b hhkaeabdfegfgfhbxhhoo b hnhaabdgfffggqbhhqwrb bc hnhaabedggffghhkxusjk bd hlkaeabdfegfgfhohxhff be hnkaeabdfegfgfhgqxhbf b hlkaiabdfeeggfhohqkan b hdoaiaefddeggkqqqfgqb bc hhdabbcdefgggqeefrdml b hnfaibcdfefggaaannban bc hlhaabfdgffegiqbqdbbb bc hnkaeabdfegfgfhghxhff b hhnaabdefgggflsmhiwvn bc hdhaibcfffegguktxedan bd hkpaadffgfggecuaulieg b hnfaibcdfefggaaannbqg b hhlaacfgegfgfqpapllgk bc hcpabcdegfgfgnknahqhc bc hfhabdcegffggafxdmoff b hbhaddccegfggafoxvvbg b hhhaadfegegfghillhejg c hcnajbddeefggpowmtfqb bc hknacbbdeeggghgkxknor c hcpabbcffgfegggxxqxgn b hknaibbdfefgghfkqqcqn c hlkaiabefefggfhilppdn be hnhaadcfgeggfhrammfok b hnkababdgffgggqbdsrgs bc hhnaabeffeegghulmhxff c hbpaeecfdegfgjohsvqgf b hlkaiabdfeeggfhohxoan b hdpaacefdggfgbgufaxxg b hglabbcegefggqohaqjnk b hhkaiabdfeeggfhfqhkak b hboafacedfgfgfohaqqgb bd hlkaeabdfegfgfhoqxhbf b hcpabbcffgfegfgxxqxfn bf hpjaaabgegffgnqtddjob bf hboakadeedfggklqdeahg bd hdfakbcedefggaaqqbahg bc hnkababdegfggfhgxbgcv bc hbnakaededfggnteeaehg bc hlkaeabdfegfgfhohqafk b hcoafacdefgfgfkxaaank b hfpaafeddfgfgdsrkiquk b hhfabacegefggbbqagbax bcc hboafaedefgfgktuurmgk b hdnabbefegfggpidluaob b hdfakbcedefggaaqqbqhk b hknabbcdeffgglwcdkwnf b hmpaaecfedfggwchbnuvk bf hlkaiabdfeeggfhoqxoak b hkpaaeegfffegvkqtxebg bc hmlaebdcfegfgannqqhfk bc hjpaafefgdeggqoiuootw bh hcpacbfcegfggfhfjmqmb b hpkaaabgfeefgfhxqxgng b hcpaieddfffggojrleiqb bc hbhajdcedefggafegdfan bc hkoaiacdeffggfkavvrao bk hapajcdeedfggjojjoqhg c hdnaiafeddegggqqqbbak b hhkaiabdfeeggfhfqxgak b hfpaafggdefegilukvxfk bd hlkaeabdfegfgfhoqxqbb b hdpaafcggdegfqoqxnkxo b hlkaeabdfegfgfhohxqfb b hdnacbefeegfgexitmaff b hbnakaededfggfimqheaf bj hlkababdegfggfanxknsj bc hghabcdfegeggabuqtovb b hdpaafcggdegfaoqxnkxf b hclafcddefggghfbhxaax b hglaibbeffeggaklqdeaf be hdnaebfddefgghxonhbhk bf hhkaeabdfegfgfhbqhann bc hpjaabafgeefgafqqafof bc hkoaeaceeffggknqfgjqg bc hdhaibfcffegguikxmdho b honaaacgffefgforornkb b hdoacaedfgefgnxhhhkgf bc hnlaacdggegffqjqqqjfg be hkpaadegdfggfnkakaaxf bu hkoaeacdfegggfkannaqh bi hdlaecdfedfggtkatkrtk bcc hcpacefcdegggkhoggtbw b hcpaebcfdffggnbxfaxqg b hbpabddeegfggngkgdprg b hhkaiabfefeggfhimqhtn b hhkaiabdfeeggfhfhhkan b hlkaeabdfegfgfhohqqfg bh hhnaabddgffgghkohgojs bd hhnaabdfgefgghkhhxgjs b hglaibbefefggqfudttdk b hhkaeabdfegfgfhbxhaon bc hpdaacbegefgghhncovkn b hdoaiaedfdeggfaxxfbhg b hdpaacgfdegfgvieogttj bc hgpaaecegfgfgvoohpidj b hhkaeabdfegfgfhbqqqng b hpkaaabgfeefgfhqqhfnk b hbpaecefdegggcfhcchfo b hpjaaabfegefgbahxafob b hohaabdeeffggtnixeowf b hdlaeadfedgfgfhhagqgo b hdlacadfegefgfahaabff bp hpjaabafgeefgafqaafof bc hkpaadcfgfgfgcvatampi c hnoaaacgfgffggfgsbcvj c hdfakbcedefggaaqqbaan b hdlaeadfedgfgfhhagqfo b hapadcbedfgggnfnnxxnk b hgpaadceegggfrocvaebb b hkpaadedgfeggnknqxbck bc hhhaabgdfeegguqkxmgfj bk hapaddcdeggfgknwciafs bf hhfabbceffgggaahjwhaa b hgpaaccfgffeggoxetdgb b hghabbdffefggtnldmbkk b hglabbddfggfgdfohrsrg b hjpaaecfegeggnraneows bp hpjaaabgfeefgnqxqhknf b hbpaicefdeeggfnakfoqf b hmhaedccfefgghokipkxf b hepaiddcfffggofgxxxqk bf hdpaaecdfegggkoflfiob b hkhacddeeffggurheqowf b hdkafabdeffggfanmtnmo c hjhaiccdfffggqfkeuwho bce hnjaiabdfefgggqbhhrhb cc hbpabcffegegggainmopr b hknaibbdfefgghgkqqchb c hooaaacgffgegnkksoggk bc hdnabbefffegghapdqdkf b hhdabbcefefggqqhkfoab b hdlaeadfedgfgkahabqfo b hhkaeabdfegggfhfhhaqh bc hfpaaeedfdfggvkntnpbb bc hdhacbfdfgefgixkihogg c hnoaaacgdgfgfofgfwnfb b hnjabbadffgggafbqbnkg bc hdlaeadfedgfgkahabaff b hfpaabfgfdfegfhhanhff bd hmhaiddceefgganftmoho bc hpkaaabgfeefgfhhqhkbn b hhkaeabdfegfgfhbqqank bc hhfaiacdfefggfghoovhn b hbpabecfgeffgboxqoxxb bc hdnaeadedfgggneedohit b hdkafabedffgggqlkiklf c hdnabafeggegffhqgosof b hlhaacdffgeggtvexakwr b hdlaiceefdeggdtxdklaf bf hdhaedcdfffgghnotdghk bg hpkaaabgfeefgfhqqhfbk b hhjababdeegggnanxkwko c hjhabbcefggfglfeaqfro b hhfaibbdefeggdilfokqn c hdlacadfegefgfhhahbgk bg hfnacaddefgfgntllftfg b hdgadacdegfggkbqqulvo bv hkoacacdefgggfkacgueu bd hgoaeaceeffggfkhvrkab bd hfpaadgeffegggunmhgpv bcc hdlacbeedfggguxufufkr cc hldaicbdeffgghhqbgrhb bcc holaabbfgeefgakxqafnf bd hfhacbefdggfghhqklsrn bd hdnaibeeddfggtxtgbfhk c hgnaibbeffegghfueuuen b hkoaiacdeffggfkavvrhk bk hdhaibffdfeggtipkxuqf bc hglabbddfgfggqsnufgnf bh hnlaabdgegfgflnitegbf bl hdhacbfeegefglpelhafg b hnkaeabdfegggfhvqqqah b hpkaaabgfeefgfhhqhfbn b hhnaaaeddfggggqhksogo b hhhaabedfgggfpiwueorf b hdnacbefeegfgextumqnf b hdnabbfedegfgpxpkunng b hdlacadfegefgfxxhaonb bc hflaccdfdfgggqolgbfkk b hnlaacdefgggfqbeqlwjb bs hepaccceeffggoknsitko bg hdlaccdcfgggfhbkqgvfb c hldabcbdgfffgqqhqnorb c hjpaadcgdfffgosqolhig c hdlacadfegefgfahaabgf bd hooaaacdfgfggnkgswkgk b hhkaeabdfegggfhbqqahq b hnjabbadfggfgafbakngw bc hdnaeaefddgfgnaaakhfg b hdnaeaefddgggnqqqoqha bf hhjaeabffegggbqullgbs bc hdlaeadfedgfgkhhagqfo b hljacabdegfggnakxnkwr bc hhkaeabdeffgggqkhhvqk c hnkababdefgggfhghbrcv bc hcpaeeffedgfgkhpbghlk b hchafbdedffgglbpghwmk b hdnacadeefgfgguiufigf bc hhjacbadfggfghkfhgbfo bc hglaccdcfgfgghvkqogfb bf hdlacadfegefgfxhhagfb b hdlaeadfedgfgkhxagqno bc hhjacabdfgggfghoxwfnk c hfoacadedgffgkldexvnb b hpjaaabgfggffghddikgr bc hdhaecdffegfgqkahaknf bc hhkaeabfefgggfhimqgbc bce hhhaadegfdefghhxaognn b hjpaaefdfegggbunmkxxw b hljababdfgggfghwxwfnf c hhkababdefgggfhfhnvgb b hdnaibcdeefggdogxdnqo c hkoabacdgfgfgfwaaftri b hhdabbcefefggqqhkfohg b hgoaeaceeffggfkhvrkak bd hkhacccefefggufhetcfg be hcpaideeedfggbkbgkahf b hpjaaabfegefgnahxafgb b hlhaacddggffgmjbmwnkw b hapajcdeedfggjojjoqhf cc hcpaibcfdffggfgxgxxqk b hlkababdefgggfanqkcjs bc hbpabfddegffgqfgfhahf bc hdpaacefdggfgbgxfaxxg b hdlaeadfedgfgkhxaganf bc hnfaebbdfegfgdiposagf c hjhaicdeedfggdodxobqb cc hdhaecdefefggeviimrcf b hgoabaceffgggfkhropth b hdlacadfegefgfxhxhfgg b hmpaadbdeffggofvohufn b hdlaeadfedgfgkhhagqgo b hfhabcdceegggankakkjg c hfnaebedffggghhkuavcn b hjhacdecegfgghenphwcv bj hcnafbbdeffgghfkitrab c hdlacadfegefgfxhxaffb b hdlacadfegefgfahaangf bd hipaeedcffgfgonfdxieg b hhgabaceeffggnfhcnnxf b hpkaaabgfeefgfhxxxggg b hflaiccdfeegghfoqqkak b hlkababeefgggfhdppscv bc hjpaacffgdgeggamqnxfv b hhdabcbdfgfggqqdsmsui b hhgaeacefefggbbakojxk bg hbpabeffdegggvmxbkeej bc hpdaabcfgefggaagwbwbg be hkpaaceefggfgogbxmeuw bd hdpaaedgfegfgjjhxkumo b hflaccdffgeggqolulkjw bc hdoaiaefddeggkaaxbnhf bc hlkaeabdeffggfanhhcqf c hdlaeadfedgfgkxxakaff bd hlkababdegfgggqnajfbg bd hipaebeedffggbnkcmhkf bc hglaicbeeefggiglpluho bd hgnaibbeffegghfthuueb b hmpaabedfggfgbjkeeamk b hpkaaabgfeefgfhqqhknk b hipabebdgeggfkkodctbn b hflaecedfegggimkduvfg b hjnaiacefffggfbxgvfqb c hdlacadfegefgfxhxafgb b hnhaacdfggfegermmeffr c hpkaaabgfeefgfhxxxoof be hboafacedffggofphlpof b hpkaaabgfeefgfhhqhknn b hipaibeeffeggbnklarnb bc hdhaeddedfgfgakxkahbg bf hffafbcdefgggaaaqhhbw bw hffafbcdefgggaaaqhhgr bs hipacbeedffggbnkcdhnb b hfpaaeegfdgfgnfxtnhmn bc hglabbbdegfggqggqsfkn b hpkaaabgfeefgkqxxhfgg bc hpjaaabfegggfbahxxnkg b hdnacbefeegfgexiumqfg bm hpjaaabfegefgbahxxkgf b hbpaicfdffegggafmisqg b hmhabdccgffggakfawoww b hglaccddfgfggdsbhfgoc b hfpaaedfdggfgvnmwutdb bd hdpaaddeffgggjvgmtxev b hdpaafgcgdegfqafxbbxg b hdpaafcggdegfaoqxnbxk br hdpaaedeffgggbgopmdar bc hpjaaabgegffgbqtddjob b hboakadedefggflhaetqg b hhhaacegdeffgtxpftkfb b hdlaiceceffggqtbuqupb bd hhhaadggefgffhuhxxhob b hkhaccdcffgggabwpnjnc bc hdkafabdeffggfhopuoik cc hboakadeedfggbpqdalhf bj hgnabbddefgfghfkdssnb b hknaiacefffggngqbnjqb c hnhaacdefgggfqgtxuwck b hkoabacdgffggnkaqnwmu bm hdpaacfdegggfohfgiiqo bm hpjaaabfgeefggqxxakbk b hhlaacgdffgegmufulrfg b hdlaeadfedgfgfahabqfo b hdnaiaeeffdggfiuifkib b hghaccdfdfeggabufbbso bc hpjaabafgeefgafqqakog bf hdlacadfegefgfhhahbfk bp hfoaeacdefgggnnagwhaq b hhjacbadfgfgghkfqgobj bc hpkaaabgfeefgfhxxhkgf b hpkaaabgfeggffhxxxfog bc hcpabcdcggffggwohteqs b hdnaibefeedggmxtudgan bc hfpaadgefgfegflbpddkk b hdnaebedffgggtmwdmrok c hapadddcefgggorccxaor c hflabcddgegfgivkhonfv bq hjpaacfggfeegstuxtgkw c hfpaadegdfgfggjagqlqj b hcladccedfgggmfecggjw b hcoafacdeffggfkapplwk bc hpfaaacfggegfnbsbfncn b hcpaibcfdffggnkxoqqqk b hcoadacdefgggnkaapigk bc hlfaebbdefgfgdidooqfb c hcpaeccffefggknxxkxhg b hlkababdefgggfhoqfjcv bc hdoacadffgfggniidhfep b hdnaeaefddgfgnxqxbqgg b hdlacadfegefgfxhxhngg bf hdnaeaefddgfgnxqxbqfg b hdlacaffdggfgfthtqegi bk hnfabbcdfggfgaaakpufe bc hjhaidbeeefggiklpathf b hdlaeadfedgfgfhaagakf be hpkaaabgfeeggfhxxhkbg b hnfabbcdfgfgghhhoentu b hdnaeaefddgfgbqqqoqgb b hfpaadeggdffgosdlomus bd hgoabacdgffggoohagvud b hdnaiaefdffggbqqqfoqb b hpjaabafgeeggafqqakbg b hnfaebbdfegfgdilffhfg c hjlabbdeffgfgqbxqhwwf b hcoadacdefgggfkaapigk bc hdlacadfegefgfhhahngf bd hhlaabeffeggglehpaqwr b hnkababdffgggnabqbnkg bc hhkababdegfggfhbqvkof b hboakaeeddfggnehalpqb bd hpjaaabfegggfbahxxnko b hgpaaccgefgfggglgtqmb b hknaibbefefgghfpdailf b hglaccdcfgfgghvfqogfb b hhhaabfgffeegueqxmdfb bc hdlaeadfedgfgfahabaff b hglabbdegffggisdllbsf b hpkaaabgfeeggfhxxxobg b hhnaabedgffggaabairkj b hdlaeadfedgfgfhhagagf b hcpacccdeffgggvfjphgn b hhhaabedfgffghmcmqkob b hlkacabdfgfggfhohgobf bc hpjaabafgeefgafqqafog b hjoaiacefffggfoxwfnhg c hpjaabafgeefgafqaakof b hdnaeaefddgfgnxqxkafk b hdpaafcggdegfqoqxnkxg b hgpaaceefgffggfoxeudb b hfhacdcffggfgafehqoof bt hjlaecdedfgggenabpaei bl honaabbfegefgabxaabkb b hpjaabafgeefgafqqafgf b hbhadbcdefgfghogisobo b hpjaabafgeefgafqaafon b hgnacacdegfgggohoukua bp hmnaebdceffggebwmdkab bj hfhacbcffefggukqexwco bg hpkaaabgfeefgfhxxxggf b hcpabdcegefggnokhohjs b hcpaeeeddfgggfbobhhsj bd hdpaadfeggegfkanxxnxk be hhdabbcefefggqqhkfoan b hhhaabedgfgfgudsqpbko bc hpfaaacdfgffgbbgfvbok b hdnabbefdegfgexifunff b hdlaeadfedgfgkahabagf b hghabdcfeggfghohxhkgw b hdlacadfegefgfxhhaggb b hgnacaddefggggippcute b hcpaeecefegfgwwwhbqng c hdlacadfegffgfxhhabgb bc hdoaiaedfdeggfhaangqf b hghabcdcfgggfabkxsngb c hdlaeadfedgfgkhhagagf b hpjaabafgeefgafqaafgn b hfhabdcedfgggafanfsnk bc hgnabacdggffgnnahlngl b hdlacadfegefgfaxaabnf b hdlacadfegefgfxhxanfb bt hdnacaefdgefgbaxxankf b hdfajacedefggnbhhrahf c hpjaabafgeggfafqaankg b hhlaacfdeefgghefahkvf b hpkaaabfegefgkahxxfon b hdlaeadfedgfgkahabqfg b hpkaaabgfeefgfhxqqbng b hjpaafdggegefhfemflfk c hdlaeadfedgfgkahabafk bt hjhaicdeedfggdfdxngqo c hpkaaabgfeffgfhxhhnkf b hpgaaaceffgggkbsnjbns c hdlaeadfedgfgkhaagakf bc hhdabbcdeffggqqmfgvhf b hdoaiaedfdeggfhaabgqf b hdlaeadfedgfgkahakqfo b hlkacabdfggfgfhoqgbfs bc hhdabbcefefggqqhkfohf be hcpabdefdfeggcnpbuncg b hbladacdegfggfnxhuunk b hfjafabdeffggghfdurho c hnfabbcdfgfgghhhfmwui b hpkaaabgfeefgfhxqxong b hnlaabdgegfgfpfihekkb b hpkaaabgfeefgfhxxxoog b hhfaibbeeffggdilsnrpg c hpkaaabgfeefgfhqqhfbb b hdlaccecefgggqtbuukof b hgoabaddggffgoidgwgvf b hpjaaabfegefgbahxhkgg b hhnaabgfeffegpaampubn b hdoaiaedfdeggfhaaboqf b hhjababdeegggnaoanwko c hdhaeddedfgfgakhkxhff b hldaebcdfegggqqhffahq bc hpkaaabgfeefgfhhqhfbf b hpkaaabgfeggffhxqxfon bc hpkaaabgfeefgfhhqxobn b hdhabcefggefgamehxagj bc hdlaeadfedgfgfhhaoqfo bk hdlabcdfgefgghkuuhgfk b hdlaeadfedgfgkhhaoqfo bl hpkaaabgfeefgfhxhhknf b hcoadacdegfggfkaaqqbg b hpjaabafgeefgafqqxbof b hfkafabdeffggkafduran c hfnacbcfefgfgmgqpqggg b hohaadcgegffgibatpnoo b hklaccdeegfgghrulajfs bd hlnaabdefgfggdjedxosj bc hfpaacddfggfggobuplmo bc hlkababdfgggfohkhwfnf c hknacbdeffgggebpqqgbo b hdpaadfeggegfkhnxxnxo b hdlaeadfedgfgfhhagqgg bl hdlaeadfedgfgkhhagaff br hdhaibcfeffgglbeatlhf bd hnoaaacggffeggfwbfkno bc hipabedcgffggrkbtxmhk b hbpabddfefgfggkxkxaxf b hdlacadfegefgfahaabfk bd hdlacadfegefgfxhxangb b hdlacadfegefgfhhahbgf bd hhdabbcefefggqqhffohf bcc hfpaafgfefggehqqohipn bd hcoadacdegfggnkaheekn b hdoaiaedfdeggfhahboqk b hlhaabefdeggguxefuojf b hdlaeadfedgfgfhhagaff b hpkaaabgfeefgfhqxhkob bg hdpaafgcgdegfqakxbbxg b hlfabacdgfffgnbahkjgf c hhfabaceffgggbnxbjipx bc hdpaaddfefegggjeodfkf bd hfhabbcefgfgglfdxuojn bdg hfhabbcefggfglfdxusfn bc hdlacadfegefgfaxaannf b hdhaidefedfgghaaqwxhf b hcpaedcdfegggnonubawb bg hlhaadeegfeggimhhqtkv bc hdhabbdfeefggtwuxuskb c hpdaabceefgggaafovcfo b hpdaabcefegggaancovno bd hdlaiadfeffggfhhafoqo bcc hdnaiaefddeggbaxqgfak bg hfjafabdeffggnafdurho c hlfaebbdefgfgdihfohgo c hndaibbdfeeggemlgggqb c hdhabbedgfffgqxbqqsgk b hcpacdfcdggfgoagfphur b hpkaaabgfeggffhxqxfob bc hdlaeadfedgfgfahabagf b hkpaabfedfgggnaffaetj b hpjaabafgeefgafqaakon bf hdpaacfggeegfnaxxknxk be hdlaeadfedgfgkahabqgo b hdlaeadfedgfgfaxabanf be hdlaeadfedgfgkahakaff b hbpabddfgfggfnkxaxqhx bo hbpabddfgfggfvcxaxaxx c hcpabbcffgfggfoxxqxha bk hdlaeadfedgfgfahakqfo bf hdpaaeggdgfefkipkxmbg bc hpkaaabfgeefggqqxafgk b hloaaaedgfeggkqxknosj b hcoadacdefgggnkahahkg bc hdlaeadfedgfgfhhagqfg b hffajacdeefggfgltglan c hbpabcedffgfgrnriupln be hdhaiddedfeggakakabaf b hdlaeadfedgfgkaxabanf bc hdlaeadfedgfgkaxabqfo b hfpaaefgdeggfohafwmmg c hjpaafdcfgggfmwfahtad bc hfnacbcdffgfgmvfasbkk b hcpaccefdfgggfkdspips bc hjhabddcgffggevwtwjcj b hpgaaacdffgggkbnvbfsb bce hpkaaabgfeefgfhqqhfnb b hbpabdccgfgfgofkqlhdj b hklabacdgfffgkfhqnorb c hpkaaabgfeefgfhqqhknb bc hpjaabafgeefgafqqafgg b hdlaeadfedgfgfhhaoqgo b hpkaaabgfeefgfhhqqbbn b hpkaaabgfeefgfhhqhknf b hhdabbcefefggqqhffohg bc hpkaaabgfeefgfhhqxobf b hdlaiadfeeeggfahankqk bc hfoaeacdfefgggfqggcqb b hcpabecdgefggvochohfo b hdjafabdeffggnakitran c hpkaaabgfeffgfhxqqnkg b hdhaeddedfgfgakxkahbo b hdnaebddfefggafwddkak bc hddafbcedegggqqaafovb c hpkaaabgfeefgfhhqxonf b hfpaafeeffgggdknaiuit b hfpaacfgeeggffaxfohhk b hpjaabafgeggfafqaanko b hkoaeacdfegggnkannatp bl hbpacefeddgggnaknnasj b hdpaafdeegffgtconqiuo b hdlaeadfedgfgkhhagqgg bn hbpacfdcfegggejohodhc b hpkaaabgfeefgfhhqhkbf b hdlaeadfedgfgkhhagqfg b hpkaaabgfeefgfhxqqkng bc hpkaaabgfeefgfhhqxonn b hdlacadfegefgfxhxhfgo b hknabbdeffgfghfxhqssk b hpjaabafgeeggafqqafbg b hpkaaabgfeeggfhqqhfgb b hdlacadfegefgfxhxafgn bw hpkaaabgfeefgfhqxhfob bd hdpaaccfgefggffidglan be hepabdefgfgfgwntatxia b hnhaadecfggfgimfutoks b hpjaabafgeefgafqqxbog b hhhaabegffgfgtipxelos bc hipaeddefdgggngkqkagr bf hjlaebddfeggglgfamaet bn hdpaacfggeegfnhxxknxo b hfpaafggedefgilunocak b hpkaaabgfeggffhhqhofb bc hjpaaceggffegcnqiatnw be hdlaeadfedgfgfahabqgo b hdhabdcfeggfguotetfcc b hpkaaabgfeefgfhhqhfnn b hpkaaabgfeefgfhqqhkbk b hhnaabddgffgghknxgbss bd hhnaabdfgefgghklxdgss b hfhaccefdegggmiegdnwn b hlhaabdfegffghchxqwon b hbpacedffgeggvglqdbig b hdlacadfegefgfxhxaffn b hdlaeadfedgfgkhhaoqgo b hbpabdeedfggggkvkqacb bd hpkaaabgfeefgfhhqqbbf b hfhabdecfggfgieguigoj bi hgpaaedfgeeggsjhhongb bcc hbpaeefedegggnqkngqof c hblafcdedfggginqkahml bi hbpabccfgfggfcvxaxaxx c hbpabccfgfggfcvxhqhxq bc hbpabccfgfggffoxhxqax bg hcpabdcegefggnokxoxsj bd honaabcegefgghjahpsko bdd hdpaacfggefgfnaxxkqxh bcc hdpaacfggefgfnxhakxqx bcc hpdaabceegfggaafkrrfk ce hpdaabceegfggaaforrfw be hdhaccedegfggddnxqnjf cc hpdaabceggffgaaforwrf be hlnaabfeeefggdppxdpsb ce hlhaacegeffggaaaemxvf c hllaabegefeggdaapqijo c hdnaebeefegggtxtdmhvk cc hpdaabcgfggffaafcnvof bg hpdaabcgfggffaafcrvor d hpdaabcgfggffaancrvwr bm hfnabacdgfgfggbqaoacq b hpkaaabgfeefgfhhxhkgn b hbhafcddeffggqgjxqbxg bc hbpabcecffgggckgatipk b hdlaeadfedgfgfahabqfg b hdnaebddfefggafwddkaf b hpkaaabgfeefgfhhqxgbf b hpkaaabgfeefgfhhxhkon b hdlaeadfedgfgfaxakanf b hfpaacdfefgfgrodkdxuf bd hdlaeadfedgfgfahabafk bx hlnaabdfegeggmvadqngs b hdlaeadfedgfgfhhagafk b hcoadacdegfggfkaheekn b hcpabbcffgfggggxxqxha bi hcpacccffggfggbxxqaxh be hdhaedfefegggauxhqarb bd hffafbcdefgggaaqqhqbw bu hffafbcdefgggaaqqhqrg by hnlaaacgffgfgknvcvfco bd hkoabacdfggfgfktjatrp b hdlaeadfedgfgkhhagafk b hcpabecdgeffgvochoqbf b hfnacbcfeegggewhlqbvn b hpkaaabgfeefgfhqqxgnk b hpkaaabgfeefgfhqqxonk b hdlacadfegefgfxhxhffo be hdlaeadfedgfgkhhaoaff b hkhabdccefgggdkodcsbg b hnlaaccgfffggdkdtajfr b hipabecefegfgfjbhsabf bc hdnaebddfegggafwddqha b hpdaabceefgggaafwsjfw bf hpdaabcefegggaawcnvwn bp hcoadacdefgggfkahahkg bc hkpaaefcgdgfgkhkabalf bc hdlaeadfedgfgfaxabqno be hbpabbdfggefgfjquqfxf bf hdhaeccdfegfgankaaanb bc hcpabbcffgfggfgxxqxha b hdlaeadfedgfgkahakagf bz hdlaeadfedgfgfahakaff b hdlaeadfedgfgkhhaoagf bf hbpacefeddgggnaknbasj be hgnaeacdfeggggohffqud bq hhdaccbeffgggqqhwnieh b hfpaacggdffegcuhnqagv b hipaeecdfffggvkvqetag b hdnabbfdgeggfpqcqesng b hdhabdcefgegghraxhkko b hdlaeadfedgfgfhhaoagf b hchafbdedffgglbpghwlk bf hpkaaabgfeefgfhhxhkgf b hdlacadfegefgfhhxhffk bf hdnaebddfefggafwddgaf b hflacbcffgfggioqmhwjs b hjpaaceggffegcnqiatfw b hdlaeadfedgfgkaxabqno bc hdlacadfegefgfxhxhngo b hdpaafgdgfeegaafhifnn bd hdpaaegddegfgbmkgwtkk b hpkaaabgfeefgfhxxqbog b hpkaaabgfeefgfhxhxgff b hdpaacdgfefggngumkaar cc hhdabbceeffggqqhkncan bcc hkhaeddcffggghbnlidid cc hfhabbcdeffgglfsmfcjk b hdlaeadfedgfgkahabagk b hhdabbceeffggqqhkncxf bc hdlaiadfeeeggfahankhk bc hdpaadecfegggwfkhkqjg b hdhaiddedfeggakakabqo bf hdlaeadfedgfgkahakqgo b hfpaafcegegfgebbtgpqg b hpkaaabgfeefgfhqqxonb b hepaedcefegfgnrkhwhfo bd hglabccdgfgfgmffqngor bd hdlaeadfedgfgfhhaoaff bc hpjaabafgeefgafqqxbgf b hlhaadceefgggtopmdwof b hdlaeadfedgfgkahakqfg b hdpaaefdgeeggjxsxonsj b hpkaaabgfeefgfhhxhkof b hpkaaabgfeefgfhxxqkog bc hdlaeadfedgfgfahakagf bA hfnacbeddgfggaaonsjfr b hdlaiadfeffggfhhafoaf bcc hpkaaabgfeefgfhhqhfnf b hdlaeadfedgfgfahakqgo bc hbpabeffdfgggvqxchhah bc hgpaaegggefffopdhbdli c hgpaacdegffggfffqaddk b hdhabbedgfffgqxbxqsgf b hdlaeadfedgfgkahabqgg b hpkaaabgfeefgfhqqhkbb bc hdfadbcedgffgaaqqxqgb bc helafcddeffgghgbaqnqf b hdlaeadfedgfgkahakafk b hdhabbefegfggqxpeuqkb bd hfnabbdefffgglntieknf be hooaaacffgggffkofvvcc bd hpkaaabgfeefgfhqqxgnb b hfhacbcffefggucqiuwnf bc hpkaaabgfeggffhhqhofn bc hdlacbedegfggqlnphfgw bc hpkaaabgfeeggfhqqhkgb b hgpaacddefgggbjffxeon b hhfaibbfefeggditbkktf c hhlaacgdfgffgetbldobf bs hfhacbcefggfglfditwfv bc hbpabeedffgfgbfgmmqhn b hjlacbdedgfggpglbnwvc b hdlaeadfedgfgkhhaoqfg bc hdhaeccdfegfgankaahng bc hdlaeadfedgfgkaxakqno b hmlacccdfffggmfjlfvsf b hdlacadfegeggfhhahnaq b hdhabdfeggefguiahiqfv bd hddadbcdefgggqqhhiegv b hlhaabdfegffghgqaqnkb b hjhaidccfefggafohqwxf b hdlacadfegggffxhxaqhg b hdhacdffdegggitqfiwns bf hchafcddefgggqnkahhhx bc hfpaadeggfefgokdlxkuf b hdlaeadfedgfgfhhaoqfg b hdlaeadfedgfgfahabagk b hdhaccfdefgggudoxxogn b hdlaeadfedgfgkaxabank b hpdaabceegfggaanwbbwn b hbpaicffeefggctpnjatf b hdnabafegggefgqhgvffk c hflacaceffgggknhwrluh b hjhabbcefeggghrdeuswf b hdlaiadfeffggfhhafoqg bc hpkaaabgfeefgfhhxhfgn b hcpabecdgefggfropweno b hpkaaabgfeefgfhhqqknf b hdhaiddedfeggakakabak bc hdlaiadfeeeggfahankqb b hdlaeadfedgfgfahabqgg b hpkaaabgfeefgfhqhhkfk b hdlaeadfedgfgfahakqfg b hjhabdecgfgfgahbteosw b hfnabbceeffggejtmqssk b hhdabbceeffggqqhkncab bc hghacbdeffgggtnpqesjs b hgpaadegffgfgcvaxxaxx c hgpaadegfgfgfofxhxaxq bg hhdacbceegfggqqhjajqh bc hdhaebfdfefggixsihsog c hnlaaacffegggfnwrvfoc c hgpaaegdfdfggctndbqhn bd hmpaabedgfgfgbnwppxpf bp hlfaibbdfefggdidjjchb d hlnaabfeeefggdppxdpsk cf hnlaaacfgfgfgfnwnrfno c hgpaacceggffgbbwepitw bd hpfaaacfgegfgnbsjokjo bd hpkaaabgfeefgfhqqxobb b hpkaaabgfeefgfhqqxobk b hdnaebddfefggafwddgak bo hpkaaabgfeefgfhqhhkfb bc hcpabdecgffggrooautqf b hlhaabffgegfgixiidabs c hdpaagddgfffghobalhde bd hlhaadffefggghduailws bd hdlacbeeffggguxuitikc cd hflaebcffefggioqmhwsn b hdhaccdfegfggqkmplnws b hfpaadeggdffgosdlgmus bd hnlaaacfefgggfnwnrgbk bd hpkaaabgfeefgfhhqqbnn b hpkaaabgfeeggfhqqxogb b hdlaeadfedgfgfahakafk b hfhaebcffefggucqiawkk b hpkaaabgfeefgfhhqqknn b hpkaaabgfeffgfhhqqnkf b hpkaaabgfeefgfhhqqbnf b hmpaaddeggffgnggpeapc b hflacbcffefggdsquiwoo bh hdnabafdegggffhajfcsn b hddadbcedfgfgaahhaabb bk hddadbcedgfggaaaaxxwr bc hpkaaabgfeffgfhhqqnkn bc hpkaaabgfeefgfhhxhfon b hjlabacdgffggknaafoqq b hhnaaafddfgggfahfjjgf b hflabbddefgfgqbnewgfg bf hdnaebddeffggafwtpsaf be hlfaibbeeffggdilnvghn c hmpaadcgdgffgknhnaluv bd hdoabaedgfeggfhannofo b hdlabadfdfgggfxhfbknr b hlhaadefggffgdupqedof b hpkaaabgfeggffhhxhofo bce hdhabbdfegfgghgxpmkro bc hjhaccecfgfggplvapwrs c hcladccedgfggmfaovvof bs hbpabefdefgggcujktdaw bc hkpaadffeggegsuuoutnw c hpkaaabgfeefgfhqqxgbk b hepaecefefeggkganlkkk bd hdlaeadfedgfgkahakagk b hflabbddefgfgqbnebbbb b hhnaaafddfggggaxojjgf b hdoabaeddgfggfhanoonk bc hpkaaabgfeefgfhhxhfof be hghacccdeegggqbgxnkjg c hdlacadfegggffahaahqf bc hdnaebddfefggafwddgqg b hdlaeadfedgfgkahakqgg b hdpaafedfdgggmnnendlb bc hghaeddefdfggtrppbnak b hjpaaefgdefggoudfwpew b hjpaaeddeffggbwnopuwk bc hchaddccefggghonhsjvf bd hdoabaeddegggfhabonor c hpkaaabgfeeggfhqqxggb bc hjhaidccfefggafohqwxn b hpkaaabgfeefgfhqqxgbb b hdhabdcfeggfgicpidwno c hflabbddefgfgqbnewgff b hfhacdcdffgggigflbvwk bg hepaeecffegggvfehfthw bk hhdabbceffgggqqhknhxx bc hflaeaceffgggfnxwrhqa b hnhaacddgfgfgmfgmwwrw c hlhaadddgfgfghffqckfc bo hepabedffdgggsolhopeo be hdlaeadfedgfgfahakagk bc hdlaeadfedgfgfahakqgg bc hpfaabbegfefgdifoskbf c hohaacdfgefggtcthunbo b hdhabbfdgeffgqtbqtsgb bc hdlabcddegggfdorqvbkn bc hapadcecdfgfgkjosaqfk bc hdlacadfegeggfhhahbaq b hgpaaegddfgfgvtgsiuef bd hdnacbffdefggmxeceswk c hpdaabceefgggaanwsjnw b hpdaabcfeegggaaronron b hcpacccdeffggfvfjdhrb b hooaaaceffgggfkvfojsc bd hbnadbcedgffghflkofko b hdoabaedgfeggfhanngfo b hloaaaefgggeffxawfnfk c hjlabacdgfgfgknhhflom b hldabbcdgfgfgqqhhnlom b hdlacadfegggffahaahqg b hddadbcedfgfgaahhaabk b hjpaadcgdffggosqophpc be hgoabaceffgggfkafohqx b holaacddgegfgevbtfrbb b hnlaaacffgfggfnwrkkvc bd hbnadbcedgffghflkffkf b hnlaaaceffgggknnjsfok bd hpdaabcfefgggaaogfjrg bd hkoabacdgffggfkhqfwmm b hlnaaaeddegggnaakfkjg c hnhaaccgfggffacethrgk bd hflabcddggefgivkhvffv bi hohaabddegfggtnchccof bcc hclafccdefgggmfktaaei bi hhfabbcfegfggahqntsap bq hpdaabceggffgaajorwrs be hpdaabcfgggffaanwrnkr be hlnaaafddgggfghhfovbn c hmpaadegefgfgonlrilmc c hdhacbdffeggghgilqcsk bd hpdaabceffgggaajrsgbj bd hgnabbddefgfghfkdfoff bf hkpaaefdfdgggcdnxbidc c hgoacaceffgggfkhormla b hdoabaeddgfggfhaboonk bc hdpaadfegeggfkhnxwitg bc hhhaadfegefgglxiiixvv b hmpaabefgfggfbnlplxxl c hhhaabfefegggppqlptwj c hddadbcedfgfgaahhaank bi hddadbcedfgggaahhaakn be hfdafbcdefgggaahhqqcv bC hfdafbcdefgggaahhqqnk by hnlaaacgffggfknkcvfok bd hpdaabcfgfgfgaaosfrsj bd hdnabbdfgefggafutlbgv bd hooaaacffegggfkofnjsk bd hcladbdeefgggdfxiascv bd hhhaadfgeefgghdhtltwn bd hfpaafcdgffggekkxeemx c hnhaadcggeffguklqqgkj b holaabcggeffgpnempgso b hflacaceffgggfnhwrluh b hdpaafgdgfegfaanddbei b hkoabacdgffggfkxawouu b hgoacaceffgggfkawodla b hflabbddefgfgqbnebbbk bc hdpaadfegeggfkhnxwito bcc hjlaebdeffgggpkletjcg cc hcpabccfeegggonhkohsj bd hcpaccdefegggoknhohsj b hhoaaafddgggfkqaksngf c hdhabbdegffgghglxlnck b hgoaeaceffgggfkhofeih b hooaaacgffggffknfovcn bd hkoabacdgffggfkhqnwmm b hbnadbcedgffghflkofkg b hbpabecdgffggbbohuxtb b hpjaabbegfefgtuckcggn c hbnadbcedgffghflkffkn bc hbnadbcedgfgghflkffnk b hgnabbddefgfghfkdfofg b hgoacaceffgggfkhonmla b hdnabafegggfffqqgvfgb c hdpaacgdfegfgnmwekiln bd hhhaadgeeeggfdxdxiibn c hghacccdeegggqbgqgkjg cc hnhaadcgfgffglgltxbgb c hdoabaefgggefkqqbskog c hpdaabceefgggaqfkssgo c hhoaaaeddegggoqxgbcfb c hpfaabbegfefgdifosfbg c hpfaabbegfefgdiosffkb cc regina-4.95/engine/data/verify-snappea-census-data.py000644 000765 000024 00000002120 12234011536 022465 0ustar00babstaff000000 000000 #!/usr/bin/regina-python --nolibs # # TODO: Document this. import sys if len(sys.argv) != 2: print "Usage: " + sys.argv[0] + " " sys.exit(1) tree = regina.readFileMagic(sys.argv[1]) if not tree: print "E: Could not open file " + sys.argv[1] + "." print print "Usage: " + sys.argv[0] + " " sys.exit(1) def process(tri): label = tri.getPacketLabel() print label section = label[0] index = int(label[1:]) manifold = regina.NSnapPeaCensusManifold(section, index) con = manifold.construct() hom = manifold.getHomologyH1() if con == None: print 'ERROR: No construction' sys.exit(1) elif not con.isIsomorphicTo(tri): print 'ERROR: Non-isomorphic triangulation' sys.exit(1) if hom == None: print 'ERROR: No homology' sys.exit(1) elif not hom == tri.getHomologyH1(): print 'ERROR: Non-isomorphic homology' sys.exit(1) p = tree while p != None: if p.getPacketType() == regina.NTriangulation.packetType: process(p) p = p.nextTreePacket() regina-4.95/engine/dim2/canonical.cpp000644 000765 000024 00000022757 12234011536 017354 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2isomorphism.h" #include "dim2/dim2triangulation.h" namespace regina { namespace { /** * For internal use by makeCanonical(). This routines assumes that * the preimage of triangle 0 has been fixed (along with the * corresponding edge permutation), and tries to extend * this to a "possibly canonical" isomorphism. * * If it becomes clear that the isomorphism cannot be made canonical * and/or cannot be made better (i.e., lexicographically smaller) than * the best isomorphism found so far, this routine returns \c false * (possibly before the full isomorphism has been constructed). * Otherwise it returns \c true (and it is guaranteed that the * isomorphism is both fully constructed and moreover a strict * improvement upon the best found so far). * * This routine currently only works for connected triangulations. */ bool extendIsomorphism(const Dim2Triangulation* tri, Dim2Isomorphism& current, Dim2Isomorphism& currentInv, const Dim2Isomorphism& best, const Dim2Isomorphism& bestInv) { bool better = false; unsigned nTriangles = tri->getNumberOfTriangles(); unsigned triangle; for (triangle = 0; triangle < nTriangles; ++triangle) if (triangle != currentInv.simpImage(0)) current.simpImage(triangle) = -1; int edge; unsigned origTri, origTriBest; int origEdge, origEdgeBest; Dim2Triangle *adjTri, *adjTriBest; unsigned adjTriIndex, adjTriIndexBest; unsigned finalImage, finalImageBest; NPerm3 gluingPerm, gluingPermBest; NPerm3 finalGluing, finalGluingBest; int comp; bool justAssigned; unsigned lastAssigned = 0; for (triangle = 0; triangle < nTriangles; ++triangle) { // INV: We have already selected the preimage of triangle and // the corresponding edge permutation by the time we reach // this point. origTri = currentInv.simpImage(triangle); origTriBest = bestInv.simpImage(triangle); for (edge = 0; edge < 3; ++edge) { origEdge = current.facetPerm(origTri).preImageOf(edge); origEdgeBest = best.facetPerm(origTriBest).preImageOf(edge); // Check out the adjacency along triangle/edge. adjTri = tri->getTriangle(origTri)->adjacentTriangle( origEdge); adjTriIndex = (adjTri ? tri->triangleIndex(adjTri) : nTriangles); adjTriBest = tri->getTriangle(origTriBest)-> adjacentTriangle(origEdgeBest); adjTriIndexBest = (adjTriBest ? tri->triangleIndex(adjTriBest) : nTriangles); justAssigned = false; if (adjTri && current.simpImage(adjTriIndex) < 0) { // We have a new triangle that needs assignment. ++lastAssigned; current.simpImage(adjTriIndex) = lastAssigned; currentInv.simpImage(lastAssigned) = adjTriIndex; justAssigned = true; } finalImage = (adjTri ? current.simpImage(adjTriIndex) : nTriangles); finalImageBest = (adjTriBest ? best.simpImage(adjTriIndexBest) : nTriangles); // We now have a gluing (but possibly not a gluing // permutation). Compare adjacent triangle indices. if ((! better) && finalImage > finalImageBest) return false; // Worse than best-so-far. if (finalImage < finalImageBest) better = true; // Time now to look at the gluing permutation. if (! adjTri) continue; gluingPerm = tri->getTriangle(origTri)->adjacentGluing( origEdge); gluingPermBest = tri->getTriangle(origTriBest)-> adjacentGluing(origEdgeBest); if (justAssigned) { // We can choose the permutation ourselves. // Make it so that the final gluing (computed later // below) becomes the identity. current.facetPerm(adjTriIndex) = current.facetPerm(origTri) * gluingPerm.inverse(); currentInv.facetPerm(lastAssigned) = current.facetPerm(adjTriIndex).inverse(); } // Although adjTri is guaranteed to exist, adjTriBest is // not. However, if adjTriBest does not exist then our // isomorphism-under-construction must already be an // improvement over best. if (better) continue; // Now we are guaranteed that adjTriBest exists. finalGluing = current.facetPerm(adjTriIndex) * gluingPerm * current.facetPerm(origTri).inverse(); finalGluingBest = best.facetPerm(adjTriIndexBest) * gluingPermBest * best.facetPerm(origTriBest).inverse(); comp = finalGluing.compareWith(finalGluingBest); if ((! better) && comp > 0) return false; // Worse than best-so-far. if (comp < 0) better = true; } } return better; } } bool Dim2Triangulation::makeCanonical() { unsigned nTriangles = getNumberOfTriangles(); // Get the empty triangulation out of the way. if (nTriangles == 0) return false; // Prepare to search for isomorphisms. Dim2Isomorphism current(nTriangles), currentInv(nTriangles); Dim2Isomorphism best(nTriangles), bestInv(nTriangles); // The thing to best is the identity isomorphism. unsigned tri, inner; for (tri = 0; tri < nTriangles; ++tri) { best.simpImage(tri) = bestInv.simpImage(tri) = tri; best.facetPerm(tri) = bestInv.facetPerm(tri) = NPerm3(); } // Run through potential preimages of triangle 0. int perm; for (tri = 0; tri < nTriangles; ++tri) { for (perm = 0; perm < 6; ++perm) { // Build a "perhaps canonical" isomorphism based on this // preimage of triangle 0. current.simpImage(tri) = 0; currentInv.simpImage(0) = tri; current.facetPerm(tri) = NPerm3::S3[NPerm3::invS3[perm]]; currentInv.facetPerm(0) = NPerm3::S3[perm]; if (extendIsomorphism(this, current, currentInv, best, bestInv)) { // This is better than anything we've seen before. for (inner = 0; inner < nTriangles; ++inner) { best.simpImage(inner) = current.simpImage(inner); best.facetPerm(inner) = current.facetPerm(inner); bestInv.simpImage(inner) = currentInv.simpImage(inner); bestInv.facetPerm(inner) = currentInv.facetPerm(inner); } } } } // Is there anything to do? if (best.isIdentity()) return false; // Do it. best.applyInPlace(this); return true; } } // namespace regina regina-4.95/engine/dim2/CMakeLists.txt000644 000765 000024 00000001346 12234011536 017450 0ustar00babstaff000000 000000 # dim2 # Files to compile SET ( FILES canonical dim2boundarycomponent dim2component dim2edge dim2exampletriangulation dim2triangle dim2isomorphism dim2triangulation dim2vertex isomorphic nxmldim2trireader skeleton ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} dim2/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES dim2boundarycomponent.h dim2component.h dim2edge.h dim2exampletriangulation.h dim2triangle.h dim2isomorphism.h dim2triangulation.h dim2vertex.h nxmldim2trireader.h DESTINATION ${INCLUDEDIR}/dim2 COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/dim2/dim2boundarycomponent.cpp000644 000765 000024 00000005521 12234011536 021735 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2boundarycomponent.h" #include "dim2/dim2edge.h" namespace regina { void Dim2BoundaryComponent::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << (edges_.size() == 1 ? "Edge:" : "Edges:") << std::endl; std::vector::const_iterator it; for (it = edges_.begin(); it != edges_.end(); ++it) { const Dim2EdgeEmbedding& emb((*it)->getEmbedding(0)); out << " " << emb.getTriangle()->markedIndex() << " (" << emb.getVertices().trunc2() << ')' << std::endl; } } } // namespace regina regina-4.95/engine/dim2/dim2boundarycomponent.h000644 000765 000024 00000013474 12234011536 021410 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __DIM2BOUNDARYCOMPONENT_H #ifndef __DOXYGEN #define __DIM2BOUNDARYCOMPONENT_H #endif /*! \file dim2/dim2boundarycomponent.h * \brief Deals with boundary components of a 2-manifold triangulation. */ #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2Edge; class Dim2Vertex; /** * \weakgroup dim2 * @{ */ /** * Represents a component of the boundary of a 2-manifold triangulation. * * Boundary components are highly temporary; once a triangulation * changes, all its boundary component objects will be deleted and new * ones will be created. */ class REGINA_API Dim2BoundaryComponent : public ShareableObject, public NMarkedElement { private: std::vector edges_; /**< List of edges in the component. */ std::vector vertices_; /**< List of vertices in the component. */ public: /** * Default destructor. */ virtual ~Dim2BoundaryComponent(); /** * Returns the number of edges in this boundary component. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of vertices in this boundary component. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the requested edge in this boundary component. * * The index of a Dim2Edge in the boundary component need * not be the index of the same edge in the entire * 2-manifold triangulation. * * @param index the index of the requested edge in the boundary * component. This should be between 0 and getNumberOfEdges()-1 * inclusive. * @return the requested edge. */ Dim2Edge* getEdge(unsigned long index) const; /** * Returns the requested vertex in this boundary component. * * The index of a Dim2Vertex in the boundary component need * not be the index of the same vertex in the entire * 2-manifold triangulation. * * @param index the index of the requested vertex in the boundary * component. This should be between 0 and getNumberOfVertices()-1 * inclusive. * @return the requested vertex. */ Dim2Vertex* getVertex(unsigned long index) const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Default constructor. */ Dim2BoundaryComponent(); friend class Dim2Triangulation; /**< Allow access to private members. */ }; /*@}*/ // Inline functions for Dim2BoundaryComponent inline Dim2BoundaryComponent::Dim2BoundaryComponent() { } inline Dim2BoundaryComponent::~Dim2BoundaryComponent() { } inline unsigned long Dim2BoundaryComponent::getNumberOfEdges() const { return edges_.size(); } inline unsigned long Dim2BoundaryComponent::getNumberOfVertices() const { return vertices_.size(); } inline Dim2Edge* Dim2BoundaryComponent::getEdge(unsigned long index) const { return edges_[index]; } inline Dim2Vertex* Dim2BoundaryComponent::getVertex(unsigned long index) const { return vertices_[index]; } inline void Dim2BoundaryComponent::writeTextShort(std::ostream& out) const { out << "Boundary component"; } } // namespace regina #endif regina-4.95/engine/dim2/dim2component.cpp000644 000765 000024 00000005657 12234011536 020203 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2component.h" #include "dim2/dim2triangle.h" namespace regina { void Dim2Component::writeTextShort(std::ostream& out) const { if (triangles_.size() == 1) out << "Component with 1 triangle"; else out << "Component with " << getNumberOfTriangles() << " triangles"; } void Dim2Component::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << (triangles_.size() == 1 ? "Triangle:" : "Triangles:"); std::vector::const_iterator it; for (it = triangles_.begin(); it != triangles_.end(); ++it) out << ' ' << (*it)->markedIndex(); out << std::endl; } } // namespace regina regina-4.95/engine/dim2/dim2component.h000644 000765 000024 00000025737 12234011536 017651 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __DIM2COMPONENT_H #ifndef __DOXYGEN #define __DIM2COMPONENT_H #endif /*! \file dim2/dim2component.h * \brief Deals with components of a 2-manifold triangulation. */ #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/nmarkedvector.h" namespace regina { class Dim2BoundaryComponent; class Dim2Edge; class Dim2Triangle; class Dim2Vertex; /** * \weakgroup dim2 * @{ */ /** * Represents a component of a 2-manifold triangulation. * Components are highly temporary; once a triangulation changes, all * its component objects will be deleted and new ones will be created. */ class REGINA_API Dim2Component : public ShareableObject, public NMarkedElement { private: std::vector triangles_; /**< List of triangles in the component. */ std::vector edges_; /**< List of edges in the component. */ std::vector vertices_; /**< List of vertices in the component. */ std::vector boundaryComponents_; /**< List of boundary components in the component. */ bool orientable_; /**< Is the component orientable? */ public: /** * Default destructor. */ virtual ~Dim2Component(); /** * Returns the number of triangles in this component. * * @return the number of triangles. */ unsigned long getNumberOfTriangles() const; /** * A dimension-agnostic alias for getNumberOfTriangles(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getNumberOfTriangles() for further information. */ unsigned long getNumberOfSimplices() const; /** * Returns the number of edges in this component. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of vertices in this component. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the number of boundary components in this component. * * @return the number of boundary components. */ unsigned long getNumberOfBoundaryComponents() const; /** * Returns all triangular faces in the component. * * The reference returned will remain valid for as long as this * component object exists, always reflecting the triangles currently * in the component. * * \ifacespython This routine returns a python list. */ const std::vector& getTriangles() const; /** * Returns all edges in the component. * * The reference returned will remain valid for as long as this * component object exists, always reflecting the edges currently * in the component. * * \ifacespython This routine returns a python list. */ const std::vector& getEdges() const; /** * Returns all vertices in the component. * * The reference returned will remain valid for as long as this * component object exists, always reflecting the vertices currently * in the component. * * \ifacespython This routine returns a python list. */ const std::vector& getVertices() const; /** * Returns the requested triangle in this component. * * @param index the index of the requested triangle in the * component. This should be between 0 and * getNumberOfTriangles()-1 inclusive. * Note that the index of a triangle in the component need * not be the index of the same triangle in the entire * triangulation. * @return the requested triangle. */ Dim2Triangle* getTriangle(unsigned long index) const; /** * A dimension-agnostic alias for getTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getTriangle() for further information. */ Dim2Triangle* getSimplex(unsigned long index) const; /** * Returns the requested edge in this component. * * @param index the index of the requested edge in the * component. This should be between 0 and * getNumberOfEdges()-1 inclusive. * Note that the index of an edge in the component need * not be the index of the same edge in the entire * triangulation. * @return the requested edge. */ Dim2Edge* getEdge(unsigned long index) const; /** * Returns the requested vertex in this component. * * @param index the index of the requested vertex in the * component. This should be between 0 and * getNumberOfVertices()-1 inclusive. * Note that the index of a vertex in the component need * not be the index of the same vertex in the entire * triangulation. * @return the requested vertex. */ Dim2Vertex* getVertex(unsigned long index) const; /** * Returns the requested boundary component in this component. * * @param index the index of the requested boundary component in * this component. This should be between 0 and * getNumberOfBoundaryComponents()-1 inclusive. * Note that the index of a boundary component in the component * need not be the index of the same boundary component in the * entire triangulation. * @return the requested boundary component. */ Dim2BoundaryComponent* getBoundaryComponent(unsigned long index) const; /** * Determines if this component is orientable. * * @return \c true if and only if this component is orientable. */ bool isOrientable() const; /** * Determines if this component is closed. * This is the case if and only if it has no boundary. * * @return \c true if and only if this component is closed. */ bool isClosed() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Default constructor. * * Marks the component as orientable. */ Dim2Component(); friend class Dim2Triangulation; /**< Allow access to private members. */ }; /*@}*/ // Inline functions for Dim2Component inline Dim2Component::Dim2Component() : orientable_(true) { } inline Dim2Component::~Dim2Component() { } inline unsigned long Dim2Component::getNumberOfTriangles() const { return triangles_.size(); } inline unsigned long Dim2Component::getNumberOfSimplices() const { return triangles_.size(); } inline unsigned long Dim2Component::getNumberOfEdges() const { return edges_.size(); } inline unsigned long Dim2Component::getNumberOfVertices() const { return vertices_.size(); } inline unsigned long Dim2Component::getNumberOfBoundaryComponents() const { return boundaryComponents_.size(); } inline const std::vector& Dim2Component::getTriangles() const { return triangles_; } inline const std::vector& Dim2Component::getEdges() const { return edges_; } inline const std::vector& Dim2Component::getVertices() const { return vertices_; } inline Dim2Triangle* Dim2Component::getTriangle(unsigned long index) const { return triangles_[index]; } inline Dim2Triangle* Dim2Component::getSimplex(unsigned long index) const { return triangles_[index]; } inline Dim2Edge* Dim2Component::getEdge(unsigned long index) const { return edges_[index]; } inline Dim2Vertex* Dim2Component::getVertex(unsigned long index) const { return vertices_[index]; } inline Dim2BoundaryComponent* Dim2Component::getBoundaryComponent( unsigned long index) const { return boundaryComponents_[index]; } inline bool Dim2Component::isOrientable() const { return orientable_; } inline bool Dim2Component::isClosed() const { return (boundaryComponents_.empty()); } } // namespace regina #endif regina-4.95/engine/dim2/dim2edge.cpp000644 000765 000024 00000005343 12234011536 017075 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2edge.h" namespace regina { const NPerm3 Dim2Edge::ordering[3] = { NPerm3(1,2,0), NPerm3(0,2,1), NPerm3(0,1,2), }; void Dim2Edge::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << "Appears as:" << std::endl; for (int i = 0; i < nEmb_; ++i) out << " " << emb_[i].getTriangle()->markedIndex() << " (" << emb_[i].getVertices().trunc2() << ')' << std::endl; } } // namespace regina regina-4.95/engine/dim2/dim2edge.h000644 000765 000024 00000027171 12234011536 016545 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2edge.h * \brief Deals with edges in the 1-skeleton of a 2-manifold triangulation. */ #ifndef __DIM2EDGE_H #ifndef __DOXYGEN #define __DIM2EDGE_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm3.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2BoundaryComponent; class Dim2Component; class Dim2Triangle; class Dim2Triangulation; class Dim2Vertex; /** * \weakgroup dim2 * @{ */ /** * Details how an edge in the 1-skeleton of a 2-manifold triangulation * forms part of an individual triangle. */ class REGINA_API Dim2EdgeEmbedding { private: Dim2Triangle* triangle_; /**< The triangle in which this edge is contained. */ int edge_; /**< The edge number of the triangle that is this edge. */ public: /** * Default constructor. The embedding descriptor created is * unusable until it has some data assigned to it using * operator =. * * \ifacespython Not present. */ Dim2EdgeEmbedding(); /** * Creates an embedding descriptor containing the given data. * * @param tri the triangle in which this edge is contained. * @param edge the edge number of \a tri that is this edge. */ Dim2EdgeEmbedding(Dim2Triangle* tri, int edge); /** * Creates an embedding descriptor containing the same data as * the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ Dim2EdgeEmbedding(const Dim2EdgeEmbedding& cloneMe); /** * Assigns to this embedding descriptor the same data as is * contained in the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ Dim2EdgeEmbedding& operator = (const Dim2EdgeEmbedding& cloneMe); /** * Returns the triangle in which this edge is contained. * * @return the triangle. */ Dim2Triangle* getTriangle() const; /** * Returns the edge number within getTriangle() that is this edge. * * @return the edge number that is this edge. */ int getEdge() const; /** * Returns a mapping from vertices (0,1) of this edge to the * corresponding vertex numbers in getTriangle(), as described * in Dim2Triangle::getEdgeMapping(). * * @return a mapping from the vertices of this edge to the * corresponding vertices of getTriangle(). */ NPerm3 getVertices() const; }; /** * Represents an edge in the 1-skeleton of a 2-manifold triangulation. * Edges are highly temporary; once a triangulation changes, all its * edge objects will be deleted and new ones will be created. */ class REGINA_API Dim2Edge : public ShareableObject, public NMarkedElement { public: /** * An array that maps edge numbers within a triangle to the canonical * ordering of the individual triangle vertices that form each edge. * * This means that the vertices of edge \a i in a triangle * are, in canonical order, ordering[i][0,1]. As an * immediate consequence, we obtain ordering[i][2] == i. * * Regina defines canonical order to be \e increasing order. * That is, ordering[i][0] < ordering[i][1]. * * This table does \e not describe the mapping from specific * edges within a triangulation into individual triangles * (for that, see Dim2Triangle::getEdgeMapping() instead). * This table merely provides a neat and consistent way of * listing the vertices of any given edge of a triangle. */ static const NPerm3 ordering[3]; private: Dim2EdgeEmbedding emb_[2]; /**< A list of descriptors telling how this edge forms a part of each individual triangle that it belongs to. */ unsigned nEmb_; /**< The number of descriptors stored in the list \a emb_. This will never exceed two. */ Dim2Component* component_; /**< The component that this edge is a part of. */ Dim2BoundaryComponent* boundaryComponent_; /**< The boundary component that this edge is a part of, or 0 if this edge is internal. */ public: /** * Default destructor. */ ~Dim2Edge(); /** * Returns the number of descriptors available through getEmbedding(). * Note that this number will never be greater than two. * * @return the number of embedding descriptors. */ unsigned getNumberOfEmbeddings() const; /** * Returns the requested descriptor detailing how this edge * forms a part of a particular triangle in the triangulation. * Note that if this edge represents multiple edges of a * particular triangle, then there will be multiple embedding * descriptors available regarding that triangle. * * @param index the index of the requested descriptor. This * should be between 0 and getNumberOfEmbeddings()-1 inclusive. * @return the requested embedding descriptor. */ const Dim2EdgeEmbedding& getEmbedding(unsigned index) const; /** * Returns the triangulation to which this edge belongs. * * @return the triangulation containing this edge. */ Dim2Triangulation* getTriangulation() const; /** * Returns the component of the triangulation to which this * edge belongs. * * @return the component containing this edge. */ Dim2Component* getComponent() const; /** * Returns the boundary component of the triangulation to which * this edge belongs. * * @return the boundary component containing this edge, or 0 * if this edge does not lie entirely within the boundary of * the triangulation. */ Dim2BoundaryComponent* getBoundaryComponent() const; /** * Returns the vertex of the 2-manifold triangulation corresponding * to the given vertex of this edge. * * @param vertex the vertex of this edge to examine. This * should be either 0 or 1. * @return the corresponding vertex of the 2-manifold triangulation. */ Dim2Vertex* getVertex(int vertex) const; /** * Determines if this edge lies entirely on the boundary of the * triangulation. * * @return \c true if and only if this edge lies on the boundary. */ bool isBoundary() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new edge and marks it as belonging to the * given triangulation component. * * @param component the triangulation component to which this * edge belongs. */ Dim2Edge(Dim2Component* component); friend class Dim2Triangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "dim2/dim2triangle.h" namespace regina { // Inline functions for Dim2EdgeEmbedding inline Dim2EdgeEmbedding::Dim2EdgeEmbedding() : triangle_(0) { } inline Dim2EdgeEmbedding::Dim2EdgeEmbedding( Dim2Triangle* tri, int edge) : triangle_(tri), edge_(edge) { } inline Dim2EdgeEmbedding::Dim2EdgeEmbedding( const Dim2EdgeEmbedding& cloneMe) : triangle_(cloneMe.triangle_), edge_(cloneMe.edge_) { } inline Dim2EdgeEmbedding& Dim2EdgeEmbedding::operator = (const Dim2EdgeEmbedding& cloneMe) { triangle_ = cloneMe.triangle_; edge_ = cloneMe.edge_; return *this; } inline Dim2Triangle* Dim2EdgeEmbedding::getTriangle() const { return triangle_; } inline int Dim2EdgeEmbedding::getEdge() const { return edge_; } inline NPerm3 Dim2EdgeEmbedding::getVertices() const { return triangle_->getEdgeMapping(edge_); } // Inline functions for Dim2Edge inline Dim2Edge::Dim2Edge(Dim2Component* component) : nEmb_(0), component_(component), boundaryComponent_(0) { } inline Dim2Edge::~Dim2Edge() { } inline unsigned Dim2Edge::getNumberOfEmbeddings() const { return nEmb_; } inline const Dim2EdgeEmbedding& Dim2Edge::getEmbedding( unsigned index) const { return emb_[index]; } inline Dim2Triangulation* Dim2Edge::getTriangulation() const { return emb_[0].getTriangle()->getTriangulation(); } inline Dim2Component* Dim2Edge::getComponent() const { return component_; } inline Dim2BoundaryComponent* Dim2Edge::getBoundaryComponent() const { return boundaryComponent_; } inline Dim2Vertex* Dim2Edge::getVertex(int vertex) const { return emb_[0].getTriangle()->getVertex(emb_[0].getVertices()[vertex]); } inline bool Dim2Edge::isBoundary() const { return (boundaryComponent_ != 0); } inline void Dim2Edge::writeTextShort(std::ostream& out) const { out << (boundaryComponent_ ? "Boundary " : "Internal ") << "edge"; } } // namespace regina #endif regina-4.95/engine/dim2/dim2exampletriangulation.cpp000644 000765 000024 00000020773 12234011536 022431 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2exampletriangulation.h" #include "dim2/dim2triangulation.h" namespace regina { Dim2Triangulation* Dim2ExampleTriangulation::orientable( unsigned genus, unsigned punctures) { Dim2Triangulation* ans = new Dim2Triangulation(); if (genus == 0) { if (punctures == 0) return sphere(); unsigned n = 3 * punctures - 2; unsigned i; for (i = 0; i < n; ++i) ans->newTriangle(); for (i = 0; i < n - 1; ++i) ans->getTriangle(i)->joinTo(1, ans->getTriangle(i + 1), NPerm3(1, 2)); ans->getTriangle(0)->joinTo(0, ans->getTriangle(n - 1), NPerm3(0, 1)); for (i = 1; i < punctures; ++i) ans->getTriangle(3 * i - 2)->joinTo(0, ans->getTriangle(3 * i), NPerm3(1, 2)); } else { unsigned n = 4 * genus + 3 * punctures - 2; unsigned i; for (i = 0; i < n; ++i) ans->newTriangle(); for (i = 0; i < n - 1; ++i) ans->getTriangle(i)->joinTo(1, ans->getTriangle(i + 1), NPerm3(1, 2)); ans->getTriangle(0)->joinTo(2, ans->getTriangle(n - 1), NPerm3(0, 2)); ans->getTriangle(0)->joinTo(0, ans->getTriangle(n - 1), NPerm3(0, 1)); for (i = 1; i < genus; ++i) { ans->getTriangle(4 * i - 3)->joinTo(0, ans->getTriangle(4 * i - 1), NPerm3(1, 2)); ans->getTriangle(4 * i - 2)->joinTo(0, ans->getTriangle(4 * i), NPerm3(1, 2)); } for (i = 0; i < punctures; ++i) ans->getTriangle(4 * genus + 3 * i - 3)->joinTo( 0, ans->getTriangle(4 * genus + 3 * i - 1), NPerm3(1, 2)); } return ans; } Dim2Triangulation* Dim2ExampleTriangulation::nonOrientable( unsigned genus, unsigned punctures) { if (genus == 0) return orientable(0, punctures); // Just in case. *shrug* if (genus == 1 && punctures == 0) return rp2(); // Avoid 2-gons. Dim2Triangulation* ans = new Dim2Triangulation(); unsigned n = 2 * genus + 3 * punctures - 2; unsigned i; for (i = 0; i < n; ++i) ans->newTriangle(); for (i = 0; i < n - 1; ++i) ans->getTriangle(i)->joinTo(1, ans->getTriangle(i + 1), NPerm3(1, 2)); ans->getTriangle(0)->joinTo(2, ans->getTriangle(n - 1), NPerm3(2, 0, 1)); for (i = 1; i < genus; ++i) ans->getTriangle(2 * i - 2)->joinTo(0, ans->getTriangle(2 * i - 1), NPerm3()); for (i = 0; i < punctures; ++i) ans->getTriangle(2 * genus + 3 * i - 2)->joinTo( 0, ans->getTriangle(2 * genus + 3 * i), NPerm3(1, 2)); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::sphere() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Sphere"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); r->joinTo(0, s, NPerm3()); r->joinTo(1, s, NPerm3()); r->joinTo(2, s, NPerm3()); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::sphereTetrahedron() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Sphere (tetrahedron boundary)"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); Dim2Triangle* t = ans->newTriangle(); Dim2Triangle* u = ans->newTriangle(); r->joinTo(1, s, NPerm3(1, 2)); s->joinTo(1, t, NPerm3(1, 2)); t->joinTo(1, r, NPerm3(1, 2)); r->joinTo(0, u, NPerm3(0, 1, 2)); s->joinTo(0, u, NPerm3(1, 2, 0)); t->joinTo(0, u, NPerm3(2, 0, 1)); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::sphereOctahedron() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Sphere (octahedron boundary)"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); Dim2Triangle* t = ans->newTriangle(); Dim2Triangle* u = ans->newTriangle(); Dim2Triangle* v = ans->newTriangle(); Dim2Triangle* w = ans->newTriangle(); Dim2Triangle* x = ans->newTriangle(); Dim2Triangle* y = ans->newTriangle(); r->joinTo(1, s, NPerm3(1, 2)); s->joinTo(1, t, NPerm3(1, 2)); t->joinTo(1, u, NPerm3(1, 2)); u->joinTo(1, r, NPerm3(1, 2)); v->joinTo(1, w, NPerm3(1, 2)); w->joinTo(1, x, NPerm3(1, 2)); x->joinTo(1, y, NPerm3(1, 2)); y->joinTo(1, v, NPerm3(1, 2)); r->joinTo(0, v, NPerm3()); s->joinTo(0, w, NPerm3()); t->joinTo(0, x, NPerm3()); u->joinTo(0, y, NPerm3()); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::disc() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Disc"); ans->newTriangle(); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::annulus() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Annulus"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); r->joinTo(0, s, NPerm3(1, 2)); r->joinTo(2, s, NPerm3(0, 1)); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::mobius() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Mobius band"); Dim2Triangle* r = ans->newTriangle(); r->joinTo(0, r, NPerm3(2, 0, 1)); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::torus() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Torus"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); r->joinTo(0, s, NPerm3(1, 2)); r->joinTo(1, s, NPerm3(2, 0)); r->joinTo(2, s, NPerm3(0, 1)); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::rp2() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Projective plane"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); r->joinTo(0, s, NPerm3(1, 2)); r->joinTo(1, s, NPerm3()); r->joinTo(2, s, NPerm3()); return ans; } Dim2Triangulation* Dim2ExampleTriangulation::kb() { Dim2Triangulation* ans = new Dim2Triangulation(); ans->setPacketLabel("Klein bottle"); Dim2Triangle* r = ans->newTriangle(); Dim2Triangle* s = ans->newTriangle(); r->joinTo(0, s, NPerm3(1, 2)); r->joinTo(1, s, NPerm3(2, 0)); r->joinTo(2, s, NPerm3()); return ans; } } // namespace regina regina-4.95/engine/dim2/dim2exampletriangulation.h000644 000765 000024 00000015323 12234011536 022071 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2exampletriangulation.h * \brief Offers several example 2-manifold triangulations as starting * points for testing code or getting used to Regina. */ #ifndef __NEXAMPLETRIANGULATION_H #ifndef __DOXYGEN #define __NEXAMPLETRIANGULATION_H #endif #include "regina-core.h" namespace regina { class Dim2Triangulation; /** * \weakgroup triangulation * @{ */ /** * This class offers routines for constructing sample 2-manifold triangulations * of various types. These triangulations may be useful for testing new * code, or for simply getting a feel for how Regina works. * * The sample triangulations offered here may prove especially useful in * Regina's scripting interface, where working with pre-existing files * is more complicated than in the GUI. * * Note that each of these routines constructs a new triangulation from * scratch. It is up to the caller of each routine to destroy the * triangulation that is returned. */ class REGINA_API Dim2ExampleTriangulation { public: /** * Returns a triangulation of the given orientable surface. * * @param genus the genus of the surface; this must be greater * than or equal to zero. * @param punctures the number of punctures in the surface; * this must be greater than or equal to zero. * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* orientable( unsigned genus, unsigned punctures); /** * Returns a triangulation of the given non-orientable surface. * * @param genus the non-orientable genus of the surface, i.e., * the number of crosscaps that it contains; this must be greater * than or equal to one. * @param punctures the number of punctures in the surface; * this must be greater than or equal to zero. * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* nonOrientable( unsigned genus, unsigned punctures); /** * Returns a two-triangle 2-sphere. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* sphere(); /** * Returns the four-triangle 2-sphere formed from the boundary * of a tetrahedron. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* sphereTetrahedron(); /** * Returns the eight-triangle 2-sphere formed from the boundary * of an octahedron. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* sphereOctahedron(); /** * Returns a one-triangle disc. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* disc(); /** * Returns a two-triangle annulus. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* annulus(); /** * Returns a one-triangle Mobius band. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* mobius(); /** * Returns a two-triangle torus. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* torus(); /** * Returns a two-triangle projective plane. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* rp2(); /** * Returns a two-triangle Klein bottle. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static Dim2Triangulation* kb(); }; /*@}*/ } // namespace regina #endif regina-4.95/engine/dim2/dim2isomorphism.cpp000644 000765 000024 00000006027 12234011536 020542 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "dim2/dim2isomorphism.h" #include "dim2/dim2triangulation.h" #include "generic/ngenericisomorphism-impl.h" namespace regina { // Instantiate all templates from the -impl.h file. template void NGenericIsomorphism<2>::writeTextShort(std::ostream&) const; template void NGenericIsomorphism<2>::writeTextLong(std::ostream&) const; template bool NGenericIsomorphism<2>::isIdentity() const; template NGenericIsomorphism<2>::NGenericIsomorphism( const NGenericIsomorphism<2>&); template Dim2Isomorphism* NGenericIsomorphism<2>::random(unsigned); template Dim2Triangulation* NGenericIsomorphism<2>::apply( const Dim2Triangulation*) const; template void NGenericIsomorphism<2>::applyInPlace(Dim2Triangulation*) const; } // namespace regina regina-4.95/engine/dim2/dim2isomorphism.h000644 000765 000024 00000023004 12234011536 020201 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2isomorphism.h * \brief Deals with combinatorial isomorphisms of 2-manifold triangulations. */ #ifndef __DIM2ISOMORPHISM_H #ifndef __DOXYGEN #define __DIM2ISOMORPHISM_H #endif #include "regina-core.h" #include "shareableobject.h" #include "generic/nfacetspec.h" #include "generic/ngenericisomorphism.h" #include "maths/nperm3.h" namespace regina { class Dim2Triangulation; /** * \weakgroup dim2 * @{ */ /** * Represents a combinatorial isomorphism from one 2-manifold triangulation * into another. * * In essence, a combinatorial isomorphism from triangulation T to * triangulation U is a one-to-one map from the triangles of T to the * triangles of U that allows relabelling of both the triangles and * their edges (or equivalently, their vertices), and that preserves * gluings across adjacent triangles. * * More precisely: An isomorphism consists of (i) a one-to-one map f * from the triangles of T to the triangles of U, and (ii) for each * triangle S of T, a permutation f_S of the edges (0,1,2) of S, * for which the following condition holds: * * - If edge k of triangle S and edge k' of triangle S' * are identified in T, then edge f_S(k) of f(S) and edge f_S'(k') * of f(S') are identified in U. Moreover, their gluing is consistent * with the edge/vertex permutations; that is, there is a commutative * square involving the gluing maps in T and U and the permutations * f_S and f_S'. * * Isomorphisms can be boundary complete or * boundary incomplete. A boundary complete isomorphism * satisfies the additional condition: * * - If edge x is a boundary edge of T then edge f(x) is a boundary * edge of U. * * A boundary complete isomorphism thus indicates that a copy of * triangulation T is present as an entire component (or components) of U, * whereas a boundary incomplete isomorphism represents an embedding of a * copy of triangulation T as a subcomplex of some possibly larger component * (or components) of U. * * Note that in all cases triangulation U may contain more triangles * than triangulation T. */ class REGINA_API Dim2Isomorphism : public NGenericIsomorphism<2> { public: /** * Creates a new isomorphism with no initialisation. * * \ifacespython Not present. * * @param sourceTriangles the number of triangles in the source * triangulation associated with this isomorphism; this may be zero. */ Dim2Isomorphism(unsigned sourceTriangles); /** * Creates a new isomorphism identical to the given isomorphism. * * @param cloneMe the isomorphism upon which to base the new * isomorphism. */ Dim2Isomorphism(const Dim2Isomorphism& cloneMe); /** * Returns the number of triangles in the source triangulation * associated with this isomorphism. Note that this is always * less than or equal to the number of triangles in the * destination triangulation. * * This is a convenience routine specific to two dimensions, and is * identical to the dimension-agnostic routine getSourceSimplices(). * * @return the number of triangles in the source triangulation. */ unsigned getSourceTriangles() const; /** * Determines the image of the given source triangle under * this isomorphism. * * This is a convenience routine specific to two dimensions, and is * identical to the dimension-agnostic routine simpImage(). * * \ifacespython Not present, though the read-only version of this * routine is. * * @param sourceTriangle the index of the source triangle; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return a reference to the index of the destination triangle * that the source triangle maps to. */ int& triImage(unsigned sourceTriangle); /** * Determines the image of the given source triangle under * this isomorphism. * * This is a convenience routine specific to two dimensions, and is * identical to the dimension-agnostic routine simpImage(). * * @param sourceTriangle the index of the source triangle; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return the index of the destination triangle * that the source triangle maps to. */ int triImage(unsigned sourceTriangle) const; /** * Returns a read-write reference to the permutation that is * applied to the three edges of the given source triangle * under this isomorphism. * Edge \a i of source triangle \a sourceTriangle will be mapped to * edge facetPerm(sourceTriangle)[i] of triangle * simpImage(sourceTriangle). * * This is a convenience routine specific to two dimensions, and is * identical to the dimension-agnostic routine facetPerm(). * * \ifacespython Not present, though the read-only version of this * routine is. * * @param sourceTriangle the index of the source triangle containing * the original three edges; this must be between 0 and * getSourceTriangles()-1 inclusive. * @return a read-write reference to the permutation applied to the * three edges of the source triangle. */ NPerm3& edgePerm(unsigned sourceTriangle); /** * Determines the permutation that is applied to the three edges * of the given source triangle under this isomorphism. * Edge \a i of source triangle \a sourceTriangle will be mapped to * triangle facetPerm(sourceTriangle)[i] of triangle * simpImage(sourceTriangle). * * This is a convenience routine specific to two dimensions, and is * identical to the dimension-agnostic routine facetPerm(). * * @param sourceTriangle the index of the source triangle containing * the original three edges; this must be between 0 and * getSourceTriangles()-1 inclusive. * @return the permutation applied to the three edges of the * source triangle. */ NPerm3 edgePerm(unsigned sourceTriangle) const; }; /*@}*/ // Inline functions for Dim2Isomorphism inline Dim2Isomorphism::Dim2Isomorphism(unsigned sourceTriangles) : NGenericIsomorphism<2>(sourceTriangles) { } inline Dim2Isomorphism::Dim2Isomorphism(const Dim2Isomorphism& cloneMe) : NGenericIsomorphism<2>(cloneMe) { } inline unsigned Dim2Isomorphism::getSourceTriangles() const { return nSimplices_; } inline int& Dim2Isomorphism::triImage(unsigned sourceTriangle) { return simpImage_[sourceTriangle]; } inline int Dim2Isomorphism::triImage(unsigned sourceTriangle) const { return simpImage_[sourceTriangle]; } inline NPerm3& Dim2Isomorphism::edgePerm(unsigned sourceTriangle) { return facetPerm_[sourceTriangle]; } inline NPerm3 Dim2Isomorphism::edgePerm(unsigned sourceTriangle) const { return facetPerm_[sourceTriangle]; } } // namespace regina #endif regina-4.95/engine/dim2/dim2triangle.cpp000644 000765 000024 00000007651 12234011536 020002 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2triangle.h" #include "dim2/dim2triangulation.h" #include namespace regina { Dim2Triangle::Dim2Triangle(Dim2Triangulation* tri) : tri_(tri) { std::fill(adj_, adj_ + 3, static_cast(0)); } Dim2Triangle::Dim2Triangle(const std::string& desc, Dim2Triangulation* tri) : desc_(desc), tri_(tri) { std::fill(adj_, adj_ + 3, static_cast(0)); } bool Dim2Triangle::hasBoundary() const { for (int i=0; i<3; ++i) if (adj_[i] == 0) return true; return false; } void Dim2Triangle::joinTo(int myEdge, Dim2Triangle* you, NPerm3 gluing) { NPacket::ChangeEventSpan span(tri_); adj_[myEdge] = you; adjPerm_[myEdge] = gluing; int yourEdge = gluing[myEdge]; you->adj_[yourEdge] = this; you->adjPerm_[yourEdge] = gluing.inverse(); tri_->clearAllProperties(); } Dim2Triangle* Dim2Triangle::unjoin(int myEdge) { NPacket::ChangeEventSpan span(tri_); Dim2Triangle* you = adj_[myEdge]; int yourEdge = adjPerm_[myEdge][myEdge]; you->adj_[yourEdge] = 0; adj_[myEdge] = 0; tri_->clearAllProperties(); return you; } void Dim2Triangle::isolate() { for (int i=0; i<3; ++i) if (adj_[i]) unjoin(i); } void Dim2Triangle::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; for (int i = 2; i >= 0; --i) { out << Dim2Edge::ordering[i].trunc2() << " -> "; if (! adj_[i]) out << "boundary"; else out << adj_[i]->markedIndex() << " (" << (adjPerm_[i] * Dim2Edge::ordering[i]).trunc2() << ')'; out << std::endl; } } } // namespace regina regina-4.95/engine/dim2/dim2triangle.h000644 000765 000024 00000050673 12234011536 017451 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2triangle.h * \brief Deals with triangular faces in a 2-manifold triangulation. */ #ifndef __DIM2TRIANGLE_H #ifndef __DOXYGEN #define __DIM2TRIANGLE_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm3.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2Edge; class Dim2Vertex; class Dim2Component; class Dim2Triangulation; /** * \weakgroup dim2 * @{ */ /** * Represents a triangular face in a 2-manifold triangulation. * * With each triangle is stored various pieces of information * regarding the overall skeletal structure and component structure of * the triangulation. This skeletal information will be allocated, calculated * and deallocated by the Dim2Triangulation object containing the * corresponding triangles. * * A triangle must always belong to a 2-manifold triangulation. You can * construct new triangles using either Dim2Triangulation::newTriangle() * or Dim2Triangulation::newTriangle(const std::string&); these * routines will automatically add the new triangles to the triangulation. * You can destroy triangles by calling Dim2Trianguation::removeTriangle(), * Dim2Trianguation::removeTriangleAt() or * Dim2Triangulation::removeAllTriangles(); these routines will * automatically destroy the triangles as they are removed. */ class REGINA_API Dim2Triangle : public ShareableObject, public NMarkedElement { private: Dim2Triangle* adj_[3]; /**< Stores the adjacent triangles glued to each edge of this triangle. Specifically, adj_[e] represents the triangle joined to edge \c e of this triangle, or is 0 if edge \c e lies on the triangulation boundary. Edges are numbered from 0 to 2 inclusive, where edge \c i is opposite vertex \c i. */ NPerm3 adjPerm_[3]; /**< Stores the corresponence between vertices of this triangle and adjacent triangles. If edge \c e is joined to another triangle, adjPerm_[3] represents the permutation \c p whereby vertex \c v of this triangle is identified with vertex p[v] of the adjacent triangle along edge \c e. */ std::string desc_; /**< A text description of this triangle. Descriptions are not mandatory and need not be unique. */ Dim2Vertex* vertex_[3]; /**< Vertices in the triangulation skeleton that are vertices of this triangle. */ Dim2Edge* edge_[3]; /**< Edges in the triangulation skeleton that are edges of this triangle. */ NPerm3 vertexMapping_[3]; /**< Maps 0 to each vertex of this triangle in turn whilst mapping (1,2) in a suitably "orientation-preserving" way, as described in getVertexMapping(). */ NPerm3 edgeMapping_[3]; /**< Maps (0,1) to the vertices of this triangle that form each edge, as described in getEdgeMapping(). */ int orientation_; /**< The orientation of this triangle in the triangulation. This will either be 1 or -1. */ Dim2Triangulation* tri_; /**< The triangulation to which this triangle belongs. */ Dim2Component* component_; /**< The component to which this triangle belongs in the triangulation. */ public: /** * Destroys this triangle. */ virtual ~Dim2Triangle(); /** * Returns the text description associated with this * triangle. * * @return the description of this triangle. */ const std::string& getDescription() const; /** * Sets the text description associated with this triangle. * Note that descriptions need not be unique, and may be empty. * * @param desc the new description to assign to this * triangle. */ void setDescription(const std::string& desc); /** * Returns the adjacent triangle glued to the given edge of this * triangle, or 0 if the given edge is on the boundary of the * 2-manifold triangulation. * * @param edge the edge of this triangle to examine. This * should be between 0 and 2 inclusive, where edge \c i is * opposite vertex \c i of the triangle. * @return the adjacent triangle glued to the given edge, or 0 * if the given edge lies on the boundary. */ Dim2Triangle* adjacentTriangle(int edge) const; /** * A dimension-agnostic alias for adjacentTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See adjacentTriangle() for further information. */ Dim2Triangle* adjacentSimplex(int edge) const; /** * Returns a permutation describing the correspondence between * vertices of this triangle and vertices of the adjacent * triangle glued to the given edge of this triangle. * * If we call this permutation \c p, then for each vertex \c v of this * triangle, p[v] will be the vertex of the adjacent * triangle that is identified with \c v according to the gluing * along the given edge of this triangle. * * \pre The given edge of this triangle has some triangle * (possibly this one) glued to it. * * @param edge the edge of this triangle whose gluing we * will examine. This should be between 0 and 2 inclusive, where * edge \c i is opposite vertex \c i of the triangle. * @return a permutation mapping the vertices of this * triangle to the vertices of the triangle adjacent along * the given edge. */ NPerm3 adjacentGluing(int edge) const; /** * Examines the triangle glued to the given edge of this * triangle, and returns the corresponding edge of that * triangle. That is, the returned edge of the adjacent * triangle is glued to the given edge of this triangle. * * \pre The given edge of this triangle has some triangle * (possibly this one) glued to it. * * @param edge the edge of this triangle whose gluing we * will examine. This should be between 0 and 2 inclusive, where * edge \c i is opposite vertex \c i of the triangle. * @return the edge of the triangle adjacent along the given * edge that is in fact glued to the given edge of this triangle. */ int adjacentEdge(int edge) const; /** * A dimension-agnostic alias for adjacentEdge(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "facet" refers to a facet of a top-dimensional simplex * (which for 2-manifold triangulations means an edge of a triangle). * * See adjacentEdge() for further information. */ int adjacentFacet(int facet) const; /** * Determines if this triangle has any edges that are boundary edges. * * @return \c true if and only if this triangle has any boundary edges. */ bool hasBoundary() const; /** * Joins the given edge of this triangle to another triangle. * The other triangle involved will be automatically updated. * * \pre This and the given triangle do not belong to * different triangulations. * \pre The given edge of this triangle is not currently glued * to anything. * \pre The edge of the other triangle that will be glued to the * given edge of this triangle is not currently glued to anything. * \pre If the other triangle involved is this triangle, we are * not attempting to glue an edge to itself. * * @param myEdge the edge of this triangle that will be glued to * the given other triangle. This should be between 0 and 2 * inclusive, where edge \c i is opposite vertex \c i of the triangle. * @param you the triangle (possibly this one) that will be * glued to the given edge of this triangle. * @param gluing a permutation describing the mapping of * vertices by which the two triangles will be joined. Each * vertex \c v of this triangle that lies on the given edge will * be identified with vertex gluing[v] of triangle * you. In addition, the edge of you that * will be glued to the given edge of this triangle will be * edge number gluing[myEdge]. */ void joinTo(int myEdge, Dim2Triangle* you, NPerm3 gluing); /** * Unglues the given edge of this triangle from whatever is * joined to it. The other triangle involved (possibly this * one) will be automatically updated. * * \pre The given edge of this triangle has some triangle * (possibly this one) glued to it. * * @param myEdge the edge of this triangle whose gluing we * will undo. This should be between 0 and 2 inclusive, where * edge \c i is opposite vertex \c i of the triangle. * @return the ex-adjacent triangle that was originally glued to the * given edge of this triangle. */ Dim2Triangle* unjoin(int myEdge); /** * Undoes any edge gluings involving this triangle. * Any other triangles involved will be automatically updated. */ void isolate(); /** * Returns the triangulation to which this triangle belongs. * * @return the triangulation containing this triangle. */ Dim2Triangulation* getTriangulation() const; /** * Returns the 2-manifold triangulation component to which this * triangle belongs. * * @return the component containing this triangle. */ Dim2Component* getComponent() const; /** * Returns the vertex in the 2-manifold triangulation skeleton * corresponding to the given vertex of this triangle. * * @param vertex the vertex of this triangle to examine. * This should be between 0 and 2 inclusive. * @return the vertex of the skeleton corresponding to the * requested triangle vertex. */ Dim2Vertex* getVertex(int vertex) const; /** * Returns the edge in the 2-manifold triangulation skeleton * corresponding to the given edge of this triangle. Edge \c i * of a triangle is always opposite vertex \c i of that triangle. * * @param edge the edge of this triangle to examine. * This should be between 0 and 2 inclusive. * @return the edge of the skeleton corresponding to the * requested triangle edge. */ Dim2Edge* getEdge(int edge) const; /** * Returns a permutation that maps 0 to the given vertex of this * triangle, and that maps (1,2) to the two remaining vertices * in the following "orientation-preserving" fashion. * * The images of 1 and 2 under the permutations that are returned * have the following properties. In each triangle, the images * of 1 and 2 under this map form a directed edge of the triangle * (running from the image of vertex 1 to the image of vertex 2). * For any given vertex of the triangulation, these corresponding * directed edges together form an ordered path within the * triangulation that circles the common vertex of the triangulation * (like a vertex link, except that it is not near to the vertex * and so might intersect itself). Furthermore, if we consider the * individual triangles in the order in which they appear in the list * Dim2Vertex::getEmbeddings(), these corresponding directed edges * appear in order from the start of this path to the finish * (for internal vertices this path is actually a cycle, and the * starting point is arbitrary). * * @param vertex the vertex of this triangle to examine. * This should be between 0 and 2 inclusive. * @return a permutation that maps 0 to the given vertex of this * triangle, with the properties outlined above. */ NPerm3 getVertexMapping(int vertex) const; /** * Examines the given edge of this triangle, and returns a mapping from * the "canonical" vertices of the corresponding edge of the * triangulation to the matching vertices of this triangle. * * In detail: Suppose two edges of two triangles are * identified within the overall 2-manifold triangulation. We call * this a single "edge of the triangulation", and arbitrarily * label its vertices (0,1). This routine then maps the vertices * (0,1) of this edge of the triangulation to the individual * vertices of this triangle that make up the given edge. * * Because we are passing the argument \a edge, we already know * \e which vertices of this triangle are involved. What this * routine tells us is the \a order in which they appear to form the * overall edge of the triangulation. * * As a consequence: Consider two triangle edges that are * identified together as a single edge of the triangulation, * and choose some \a i from the set {0,1}. Then the vertices * getEdgeMapping(...)[i] of the individual triangles * are identified together, since they both become the same * vertex of the same edge of the triangulation (assuming of * course that we pass the correct edge number in each case to * getEdgeMapping()). * * @param edge the edge of this triangle to examine. * This should be between 0 and 2 inclusive. * @return a mapping from vertices (0,1) of the requested * edge to the corresponding vertices of this triangle. */ NPerm3 getEdgeMapping(int edge) const; /** * Returns the orientation of this triangle in the 2-manifold * triangulation. * * The orientation of each triangle is always +1 or -1. * In an orientable component of a triangulation, * adjacent triangles have the same orientations if one could be * transposed onto the other without reflection, and they have * opposite orientations if a reflection would be required. * In a non-orientable component, orientations are still +1 and * -1 but no further guarantees can be made. * * @return +1 or -1 according to the orientation of this triangle. */ int orientation() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new triangle with empty description and no * edges joined to anything. * * @param tri the triangulation to which the new triangle belongs. */ Dim2Triangle(Dim2Triangulation* tri); /** * Creates a new triangle with the given description and * no edges joined to anything. * * @param desc the description to give the new triangle. * @param tri the triangulation to which the new triangle belongs. */ Dim2Triangle(const std::string& desc, Dim2Triangulation* tri); friend class Dim2Triangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "dim2/dim2triangulation.h" namespace regina { // Inline functions for Dim2Triangle inline Dim2Triangle::~Dim2Triangle() { } inline const std::string& Dim2Triangle::getDescription() const { return desc_; } inline void Dim2Triangle::setDescription(const std::string& desc) { desc_ = desc; } inline Dim2Triangle* Dim2Triangle::adjacentTriangle(int edge) const { return adj_[edge]; } inline Dim2Triangle* Dim2Triangle::adjacentSimplex(int edge) const { return adj_[edge]; } inline NPerm3 Dim2Triangle::adjacentGluing(int edge) const { return adjPerm_[edge]; } inline int Dim2Triangle::adjacentEdge(int edge) const { return adjPerm_[edge][edge]; } inline int Dim2Triangle::adjacentFacet(int facet) const { return adjPerm_[facet][facet]; } inline Dim2Triangulation* Dim2Triangle::getTriangulation() const { return tri_; } inline Dim2Component* Dim2Triangle::getComponent() const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return component_; } inline Dim2Vertex* Dim2Triangle::getVertex(int vertex) const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return vertex_[vertex]; } inline Dim2Edge* Dim2Triangle::getEdge(int edge) const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return edge_[edge]; } inline NPerm3 Dim2Triangle::getVertexMapping(int vertex) const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return vertexMapping_[vertex]; } inline NPerm3 Dim2Triangle::getEdgeMapping(int edge) const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return edgeMapping_[edge]; } inline int Dim2Triangle::orientation() const { if (! tri_->calculatedSkeleton_) tri_->calculateSkeleton(); return orientation_; } inline void Dim2Triangle::writeTextShort(std::ostream& out) const { out << "Triangle"; if (desc_.length() > 0) out << ": " << desc_; } } // namespace regina #endif regina-4.95/engine/dim2/dim2triangulation.cpp000644 000765 000024 00000032620 12236713375 021063 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include #include "dim2/dim2triangulation.h" #include "utilities/xmlutils.h" namespace regina { Dim2Triangulation::Dim2Triangulation(const std::string& description) : calculatedSkeleton_(false) { Dim2Triangulation* attempt; if ((attempt = fromIsoSig(description))) { cloneFrom(*attempt); setPacketLabel(description); } delete attempt; } bool Dim2Triangulation::isMinimal() const { // 2-sphere: if (getEulerChar() == 2) return (triangles_.size() == 2); // Projective plane and disc: if (getEulerChar() == 1) return (triangles_.size() == (isClosed() ? 2 : 1)); // All other closed manifolds: if (isClosed()) return (vertices_.size() == 1); // All other bounded manifolds: return (vertices_.size() == boundaryComponents_.size()); } void Dim2Triangulation::swapContents(Dim2Triangulation& other) { ChangeEventSpan span1(this); ChangeEventSpan span2(&other); clearAllProperties(); other.clearAllProperties(); triangles_.swap(other.triangles_); TriangleIterator it; for (it = triangles_.begin(); it != triangles_.end(); ++it) (*it)->tri_ = this; for (it = other.triangles_.begin(); it != other.triangles_.end(); ++it) (*it)->tri_ = &other; } void Dim2Triangulation::moveContentsTo(Dim2Triangulation& dest) { ChangeEventSpan span1(this); ChangeEventSpan span2(&dest); clearAllProperties(); dest.clearAllProperties(); TriangleIterator it; for (it = triangles_.begin(); it != triangles_.end(); ++it) { // This is an abuse of NMarkedVector, since for a brief moment // each triangle belongs to both vectors triangles_ and dest.triangles_. // However, the subsequent clear() operation does not touch the // triangle markings (indices), and so we end up with the // correct result (i.e., the markings are correct for dest). (*it)->tri_ = &dest; dest.triangles_.push_back(*it); } triangles_.clear(); } void Dim2Triangulation::writeTextLong(std::ostream& out) const { if (! calculatedSkeleton_) calculateSkeleton(); out << "Size of the skeleton:\n"; out << " Triangles: " << triangles_.size() << '\n'; out << " Edges: " << edges_.size() << '\n'; out << " Vertices: " << vertices_.size() << '\n'; out << '\n'; Dim2Triangle* tri; Dim2Triangle* adjTri; unsigned triPos; int i, j; NPerm3 adjPerm; out << "Triangle gluing:\n"; out << " Triangle | glued to: (01) (02) (12)\n"; out << " ----------+--------------------------------------\n"; for (triPos=0; triPos < triangles_.size(); triPos++) { tri = triangles_[triPos]; out << " " << std::setw(4) << triPos << " | "; for (i = 2; i >= 0; --i) { out << " "; adjTri = tri->adjacentTriangle(i); if (! adjTri) out << "boundary"; else { adjPerm = tri->adjacentGluing(i); out << std::setw(3) << triangleIndex(adjTri) << " ("; for (j = 0; j < 3; ++j) { if (j == i) continue; out << adjPerm[j]; } out << ")"; } } out << '\n'; } out << '\n'; out << "Vertices:\n"; out << " Triangle | vertex: 0 1 2\n"; out << " ----------+----------------------\n"; for (triPos = 0; triPos < triangles_.size(); ++triPos) { tri = triangles_[triPos]; out << " " << std::setw(4) << triPos << " | "; for (i = 0; i < 3; ++i) out << ' ' << std::setw(3) << vertexIndex(tri->getVertex(i)); out << '\n'; } out << '\n'; out << "Edges:\n"; out << " Triangle | edge: 01 02 12\n"; out << " ----------+--------------------\n"; for (triPos = 0; triPos < triangles_.size(); ++triPos) { tri = triangles_[triPos]; out << " " << std::setw(4) << triPos << " | "; for (i = 2; i >= 0; --i) out << ' ' << std::setw(3) << edgeIndex(tri->getEdge(i)); out << '\n'; } out << '\n'; } void Dim2Triangulation::insertTriangulation(const Dim2Triangulation& X) { ChangeEventSpan span(this); unsigned long nOrig = getNumberOfTriangles(); unsigned long nX = X.getNumberOfTriangles(); unsigned long triPos; for (triPos = 0; triPos < nX; ++triPos) newTriangle(X.triangles_[triPos]->getDescription()); // Make the gluings. unsigned long adjPos; Dim2Triangle* tri; Dim2Triangle* adjTri; NPerm3 adjPerm; int edge; for (triPos = 0; triPos < nX; ++triPos) { tri = X.triangles_[triPos]; for (edge = 0; edge < 3; ++edge) { adjTri = tri->adjacentTriangle(edge); if (adjTri) { adjPos = X.triangleIndex(adjTri); adjPerm = tri->adjacentGluing(edge); if (adjPos > triPos || (adjPos == triPos && adjPerm[edge] > edge)) { triangles_[nOrig + triPos]->joinTo(edge, triangles_[nOrig + adjPos], adjPerm); } } } } } void Dim2Triangulation::insertConstruction(unsigned long nTriangles, const int adjacencies[][3], const int gluings[][3][3]) { if (nTriangles == 0) return; Dim2Triangle** tri = new Dim2Triangle*[nTriangles]; unsigned i, j; NPerm3 p; ChangeEventSpan span(this); for (i = 0; i < nTriangles; ++i) tri[i] = newTriangle(); for (i = 0; i < nTriangles; ++i) for (j = 0; j < 3; ++j) if (adjacencies[i][j] >= 0 && ! tri[i]->adjacentTriangle(j)) { p = NPerm3(gluings[i][j][0], gluings[i][j][1], gluings[i][j][2]); tri[i]->joinTo(j, tri[adjacencies[i][j]], p); } delete[] tri; } std::string Dim2Triangulation::dumpConstruction() const { std::ostringstream ans; ans << "/**\n"; if (! getPacketLabel().empty()) ans << " * 2-manifold triangulation: " << getPacketLabel() << "\n"; ans << " * Code automatically generated by dumpConstruction().\n" " */\n" "\n"; if (triangles_.empty()) { ans << "/* This triangulation is empty. No code is being generated. */\n"; return ans.str(); } ans << "/**\n" " * The following arrays describe the individual gluings of\n" " * triangle edges.\n" " */\n" "\n"; unsigned long nTriangles = triangles_.size(); Dim2Triangle* tri; NPerm3 perm; unsigned long p; int e, i; ans << "const int adjacencies[" << nTriangles << "][3] = {\n"; for (p = 0; p < nTriangles; ++p) { tri = triangles_[p]; ans << " { "; for (e = 0; e < 3; ++e) { if (tri->adjacentTriangle(e)) { ans << triangleIndex(tri->adjacentTriangle(e)); } else ans << "-1"; if (e < 2) ans << ", "; else if (p != nTriangles - 1) ans << "},\n"; else ans << "}\n"; } } ans << "};\n\n"; ans << "const int gluings[" << nTriangles << "][3][3] = {\n"; for (p = 0; p < nTriangles; ++p) { tri = triangles_[p]; ans << " { "; for (e = 0; e < 3; ++e) { if (tri->adjacentTriangle(e)) { perm = tri->adjacentGluing(e); ans << "{ "; for (i = 0; i < 3; ++i) { ans << perm[i]; if (i < 2) ans << ", "; else ans << " }"; } } else ans << "{ 0, 0, 0 }"; if (e < 2) ans << ", "; else if (p != nTriangles - 1) ans << " },\n"; else ans << " }\n"; } } ans << "};\n\n"; ans << "/**\n" " * The following code actually constructs a 2-manifold triangulation\n" " * based on the information stored in the arrays above.\n" " */\n" "\n" "Dim2Triangulation tri;\n" "tri.insertConstruction(" << nTriangles << ", adjacencies, gluings);\n" "\n"; return ans.str(); } void Dim2Triangulation::writeXMLPacketData(std::ostream& out) const { using regina::xml::xmlEncodeSpecialChars; using regina::xml::xmlValueTag; // Write the triangle gluings. TriangleIterator it; Dim2Triangle* adjTri; int edge; out << " \n"; for (it = triangles_.begin(); it != triangles_.end(); ++it) { out << " getDescription()) << "\"> "; for (edge = 0; edge < 3; ++edge) { adjTri = (*it)->adjacentTriangle(edge); if (adjTri) { out << triangleIndex(adjTri) << ' ' << static_cast((*it)-> adjacentGluing(edge).getPermCode()) << ' '; } else out << "-1 -1 "; } out << "\n"; } out << " \n"; } void Dim2Triangulation::cloneFrom(const Dim2Triangulation& X) { ChangeEventSpan span(this); removeAllTriangles(); TriangleIterator it; for (it = X.triangles_.begin(); it != X.triangles_.end(); ++it) newTriangle((*it)->getDescription()); // Make the gluings. long triPos, adjPos; Dim2Triangle* tri; Dim2Triangle* adjTri; NPerm3 adjPerm; int edge; triPos = 0; for (it = X.triangles_.begin(); it != X.triangles_.end(); ++it) { tri = *it; for (edge = 0; edge < 3; ++edge) { adjTri = tri->adjacentTriangle(edge); if (adjTri) { adjPos = X.triangleIndex(adjTri); adjPerm = tri->adjacentGluing(edge); if (adjPos > triPos || (adjPos == triPos && adjPerm[edge] > edge)) { triangles_[triPos]->joinTo(edge, triangles_[adjPos], adjPerm); } } } ++triPos; } // Properties: // None yet for 2-manifold triangulations. } void Dim2Triangulation::deleteTriangles() { for (TriangleIterator it = triangles_.begin(); it != triangles_.end(); ++it) delete *it; triangles_.clear(); } void Dim2Triangulation::deleteSkeleton() { for (VertexIterator it = vertices_.begin(); it != vertices_.end(); ++it) delete *it; for (EdgeIterator it = edges_.begin(); it != edges_.end(); ++it) delete *it; for (ComponentIterator it = components_.begin(); it != components_.end(); ++it) delete *it; for (BoundaryComponentIterator it = boundaryComponents_.begin(); it != boundaryComponents_.end(); ++it) delete *it; vertices_.clear(); edges_.clear(); components_.clear(); boundaryComponents_.clear(); calculatedSkeleton_ = false; } void Dim2Triangulation::clearAllProperties() { if (calculatedSkeleton_) deleteSkeleton(); } } // namespace regina regina-4.95/engine/dim2/dim2triangulation.h000644 000765 000024 00000162711 12236713375 020535 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2triangulation.h * \brief Deals with 2-manifold triangulations. */ #ifndef __DIM2TRIANGULATION_H #ifndef __DOXYGEN #define __DIM2TRIANGULATION_H #endif #include #include #include #include "regina-core.h" #include "generic/ngenerictriangulation.h" #include "packet/npacket.h" #include "utilities/nmarkedvector.h" #include "utilities/nproperty.h" // The following headers are necessary so that std::auto_ptr can invoke // destructors where necessary. #include "dim2/dim2isomorphism.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2BoundaryComponent; class Dim2Component; class Dim2Edge; class Dim2Triangle; class Dim2Triangulation; class Dim2Vertex; class NXMLDim2TriangulationReader; class NXMLPacketReader; /** * \addtogroup dim2 2-Manifold Triangulations * Triangulations of 2-manifolds. * @{ */ /** * Stores information about the 2-manifold triangulation packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef Dim2Triangulation Class; inline static const char* name() { return "2-Manifold Triangulation"; } }; /** * Stores the triangulation of a 2-manifold along with its * various cellular structures and other information. A 2-manifold * triangulation is built from triangular faces. * * When the triangulation is deleted, the corresponding * triangles, the cellular structure and all other properties * will be deallocated. * * Elements of the 1- and 0-skeletons (edges and vertices respectively) are * always temporary, as are components and * boundary components. Whenever a change occurs with the triangulation, * these objects will all be deleted and a new skeletal structure will be * calculated. The same is true of various other triangulation properties. */ class REGINA_API Dim2Triangulation : public NPacket, public NGenericTriangulation<2> { REGINA_PACKET(Dim2Triangulation, PACKET_DIM2TRIANGULATION) public: typedef std::vector::const_iterator TriangleIterator; /**< Used to iterate through triangles. */ typedef std::vector::const_iterator EdgeIterator; /**< Used to iterate through edges. */ typedef std::vector::const_iterator VertexIterator; /**< Used to iterate through vertices. */ typedef std::vector::const_iterator ComponentIterator; /**< Used to iterate through components. */ typedef std::vector::const_iterator BoundaryComponentIterator; /**< Used to iterate through boundary components. */ private: mutable bool calculatedSkeleton_; /**< Has the skeleton been calculated? */ NMarkedVector triangles_; /**< The triangular faces that form the triangulation. */ mutable NMarkedVector edges_; /**< The edges in the triangulation skeleton. */ mutable NMarkedVector vertices_; /**< The vertices in the triangulation skeleton. */ mutable NMarkedVector components_; /**< The components that form the triangulation. */ mutable NMarkedVector boundaryComponents_; /**< The components that form the boundary of the triangulation. */ mutable bool orientable_; /**< Is the triangulation orientable? */ public: /** * \name Constructors and Destructors */ /*@{*/ /** * Default constructor. * * Creates an empty triangulation. */ Dim2Triangulation(); /** * Copy constructor. * * Creates a new triangulation identical to the given triangulation. * The packet tree structure and packet label are \e not copied. * * @param cloneMe the triangulation to clone. */ Dim2Triangulation(const Dim2Triangulation& cloneMe); /** * "Magic" constructor that tries to find some way to interpret * the given string as a triangulation. * * At present, Regina understands the following types of strings * (and attempts to parse them in the following order): * * - isomorphism signatures (see fromIsoSig()). * * This list may grow in future versions of Regina. * * Regina will also set the packet label accordingly. * * If Regina cannot interpret the given string, this will be * left as the empty triangulation. * * @param description a string that describes a 2-manifold * triangulation. */ Dim2Triangulation(const std::string& description); /** * Destroys this triangulation. * * The constituent triangles, the cellular structure and all other * properties will also be deallocated. */ virtual ~Dim2Triangulation(); /*@}*/ /** * \name Packet Administration */ /*@{*/ virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; virtual bool dependsOnParent() const; /*@}*/ /** * \name Triangles */ /*@{*/ /** * Returns the number of triangular faces in the triangulation. * * @return the number of triangles. */ unsigned long getNumberOfTriangles() const; /** * A dimension-agnostic alias for getNumberOfTriangles(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getNumberOfTriangles() for further information. */ unsigned long getNumberOfSimplices() const; /** * Returns all triangular faces in the triangulation. * * The reference returned will remain valid for as long as the * triangulation exists, always reflecting the triangles currently * in the triangulation. * * \ifacespython This routine returns a python list. * * @return the list of all triangles. */ const std::vector& getTriangles() const; /** * A dimension-agnostic alias for getTriangles(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getTriangles() for further information. */ const std::vector& getSimplices() const; /** * Returns the triangle with the given index number in the * triangulation. Note that triangle indexing may change when * a triangle is added or removed from the triangulation. * * @param index specifies which triangle to return; this * value should be between 0 and getNumberOfTriangles()-1 inclusive. * @return the indexth triangle in the triangulation. */ Dim2Triangle* getTriangle(unsigned long index); /** * A dimension-agnostic alias for getTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getTriangle() for further information. */ Dim2Triangle* getSimplex(unsigned long index); /** * Returns the triangle with the given index number in the * triangulation. Note that triangle indexing may change when * a triangle is added or removed from the triangulation. * * @param index specifies which triangle to return; this * value should be between 0 and getNumberOfTriangles()-1 inclusive. * @return the indexth triangle in the triangulation. */ const Dim2Triangle* getTriangle(unsigned long index) const; /** * A dimension-agnostic alias for getTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See getTriangle() for further information. */ const Dim2Triangle* getSimplex(unsigned long index) const; /** * Returns the index of the given triangle in the triangulation. * * Note that triangle indexing may change when a triangle * is added or removed from the triangulation. * * \pre The given triangle is contained in this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. If you are passing the result of some other * routine that \e might return null (such as * Dim2Triangle::adjacentTriangle), it might be worth explicitly * testing for null beforehand. * * @param tri specifies which triangle to find in the triangulation. * @return the index of the specified triangle, where 0 is * the first triangle, 1 is the second and so on. */ long triangleIndex(const Dim2Triangle* tri) const; /** * A dimension-agnostic alias for triangleIndex(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See triangleIndex() for further information. */ long simplexIndex(const Dim2Triangle* tri) const; /** * Creates a new triangle and adds it to this triangulation. * The new triangle will have an empty description. * All three edges of the new triangle will be boundary edges. * * The new triangle will become the last triangle in this * triangulation. * * @return the new triangle. */ Dim2Triangle* newTriangle(); /** * A dimension-agnostic alias for newTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See newTriangle() for further information. */ Dim2Triangle* newSimplex(); /** * Creates a new triangle with the given description and adds * it to this triangulation. * All three edges of the new triangle will be boundary edges. * * @param desc the description to assign to the new triangle. * @return the new triangle. */ Dim2Triangle* newTriangle(const std::string& desc); /** * A dimension-agnostic alias for newTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See newTriangle() for further information. */ Dim2Triangle* newSimplex(const std::string& desc); /** * Removes the given triangle from the triangulation. * All triangles glued to this triangle will be unglued. * The triangle will be deallocated. * * \pre The given triangle exists in the triangulation. * * @param tri the triangle to remove. */ void removeTriangle(Dim2Triangle* tri); /** * A dimension-agnostic alias for removeTriangle(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See removeTriangle() for further information. */ void removeSimplex(Dim2Triangle* tri); /** * Removes the triangle with the given index number * from the triangulation. Note that triangle indexing may * change when a triangle is added or removed from the * triangulation. * * All triangles glued to this triangle will be unglued. * The triangle will be deallocated. * * @param index specifies which triangle to remove; this * should be between 0 and getNumberOfTriangles()-1 inclusive. */ void removeTriangleAt(unsigned long index); /** * A dimension-agnostic alias for removeTriangleAt(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See removeTriangleAt() for further information. */ void removeSimplexAt(unsigned long index); /** * Removes all triangles from the triangulation. * All triangles will be deallocated. */ void removeAllTriangles(); /** * A dimension-agnostic alias for removeAllTriangles(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 2-manifold triangulations means a triangle). * * See removeAllTriangles() for further information. */ void removeAllSimplices(); /** * Swaps the contents of this and the given triangulation. * That is, all triangles that belong to this triangulation * will be moved to \a other, and all triangles that belong to * \a other will be moved to this triangulation. * * All Dim2Triangle pointers or references will remain valid. * * @param other the triangulation whose contents should be * swapped with this. */ void swapContents(Dim2Triangulation& other); /** * Moves the contents of this triangulation into the given * destination triangulation, without destroying any pre-existing * contents. That is, all triangles that currently belong to * \a dest will remain there, and all triangles that belong to this * triangulation will be moved across as additional * triangles in \a dest. * * All Dim2Triangle pointers or references will remain valid. * After this operation, this triangulation will be empty. * * @param dest the triangulation to which triangles should be * moved. */ void moveContentsTo(Dim2Triangulation& dest); /*@}*/ /** * \name Skeletal Queries */ /*@{*/ /** * Returns the number of boundary components in this triangulation. * * @return the number of boundary components. */ unsigned long getNumberOfBoundaryComponents() const; /** * Returns the number of components in this triangulation. * * @return the number of components. */ unsigned long getNumberOfComponents() const; /** * Returns the number of vertices in this triangulation. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the number of edges in this triangulation. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of faces of the given dimension in this * triangulation. * * This template function is to assist with writing dimension-agnostic * code that can be reused to work in different dimensions. * * \pre the template argument \a dim is between 0 and 2 inclusive. * * \ifacespython Not present. * * @return the number of faces of the given dimension. */ template unsigned long getNumberOfFaces() const; /** * Returns all components of this triangulation. * * Bear in mind that each time the triangulation changes, the * components will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all components. */ const std::vector& getComponents() const; /** * Returns all boundary components of this triangulation. * * Bear in mind that each time the triangulation changes, the * boundary components will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all boundary components. */ const std::vector& getBoundaryComponents() const; /** * Returns all vertices of this triangulation. * * Bear in mind that each time the triangulation changes, the * vertices will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all vertices. */ const std::vector& getVertices() const; /** * Returns all edges of this triangulation. * * Bear in mind that each time the triangulation changes, the * edges will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all edges. */ const std::vector& getEdges() const; /** * Returns the requested triangulation component. * * Bear in mind that each time the triangulation changes, the * components will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired component, ranging from 0 * to getNumberOfComponents()-1 inclusive. * @return the requested component. */ Dim2Component* getComponent(unsigned long index) const; /** * Returns the requested triangulation boundary component. * * Bear in mind that each time the triangulation changes, the * boundary components will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired boundary component, ranging * from 0 to getNumberOfBoundaryComponents()-1 inclusive. * @return the requested boundary component. */ Dim2BoundaryComponent* getBoundaryComponent(unsigned long index) const; /** * Returns the requested triangulation vertex. * * Bear in mind that each time the triangulation changes, the * vertices will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired vertex, ranging from 0 * to getNumberOfVertices()-1 inclusive. * @return the requested vertex. */ Dim2Vertex* getVertex(unsigned long index) const; /** * Returns the requested triangulation edge. * * Bear in mind that each time the triangulation changes, the * edges will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired edge, ranging from 0 * to getNumberOfEdges()-1 inclusive. * @return the requested edge. */ Dim2Edge* getEdge(unsigned long index) const; /** * Returns the index of the given component in the triangulation. * * \pre The given component belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param component specifies which component to find in the * triangulation. * @return the index of the specified component, where 0 is the first * component, 1 is the second and so on. */ long componentIndex(const Dim2Component* component) const; /** * Returns the index of the given boundary component * in the triangulation. * * \pre The given boundary component belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param bc specifies which boundary component to find in the * triangulation. * @return the index of the specified boundary component, * where 0 is the first boundary component, 1 is the second and so on. */ long boundaryComponentIndex(const Dim2BoundaryComponent* bc) const; /** * Returns the index of the given vertex in the triangulation. * * \pre The given vertex belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param vertex specifies which vertex to find in the triangulation. * @return the index of the specified vertex, where 0 is the first * vertex, 1 is the second and so on. */ long vertexIndex(const Dim2Vertex* vertex) const; /** * Returns the index of the given edge in the triangulation. * * \pre The given edge belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param edge specifies which edge to find in the triangulation. * @return the index of the specified edge, where 0 is the first * edge, 1 is the second and so on. */ long edgeIndex(const Dim2Edge* edge) const; /*@}*/ /** * \name Isomorphism Testing */ /*@{*/ /** * Determines if this triangulation is combinatorially * isomorphic to the given triangulation. * * Specifically, this routine determines if there is a * one-to-one and onto boundary complete combinatorial * isomorphism from this triangulation to \a other. Boundary * complete isomorphisms are described in detail in the * Dim2Isomorphism class notes. * * In particular, note that this triangulation and \a other must * contain the same number of triangles for such an isomorphism * to exist. * * If a boundary complete isomorphism is found, the details of * this isomorphism are returned. The isomorphism is newly * constructed, and so to assist with memory management is * returned as a std::auto_ptr. Thus, to test whether an * isomorphism exists without having to explicitly deal with the * isomorphism itself, you can call * if (isIsomorphicTo(other).get()) and the newly * created isomorphism (if it exists) will be automatically * destroyed. * * @param other the triangulation to compare with this one. * @return details of the isomorphism if the two triangulations * are combinatorially isomorphic, or a null pointer otherwise. */ std::auto_ptr isIsomorphicTo( const Dim2Triangulation& other) const; /** * Determines if an isomorphic copy of this triangulation is * contained within the given triangulation, possibly as a * subcomplex of some larger component (or components). * * Specifically, this routine determines if there is a boundary * incomplete combinatorial isomorphism from this triangulation * to \a other. Boundary incomplete isomorphisms are described * in detail in the Dim2Isomorphism class notes. * * In particular, note that boundary edges of this triangulation * need not correspond to boundary edges of \a other, and that * \a other can contain more triangles than this triangulation. * * If a boundary incomplete isomorphism is found, the details of * this isomorphism are returned. The isomorphism is newly * constructed, and so to assist with memory management is * returned as a std::auto_ptr. Thus, to test whether an * isomorphism exists without having to explicitly deal with the * isomorphism itself, you can call * if (isContainedIn(other).get()) and the newly * created isomorphism (if it exists) will be automatically * destroyed. * * If more than one such isomorphism exists, only one will be * returned. For a routine that returns all such isomorphisms, * see findAllSubcomplexesIn(). * * @param other the triangulation in which to search for an * isomorphic copy of this triangulation. * @return details of the isomorphism if such a copy is found, * or a null pointer otherwise. */ std::auto_ptr isContainedIn( const Dim2Triangulation& other) const; /** * Finds all ways in which an isomorphic copy of this triangulation * is contained within the given triangulation, possibly as a * subcomplex of some larger component (or components). * * This routine behaves identically to isContainedIn(), except * that instead of returning just one isomorphism (which may be * boundary incomplete and need not be onto), all such isomorphisms * are returned. * * See the isContainedIn() notes for additional information. * * The isomorphisms that are found will be inserted into the * given list. These isomorphisms will be newly created, and * the caller of this routine is responsible for destroying * them. The given list will not be emptied before the new * isomorphisms are inserted. * * \ifacespython Not present. * * @param other the triangulation in which to search for * isomorphic copies of this triangulation. * @param results the list in which any isomorphisms found will * be stored. * @return the number of isomorphisms that were found. */ unsigned long findAllSubcomplexesIn(const Dim2Triangulation& other, std::list& results) const; /** * Relabel the triangles and their vertices so that this * triangulation is in canonical form. This is essentially * the lexicographically smallest labelling when the edge * gluings are written out in order. * * Two triangulations are isomorphic if and only if their canonical * forms are identical. * * The lexicographic ordering assumes that the edge gluings are * written in order of triangle index and then edge number. * Each gluing is written as the destination triangle index * followed by the gluing permutation (which in turn is written * as the images of 0,1,2 in order). * * \pre This routine currently works only when the triangulation * is connected. It may be extended to work with disconnected * triangulations in later versions of Regina. * * @return \c true if the triangulation was changed, or \c false * if the triangulation was in canonical form to begin with. */ bool makeCanonical(); /*@}*/ /** * \name Basic Properties */ /*@{*/ /** * Always returns \c true. * * This routine determines if this triangulation is valid; however, * there is nothing that can go wrong with vertex links in 2-manifold * triangulations, and so this routine always returns \c true. * * This no-op routine is provided for consistency with higher * dimensional triangulations, and to assist with writing * dimension-agnostic code. * * @return \c true. */ bool isValid() const; /** * Returns the Euler characteristic of this triangulation. * This will be evaluated as \a V-E+F. * * @return the Euler characteristic of this triangulation. */ long getEulerChar() const; /** * Determines if this triangulation is closed. * This is the case if and only if it has no boundary components. * * @return \c true if and only if this triangulation is closed. */ bool isClosed() const; /** * Determines if this triangulation is orientable. * * @return \c true if and only if this triangulation is orientable. */ bool isOrientable() const; /** * Determines if this triangulation is connected. * * @return \c true if and only if this triangulation is connected. */ bool isConnected() const; /** * Always returns \c false. * * This routine determines if this triangulation is ideal (has a * non-trivial vertex link); however, every vertex link in a * 2-manifold triangulation is either the interval or the * circle, and so ideal triangulations cannot exist. * Therefore this routine always returns \c false. * * This no-op routine is provided for consistency with higher * dimensional triangulations, and to assist with writing * dimension-agnostic code. * * @return \c false. */ bool isIdeal() const; /** * Determines whether this is a minimal triangulation of the * underlying 2-manifold; that is, it uses the fewest possible * triangles. * * Testing for minimality is simple in two dimensions (unlike * higher dimensions, where it becomes extremely difficult). * With the exception of the sphere, disc and projective plane * (which require a minimum of 2, 1 and 2 triangles respectively), * a closed triangulation is minimal if and only if it has one * vertex, and a bounded triangulation is minimal if and only if * it has one vertex per boundary component and no internal vertices. * * The proof is based on a simple Euler characteristic calculation, * whereby the number of triangles T is * T = 2Vi + Vb - 2C, where Vi and Vb * are the number of internal and boundary vertices respectively, * and where C is the Euler characteristic of the * underlying manifold. * * @return \c true if and only if this is a minimal triangulation. */ bool isMinimal() const; /*@}*/ /** * \name Building Triangulations */ /*@{*/ /** * Inserts a copy of the given triangulation into this triangulation. * * The new triangles will be inserted into this triangulation * in the order in which they appear in the given triangulation, * and the numbering of their vertices (0-2) will not change. * They will be given the same descriptions as appear in the * given triangulation. * * @param source the triangulation whose copy will be inserted. */ void insertTriangulation(const Dim2Triangulation& source); /** * Inserts into this triangulation a set of triangles and their * gluings as described by the given integer arrays. * * This routine is provided to make it easy to hard-code a * medium-sized triangulation in a C++ source file. All of the * pertinent data can be hard-coded into a pair of integer arrays at * the beginning of the source file, avoiding an otherwise tedious * sequence of many joinTo() calls. * * An additional \a nTriangles triangles will be inserted into * this triangulation. The relationships between these triangles * should be stored in the two arrays as follows. Note that the * new triangles are numbered from 0 to (\a nTriangles - 1), and * individual triangle edges are numbered from 0 to 2. * * The \a adjacencies array describes which triangle edges are * joined to which others. Specifically, adjacencies[f][e] * should contain the number of the triangle joined to edge \a e * of triangle \a f. If this edge is to be left as a * boundary edge, adjacencies[f][e] should be -1. * * The \a gluings array describes the particular gluing permutations * used when joining these triangle edges together. Specifically, * gluings[f][e][0..2] should describe the permutation * used to join edge \a e of triangle \a f to its adjacent * triangle. These three integers should be 0, 1 and 2 in some * order, so that gluings[f][e][i] contains the image of * \a i under this permutation. If edge \a e of triangle \a f * is to be left as a boundary edge, gluings[f][e][0..2] * may contain anything (and will be duly ignored). * * It is the responsibility of the caller of this routine to * ensure that the given arrays are correct and consistent. * No error checking will be performed by this routine. * * Note that, for an existing triangulation, dumpConstruction() * will output a pair of C++ arrays that can be copied into a * source file and used to reconstruct the triangulation via * this routine. * * \ifacespython Not present. * * @param nTriangles the number of additional triangles to insert. * @param adjacencies describes which of the new triangle edges * are to be identified. This array must have initial * dimension at least \a nTriangles. * @param gluings describes the specific gluing permutations by * which these new triangle edges should be identified. This * array must also have initial dimension at least \a nTriangles. */ void insertConstruction(unsigned long nTriangles, const int adjacencies[][3], const int gluings[][3][3]); /*@}*/ /** * \name Exporting Triangulations */ /*@{*/ /** * Constructs the isomorphism signature for this triangulation. * * An isomorphism signature is a compact text representation of * a triangulation. Unlike dehydrations for 3-manifold triangulations, * an isomorphism signature uniquely determines a triangulation up * to combinatorial isomorphism. That is, two 2-manifold * triangulations are combinatorially isomorphic if and only if * their isomorphism signatures are the same. * * The isomorphism signature is constructed entirely of * printable characters, and has length proportional to * n log n, where \a n is the number of triangles. * * Isomorphism signatures are more general than dehydrations: * they can be used with any triangulation (including closed, * bounded and/or disconnected triangulations, as well * as triangulations with large numbers of triangles). * * The time required to construct the isomorphism signature of a * triangulation is O(n^2 log^2 n). * * The routine fromIsoSig() can be used to recover a * triangulation from an isomorphism signature. The triangulation * recovered might not be identical to the original, but it will be * combinatorially isomorphic. * * If \a relabelling is non-null (i.e., it points to some * Dim2Isomorphism pointer \a p), then it will be modified to point * to a new Dim2Isomorphism that describes the precise relationship * between this triangulation and the reconstruction from fromIsoSig(). * Specifically, the triangulation that is reconstructed from * fromIsoSig() will be combinatorially identical to * relabelling.apply(this). * * \ifacespython The isomorphism argument is not present. * Instead there are two routines: fromIsoSig(), which returns a * string only, and fromIsoSigDetail(), which returns a pair * (signature, relabelling). * * \pre If \a relabelling is non-null, then this triangulation * must be non-empty and connected. The facility to return a * relabelling for disconnected triangulations may be added to * Regina in a later release. * * @param relabelling if non-null, this will be modified to point to a * new isomorphism describing the relationship between this * triangulation and that reconstructed from fromIsoSig(), as * described above. * @return the isomorphism signature of this triangulation. */ std::string isoSig(Dim2Isomorphism** relabelling = 0) const; /** * Returns C++ code that can be used with insertConstruction() * to reconstruct this triangulation. * * The code produced will consist of the following: * * - the declaration and initialisation of two integer arrays, * describing the triangle gluings in this trianguation; * - two additional lines that declare a new Dim2Triangulation and * call insertConstruction() to rebuild this triangulation. * * The main purpose of this routine is to generate the two integer * arrays, which can be tedious and error-prone to code up by hand. * * Note that the number of lines of code produced grows linearly * with the number of triangles. If this triangulation is very * large, the returned string will be very large as well. * * @return the C++ code that was generated. */ std::string dumpConstruction() const; /*@}*/ /** * \name Importing Triangulations */ /*@{*/ /** * Recovers a full triangulation from an isomorphism signature. * See isoSig() for more information on isomorphism signatures. * * The triangulation that is returned will be newly created. * * Calling isoSig() followed by fromIsoSig() is not guaranteed to * produce an identical triangulation to the original, but it * \e is guaranteed to produce a combinatorially isomorphic * triangulation. * * @param signature the isomorphism signature of the * triangulation to construct. Note that, unlike dehydration * strings for 3-manifold triangulations, case is important for * isomorphism signatures. * @return a newly allocated triangulation if the reconstruction was * successful, or null if the given string was not a valid * isomorphism signature. */ static Dim2Triangulation* fromIsoSig(const std::string& signature); using NGenericTriangulation<2>::isoSigComponentSize; /*@}*/ static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; /** * Turns this triangulation into a clone of the given triangulation. * The tree structure and label of this triangulation are not touched. * * @param from the triangulation from which this triangulation * will be cloned. */ void cloneFrom(const Dim2Triangulation& from); private: void deleteTriangles(); /**< Deallocates all triangles and empties the list. */ void deleteSkeleton(); /**< Deallocates all skeletal objects and empties all corresponding lists. */ /** * Clears any calculated properties and declares them all * unknown. All dynamic memory used for storing known * properties is deallocated. * * In most cases this routine is followed immediately by firing * a packet change event. */ virtual void clearAllProperties(); /** * Recalculates vertices, edges, components and * boundary components, as well as various other skeletal properties. * All appropriate lists are filled. * * \pre All skeletal lists are empty. */ void calculateSkeleton() const; /** * Internal to calculateSkeleton(). See the comments within * calculateSkeleton() for precisely what this routine does. */ void calculateComponents() const; /** * Internal to calculateSkeleton(). See the comments within * calculateSkeleton() for precisely what this routine does. */ void calculateVertices() const; /** * Internal to calculateSkeleton(). See the comments within * calculateSkeleton() for precisely what this routine does. */ void calculateBoundary() const; /** * Determines if an isomorphic copy of this triangulation is * contained within the given triangulation. * * If the argument \a completeIsomorphism is \c true, the * isomorphism must be onto and boundary complete. * That is, this triangulation must be combinatorially * isomorphic to the given triangulation. * * If the argument \a completeIsomorphism is \c false, the * isomorphism may be boundary incomplete and may or may not be * onto. That is, this triangulation must appear as a * subcomplex of the given triangulation, possibly with some * original boundary edges joined to new triangles. * * See the Dim2Isomorphism class notes for further details * regarding boundary complete and boundary incomplete * isomorphisms. * * The isomorphisms found, if any, will be appended to the * list \a results. This list will not be emptied before * calculations begin. All isomorphisms will be newly created, * and the caller of this routine is responsible for destroying * them. * * If \a firstOnly is passed as \c true, only the first * isomorphism found (if any) will be returned, after which the * routine will return immediately. Otherwise all isomorphisms * will be returned. * * @param other the triangulation in which to search for an * isomorphic copy of this triangulation. * @param results the list in which any isomorphisms found will * be stored. * @param completeIsomorphism \c true if isomorphisms must be * onto and boundary complete, or \c false if neither of these * restrictions should be imposed. * @param firstOnly \c true if only one isomorphism should be * returned (if any), or \c false if all isomorphisms should be * returned. * @return the total number of isomorphisms found. */ unsigned long findIsomorphisms(const Dim2Triangulation& other, std::list& results, bool completeIsomorphism, bool firstOnly) const; /** * Internal to findIsomorphisms(). * * Examines properties of the given triangle to find any * immediate evidence that \a src may not map to \a dest in a * boundary complete isomorphism (in which the vertices of \a src * are mapped to the vertices of \a dest according to the * permutation \a p). * * In particular, the degrees of vertices are examined. * * @param src the first of the two triangles to examine. * @param dest the second of the two triangles to examine. * @param p the permutation under which the vertices of \a src * must map to the vertices of \a dest. * @return \c true if no immediate incompatibilities between the * triangles were found, or \c false if properties of the * triangles were found that differ between \a src and \a dest. */ static bool compatibleTriangles(Dim2Triangle* src, Dim2Triangle* dest, NPerm3 p); friend class regina::Dim2Triangle; friend class regina::NXMLDim2TriangulationReader; }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "dim2/dim2triangle.h" #include "dim2/dim2edge.h" #include "dim2/dim2vertex.h" #include "dim2/dim2component.h" #include "dim2/dim2boundarycomponent.h" namespace regina { // Inline functions for Dim2Triangulation inline Dim2Triangulation::Dim2Triangulation() : NPacket(), calculatedSkeleton_(false) { } inline Dim2Triangulation::Dim2Triangulation(const Dim2Triangulation& cloneMe) : NPacket(), calculatedSkeleton_(false) { cloneFrom(cloneMe); } inline Dim2Triangulation::~Dim2Triangulation() { clearAllProperties(); deleteTriangles(); } inline void Dim2Triangulation::writeTextShort(std::ostream& out) const { out << "Triangulation with " << triangles_.size() << (triangles_.size() == 1 ? " triangle" : " triangles"); } inline bool Dim2Triangulation::dependsOnParent() const { return false; } inline unsigned long Dim2Triangulation::getNumberOfTriangles() const { return triangles_.size(); } inline unsigned long Dim2Triangulation::getNumberOfSimplices() const { return triangles_.size(); } inline const std::vector& Dim2Triangulation::getTriangles() const { return (const std::vector&)(triangles_); } inline const std::vector& Dim2Triangulation::getSimplices() const { return (const std::vector&)(triangles_); } inline Dim2Triangle* Dim2Triangulation::getTriangle(unsigned long index) { return triangles_[index]; } inline Dim2Triangle* Dim2Triangulation::getSimplex(unsigned long index) { return triangles_[index]; } inline const Dim2Triangle* Dim2Triangulation::getTriangle(unsigned long index) const { return triangles_[index]; } inline const Dim2Triangle* Dim2Triangulation::getSimplex(unsigned long index) const { return triangles_[index]; } inline long Dim2Triangulation::triangleIndex(const Dim2Triangle* tri) const { return tri->markedIndex(); } inline long Dim2Triangulation::simplexIndex(const Dim2Triangle* tri) const { return tri->markedIndex(); } inline Dim2Triangle* Dim2Triangulation::newTriangle() { ChangeEventSpan span(this); Dim2Triangle* tri = new Dim2Triangle(this); triangles_.push_back(tri); clearAllProperties(); return tri; } inline Dim2Triangle* Dim2Triangulation::newSimplex() { return newTriangle(); } inline Dim2Triangle* Dim2Triangulation::newTriangle(const std::string& desc) { ChangeEventSpan span(this); Dim2Triangle* tri = new Dim2Triangle(desc, this); triangles_.push_back(tri); clearAllProperties(); return tri; } inline Dim2Triangle* Dim2Triangulation::newSimplex(const std::string& desc) { return newTriangle(desc); } inline void Dim2Triangulation::removeTriangle(Dim2Triangle* tri) { ChangeEventSpan span(this); tri->isolate(); triangles_.erase(triangles_.begin() + triangleIndex(tri)); delete tri; clearAllProperties(); } inline void Dim2Triangulation::removeSimplex(Dim2Triangle* tri) { removeTriangle(tri); } inline void Dim2Triangulation::removeTriangleAt(unsigned long index) { ChangeEventSpan span(this); Dim2Triangle* ans = triangles_[index]; ans->isolate(); triangles_.erase(triangles_.begin() + index); delete ans; clearAllProperties(); } inline void Dim2Triangulation::removeSimplexAt(unsigned long index) { removeTriangleAt(index); } inline void Dim2Triangulation::removeAllTriangles() { ChangeEventSpan span(this); deleteTriangles(); clearAllProperties(); } inline void Dim2Triangulation::removeAllSimplices() { removeAllTriangles(); } inline unsigned long Dim2Triangulation::getNumberOfBoundaryComponents() const { if (! calculatedSkeleton_) calculateSkeleton(); return boundaryComponents_.size(); } inline unsigned long Dim2Triangulation::getNumberOfComponents() const { if (! calculatedSkeleton_) calculateSkeleton(); return components_.size(); } inline unsigned long Dim2Triangulation::getNumberOfVertices() const { if (! calculatedSkeleton_) calculateSkeleton(); return vertices_.size(); } inline unsigned long Dim2Triangulation::getNumberOfEdges() const { if (! calculatedSkeleton_) calculateSkeleton(); return edges_.size(); } template <> inline unsigned long Dim2Triangulation::getNumberOfFaces<0>() const { return getNumberOfVertices(); } template <> inline unsigned long Dim2Triangulation::getNumberOfFaces<1>() const { return getNumberOfEdges(); } template <> inline unsigned long Dim2Triangulation::getNumberOfFaces<2>() const { return getNumberOfTriangles(); } inline const std::vector& Dim2Triangulation::getComponents() const { if (! calculatedSkeleton_) calculateSkeleton(); return (const std::vector&)(components_); } inline const std::vector& Dim2Triangulation::getBoundaryComponents() const { if (! calculatedSkeleton_) calculateSkeleton(); return (const std::vector&)(boundaryComponents_); } inline const std::vector& Dim2Triangulation::getVertices() const { if (! calculatedSkeleton_) calculateSkeleton(); return (const std::vector&)(vertices_); } inline const std::vector& Dim2Triangulation::getEdges() const { if (! calculatedSkeleton_) calculateSkeleton(); return (const std::vector&)(edges_); } inline Dim2Component* Dim2Triangulation::getComponent(unsigned long index) const { if (! calculatedSkeleton_) calculateSkeleton(); return components_[index]; } inline Dim2BoundaryComponent* Dim2Triangulation::getBoundaryComponent( unsigned long index) const { if (! calculatedSkeleton_) calculateSkeleton(); return boundaryComponents_[index]; } inline Dim2Vertex* Dim2Triangulation::getVertex(unsigned long index) const { if (! calculatedSkeleton_) calculateSkeleton(); return vertices_[index]; } inline Dim2Edge* Dim2Triangulation::getEdge(unsigned long index) const { if (! calculatedSkeleton_) calculateSkeleton(); return edges_[index]; } inline long Dim2Triangulation::componentIndex(const Dim2Component* component) const { return component->markedIndex(); } inline long Dim2Triangulation::boundaryComponentIndex( const Dim2BoundaryComponent* boundaryComponent) const { return boundaryComponent->markedIndex(); } inline long Dim2Triangulation::vertexIndex(const Dim2Vertex* vertex) const { return vertex->markedIndex(); } inline long Dim2Triangulation::edgeIndex(const Dim2Edge* edge) const { return edge->markedIndex(); } inline bool Dim2Triangulation::isValid() const { return true; } inline long Dim2Triangulation::getEulerChar() const { if (! calculatedSkeleton_) calculateSkeleton(); // Cast away the unsignedness of std::vector::size(). return static_cast(vertices_.size()) - static_cast(edges_.size()) + static_cast(triangles_.size()); } inline bool Dim2Triangulation::isClosed() const { if (! calculatedSkeleton_) calculateSkeleton(); return boundaryComponents_.empty(); } inline bool Dim2Triangulation::isOrientable() const { if (! calculatedSkeleton_) calculateSkeleton(); return orientable_; } inline bool Dim2Triangulation::isIdeal() const { return false; } inline bool Dim2Triangulation::isConnected() const { if (! calculatedSkeleton_) calculateSkeleton(); return (components_.size() <= 1); } inline NPacket* Dim2Triangulation::internalClonePacket(NPacket*) const { return new Dim2Triangulation(*this); } inline std::string Dim2Triangulation::isoSig(Dim2Isomorphism** relabelling) const { return NGenericTriangulation<2>::isoSig(*this, relabelling); } inline Dim2Triangulation* Dim2Triangulation::fromIsoSig( const std::string& signature) { return NGenericTriangulation<2>::fromIsoSig(signature); } } // namespace regina #endif regina-4.95/engine/dim2/dim2vertex.cpp000644 000765 000024 00000005267 12234011536 017513 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2vertex.h" namespace regina { void Dim2Vertex::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << "Appears as:" << std::endl; std::deque::const_iterator it; for (it = emb_.begin(); it != emb_.end(); ++it) out << " " << it->getTriangle()->markedIndex() << " (" << it->getVertex() << ')' << std::endl; } } // namespace regina regina-4.95/engine/dim2/dim2vertex.h000644 000765 000024 00000026031 12234011536 017150 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/dim2vertex.h * \brief Deals with vertices in a 2-manifold triangulation. */ #ifndef __DIM2VERTEX_H #ifndef __DOXYGEN #define __DIM2VERTEX_H #endif #include #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm3.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2BoundaryComponent; class Dim2Component; class Dim2Triangle; class Dim2Triangulation; /** * \weakgroup dim2 * @{ */ /** * Details how a vertex in the skeleton of a 2-manifold triangulation forms * part of an individual triangle. */ class REGINA_API Dim2VertexEmbedding { private: Dim2Triangle* triangle_; /**< The triangle in which this vertex is contained. */ int vertex_; /**< The vertex number of the triangle that is this vertex. */ public: /** * Default constructor. The embedding descriptor created is * unusable until it has some data assigned to it using * operator =. * * \ifacespython Not present. */ Dim2VertexEmbedding(); /** * Creates an embedding descriptor containing the given data. * * @param tri the triangle in which this vertex is contained. * @param vertex the vertex number of \a tri that is this vertex. */ Dim2VertexEmbedding(Dim2Triangle* tri, int vertex); /** * Creates an embedding descriptor containing the same data as * the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ Dim2VertexEmbedding(const Dim2VertexEmbedding& cloneMe); /** * Assigns to this embedding descriptor the same data as is * contained in the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ Dim2VertexEmbedding& operator =(const Dim2VertexEmbedding& cloneMe); /** * Returns the triangle in which this vertex is contained. * * @return the triangle. */ Dim2Triangle* getTriangle() const; /** * Returns the vertex number within getTriangle() that is this vertex. * * @return the vertex number that is this vertex. */ int getVertex() const; /** * Returns a permutation that maps 0 to the vertex number within * getTriangle() that is this vertex. This permutation also maps * (1,2) to the two remaining triangle vertices in a manner that * preserves orientation as you walk around the vertex. * See Dim2Triangle::getVertexMapping() for details. * * @return a permutation that maps 0 to the corresponding * vertex number of getTriangle(). */ NPerm3 getVertices() const; }; /** * Represents a vertex in the skeleton of a 2-manifold triangulation. * Vertices are highly temporary; once a triangulation changes, all its * vertex objects will be deleted and new ones will be created. */ class REGINA_API Dim2Vertex : public ShareableObject, public NMarkedElement { private: std::deque emb_; /**< A list of descriptors telling how this vertex forms a part of each individual triangle that it belongs to. */ Dim2Component* component_; /**< The component that this vertex is a part of. */ Dim2BoundaryComponent* boundaryComponent_; /**< The boundary component that this vertex is a part of, or 0 if this vertex is internal. */ public: /** * Returns the list of descriptors detailing how this vertex forms * a part of various triangles in the triangulation. * Note that if this vertex represents multiple vertices of a * particular triangle, then there will be multiple embedding * descriptors in the list regarding that triangle. * * \ifacespython This routine returns a python list. * * @return the list of embedding descriptors. * @see Dim2VertexEmbedding */ const std::deque& getEmbeddings() const; /** * Returns the number of descriptors in the list returned by * getEmbeddings(). Note that this is identical to getDegree(). * * @return the number of embedding descriptors. */ unsigned long getNumberOfEmbeddings() const; /** * Returns the requested descriptor from the list returned by * getEmbeddings(). * * @param index the index of the requested descriptor. This * should be between 0 and getNumberOfEmbeddings()-1 inclusive. * @return the requested embedding descriptor. */ const Dim2VertexEmbedding& getEmbedding(unsigned long index) const; /** * Returns the triangulation to which this vertex belongs. * * @return the triangulation containing this vertex. */ Dim2Triangulation* getTriangulation() const; /** * Returns the component of the triangulation to which this * vertex belongs. * * @return the component containing this vertex. */ Dim2Component* getComponent() const; /** * Returns the boundary component of the triangulation to which * this vertex belongs. * * @return the boundary component containing this vertex, * or 0 if this vertex is not on the boundary of the triangulation. */ Dim2BoundaryComponent* getBoundaryComponent() const; /** * Returns the degree of this vertex. Note that this is * identical to getNumberOfEmbeddings(). * * @return the degree of this vertex. */ unsigned long getDegree() const; /** * Determines if this vertex lies on the boundary of the * triangulation. * * @return \c true if and only if this vertex lies on the boundary. */ bool isBoundary() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new vertex and marks it as belonging to the * given triangulation component. * * @param component the triangulation component to which this * vertex belongs. */ Dim2Vertex(Dim2Component* component); friend class Dim2Triangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "dim2/dim2triangle.h" namespace regina { // Inline functions for Dim2VertexEmbedding inline Dim2VertexEmbedding::Dim2VertexEmbedding() : triangle_(0) { } inline Dim2VertexEmbedding::Dim2VertexEmbedding(Dim2Triangle* tri, int vertex) : triangle_(tri), vertex_(vertex) { } inline Dim2VertexEmbedding::Dim2VertexEmbedding( const Dim2VertexEmbedding& cloneMe) : triangle_(cloneMe.triangle_), vertex_(cloneMe.vertex_) { } inline Dim2VertexEmbedding& Dim2VertexEmbedding::operator = (const Dim2VertexEmbedding& cloneMe) { triangle_ = cloneMe.triangle_; vertex_ = cloneMe.vertex_; return *this; } inline Dim2Triangle* Dim2VertexEmbedding::getTriangle() const { return triangle_; } inline int Dim2VertexEmbedding::getVertex() const { return vertex_; } // Inline functions for Dim2Vertex inline Dim2Vertex::Dim2Vertex(Dim2Component* component) : component_(component), boundaryComponent_(0) { } inline const std::deque& Dim2Vertex::getEmbeddings() const { return emb_; } inline unsigned long Dim2Vertex::getNumberOfEmbeddings() const { return emb_.size(); } inline const Dim2VertexEmbedding& Dim2Vertex::getEmbedding(unsigned long index) const { return emb_[index]; } inline NPerm3 Dim2VertexEmbedding::getVertices() const { return triangle_->getVertexMapping(vertex_); } inline Dim2Triangulation* Dim2Vertex::getTriangulation() const { return emb_.front().getTriangle()->getTriangulation(); } inline Dim2Component* Dim2Vertex::getComponent() const { return component_; } inline Dim2BoundaryComponent* Dim2Vertex::getBoundaryComponent() const { return boundaryComponent_; } inline unsigned long Dim2Vertex::getDegree() const { return emb_.size(); } inline bool Dim2Vertex::isBoundary() const { return (boundaryComponent_ != 0); } inline void Dim2Vertex::writeTextShort(std::ostream& out) const { out << (boundaryComponent_ ? "Boundary " : "Internal ") << "vertex of degree " << emb_.size(); } } // namespace regina #endif regina-4.95/engine/dim2/isomorphic.cpp000644 000765 000024 00000041030 12234011536 017562 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "dim2/dim2isomorphism.h" #include "dim2/dim2triangulation.h" namespace regina { std::auto_ptr Dim2Triangulation::isIsomorphicTo( const Dim2Triangulation& other) const { std::list results; if (findIsomorphisms(other, results, true, true)) return std::auto_ptr(results.front()); else return std::auto_ptr(0); } std::auto_ptr Dim2Triangulation::isContainedIn( const Dim2Triangulation& other) const { std::list results; if (findIsomorphisms(other, results, false, true)) return std::auto_ptr(results.front()); else return std::auto_ptr(0); } unsigned long Dim2Triangulation::findAllSubcomplexesIn( const Dim2Triangulation& other, std::list& results) const { return findIsomorphisms(other, results, false, false); } unsigned long Dim2Triangulation::findIsomorphisms( const Dim2Triangulation& other, std::list& results, bool completeIsomorphism, bool firstOnly) const { if (! calculatedSkeleton_) calculateSkeleton(); if (! other.calculatedSkeleton_) other.calculateSkeleton(); // Deal with the empty triangulation first. if (triangles_.empty()) { if (completeIsomorphism && ! other.triangles_.empty()) return 0; results.push_back(new Dim2Isomorphism(0)); return 1; } // Basic property checks. Unfortunately, if we allow boundary // incomplete isomorphisms then we can't test that many properties. if (completeIsomorphism) { // Must be boundary complete, 1-to-1 and onto. // That is, combinatorially the two triangulations must be // identical. if (triangles_.size() != other.triangles_.size()) return 0; if (edges_.size() != other.edges_.size()) return 0; if (vertices_.size() != other.vertices_.size()) return 0; if (components_.size() != other.components_.size()) return 0; if (boundaryComponents_.size() != other.boundaryComponents_.size()) return 0; if (orientable_ ^ other.orientable_) return 0; // Test degree sequences and the like. std::map map1; std::map map2; std::map::iterator mapIt; { VertexIterator it; for (it = vertices_.begin(); it != vertices_.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } for (it = other.vertices_.begin(); it != other.vertices_.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } { ComponentIterator it; for (it = components_.begin(); it != components_.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfTriangles(), 0)).first; (*mapIt).second++; } for (it = other.components_.begin(); it != other.components_.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfTriangles(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } { BoundaryComponentIterator it; for (it = boundaryComponents_.begin(); it != boundaryComponents_.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfEdges(), 0)).first; (*mapIt).second++; } for (it = other.boundaryComponents_.begin(); it != other.boundaryComponents_.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfEdges(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } } else { // May be boundary incomplete, and need not be onto. // Not much we can test for unfortunately. if (triangles_.size() > other.triangles_.size()) return 0; if ((! orientable_) && other.orientable_) return 0; } // Start searching for the isomorphism. // From the tests above, we are guaranteed that both triangulations // have at least one triangle. unsigned long nResults = 0; unsigned long nTriangles = triangles_.size(); unsigned long nDestTriangles = other.triangles_.size(); unsigned long nComponents = components_.size(); unsigned i; Dim2Isomorphism iso(nTriangles); for (i = 0; i < nTriangles; i++) iso.simpImage(i) = -1; // Which source component does each destination triangle correspond to? long* whichComp = new long[nDestTriangles]; std::fill(whichComp, whichComp + nDestTriangles, -1); // The image of the first source triangle of each component. The // remaining images can be derived by following gluings. unsigned long* startTri = new unsigned long[nComponents]; std::fill(startTri, startTri + nComponents, 0); int* startPerm = new int[nComponents]; std::fill(startPerm, startPerm + nComponents, 0); // The triangles whose neighbours must be processed when filling // out the current component. std::queue toProcess; // Temporary variables. unsigned long compSize; Dim2Triangle* tri; Dim2Triangle* adj; Dim2Triangle* destTri; Dim2Triangle* destAdj; unsigned long myTriIndex, adjIndex; unsigned long destTriIndex, destAdjIndex; NPerm3 triPerm, adjPerm; int edge; bool broken; long comp = 0; while (comp >= 0) { // Continue trying to find a mapping for the current component. // The next mapping to try is the one that starts with // startTri[comp] and startPerm[comp]. if (comp == static_cast(nComponents)) { // We have an isomorphism!!! results.push_back(new Dim2Isomorphism(iso)); if (firstOnly) { delete[] whichComp; delete[] startTri; delete[] startPerm; return 1; } else nResults++; // Back down to the previous component, and clear the // mapping for that previous component so we can make way // for a new one. // Since nComponents > 0, we are guaranteed that comp > 0 also. comp--; for (i = 0; i < nTriangles; i++) if (iso.simpImage(i) >= 0 && whichComp[iso.simpImage(i)] == comp) { whichComp[iso.simpImage(i)] = -1; iso.simpImage(i) = -1; } startPerm[comp]++; continue; } // Sort out the results of any previous startPerm++. if (startPerm[comp] == 6) { // Move on to the next destination triangle. startTri[comp]++; startPerm[comp] = 0; } // Be sure we're looking at a triangle we can use. compSize = components_[comp]->getNumberOfTriangles(); if (completeIsomorphism) { // Conditions: // 1) The destination triangle is unused. // 2) The component sizes match precisely. while (startTri[comp] < nDestTriangles && (whichComp[startTri[comp]] >= 0 || other.triangles_[startTri[comp]]->getComponent()-> getNumberOfTriangles() != compSize)) startTri[comp]++; } else { // Conditions: // 1) The destination triangle is unused. // 2) The destination component is at least as large as // the source component. while (startTri[comp] < nDestTriangles && (whichComp[startTri[comp]] >= 0 || other.triangles_[startTri[comp]]->getComponent()-> getNumberOfTriangles() < compSize)) startTri[comp]++; } // Have we run out of possibilities? if (startTri[comp] == nDestTriangles) { // No more possibilities for filling this component. // Move back to the previous component, and clear the // mapping for that previous component. startTri[comp] = 0; startPerm[comp] = 0; comp--; if (comp >= 0) { for (i = 0; i < nTriangles; i++) if (iso.simpImage(i) >= 0 && whichComp[iso.simpImage(i)] == comp) { whichComp[iso.simpImage(i)] = -1; iso.simpImage(i) = -1; } startPerm[comp]++; } continue; } // Try to fill the image of this component based on the selected // image of its first source triangle. // Note that there is only one way of doing this (as seen by // following adjacent triangle gluings). It either works or // it doesn't. myTriIndex = triangleIndex(components_[comp]->getTriangle(0)); whichComp[startTri[comp]] = comp; iso.simpImage(myTriIndex) = startTri[comp]; iso.facetPerm(myTriIndex) = NPerm3::S3[startPerm[comp]]; toProcess.push(myTriIndex); broken = false; while ((! broken) && (! toProcess.empty())) { myTriIndex = toProcess.front(); toProcess.pop(); tri = triangles_[myTriIndex]; triPerm = iso.facetPerm(myTriIndex); destTriIndex = iso.simpImage(myTriIndex); destTri = other.triangles_[destTriIndex]; // If we are after a complete isomorphism, we might as well // test whether the lower-dimensional face degrees match. if (completeIsomorphism && ! compatibleTriangles(tri, destTri, triPerm)) { broken = true; break; } for (edge = 0; edge < 3; ++edge) { adj = tri->adjacentTriangle(edge); if (adj) { // There is an adjacent source triangle. // Is there an adjacent destination triangle? destAdj = destTri->adjacentTriangle(triPerm[edge]); if (! destAdj) { broken = true; break; } // Work out what the isomorphism *should* say. adjIndex = triangleIndex(adj); destAdjIndex = other.triangleIndex(destAdj); adjPerm = destTri->adjacentGluing(triPerm[edge]) * triPerm * tri->adjacentGluing(edge).inverse(); if (iso.simpImage(adjIndex) >= 0) { // We've already decided upon an image for this // source triangle. Does it match? if (static_cast(destAdjIndex) != iso.simpImage(adjIndex) || adjPerm != iso.facetPerm(adjIndex)) { broken = true; break; } } else if (whichComp[destAdjIndex] >= 0) { // We haven't decided upon an image for this // source triangle but the destination // triangle has already been used. broken = true; break; } else { // We haven't seen either the source or the // destination triangle. whichComp[destAdjIndex] = comp; iso.simpImage(adjIndex) = destAdjIndex; iso.facetPerm(adjIndex) = adjPerm; toProcess.push(adjIndex); } } else if (completeIsomorphism) { // There is no adjacent source triangle, and we // are after a boundary complete isomorphism. // There had better be no adjacent destination // triangle also. if (destTri->adjacentTriangle(triPerm[edge])) { broken = true; break; } } } } if (! broken) { // Therefore toProcess is empty. // The image for this component was successfully filled out. // Move on to the next component. comp++; } else { // The image for this component was not successfully filled out. // Undo our partially created image, and then try another // starting image for this component. while (! toProcess.empty()) toProcess.pop(); for (i = 0; i < nTriangles; i++) if (iso.simpImage(i) >= 0 && whichComp[iso.simpImage(i)] == comp) { whichComp[iso.simpImage(i)] = -1; iso.simpImage(i) = -1; } startPerm[comp]++; } } // All out of options. delete[] whichComp; delete[] startTri; delete[] startPerm; return nResults; } bool Dim2Triangulation::compatibleTriangles( Dim2Triangle* src, Dim2Triangle* dest, NPerm3 p) { for (int vertex = 0; vertex < 3; vertex++) if (src->getVertex(vertex)->getNumberOfEmbeddings() != dest->getVertex(p[vertex])->getNumberOfEmbeddings()) return false; return true; } } // namespace regina regina-4.95/engine/dim2/nxmldim2trireader.cpp000644 000765 000024 00000014443 12236524106 021056 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "algebra/nxmlalgebrareader.h" #include "dim2/nxmldim2trireader.h" #include "utilities/stringutils.h" namespace regina { /** * A unique namespace containing various specific-task packet readers. */ namespace { /** * Reads a single triangle with its name and gluings. */ class Dim2TriangleReader : public NXMLElementReader { private: Dim2Triangulation* tri_; Dim2Triangle* triangle_; public: Dim2TriangleReader(Dim2Triangulation* tri, unsigned whichTri) : tri_(tri), triangle_(tri->getTriangles()[whichTri]) { } virtual void startElement(const std::string&, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { triangle_->setDescription(props.lookup("desc")); } virtual void initialChars(const std::string& chars) { std::vector tokens; if (basicTokenise(back_inserter(tokens), chars) != 6) return; long triIndex, permCode; NPerm3 perm; Dim2Triangle* adjTri; int adjEdge; for (int k = 0; k < 3; ++k) { if (! valueOf(tokens[2 * k], triIndex)) continue; if (! valueOf(tokens[2 * k + 1], permCode)) continue; if (triIndex < 0 || triIndex >= static_cast(tri_->getNumberOfTriangles())) continue; if (! NPerm3::isPermCode(permCode)) continue; perm.setPermCode(permCode); adjTri = tri_->getTriangles()[triIndex]; adjEdge = perm[k]; if (adjTri == triangle_ && adjEdge == k) continue; if (triangle_->adjacentTriangle(k)) continue; if (adjTri->adjacentTriangle(adjEdge)) continue; triangle_->joinTo(k, adjTri, perm); } } }; /** * Reads an entire set of triangles with their names and gluings. */ class Dim2TrianglesReader : public NXMLElementReader { private: Dim2Triangulation* tri_; unsigned readTriangles_; public: Dim2TrianglesReader(Dim2Triangulation* tri) : tri_(tri), readTriangles_(0) { } virtual void startElement(const std::string& /* tagName */, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { long nTriangles; if (valueOf(props.lookup("ntriangles"), nTriangles)) for ( ; nTriangles > 0; --nTriangles) tri_->newTriangle(); } virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "triangle") { if (readTriangles_ < tri_->getNumberOfTriangles()) return new Dim2TriangleReader(tri_, readTriangles_++); else return new NXMLElementReader(); } else return new NXMLElementReader(); } }; } NXMLElementReader* NXMLDim2TriangulationReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "triangles") return new Dim2TrianglesReader(tri_); return new NXMLElementReader(); } void NXMLDim2TriangulationReader::endContentSubElement(const std::string&, NXMLElementReader*) { } NXMLPacketReader* Dim2Triangulation::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLDim2TriangulationReader(resolver); } } // namespace regina regina-4.95/engine/dim2/nxmldim2trireader.h000644 000765 000024 00000007524 12236524106 020525 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file dim2/nxmldim2trireader.h * \brief Deals with parsing XML data for 4-manifold triangulation packets. */ #ifndef __NXMLDIM2TRIREADER_H #ifndef __DOXYGEN #define __NXMLDIM2TRIREADER_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "dim2/dim2triangulation.h" namespace regina { /** * \weakgroup dim2 * @{ */ /** * An XML packet reader that reads a single 2-manifold triangulation. * * \ifacespython Not present. */ class REGINA_API NXMLDim2TriangulationReader : public NXMLPacketReader { private: Dim2Triangulation* tri_; /**< The triangulation currently being read. */ public: /** * Creates a new triangulation reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLDim2TriangulationReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLDim2TriangulationReader inline NXMLDim2TriangulationReader::NXMLDim2TriangulationReader( NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), tri_(new Dim2Triangulation()) { } inline NPacket* NXMLDim2TriangulationReader::getPacket() { return tri_; } } // namespace regina #endif regina-4.95/engine/dim2/skeleton.cpp000644 000765 000024 00000035734 12234011536 017250 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "dim2/dim2triangulation.h" namespace regina { void Dim2Triangulation::calculateSkeleton() const { // Triangulations are orientable until proven otherwise. orientable_ = true; // Set this now so that any triangle query routines do not try to // recursively recompute the skeleton again. calculatedSkeleton_ = true; // Get rid of the empty triangulation now, so that all the helper routines // can happily assume at least one triangle. if (triangles_.empty()) return; // Off we go! calculateComponents(); // Sets: // - components_ // - edges_ // - orientable_ // - Dim2Component::edges_ // - Dim2Component::orientable_ // - Dim2Triangle::component_ // - Dim2Triangle::orientation_ // - Dim2Triangle::edge_ // - Dim2Triangle::edgeMapping_ // - all Dim2Edge members except boundaryComponent_ calculateVertices(); // Sets: // - vertices_ // - Dim2Component::vertices_ // - Dim2Triangle::vertex_ // - Dim2Triangle::vertexMapping_ // - all Dim2Vertex members except boundaryComponent_ calculateBoundary(); // Sets: // - boundaryComponents_ // - Dim2Component::boundaryComponents_ // - Dim2 [ Edge, Vertex ]::boundaryComponent_ // - all Dim2BoundaryComponent members } void Dim2Triangulation::calculateComponents() const { TriangleIterator it; for (it = triangles_.begin(); it != triangles_.end(); ++it) { (*it)->component_ = 0; std::fill((*it)->edge_, (*it)->edge_ + 3, static_cast(0)); } Dim2Component* label; Dim2Triangle** stack = new Dim2Triangle*[triangles_.size()]; unsigned stackSize = 0; Dim2Triangle *loopTri, *tri, *adjTri; Dim2Edge* e; int edge, adjEdge, adjOrientation; for (it = triangles_.begin(); it != triangles_.end(); ++it) { loopTri = *it; if (loopTri->component_) continue; label = new Dim2Component(); components_.push_back(label); // Run a depth-first search from this triangle to // completely enumerate all triangles in this component. // // Since we are walking from one triangle to another via // edges, we might as well collect information on the // edges while we're at it. loopTri->component_ = label; label->triangles_.push_back(loopTri); loopTri->orientation_ = 1; stack[0] = loopTri; stackSize = 1; while (stackSize > 0) { tri = stack[--stackSize]; for (edge = 0; edge < 3; ++edge) { // Have we already checked out this edge from the other side? if (tri->edge_[edge]) continue; // Make a new edge, but leave the embeddings and // mappings until we see which side we're on. This is so // that the edges *look* as though we enumerated them in // lexicographical order (instead of via a depth-first search // through each triangulation component). e = new Dim2Edge(tri->component_); adjTri = tri->adjacentTriangle(edge); if (adjTri) { // We have an adjacent triangle. adjEdge = tri->adjacentEdge(edge); tri->edge_[edge] = e; adjTri->edge_[adjEdge] = e; // Choose an edge mapping according to which side // comes "first" lexicographically. Note that edge 2 is // really edge 01, and so is "less than" edge 0, which // is really edge 12. In short, edges get ordered in // reverse. if (tri->markedIndex() < adjTri->markedIndex() || (tri->markedIndex() == adjTri->markedIndex() && edge > adjEdge)) { // tri comes first. tri->edgeMapping_[edge] = Dim2Edge::ordering[edge]; adjTri->edgeMapping_[adjEdge] = tri->adjacentGluing(edge) * Dim2Edge::ordering[edge]; e->emb_[0] = Dim2EdgeEmbedding(tri, edge); e->emb_[1] = Dim2EdgeEmbedding(adjTri, adjEdge); } else { // adjTri comes first. adjTri->edgeMapping_[adjEdge] = Dim2Edge::ordering[adjEdge]; tri->edgeMapping_[edge] = adjTri->adjacentGluing(adjEdge) * Dim2Edge::ordering[adjEdge]; e->emb_[0] = Dim2EdgeEmbedding(adjTri, adjEdge); e->emb_[1] = Dim2EdgeEmbedding(tri, edge); } e->nEmb_ = 2; // Deal with orientations and connected components. adjOrientation = (tri->adjacentGluing(edge).sign() == 1 ? - tri->orientation_ : tri->orientation_); if (adjTri->component_) { if (adjOrientation != adjTri->orientation_) orientable_ = label->orientable_ = false; } else { // Wheee! A triangle we haven't seen before. adjTri->component_ = label; label->triangles_.push_back(adjTri); adjTri->orientation_ = adjOrientation; stack[stackSize++] = adjTri; } } else { // This is a boundary edge. tri->edge_[edge] = e; tri->edgeMapping_[edge] = Dim2Edge::ordering[edge]; e->emb_[0] = Dim2EdgeEmbedding(tri, edge); e->nEmb_ = 1; } } } } // Now run through again and number the edges (i.e., insert them // into the main list) in lexicographical order. Again, edges are // ordered in reverse. for (it = triangles_.begin(); it != triangles_.end(); ++it) { tri = *it; for (edge = 2; edge >= 0; --edge) { e = tri->edge_[edge]; if (e->nEmb_ == 2 && (e->emb_[0].getTriangle() != tri || e->emb_[0].getEdge() != edge)) continue; edges_.push_back(e); tri->component_->edges_.push_back(e); } } delete[] stack; } void Dim2Triangulation::calculateVertices() const { TriangleIterator it; int loopVtx; for (it = triangles_.begin(); it != triangles_.end(); ++it) for (loopVtx = 0; loopVtx < 3; ++loopVtx) (*it)->vertex_[loopVtx] = 0; Dim2Vertex* label; Dim2Triangle *loopTri, *tri, *adjTri; int adjVertex; NPerm3 map, adjMap; int dir, exitEdge; for (it = triangles_.begin(); it != triangles_.end(); ++it) { loopTri = *it; for (loopVtx = 2; loopVtx >= 0; --loopVtx) { if (loopTri->vertex_[loopVtx]) continue; label = new Dim2Vertex(loopTri->component_); vertices_.push_back(label); loopTri->component_->vertices_.push_back(label); // Since triangle vertices are joined together in a loop, the // depth-first search is really just a straight line in either // direction. We therefore do away with the usual stack and // just keep track of the next triangle to process in the current // direction. loopTri->vertex_[loopVtx] = label; loopTri->vertexMapping_[loopVtx] = NPerm3(loopVtx, (loopVtx + 1) % 3, (loopVtx + 2) % 3); label->emb_.push_back(Dim2VertexEmbedding(loopTri, loopVtx)); for (dir = 0; dir < 2; ++dir) { // Start at the start and walk in one particular direction. tri = loopTri; map = tri->vertexMapping_[loopVtx]; while (true) { // Move through to the next triangle. exitEdge = map[dir == 0 ? 1 : 2]; adjTri = tri->adjacentTriangle(exitEdge); if (! adjTri) break; adjMap = tri->adjacentGluing(exitEdge) * map * NPerm3(0, 2, 1); adjVertex = adjMap[0]; if (adjTri->vertex_[adjVertex]) { // We looped right around. break; } // We have not yet seen this triangle vertex. Label it. adjTri->vertex_[adjVertex] = label; adjTri->vertexMapping_[adjVertex] = adjMap; if (dir == 0) label->emb_.push_back(Dim2VertexEmbedding( adjTri, adjVertex)); else label->emb_.push_front(Dim2VertexEmbedding( adjTri, adjVertex)); tri = adjTri; map = adjMap; } } } } } void Dim2Triangulation::calculateBoundary() const { // Are there any boundary edges at all? long nBdry = 2 * edges_.size() - 3 * triangles_.size(); if (nBdry == 0) return; Dim2BoundaryComponent* label; EdgeIterator it; Dim2Triangle *tri, *adjTri; int edgeId, adjEdgeId; int vertexId, adjVertexId; Dim2Edge *edge, *adjEdge; Dim2Vertex* vertex; Dim2VertexEmbedding vertexEmb; for (it = edges_.begin(); it != edges_.end(); ++it) { edge = *it; // We only care about boundary edges that we haven't yet seen.. if (edge->nEmb_ == 2 || edge->boundaryComponent_) continue; label = new Dim2BoundaryComponent(); boundaryComponents_.push_back(label); edge->component_->boundaryComponents_.push_back(label); // Loop around from this boundary edge to // completely enumerate all edges in this boundary component. tri = edge->emb_[0].getTriangle(); edgeId = edge->emb_[0].getEdge(); vertexId = edge->emb_[0].getVertices()[0]; vertex = tri->vertex_[vertexId]; while (true) { if (! edge->boundaryComponent_) { edge->boundaryComponent_ = label; label->edges_.push_back(edge); vertex->boundaryComponent_ = label; label->vertices_.push_back(vertex); } else { // We've looped right around. break; } // Find the next edge along the boundary. // We can be clever about this. The current // boundary edge is one end of the vertex link; the // *adjacent* boundary edge must be at the other. vertexEmb = vertex->emb_.front(); if (vertexEmb.getTriangle() == tri && vertexEmb.getVertices()[0] == vertexId && vertexEmb.getVertices()[2] == edgeId) { // We are currently looking at the embedding at the // front of the list. Take the one at the back. vertexEmb = vertex->emb_.back(); adjTri = vertexEmb.getTriangle(); adjEdgeId = vertexEmb.getVertices()[1]; adjEdge = adjTri->edge_[adjEdgeId]; adjVertexId = vertexEmb.getVertices()[2]; } else { // We must be looking at the embedding at the back // of the list. Take the one at the front (which is // already stored in vertexEmb). adjTri = vertexEmb.getTriangle(); adjEdgeId = vertexEmb.getVertices()[2]; adjEdge = adjTri->edge_[adjEdgeId]; adjVertexId = vertexEmb.getVertices()[1]; // TODO: Sanity checking; remove this eventually. vertexEmb = vertex->emb_.back(); if (! (vertexEmb.getTriangle() == tri && vertexEmb.getVertices()[0] == vertexId && vertexEmb.getVertices()[1] == edgeId)) { std::cerr << "ERROR: Something has gone terribly " "wrong in computeBoundaryComponents()." << std::endl; ::exit(1); } } edge = adjEdge; tri = adjTri; edgeId = adjEdgeId; vertexId = adjVertexId; vertex = tri->vertex_[vertexId]; } } } } // namespace regina regina-4.95/engine/docs.h000644 000765 000024 00000021706 12234011536 015160 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file docs.h * \brief Contains miscellaneous documentation. */ /*! \mainpage * *
* Regina
* Software for 3-manifold topology and normal surface theory
* Copyright © 1999-2013, The Regina development team *
* * This documentation describes the functions, classes and related * entities in the C++ calculation engine, as well as how these can * be accessed through Python. * * This API documentation is written in terms of C++. * However, Python programmers can use exactly the same functions, * classes, methods and so on. If the Python version of a function * differs from the C++ version, you will see a bold Python: note * telling you how it differs. * * To start: visit the Modules page and take * a look around, or browse through the classes regina::NTriangulation and * regina::NNormalSurfaceList. * *

Citation

* * If you find Regina useful in your research, please consider citing it as * you would any other paper that you use. A suggested form of reference is: * * Benjamin A. Burton, Ryan Budney, William Pettersson, et al., * "Regina: Software for 3-manifold topology and normal surface theory", * http://regina.sourceforge.net/ , 1999-2013. * *

Authors

* * The primary developers of Regina are: *
    *
  • Benjamin Burton <bab@debian.org>
  • *
  • Ryan Budney <rybu@uvic.ca>
  • *
  • William Pettersson <william.pettersson@gmail.com>
  • *
*

* Many others have been of assistance with this project, be it through * time, knowledge, testing or code. Please see the full list of * acknowledgements in the users' handbook. * *

Copying and Modification

* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Some of this code comes with additional permissions; see the * section below regarding online distribution. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301, USA. * *

Online Distribution

* * Regina's own source code comes with the following permissions in * addition to the GNU General Public License: * * As an exception, when this program is distributed through (i) the * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * (iii) Google Play by Google Inc., then that store may impose any * digital rights management, device limits and/or redistribution * restrictions that are required by its terms of service. * * Some third-party libraries included in Regina are not granted this * exception, and must be removed from any build that is distributed on * stores that cannot comply with the GNU General Public License (such as * Apple's App Store). See the third-party licenses below for details. * *

SnapPea and SnapPy

* * Regina includes portions of the SnapPea kernel and its successor SnapPy, * which it uses for some geometric calculations. The SnapPea kernel was * written by Jeff Weeks, and SnapPy was written by Marc Culler, Nathan * Dunfield, and others. SnapPy and the corresponding SnapPea kernel are * distributed under the terms of the GNU General Public License, version 2 * or any later version, as published by the Free Software Foundation. * *

Normaliz Library

* * Regina includes a copy of libnormaliz, which it uses to help with the * enumeration of fundamental normal surfaces. Normaliz was written by * Winfried Bruns, Bogdan Ichim and Christof Soeger. It is distributed * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * *

Orb Kernel

* * Regina includes snippets of code from Orb, for use in importing and * exporting files in Orb / Casson format. Orb is based on SnapPea * (see above) with additional code written by Damian Heard, who has also * given permission for his code to be distributed under the terms of the * GNU General Public License. */ /*! \page i18n Encodings for international strings * * As of version 4.5, Regina (finally) pays attention to character encodings. * * The calculation engine uses UTF-8 for all strings (except possibly * for filenames; see below). This means that programmers who pass * strings \e into routines must ensure that they use UTF-8, and * programmers who receive strings \e from routines may assume that * they are returned in UTF-8. Note that plain ASCII is a subset of * UTF-8, so plain ASCII text is always fine to use. * * Regina's XML data files are also stored using UTF-8. Older * versions of Regina used LATIN1 (the default for the Qt libraries) * and did not specify an encoding in the XML header; however, Regina's * file I/O routines are aware of this, and will convert older data into * UTF-8 as it is loaded into memory (the files themselves are * of course not modified). The routine versionUsesUTF8() may be * useful for programmers who need to work with older data files at a * low level. * * File \e names are a special case, since here Regina must interact with * the underlying operating system. All filenames that are passed into * routines must be presented in whatever encoding the operating system * expects; Regina will simply pass them through to the standard C/C++ file * I/O routines (such as fopen() or std::ifstream::open()) without modifying * them in any way. * * It should be noted that ancient data files that use the old binary * format (Regina 2.x, before mid-2002) only support plain ASCII text. * Support for the old binary format is likely to be removed entirely in the * very near future. * * \ifacespython Users and programmers who use the Python interface must * take special care, since Python does not pass strings around in UTF-8 * by default. * * Proper support for character encodings is quite new, and the main * author rarely uses this (being a native English speaker). If you * see Regina treating international characters in unexpected ways, * please mail the author(s) or file a bug report so the problem can be * fixed! */ regina-4.95/engine/doxygen/CMakeLists.txt000644 000765 000024 00000000136 12234011536 020266 0ustar00babstaff000000 000000 CONFIGURE_FILE ( docs.conf.in "${PROJECT_BINARY_DIR}/engine/doxygen/docs.conf" @ONLY ) regina-4.95/engine/doxygen/doc-footer.html000644 000765 000024 00000000550 12234011536 020455 0ustar00babstaff000000 000000
Copyright © 1999-2013, The Regina development team
This software is released under the GNU General Public License, with some additional permissions; see the source code for details.
For further information, or to submit a bug or other problem, please contact Ben Burton (bab@debian.org). regina-4.95/engine/doxygen/docs.conf.in000644 000765 000024 00000006655 12236713237 017757 0ustar00babstaff000000 000000 # # File docs.conf -- Doxygen configuration for Regina. # # General options PROJECT_NAME = "Regina Calculation Engine" OUTPUT_DIRECTORY = @PROJECT_BINARY_DIR@/docs/engine DISABLE_INDEX = NO EXTRACT_ALL = NO EXTRACT_PRIVATE = NO EXTRACT_STATIC = YES BRIEF_MEMBER_DESC = YES JAVADOC_AUTOBRIEF = YES REPEAT_BRIEF = YES ALWAYS_DETAILED_SEC = YES FULL_PATH_NAMES = YES STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@/engine @PROJECT_SOURCE_DIR@ STRIP_FROM_INC_PATH = @PROJECT_SOURCE_DIR@/engine @PROJECT_SOURCE_DIR@ CLASS_DIAGRAMS = YES SOURCE_BROWSER = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES CASE_SENSE_NAMES = YES VERBATIM_HEADERS = NO # Aliases: ALIASES = \ "ifaces=\par Interfaces:\n" \ "ifacescpp=\par C++:\n" \ "ifacespython=\par Python:\n" \ "i18n=\par Internationalisation:\n" \ "proburgent=Bug (urgent):" \ "prob=Bug:" \ "problong=Bug (long-term):" \ "featureurgent=Feature (urgent):" \ "feature=Feature:" \ "featurelong=Feature (long-term):" \ "opturgent=Optimise (urgent):" \ "opt=Optimise:" \ "optlong=Optimise (long-term):" \ "tidyurgent=Tidy (urgent):" \ "tidy=Tidy:" \ "tidylong=Tidy (long-term):" \ "testfull=\test Exhaustively tested in the test suite." \ "testpart=\test Included in the test suite." \ "apinotfinal=\warning The API for this class has not yet been finalised. This means that the class interface may change in new versions of Regina, without maintaining backward compatibility. If you use this class directly in your own code, please watch the detailed changelogs upon new releases to see if you need to make changes to your code." # Warning and progress related options: QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES # Input related options: INPUT = \ @PROJECT_SOURCE_DIR@/engine \ @PROJECT_SOURCE_DIR@/python/globalarray.h FILE_PATTERNS = *.h RECURSIVE = YES EXCLUDE = \ @PROJECT_SOURCE_DIR@/engine/utilities/nmatrix2.h \ @PROJECT_SOURCE_DIR@/engine/utilities/nmpi.h \ @PROJECT_SOURCE_DIR@/engine/utilities/nrational.h \ @PROJECT_SOURCE_DIR@/engine/enumerate/normaliz \ @PROJECT_SOURCE_DIR@/engine/foreign/casson.h \ @PROJECT_SOURCE_DIR@/engine/snappea/kernel EXCLUDE_PATTERNS = *-impl.h IMAGE_PATH = # Alphabetical index options: ALPHABETICAL_INDEX = YES IGNORE_PREFIX = N # HTML related options: GENERATE_HTML = YES HTML_OUTPUT = . HTML_FOOTER = @PROJECT_SOURCE_DIR@/engine/doxygen/doc-footer.html HTML_ALIGN_MEMBERS = YES GENERATE_HTMLHELP = NO # LaTeX related options: GENERATE_LATEX = NO # RTF related options: GENERATE_RTF = NO # Man page related options: GENERATE_MAN = NO # Preprocessor related options: ENABLE_PREPROCESSING = YES MACRO_EXPANSION = NO SEARCH_INCLUDES = YES INCLUDE_PATH = @PROJECT_SOURCE_DIR@/engine PREDEFINED = __DOXYGEN # External reference options: ALLEXTERNALS = NO PERL_PATH = /usr/bin/perl # Dot options: HAVE_DOT = NO CLASS_GRAPH = YES COLLABORATION_GRAPH = YES INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES GRAPHICAL_HIERARCHY = NO # Search engine options: SEARCHENGINE = NO regina-4.95/engine/engine.cpp000644 000765 000024 00000007503 12234011536 016027 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "engine.h" namespace regina { const char* getVersionString() { return PACKAGE_VERSION; } int getVersionMajor() { return PACKAGE_VERSION_MAJOR; } int getVersionMinor() { return PACKAGE_VERSION_MINOR; } bool versionUsesUTF8(const char* version) { // No version is fine. // Also any version that does not begin with 0-4 is fine. if (version[0] < '0' || version[0] > '4') return true; if (version[0] < '4') { // Deal with 0.x .. 3.x. // Unless this is the beginning of a larger number, // this is a bad version. return (version[0] != '0' && version[1] >= '0' && version[1] <= '9'); } else { // The 4.x case is a bit messier to deal with. // If it's the beginning of a larger number, we're fine. if (version[1] >= '0' && version[1] <= '9') return true; // Definitely begins with 4 and 4 alone. // The only way to save ourselves is to have 4.x for x >= 5. if (version[1] != '.') return false; // We definitely begin with "4.". // The only good possibilities now are to begin with: // - 4.[number larger than 4] if (version[2] == '0') return false; else if (version[2] >= '1' && version[2] <= '4') return (version[3] >= '0' && version[3] <= '9'); else if (version[2] >= '5' && version[2] <= '9') return true; else return false; } } int testEngine(int value) { return value; } } // namespace regina regina-4.95/engine/engine.h000644 000765 000024 00000012062 12234011536 015470 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file engine.h * \brief Provides global routines for interfacing with the Regina * calculation engine. */ #ifndef __ENGINE_H #ifndef __DOXYGEN #define __ENGINE_H #endif #include "regina-core.h" /** * Contains the entire Regina calculation engine. * * \todo \prob Derive from regina::boost::noncopyable where appropriate. * \todo \featurelong Enhance the test suite for the calculation engine. */ namespace regina { /** * \addtogroup engine Engine Structure * Overall structure of the calculation engine. * @{ */ /** * Returns the full version number of this calculation engine. * For instance, version 2.3.1 would have full version "2.3.1". * * @return the version of this calculation engine. */ REGINA_API const char* getVersionString(); /** * Returns the major version number of this calculation engine. * For instance, version 2.3.1 would have major version 2. * * @return the major version number of this calculation engine. */ REGINA_API int getVersionMajor(); /** * Returns the minor version number of this calculation engine. * For instance, version 2.3.1 would have minor version 3. * * @return the minor version number of this calculation engine. */ REGINA_API int getVersionMinor(); /** * Did the given version of Regina consistently use UTF-8 in its data * files? * * In Regina versions 4.4 and below, no particular attention was paid to * character encodings. As a result, the GUI typically stored data in * LATIN1 (the default for the Qt libraries). * * As of Regina 4.5, all strings are now stored in UTF-8 where possible. * * This routine allows programs to determine which regime a particular * version of Regina belongs to. This can be useful when working with * Regina data files on a low-level basis. * * Any whitespace in the version string will confuse the result, and the * return value will be undefined. * * As a special case, an empty string is treated as belonging to the * UTF-8 regime. * * @param version a version string from some release of Regina, such as "4.2". * This must not contain any whitespace padding. * @return \c true if the given version uses UTF-8 consistently, or * \c false if the given version is an older version that did not pay * attention to character encodings. */ REGINA_API bool versionUsesUTF8(const char* version); /** * Tests to see if an interface can successfully communicate with the * underlying C++ calculation engine. * * This routine simply uses the engine to return the same value that is * passed to it; it can be used to test whether communications between * the interface and the C++ engine are working properly. * * @param value any integer; this same integer will be returned. * @return the same integer that was passed as \a value. */ REGINA_API int testEngine(int value); /*@}*/ } // namespace regina #endif regina-4.95/engine/enumerate/CMakeLists.txt000644 000765 000024 00000002267 12236247215 020614 0ustar00babstaff000000 000000 # enumerate # Files to compile SET ( FILES nenumconstraint ndoubledescription ndoubledescriptor nhilbertcd nhilbertdual nhilbertprimal nmaxadmissible ntreeconstraint ntreelp ntreetraversal ntypetrie ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} enumerate/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) IF (NOT EXCLUDE_NORMALIZ) ADD_SUBDIRECTORY("normaliz") ENDIF (NOT EXCLUDE_NORMALIZ) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nenumconstraint.h ndoubledescription.h ndoubledescription-impl.h ndoubledescription.tcc ndoubledescriptor.h ndoubledescriptor.tcc nhilbertcd.h nhilbertcd-impl.h nhilbertcd.tcc nhilbertdual.h nhilbertdual-impl.h nhilbertdual.tcc nhilbertprimal.h nhilbertprimal-impl.h nhilbertprimal.tcc nmaxadmissible.h nmaxadmissible-impl.h nmaxadmissible.tcc ntreeconstraint.h ntreelp.h ntreelp-impl.h ntreelp.tcc ntreetraversal.h ntreetraversal-impl.h ntreetraversal.tcc ntypetrie.h ordering.h DESTINATION ${INCLUDEDIR}/enumerate COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/enumerate/ndoubledescription-impl.h000644 000765 000024 00000046574 12234011536 023062 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* To be included from ndoubledescription.h. */ #ifndef __NDOUBLEDESCRIPTION_IMPL_H #ifndef __DOXYGEN #define __NDOUBLEDESCRIPTION_IMPL_H #endif #include #include #include "regina-core.h" #include "regina-config.h" #include "enumerate/ndoubledescription.h" #include "enumerate/nenumconstraint.h" #include "maths/matrixops.h" #include "maths/nray.h" #include "progress/nprogresstracker.h" #include "utilities/boostutils.h" #include "utilities/memutils.h" #include "utilities/nbitmask.h" #include "utilities/ntrieset.h" namespace regina { template NDoubleDescription::RaySpec::RaySpec(unsigned long axis, const NMatrixInt& subspace, const long* hypOrder) : NRay(subspace.rows()), facets_(subspace.columns()) { size_t i; for (i = 0; i < subspace.columns(); ++i) if (i != axis) facets_.set(i, true); for (i = 0; i < size(); ++i) elements[i] = subspace.entry(hypOrder[i], axis); } template NDoubleDescription::RaySpec::RaySpec( const RaySpec& first, const RaySpec& second) : NRay(second.size() - 1), facets_(second.facets_) { for (size_t i = 0; i < size(); ++i) elements[i] = second.elements[i + 1] * (*first.elements) - first.elements[i + 1] * (*second.elements); scaleDown(); if (*first.elements < zero) negate(); // Compute the new set of facets. facets_ &= first.facets_; } template void NDoubleDescription::RaySpec::recover( NRay& dest, const NMatrixInt& subspace) const { unsigned long i, j; unsigned long rows = subspace.rows(); unsigned long cols = subspace.columns() - facets_.bits(); // Extract the set of columns that we actually care about. unsigned long* use = new unsigned long[cols]; for (i = 0, j = 0; i < subspace.columns(); ++i) if (facets_.get(i)) { // We know in advance that this coordinate will be zero. dest.setElement(i, NLargeInteger::zero); } else { use[j++] = i; } // We know the solution space has dimension one. // If there are no equations then there must be only one non-zero // coordinate, and vice versa. if (cols == 1) { dest.setElement(*use, 1); delete[] use; return; } // We have several non-zero coordinates with at least one // non-trivial equation relating them. // Form a submatrix for the equations, looking only at non-zero coordinates. NLargeInteger* m = new NLargeInteger[rows * cols]; for (i = 0; i < rows; ++i) for (j = 0; j < cols; ++j) m[i * cols + j] = subspace.entry(i, use[j]); // Put this submatrix in echelon form; moreover, for the leading // entry in each row, set all other entries in the corresponding // column to zero. unsigned long* lead = new unsigned long[cols]; for (i = 0; i < cols; ++i) lead[i] = i; // More or less a stripped-down copy of rowBasisAndOrthComp() from here. // See rowBasisAndOrthComp() for further details on how this works. unsigned long done = 0; unsigned long tmp; NLargeInteger coeff1, coeff2, common; while (done < rows) { // Find the first non-zero entry in row done. for (i = done; i < cols; ++i) if (m[done * cols + lead[i]] != NLargeInteger::zero) break; if (i == cols) { // We have a zero row. Trash it and replace it with something else. --rows; if (done < rows) { for (j = 0; j < cols; ++j) m[done * cols + j] = m[rows * cols + j]; } } else { // We have a non-zero row. // Save the column in which we found our non-zero entry. tmp = lead[done]; lead[done] = lead[i]; lead[i] = tmp; // Make all other entries in column lead[done] equal to zero. coeff1 = m[done * cols + lead[done]]; for (i = 0; i < rows; ++i) { if (i == done) continue; coeff2 = m[i * cols + lead[done]]; common = NLargeInteger::zero; if (coeff2 != NLargeInteger::zero) { for (j = 0; j < cols; ++j) { m[i * cols + j] = m[i * cols + j] * coeff1 - m[done * cols + j] * coeff2; common = common.gcd(m[i * cols + j]); } } if (common < NLargeInteger::zero) common.negate(); if (common > NLargeInteger::one) for (j = 0; j < cols; ++j) m[i * cols + j].divByExact(common); } ++done; } } // At this point we should have rows == (cols - 1), and the column // that is *not* zeroed out almost everywhere is lead[rows]. (We // know this because we know that our solution must be one-dimensional). // // Form a solution! common = NLargeInteger::one; for (i = 0; i < rows; ++i) common = common.lcm(m[i * cols + lead[i]]); if (common < NLargeInteger::zero) common.negate(); for (i = 0; i < rows; ++i) dest.setElement(use[lead[i]], - (common * m[i * cols + lead[rows]]). divExact(m[i * cols + lead[i]])); dest.setElement(use[lead[rows]], common); dest.scaleDown(); // All done! delete[] lead; delete[] m; delete[] use; } template void NDoubleDescription::enumerateExtremalRays(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned long initialRows) { unsigned long nFacets = subspace.columns(); // If the space has dimension zero, return no results. if (nFacets == 0) return; // Choose a bitmask type for representing the set of facets that a // ray belongs to; in particular, use a (much faster) optimised // bitmask type if we can. // Then farm the work out to the real enumeration routine that is // templated on the bitmask type. if (nFacets <= 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (nFacets <= 8 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #ifdef LONG_LONG_FOUND else if (nFacets <= 8 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (nFacets <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (nFacets <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned long)) enumerateUsingBitmask >( results, subspace, constraints, tracker, initialRows); else if (nFacets <= 16 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #else else if (nFacets <= 8 * sizeof(unsigned long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (nFacets <= 16 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #endif else enumerateUsingBitmask(results, subspace, constraints, tracker, initialRows); } template void NDoubleDescription::enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned long initialRows) { // Get the dimension of the entire space in which we are working. unsigned long dim = subspace.columns(); // Are there any hyperplanes at all in the subspace? unsigned long nEqns = subspace.rows(); if (nEqns == 0) { // No! Just send back the vertices of the non-negative orthant. RayClass* ans; for (unsigned long i = 0; i < dim; ++i) { ans = new RayClass(dim); ans->setElement(i, NLargeInteger::one); *results++ = ans; } if (tracker) tracker->setPercent(100); return; } // We actually have some work to do. // We want to process the hyperplanes in a good order; // Fukuda and Prodon (1996) recommend this, and experimental // experience with Regina agrees. The ordering we use here // is based on position vectors, as described in // "Optimizing the double description method for normal surface // enumeration", B.A. Burton, Mathematics of Computation 79 (2010), 453-484. // See the class NPosOrder for details. // // Sort the integers 0..(nEqns-1) into the order in which we plan to // process the hyperplanes. long* hyperplanes = new long[nEqns]; unsigned long i; for (i = 0; i < nEqns; ++i) hyperplanes[i] = i; std::sort(hyperplanes + initialRows, hyperplanes + nEqns, NPosOrder(subspace)); // Create the two vector lists with which we will work. // Fill the first with the initial set of rays. typedef std::vector*> RaySpecList; RaySpecList list[2]; for (i = 0; i < dim; ++i) list[0].push_back(new RaySpec(i, subspace, hyperplanes)); // Convert the set of constraints into a bitmask, where for every original // facet listed in the constraint the corresponding bit is set to 1. BitmaskType* constraintsBegin = 0; BitmaskType* constraintsEnd = 0; if (constraints && ! constraints->empty()) { constraintsBegin = new BitmaskType[constraints->size()]; NEnumConstraintList::const_iterator cit; for (cit = constraints->begin(), constraintsEnd = constraintsBegin; cit != constraints->end(); ++cit, ++constraintsEnd) { constraintsEnd->reset(dim); constraintsEnd->set(cit->begin(), cit->end(), true); } } #if 0 std::cout << "Initial size: " << list[0].size() << std::endl; #endif // Intersect the hyperplanes one at a time. // At any point we should have the latest results in // list[workingList], with the other list empty. int workingList = 0; unsigned long used = 0; for (i=0; isetPercent(100.0 * i / nEqns)) break; } // We're done! delete[] hyperplanes; if (constraintsBegin) delete[] constraintsBegin; typename RaySpecList::iterator it; if (tracker && tracker->isCancelled()) { // The operation was cancelled. Clean up before returning. for (it = list[workingList].begin(); it != list[workingList].end(); ++it) delete *it; return; } // Convert the final solutions into the required ray class. RayClass* ans; for (it = list[workingList].begin(); it != list[workingList].end(); ++it) { ans = new RayClass(dim); (*it)->recover(*ans, subspace); *results++ = ans; delete *it; } // All done! if (tracker) tracker->setPercent(100); } template bool NDoubleDescription::intersectHyperplane( std::vector*>& src, std::vector*>& dest, unsigned long dim, unsigned long prevHyperplanes, const BitmaskType* constraintsBegin, const BitmaskType* constraintsEnd, NProgressTracker* tracker) { if (src.empty()) return false; typedef std::vector*> RayList; RayList pos, neg; // Run through the old rays and determine which side of the // new hyperplane they lie on. // Rays lying within the new hyperplane will be added directly to // the new solution set. typename RayList::iterator it; int sign; for (it = src.begin(); it != src.end(); it++) { sign = (*it)->sign(); if (sign == 0) dest.push_back(new RaySpec(**it)); else if (sign < 0) neg.push_back(*it); else pos.push_back(*it); } // Does one of the closed half-spaces defined by the hyperplane contain the // entire old solution set? If so, there will be no new vertices. if (pos.empty() || neg.empty()) { for (typename RayList::iterator it = src.begin(); it != src.end(); ++it) delete *it; src.clear(); return false; } // Run through the pairs of positive and negative rays. For every // pair of rays that are adjacent in the current solution space, // add the corresponding intersection with the new hyperplane to the // new solution set. typename RayList::iterator posit, negit, otherit; const BitmaskType* constraint; bool broken; // We use the NTrieSet data structure to speed up adjacency testing // in the code below. Construct an NTrieSet that records the facet // structure for every vertex in the old solution set. NTrieSet trie; for (otherit = src.begin(); otherit != src.end(); ++otherit) trie.insert((*otherit)->facets()); unsigned iterations = 0; for (posit = pos.begin(); posit != pos.end(); ++posit) for (negit = neg.begin(); negit != neg.end(); ++negit) { // Test for cancellation, but not every time (since this // involves expensive mutex locking). if (tracker && ++iterations == 100) { iterations = 0; if (tracker->isCancelled()) { for (otherit = src.begin(); otherit != src.end(); ++otherit) delete *otherit; src.clear(); for (otherit = dest.begin(); otherit != dest.end(); ++otherit) delete *otherit; dest.clear(); return false; } } BitmaskType join((*posit)->facets()); join &= ((*negit)->facets()); // We only care about adjacent rays, i.e., rays joined by an edge. // For the rays to be adjacent, the number of original facets that // both belong to must be >= dimension(subspace) - 2, // which is >= dimension(entire space) - prevHyperplanes - 2. // See Fukuda and Prodon (1996), Proposition 9. if (join.bits() + prevHyperplanes + 2 < dim) continue; // Are we supposed to check for compatibility? if (constraintsBegin) { BitmaskType inv(join); inv.flip(); broken = false; for (constraint = constraintsBegin; constraint != constraintsEnd; ++constraint) { BitmaskType mask(inv); mask &= *constraint; if (! mask.atMostOneBit()) { broken = true; break; } } if (broken) continue; } // Two rays are adjacent (joined by an edge) if and only if // there is no other ray belonging to all of their common facets. // // If the rays *are* adjacent, join them and put the // corresponding intersection in the new results set. if (! trie.hasExtraSuperset(join, (*posit)->facets(), (*negit)->facets(), dim)) dest.push_back(new RaySpec(**posit, **negit)); } // Clean up. for (otherit = src.begin(); otherit != src.end(); ++otherit) delete *otherit; src.clear(); return true; } } // namespace regina #endif regina-4.95/engine/enumerate/ndoubledescription.h000644 000765 000024 00000051623 12234011536 022112 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ndoubledescription.h * \brief Provides a modified double description method for polytope * vertex enumeration. */ #ifndef __NDOUBLEDESCRIPTION_H #ifndef __DOXYGEN #define __NDOUBLEDESCRIPTION_H #endif #include "regina-core.h" #include "enumerate/ordering.h" #include "maths/nray.h" #include "maths/nmatrixint.h" #include #include namespace regina { class NEnumConstraintList; class NMatrixInt; class NRay; class NProgressTracker; /** * \addtogroup enumerate Vertex Enumeration * Polytope vertex enumeration algorithms. * @{ */ /** * Implements a modified double description method for polytope vertex * enumeration. For details of the underlying algorithm, see * "Optimizing the double description method for normal surface enumeration", * Benjamin A. Burton, Math. Comp. 79 (2010), 453-484. * * All routines of interest within this class are static; no object of * this class should ever be created. * * \ifacespython Not present. * * \testpart */ class REGINA_API NDoubleDescription { public: /** * Determines the extremal rays of the intersection of the * n-dimensional non-negative orthant with the given linear * subspace. The resulting rays will be of the class \a RayClass, * will be newly allocated, and will be written to the given output * iterator. Their deallocation is the responsibility of whoever * called this routine. * * The non-negative orthant is an n-dimensional cone with * its vertex at the origin. The extremal rays of this cone are * the \a n non-negative coordinate axes. This cone also has \a n * facets, where the ith facet is the non-negative * orthant of the plane perpendicular to the ith coordinate * axis. * * This routine takes a linear subspace, defined by the * intersection of a set of hyperplanes through the origin (this * subspace is described as a matrix, with each row giving the * equation for one hyperplane). * * The purpose of this routine is to compute the extremal rays of * the new cone formed by intersecting the original cone with this * linear subspace. The resulting list of extremal rays will * contain no duplicates or redundancies. * * Parameter \a constraints may contain a set of validity constraints, * in which case this routine will only return \e valid extremal * rays. Each validity constraint is of the form "an extremal ray * may only lie outside at most one of these facets of the original * cone"; see the NEnumConstraintList class for details. These * contraints have the important property that, although validity is * not preserved under convex combination, \e invalidity is. * * An optional progress tracker may be passed. If so, this routine * will update the percentage progress and poll for cancellation * requests. It will be assumed that an appropriate stage has already * been declared via NProgressTracker::newStage() before this routine * is called, and that NProgressTracker::setFinished() will be * called after this routine returns. * * \pre The template argument RayClass is derived from NRay (or * may possibly be NRay itself). * * @param results the output iterator to which the resulting extremal * rays will be written; this must accept objects of type * RayClass*. * @param subspace a matrix defining the linear subspace to intersect * with the given cone. Each row of this matrix is the equation * for one of the hyperplanes whose intersection forms this linear * subspace. The number of columns in this matrix must be the * dimension of the overall space in which we are working. * @param constraints a set of validity constraints as described * above, or 0 if no additional constraints should be imposed. * @param tracker a progress tracker through which progress * will be reported, or 0 if no progress reporting is required. * @param initialRows specifies how many initial rows of \a subspace * are to be processed in the precise order in which they appear. * The remaining rows will be sorted using the NPosOrder class * before they are processed. */ template static void enumerateExtremalRays(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker = 0, unsigned long initialRows = 0); private: /** * A helper class for vertex enumeration, describing a single ray * (typically a vertex in some partial solution space). * * Although this class represents a ray, it does not actually * store the coordinates of the ray. Instead it stores: * * - the dot products of this ray with each of the hyperplanes * passed to NDoubleDescription::enumerateExtremalRays(); * * - a bitmask indicating which facets of the original cone this * ray belongs to. * * The dot products are stored as coordinates of the * superclass NRay. Dot products are only stored * for hyperplanes that have not yet been intersected (thus * the vector length becomes smaller as the main algorithm progresses). * Dot products are stored in the order in which hyperplanes are * to be processed (thus the dot product with the next hyperplane * is the first element of this vector, and the dot product with * the final hyperplane is the last element). * * The \a BitmaskType template argument describes how the set of * facets will be stored. Specifically, the set of facets is * stored as a bitmask with one bit per facet; each bit is set if * and only if this ray belongs to the corresponding original facet. * * Since this class is used heavily, faster bitmask types such * as NBitmask1 and NBitmask2 are preferred; however, if the * number of facets is too large then the slower general-use NBitmask * class will need to be used instead. * * \pre The template argument \a BitmaskType is one of Regina's * bitmask types, such as NBitmask, NBitmask1 or NBitmask2. */ template class RaySpec : private NRay { private: BitmaskType facets_; /**< A bitmask listing which original facets this ray belongs to. */ public: /** * Creates a ray specification for the non-negative * portion of the given coordinate axis. * * \pre The dimension of the space in which we are * working is strictly positive, but is small enough that * \a BitmaskType can hold the corresponding number of bits * (one bit per dimension). * * @param axis indicates which coordinate axis to use; * this must be at least zero but strictly less than the * dimension of the entire space. * @param subspace the matrix containing the set of * hyperplanes to intersect with the original cone * (one hyperplane for each row of the matrix). * @param hypOrder the order in which we plan to * intersect the hyperplanes. The length of this array * must be the number of rows in \a subspace, and the * ith hyperplane to intersect must be described * by row hypOrder[i] of \a subspace. */ inline RaySpec(unsigned long axis, const NMatrixInt& subspace, const long* hypOrder); /** * Creates a copy of the given ray specification, with the * first dot product removed. The resulting list of dot * products is thus one element shorter. * * This routine is typically called when we intersect a * hyperplane with a partial solution space and some ray * lies directly in this hyperplane (not on either side). * * @param trunc the ray to copy and truncate. */ inline RaySpec(const RaySpec& trunc); /** * Creates a new ray, describing where the plane between * two given rays meets the next intersecting hyperplane. * * The list of dot products for the new ray will be one * element shorter than the lists for the given rays * (since there will be one less hyperplane remaining to * intersect). * * \pre The two given rays come from the same partial * solution space (i.e., their lists of dot products * are the same length). Moreover, this partial * solution space still has at least one hyperplane * remaining to intersect (i.e., the lists of dot * products are not empty). * \pre The two given rays lie on opposite sides of the * next hyperplane to intersect, and neither ray actually * lies \e in this next hyperplane. * * @param first the first of the given rays. * @param second the second of the given rays. */ RaySpec(const RaySpec& first, const RaySpec& second); /** * Returns the sign of the dot product of this ray with the * next hyperplane. This is simply the sign of the first * element in the list of remaining dot products. * * @return 1, 0 or -1 according to the sign of the next * dot product. */ int sign() const; /** * Returns the bitmask listing which facets of the original * cone this ray belongs to. Each bit is set to \c true * if and only if this ray belongs to the corresponding facet. * * @return a bitmask of facets. */ inline const BitmaskType& facets() const; /** * Determines whether this ray belongs to all of the * facets that are common to both given rays. * * For this routine to return \c true, every facet that * contains both \a x and \a y must contain this ray also. * * @param x the first of the given rays. * @param y the second of the given rays. * @return \c true if and only if this ray belongs to all * of the facets that \e both \a x and \a y belong to. */ inline bool onAllCommonFacets( const RaySpec& x, const RaySpec& y) const; /** * Recovers the coordinates of the actual ray that is * described by this object. This routine is not fast, * since it needs to solve a system of linear equations. * * \pre This ray is a member of the \e final solution * space. That is, all hyperplanes have been * intersected with the original cone, and the list of * dot products stored in this object is empty. * * @param dest the ray in which the final coordinates * will be stored; the length of this ray must be the * dimension of the overall space in which we are working. * @param subspace the matrix containing the set of * hyperplanes that were intersected with the original cone * (one hyperplane for each row of the matrix). */ void recover(NRay& dest, const NMatrixInt& subspace) const; }; /** * A comparison object that helps sort hyperplanes into a good * order before running the double description algorithm. * * @deprecated The inner class LexComp no longer exists; instead * its functionality has been moved to the standalone class * NPosOrder. This typedef is offered for backward compatibility, * and will be removed in a future version of Regina. */ typedef NPosOrder LexComp; /** * Private constructor to ensure that objects of this class are * never created. */ NDoubleDescription(); /** * Identical to the public routine enumerateExtremalRays(), except * that there is an extra template parameter \a BitmaskType. * This specifies what type should be used for the bitmask * describing which original facets a ray belongs to. * * All arguments to this function are identical to those for the * public routine enumerateExtremalRays(). * * \pre The bitmask type is one of Regina's bitmask types, such * as NBitmask, NBitmask1 or NBitmask2. * \pre The type \a BitmaskType can handle at least \a f bits, * where \a f is the number of original facets in the given range. * \pre The given range of facets is not empty. */ template static void enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned long initialRows); /** * A part of the full double description algorithm that * intersects the current solution set with a new hyperplane. * * The input list \a src must contain the vertices of the * solution space after the first \a prevHyperplanes hyperplanes * have been intersected with the original cone. This routine * intersects this solution space with the next hyperplane, and * places the vertices of the new solution space in the output * list \a dest. * * The set of validity constraints must be passed here as a * C-style array of bitmasks. Each bitmask is a bitmask of facets, * as seen in the RaySpec inner class. Each constraint is of the * form "a point cannot live outside more than one of these facets"; * the bits for these facets must be set to 1 in the corresponding * bitmask, and all other bits must be set to 0. * * It should be noted that the hyperplane itself is not passed * to this routine. This is because all the necessary information * (in particular, the dot products with the new hyperplane) is * stored with the individual vertices of the current solution space. * * \pre The input list \a src owns its elements, and the output * list \a dest is empty. * \post The input list \a src will be empty, and the output * list \a dest will own all of the elements that it contains. * * @param src contains the vertices of the current solution space * before this routine is called. * @param dest contains the vertices of the new solution space * after this routine returns. * @param dim the dimension of the entire space in which we are working. * @param prevHyperplanes the number of hyperplanes that have * already been intersected with the original cone to form the * current solution set. This does not include the hyperplane * currently under consideration. * @param constraintsBegin the beginning of the C-style array of * validity constraints. This should be 0 if no additional * constraints are to be imposed. * @param constraintsEnd a pointer just past the end of the * C-style array of validity constraints. This should be 0 * if no additional constraints are to be imposed. * @param tracker an optional progress tracker that will be polled * for cancellation (though no incremental progress will be reported * within this routine). This may be null. * @return \c true if vertices of the old solution set were found * on both sides of the new hyperplane, \c false if one of * the closed half-spaces defined by the new hyperplane contained * the entire old solution set, or undefined if the operation * was cancelled via the progress tracker. */ template static bool intersectHyperplane( std::vector*>& src, std::vector*>& dest, unsigned long dim, unsigned long prevHyperplanes, const BitmaskType* constraintsBegin, const BitmaskType* constraintsEnd, NProgressTracker* tracker); }; /*@}*/ // Inline functions for NDoubleDescription::RaySpec template inline NDoubleDescription::RaySpec::RaySpec( const RaySpec& trunc) : NRay(trunc.size() - 1), facets_(trunc.facets_) { std::copy(trunc.elements + 1, trunc.end, elements); } template inline int NDoubleDescription::RaySpec::sign() const { if (*elements < NLargeInteger::zero) return -1; if (*elements > NLargeInteger::zero) return 1; return 0; } template inline const BitmaskType& NDoubleDescription::RaySpec::facets() const { return facets_; } template inline bool NDoubleDescription::RaySpec::onAllCommonFacets( const RaySpec& x, const RaySpec& y) const { return facets_.containsIntn(x.facets_, y.facets_); } // Inline functions for NDoubleDescription inline NDoubleDescription::NDoubleDescription() { } } // namespace regina // Template definitions #include "enumerate/ndoubledescription-impl.h" #endif regina-4.95/engine/enumerate/ndoubledescription.tcc000644 000765 000024 00000004714 12234011536 022433 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ndoubledescription.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use ndoubledescription-impl.h instead. #include "enumerate/ndoubledescription-impl.h" regina-4.95/engine/enumerate/ndoubledescriptor.h000644 000765 000024 00000006337 12234011536 021747 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ndoubledescriptor.h * \brief Provides a modified double description method for polytope * vertex enumeration. */ #ifndef __NDOUBLEDESCRIPTOR_H #ifndef __DOXYGEN #define __NDOUBLEDESCRIPTOR_H #endif #warning This header is deprecated; please use ndoubledescription.h instead. #include "enumerate/ndoubledescription.h" namespace regina { /** * \weakgroup enumerate * @{ */ /** * A legacy typedef provided for backward compatibility only. * * \deprecated As of Regina 4.6, the class NDoubleDescriptor has been * renamed as NDoubleDescription. This renaming is merely for * consistency in documentation and nomenclature; the functionality * of the new NDoubleDescription class is identical to the old * NDoubleDescriptor class as it was in Regina 4.5.1. This typedef is * provided for backward compatibility, and will be removed in some * future version of Regina. */ typedef NDoubleDescription NDoubleDescriptor; /*@}*/ } // namespace regina #endif regina-4.95/engine/enumerate/ndoubledescriptor.tcc000644 000765 000024 00000004742 12234011536 022267 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #warning This header is deprecated; please use ndoubledescription-impl.h instead. // Include the outer header ndoubledescription.h so that we get the // backward-compatibility typedefs. #include "enumerate/ndoubledescription.h" regina-4.95/engine/enumerate/nenumconstraint.h000644 000765 000024 00000012051 12234011536 021435 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nenumconstraint.h * \brief Deals with validity constraints in polytope vertex enumeration. */ #ifndef __NENUMCONSTRAINT_H #ifndef __DOXYGEN #define __NENUMCONSTRAINT_H #endif #include #include #include #include "regina-core.h" namespace regina { /** * \weakgroup enumerate * @{ */ /** * Represents an individual validity constraint for use with * polytope vertex enumeration. * * Vertex enumeration routines such as * NDoubleDescription::enumerateExtremalRays() take a cone (specifically * the non-negative orthant), form the intersection of that cone with a * given linear subspace, and return the extremal rays of the new cone * that results. * * In some cases we are only interested in \e valid rays of the new cone. * The NEnumConstraintList class stores a number of "validity constraints"; * a ray is then "valid" if it satisfies all of these constraints. * * Each individual constraint is presented as a set of integers; the * meaning of such a constraint is as follows. We number the facets of * the original cone 0,1,2,... (where the ith facet is the plane * perpendicular to the ith coordinate axis). If a constraint is * described by the integers \a x, \a y, \a z, ..., then it indicates that a * ray can only lie outside at most one of the facets numbered * \a x, \a y, \a z, ... . * * In practice, this allows us to represent constraints in normal * surface theory. For instance, to insist that some tetrahedron * contains at most one quadrilateral disc type, we add a constraint * with three integers, representing the original facets * \a q1=0, \a q2=0, \a q3=0 (where \a q1, \a q2 and \a q3 are the three * quadrilateral coordinates for that tetrahedron). * * The NEnumConstraintList class is simply a std::vector of constraints, * where each constraint is a std::set of unsigned integers. Typically * one will create a vector containing the desired number of constraints * and then walk through each constraint, filling the sets as appropriate. * * \ifacespython Not present. */ class REGINA_API NEnumConstraintList : public std::vector > { public: /** * Creates an empty list of constraints. */ NEnumConstraintList(); /** * Creates a new list of constraints with the given size. * Each constraint will be initialised to an empty set. * * @param size the number of constraints to include in the new list. */ NEnumConstraintList(size_t size); }; /*@}*/ // Inline functions for NEnumConstraintList inline NEnumConstraintList::NEnumConstraintList() { } inline NEnumConstraintList::NEnumConstraintList(size_t size) : std::vector >(size) { } } // namespace regina #endif regina-4.95/engine/enumerate/nhilbertcd-impl.h000644 000765 000024 00000024555 12234011536 021277 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* To be included from nhilbertcd.h. */ #ifndef __NHILBERTCD_IMPL_H #ifndef __DOXYGEN #define __NHILBERTCD_IMPL_H #endif #include "regina-core.h" #include "regina-config.h" #include "enumerate/nenumconstraint.h" #include "enumerate/nhilbertcd.h" #include "maths/nmatrixint.h" #include "maths/nray.h" #include "utilities/nbitmask.h" #include namespace regina { template void NHilbertCD::enumerateHilbertBasis(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints) { // Get the dimension of the space. size_t dim = subspace.columns(); if (dim == 0) return; // Choose a bitmask type that can hold dim bits. // Use a (much faster) optimised bitmask type if we can. // Then farm the work out to the real enumeration routine that is // templated on the bitmask type. if (dim <= 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints); else if (dim <= 8 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints); #ifdef LONG_LONG_FOUND else if (dim <= 8 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned long)) enumerateUsingBitmask >( results, subspace, constraints); else if (dim <= 16 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints); #else else if (dim <= 8 * sizeof(unsigned long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints); else if (dim <= 16 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints); #endif else enumerateUsingBitmask(results, subspace, constraints); } template void NHilbertCD::enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints) { // Stack-based Contejean-Devie algorithm (Information & Computation, 1994). size_t dim = subspace.columns(); size_t nEqns = subspace.rows(); // Convert the set of constraints into bitmasks, where for every // original coordinate listed in the constraint, the corresponding // bit is set to 1. BitmaskType* constraintsBegin = 0; BitmaskType* constraintsEnd = 0; if (constraints && ! constraints->empty()) { constraintsBegin = new BitmaskType[constraints->size()]; NEnumConstraintList::const_iterator cit; for (cit = constraints->begin(), constraintsEnd = constraintsBegin; cit != constraints->end(); ++cit, ++constraintsEnd) { constraintsEnd->reset(dim); constraintsEnd->set(cit->begin(), cit->end(), true); } } std::list*> basis; typename std::list*>::iterator bit; NRay** unitMatch = new NRay*[dim]; int i, j; for (i = 0; i < dim; ++i) { unitMatch[i] = new NRay(nEqns); for (j = 0; j < nEqns; ++j) unitMatch[i]->setElement(j, subspace.entry(j, i)); } unsigned stackSize; // All vectors/rays are created and destroyed. // Bitmasks on the other hand are reused. VecSpec** coord = new VecSpec*[dim]; NRay** match = new NRay*[dim]; BitmaskType* frozen = new BitmaskType[dim]; for (i = 0; i < dim; ++i) frozen[i].reset(dim); // All false. // Push the zero vector. coord[0] = new VecSpec(dim); match[0] = new NRay(nEqns); stackSize = 1; // The zero vector is already on top. bool first = true; VecSpec *c; NRay *m; BitmaskType f(dim); BitmaskType mask(dim), tmpMask(dim); BitmaskType* constraint; bool found, dom; while (stackSize) { c = coord[stackSize - 1]; m = match[stackSize - 1]; f = frozen[stackSize - 1]; --stackSize; // std::cerr << (*c) << " ... " << (*m) << " ... " // << stackSize << std::endl; // Do we have a non-zero solution? if (! first) { found = false; for (i = 0; i < nEqns; ++i) if ((*m)[i] != 0) { found = true; break; } if (! found) { // Yep, it's a solution. basis.push_back(c); delete m; continue; } } // Try incrementing along different coordinate axes. for (i = 0; i < dim; ++i) { if (f.get(i)) continue; // This coordinate is frozen. if (! first) { // Create the bitmask that we will have if we increment // the ith coordinate. mask = c->mask_; mask.set(i, true); // Constraint test. if (constraintsBegin) { found = false; for (constraint = constraintsBegin; constraint != constraintsEnd; ++constraint) { tmpMask = mask; tmpMask &= *constraint; if (! tmpMask.atMostOneBit()) { found = true; break; } } if (found) continue; } // Opposite direction test. if ((*unitMatch[i]) * (*m) >= 0) continue; // Domination test. found = false; for (bit = basis.begin(); bit != basis.end(); ++bit) { // Is (**bit) <= (*c + ith unit vector) ? // Quick pre-check using bitmasks. if (! ((*bit)->mask_ <= mask)) continue; // Full check. dom = true; for (j = 0; j < dim; ++j) { if ( (j != i && (*c)[j] < (**bit)[j]) || (j == i && (*c)[j] + 1 < (**bit)[j])) { dom = false; break; } } if (dom) { found = true; break; } } if (found) continue; } // Increment! if (stackSize == dim) { std::cerr << "ERROR: STACK OVERFLOW" << std::endl; std::exit(1); } coord[stackSize] = new VecSpec(*c); coord[stackSize]->setElement(i, (*coord[stackSize])[i] + 1); coord[stackSize]->mask_.set(i, true); match[stackSize] = new NRay(*m); (*match[stackSize]) += (*unitMatch[i]); frozen[stackSize] = f; f.set(i, true); ++stackSize; } // Clean up. delete c; delete m; first = false; } // Clean up. for (i = 0; i < dim; ++i) delete unitMatch[i]; delete[] unitMatch; delete[] coord; delete[] match; delete[] frozen; delete[] constraintsBegin; // Output basis elements. for (bit = basis.begin(); bit != basis.end(); ++bit) { *results++ = new RayClass(**bit); delete *bit; } } } // namespace regina #endif regina-4.95/engine/enumerate/nhilbertcd.h000644 000765 000024 00000022357 12234011536 020336 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertcd.h * \brief Provides a modified Contejean-Devie algorithm for Hilbert basis * enumeration. */ #ifndef __NHILBERTCD_H #ifndef __DOXYGEN #define __NHILBERTCD_H #endif #include "regina-core.h" #include "maths/nray.h" #include #include #include namespace regina { class NEnumConstraintList; class NMatrixInt; class NRay; /** * \weakgroup enumerate * @{ */ /** * Implements a modified Contejean-Devie algorithm for enumerating Hilbert * bases. This is based on the stack-based algorithm described in * "An efficient incremental algorithm for solving systems of linear * Diophantine equations", Inform. and Comput. 113 (1994), 143-172, * and has been modified to allow for additional constraints (such as * the quadrilateral constraints from normal surface theory). * * All routines of interest within this class are static; no object of * this class should ever be created. * * \warning For normal surface theory, the Contejean-Devie algorithm is * extremely slow, even when modified to incorporate admissibility * constraints. Consider using the much faster NHilbertPrimal or * NHilbertDual instead. * * \ifacespython Not present. */ class NHilbertCD { public: /** * Determines the Hilbert basis that generates all integer * points in the intersection of the n-dimensional * non-negative orthant with some linear subspace. * The resulting basis elements will be of the class \a RayClass, * will be newly allocated, and will be written to the given output * iterator. Their deallocation is the responsibility of whoever * called this routine. * * The non-negative orthant is an n-dimensional cone with * its vertex at the origin. The extremal rays of this cone are * the \a n non-negative coordinate axes. This cone also has \a n * facets, where the ith facet is the non-negative * orthant of the plane perpendicular to the ith coordinate * axis. * * This routine takes a linear subspace, defined by the * intersection of a set of hyperplanes through the origin (this * subspace is described as a matrix, with each row giving the * equation for one hyperplane). * * The purpose of this routine is to compute the Hilbert basis of * the set of all integer points in the intersection of the * original cone with this linear subspace. The resulting list * of basis vectors will contain no duplicates or redundancies. * * The parameter \a constraints may contain a set of validity * constraints, in which case this routine will only return \e valid * basis elements. Each validity constraint is of the form "at * most one of these coordinates may be non-zero"; see the * NEnumConstraintList class for details. These contraints have the * important property that, although validity is not preserved under * addition, \e invalidity is. * * \pre The template argument RayClass is derived from NRay (or * may possibly be NRay itself). * * \warning For normal surface theory, the Contejean-Devie algorithm is * extremely slow, even when modified to incorporate admissibility * constraints. Consider using the much faster NHilbertPrimal or * NHilbertDual instead. * * @param results the output iterator to which the resulting basis * elements will be written; this must accept objects of type * RayClass*. * @param subspace a matrix defining the linear subspace to intersect * with the given cone. Each row of this matrix is the equation * for one of the hyperplanes whose intersection forms this linear * subspace. The number of columns in this matrix must be the * dimension of the overall space in which we are working. * @param constraints a set of validity constraints as described * above, or 0 if no additional constraints should be imposed. */ template static void enumerateHilbertBasis(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints); private: /** * A helper class for Hilbert basis enumeration, describing a * single candidate basis vector. * * The coordinates of the vector are inherited through the * superclass NRay. * * The \a BitmaskType template argument is used to store one bit * per coordinate, which is \c false if the coordinate is zero * or \c true if the coordinate is non-zero. * * \pre The template argument \a BitmaskType is one of Regina's * bitmask types, such as NBitmask, NBitmask1 or NBitmask2. */ template struct VecSpec : public NRay { BitmaskType mask_; /**< A bitmask indicating which coordinates are zero (\c false) and which are non-zero (\c true). */ /** * Creates the zero vector. * * @param dim the total dimension of the space (and * therefore the toatl length of this vector). */ inline VecSpec(size_t dim); }; /** * Identical to the public routine enumerateHilbertBasis(), * except that there is an extra template parameter \a BitmaskType. * This describes what type should be used for bitmasks that * assign flags to individual coordinate positions. * * All arguments to this function are identical to those for the * public routine enumerateHilbertBasis(). * * \pre The bitmask type is one of Regina's bitmask types, such * as NBitmask, NBitmask1 or NBitmask2. * \pre The type \a BitmaskType can handle at least \a n bits, * where \a n is the dimension of the Euclidean space (i.e., the * number of columns in \a subspace). */ template static void enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints); /** * Private constructor to ensure that objects of this class are * never created. */ NHilbertCD(); }; /*@}*/ // Inline functions for NHilbertCD inline NHilbertCD::NHilbertCD() { } // Inline functions for NHilbertCD::VecSpec template inline NHilbertCD::VecSpec::VecSpec(size_t dim) : NRay(dim), mask_(dim) { // All vector elements are initialised to zero thanks to the // NLargeInteger default constructor. } } // namespace regina // Template definitions #include "enumerate/nhilbertcd-impl.h" #endif regina-4.95/engine/enumerate/nhilbertcd.tcc000644 000765 000024 00000004664 12234011536 020661 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertcd.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use nhilbertcd-impl.h instead. #include "enumerate/nhilbertcd-impl.h" regina-4.95/engine/enumerate/nhilbertdual-impl.h000644 000765 000024 00000041076 12234011536 021633 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* To be included from nhilbertdual.h. */ #ifndef __NHILBERTDUAL_IMPL_H #ifndef __DOXYGEN #define __NHILBERTDUAL_IMPL_H #endif #include #include #include "enumerate/nenumconstraint.h" #include "enumerate/nhilbertdual.h" #include "enumerate/ordering.h" #include "progress/nprogresstracker.h" #include "utilities/nbitmask.h" namespace regina { template void NHilbertDual::enumerateHilbertBasis(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned initialRows) { // Get the dimension of the entire space in which we are working. size_t dim = subspace.columns(); // If the space has dimension zero, return no results. if (dim == 0) return; // Choose a bitmask type that can hold dim bits. // Use a (much faster) optimised bitmask type if we can. // Then farm the work out to the real enumeration routine that is // templated on the bitmask type. if (dim <= 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (dim <= 8 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #ifdef LONG_LONG_FOUND else if (dim <= 8 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned long)) enumerateUsingBitmask >( results, subspace, constraints, tracker, initialRows); else if (dim <= 16 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #else else if (dim <= 8 * sizeof(unsigned long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); else if (dim <= 16 * sizeof(unsigned long)) enumerateUsingBitmask >(results, subspace, constraints, tracker, initialRows); #endif else enumerateUsingBitmask(results, subspace, constraints, tracker, initialRows); } template void NHilbertDual::enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned initialRows) { // Get the dimension of the entire space in which we are working. // At this point we are guaranteed that the dimension is non-zero. size_t dim = subspace.columns(); // Are there any hyperplanes at all in the subspace? size_t nEqns = subspace.rows(); if (nEqns == 0) { // No! Just send back the unit vectors. RayClass* ans; for (unsigned i = 0; i < dim; ++i) { ans = new RayClass(dim); ans->setElement(i, NLargeInteger::one); *results++ = ans; } if (tracker) tracker->setPercent(100); return; } // We actually have some work to do. // Process the hyperplanes in a good order. // // Sort the integers 0..(nEqns-1) into the order in which we plan to // process the hyperplanes. int* hyperplanes = new int[nEqns]; unsigned i; for (i = 0; i < nEqns; ++i) hyperplanes[i] = i; std::sort(hyperplanes + initialRows, hyperplanes + nEqns, NPosOrder(subspace)); // Convert the set of constraints into bitmasks, where for every // original coordinate listed in the constraint, the corresponding // bit is set to 1. BitmaskType* constraintsBegin = 0; BitmaskType* constraintsEnd = 0; if (constraints && ! constraints->empty()) { constraintsBegin = new BitmaskType[constraints->size()]; NEnumConstraintList::const_iterator cit; for (cit = constraints->begin(), constraintsEnd = constraintsBegin; cit != constraints->end(); ++cit, ++constraintsEnd) { constraintsEnd->reset(dim); constraintsEnd->set(cit->begin(), cit->end(), true); } } // Create the vector list with which we will work. // Fill it with the initial basis elements. std::vector*> list; for (i = 0; i < dim; ++i) list.push_back(new VecSpec(i, dim)); #if 0 std::cout << "LIST SIZE: " << list.size() << std::endl; #endif // Intersect the hyperplanes one at a time. for (i=0; isetPercent(100.0 * i / nEqns)) break; } // We're done! delete[] hyperplanes; delete[] constraintsBegin; typename std::vector*>::iterator it; if (tracker && tracker->isCancelled()) { // The operation was cancelled. Clean up before returning. for (it = list.begin(); it != list.end(); ++it) delete *it; return; } RayClass* ans; for (it = list.begin(); it != list.end(); ++it) { ans = new RayClass(dim); for (i = 0; i < dim; ++i) ans->setElement(i, (**it)[i]); *results++ = ans; delete *it; } // All done! if (tracker) tracker->setPercent(100); } template bool NHilbertDual::reduces(const VecSpec& vec, const std::list*>& against, int listSign) { typename std::list*>::const_iterator it; for (it = against.begin(); it != against.end(); ++it) { if (! (**it <= vec)) continue; if (listSign > 0) { if ((**it).nextHyp() <= vec.nextHyp()) return true; } else if (listSign < 0) { if (vec.nextHyp() <= (**it).nextHyp()) return true; } else { if (vec.nextHyp() == (**it).nextHyp()) return true; } } return false; } template void NHilbertDual::reduceBasis(std::list*>& reduce, std::list*>& against, int listSign) { if (reduce.empty()) return; typename std::list*>::iterator i, next, red; bool processed; i = reduce.begin(); next = i; ++next; while (i != reduce.end()) { processed = true; for (red = against.begin(); red != against.end(); ++red) { if (red == i) { processed = false; continue; } if (! (**red <= **i)) continue; if (listSign > 0) { if ((**red).nextHyp() <= (**i).nextHyp()) break; } else if (listSign < 0) { if ((**i).nextHyp() <= (**red).nextHyp()) break; } else { if ((**i).nextHyp() == (**red).nextHyp()) break; } } if (red == against.end()) { i = next; if (next != reduce.end()) ++next; continue; } delete *i; reduce.erase(i); #ifdef __REGINA_HILBERT_DUAL_OPT_DARWIN // Darwinistic reordering of the list against. if (processed) { against.push_front(*red); against.erase(red); i = next; if (next != reduce.end()) ++next; } else { // Both reduce and against are the same list, and the // reducing vector is one we haven't processed yet. if (red == next) ++next; against.push_front(*red); against.erase(red); i = against.begin(); } #else i = next; if (next != reduce.end()) ++next; #endif } } template void NHilbertDual::intersectHyperplane(std::vector*>& list, const NMatrixInt& subspace, unsigned row, const BitmaskType* constraintsBegin, const BitmaskType* constraintsEnd) { // These must be linked lists because we need fast insertion and // deletion at arbitrary locations. std::list*> zero, pos, neg, newZero, newPos, newNeg; typename std::list*>::iterator it, posit, negit; typename std::list*>::iterator posPrevGen, negPrevGen; // Decant the existing basis elements into 0/+/- sets according to the // new hyperplane. int s; typename std::vector*>::iterator srcit; for (srcit = list.begin(); srcit != list.end(); srcit++) { (*srcit)->initNextHyp(subspace, row); s = (*srcit)->sign(); if (s == 0) zero.push_back(*srcit); else if (s < 0) neg.push_back(*srcit); else pos.push_back(*srcit); } list.clear(); posPrevGen = pos.begin(); negPrevGen = neg.begin(); // TODO: Optimise from here down: (d), Sec.3 // Keep enlarging these sets until they enlarge no more. const BitmaskType* cit; BitmaskType comb, tmpMask; std::set::const_iterator coordit; bool broken; bool reachedPosPrevGen; VecSpec sum(subspace.columns()); #if 0 std::cerr << "Start iteration:" << std::endl; #endif while (true) { #if 0 std::cerr << " Intermediate 0/+/-: " << zero.size() << ' ' << pos.size() << ' ' << neg.size() << std::endl; #endif // Generate all valid (pos + neg) pairs that cannot be reduced using // the present lists. reachedPosPrevGen = false; for (posit = pos.begin(); posit != pos.end(); ++posit) { if (posit == posPrevGen) reachedPosPrevGen = true; for (negit = (reachedPosPrevGen ? neg.begin() : negPrevGen); negit != neg.end(); ++negit) { #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D // Check for guaranteed redundany. // See Bruns-Ichim, Remark 16(d). // Bruns and Ichim use strict inequalities, but the same // argument shows that non-strict inequalities will work also. if ((*posit)->srcNextHyp() > 0 && (*negit)->nextHyp() <= - (*posit)->srcNextHyp()) continue; if ((*negit)->srcNextHyp() < 0 && (*posit)->nextHyp() >= - (*negit)->srcNextHyp()) continue; #endif // Check for validity. if (constraintsBegin) { comb = (*posit)->mask(); comb |= (*negit)->mask(); broken = false; for (cit = constraintsBegin; cit != constraintsEnd; ++cit) { tmpMask = comb; tmpMask &= (*cit); if (! tmpMask.atMostOneBit()) { broken = true; break; } } if (broken) continue; } // Check whether the vector can be reduced; if not, use it. // We CANNOT reorder pos or neg at this point. sum.formSum(**posit, **negit); s = sum.sign(); if (s == 0) { if (! reduces(sum, zero, 0)) newZero.push_back(new VecSpec(sum)); } else if (s > 0) { // If this decomposes as a sum of (possibly many) // terms in pos and/or zero, at least one such term must // be in pos. Therefore we only need to test // reduction against pos, and not zero also. if (! reduces(sum, pos, 1)) #ifndef __REGINA_HILBERT_DUAL_OPT_NEWGEN_STRICT_ONLY if (! reduces(sum, zero, 1)) #endif newPos.push_back(new VecSpec(sum)); } else if (s < 0) { // Likewise: test only against neg, and not zero also. if (! reduces(sum, neg, -1)) #ifndef __REGINA_HILBERT_DUAL_OPT_NEWGEN_STRICT_ONLY if (! reduces(sum, zero, -1)) #endif newNeg.push_back(new VecSpec(sum)); } } } if (newZero.empty() && newPos.empty() && newNeg.empty()) { // The basis has not changed, which means we're done. break; } // Independently reduce the basis on each side. reduceBasis(newZero, newZero, 0); reduceBasis(zero, newZero, 0); reduceBasis(newPos, newPos, 1); reduceBasis(pos, newPos, 1); reduceBasis(newNeg, newNeg, -1); reduceBasis(neg, newNeg, -1); // Add the new vectors to the bases. zero.splice(zero.end(), newZero); if (newPos.empty()) { posPrevGen = pos.end(); } else { posPrevGen = newPos.begin(); pos.splice(pos.end(), newPos); } if (newNeg.empty()) { negPrevGen = neg.end(); } else { negPrevGen = newNeg.begin(); neg.splice(neg.end(), newNeg); } } // We have a final Hilbert basis! // Clean up and return. for (it = pos.begin(); it != pos.end(); ++it) delete *it; for (it = neg.begin(); it != neg.end(); ++it) delete *it; for (it = zero.begin(); it != zero.end(); ++it) list.push_back(*it); } } // namespace regina #endif regina-4.95/engine/enumerate/nhilbertdual.h000644 000765 000024 00000057232 12234011536 020675 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertdual.h * \brief Provides a modified dual algorithm for Hilbert basis enumeration. */ #ifndef __NHILBERTDUAL_H #ifndef __DOXYGEN #define __NHILBERTDUAL_H #endif #include "regina-core.h" #include "maths/nmatrixint.h" #include "maths/nray.h" #include #include #include #ifndef __DOXYGEN // Optimisations: /** * Bruns and Ichim, J. Algebra 324 (2010), 1098-1113, remark 16(d). * This doesn't seem to help for fundamental normal surfaces (and in * fact seems to slow things down a small amount). */ // #define __REGINA_HILBERT_DUAL_OPT_BI16D /** * When generating new vectors, only reduce against older vectors * that lie in the corresponding strict half-space, and do not perform * the additional (and unnecessary) reduction against older vectors that * lie directly on the hyperplane. * In practice this does speed things up, but only a little. */ #define __REGINA_HILBERT_DUAL_OPT_NEWGEN_STRICT_ONLY /** * When reducing a potential basis, "darwinistically" reorder it so * that successful reducers are near the front. See Bruns and Ichim, * J. Algebra 324 (2010), 1098-1113, remark 6(a). * In practice, for fundamental normal surfaces this does not seem * to help (and in fact it slows things down a little). */ // #define __REGINA_HILBERT_DUAL_OPT_DARWIN #endif namespace regina { class NEnumConstraintList; class NMatrixInt; class NProgressTracker; /** * \weakgroup enumerate * @{ */ /** * Implements a modified dual algorithm for enumerating Hilbert bases. * This is based on the dual algorithm as described in * "Normaliz: Algorithms for affine monoids and rational cones", * Winfried Bruns and Bogdan Ichim, J. Algebra 324 (2010), 1098-1113, * and has been modified to allow for additional constraints (such as * the quadrilateral constraints from normal surface theory). * * All routines of interest within this class are static; no object of * this class should ever be created. * * \ifacespython Not present. */ class NHilbertDual { public: /** * Determines the Hilbert basis that generates all integer * points in the intersection of the n-dimensional * non-negative orthant with the given linear subspace. * The resulting basis elements will be of the class \a RayClass, * will be newly allocated, and will be written to the given output * iterator. Their deallocation is the responsibility of whoever * called this routine. * * The non-negative orthant is an n-dimensional cone with * its vertex at the origin. The extremal rays of this cone are * the \a n non-negative coordinate axes. This cone also has \a n * facets, where the ith facet is the non-negative * orthant of the plane perpendicular to the ith coordinate * axis. * * This routine takes a linear subspace, defined by the * intersection of a set of hyperplanes through the origin (this * subspace is described as a matrix, with each row giving the * equation for one hyperplane). * * The purpose of this routine is to compute the Hilbert basis of * the set of all integer points in the intersection of the * original cone with this linear subspace. The resulting list * of basis vectors will contain no duplicates or redundancies. * * Parameter \a constraints may contain a set of validity constraints, * in which case this routine will only return \e valid basis elements. * Each validity constraint is of the form "a basis element may only * lie outside at most one of these facets of the original * cone"; see the NEnumConstraintList class for details. These * contraints have the important property that, although validity is * not preserved under addition, \e invalidity is. * * An optional progress tracker may be passed. If so, this routine * will update the percentage progress and poll for cancellation * requests. It will be assumed that an appropriate stage has already * been declared via NProgressTracker::newStage() before this routine * is called, and that NProgressTracker::setFinished() will be * called after this routine returns. * * \pre The template argument RayClass is derived from NRay (or * may possibly be NRay itself). * * @param results the output iterator to which the resulting basis * elements will be written; this must accept objects of type * RayClass*. * @param subspace a matrix defining the linear subspace to intersect * with the given cone. Each row of this matrix is the equation * for one of the hyperplanes whose intersection forms this linear * subspace. The number of columns in this matrix must be the * dimension of the overall space in which we are working. * @param constraints a set of validity constraints as described * above, or 0 if no additional constraints should be imposed. * @param tracker a progress tracker through which progress * will be reported, or 0 if no progress reporting is required. * @param initialRows specifies how many initial rows of \a subspace * are to be processed in the precise order in which they appear. * The remaining rows will be sorted using the NPosOrder class * before they are processed. */ template static void enumerateHilbertBasis(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker = 0, unsigned initialRows = 0); private: /** * A helper class for Hilbert basis enumeration, describing a * single vector (which is typically a basis element in some * partial solution space). * * The coordinates of the vector are inherited through the * superclass NRay. * * In addition, this class stores a data member \a nextHyp_, * which gives fast access to the dot product of this vector * with the hyperplane currently being processed. * * The \a BitmaskType template argument is used to store one bit * per coordinate, which is \c false if the coordinate is zero * or \c true if the coordinate is non-zero. * * \pre The template argument \a BitmaskType is one of Regina's * bitmask types, such as NBitmask, NBitmask1 or NBitmask2. */ template class VecSpec : private NRay { private: NLargeInteger nextHyp_; /**< The dot product of this vector with the hyperplane currently being processed. */ BitmaskType mask_; /**< A bitmask indicating which coordinates are zero (\c false) and which are non-zero (\c true). */ #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D NLargeInteger srcNextHyp_; /**< Stores information from the summands used to create this vector. See srcNextHyp() for details. */ #endif public: /** * Creates the zero vector. * * @param dim the total dimension of the space (and * therefore the toatl length of this vector). */ inline VecSpec(size_t dim); /** * Creates the given unit vector. * * The \a nextHyp_ data member will be left uninitialised. * * @param pos indicates which coordinate is set to one * in this unit vector. * @param dim the total dimension of the space (and * therefore the total length of this vector). */ inline VecSpec(size_t pos, size_t dim); /** * Creates a clone of the given vector. * * @param other the vector to clone. */ inline VecSpec(const VecSpec& other); /** * Updates the \a nextHyp_ member to reflect the dot * product with the given hyperplane. * * This routine also sets the member \a srcNextHyp_ to zero. * * @param subspace the matrix containing the full set of * hyperplanes. * @param row the row of the given matrix that stores * the specific hyperplane in which we are interested. */ inline void initNextHyp(const NMatrixInt& subspace, unsigned row); /** * Sets this to the sum of the two given vectors. * * \pre pos.nextHyp() > 0, and * neg.nextHyp() < 0. * * @param pos the first vector to add, which must lie on * the strictly positive side of the current hyperplane. * @param neg the second vector to add, which must lie * on the strictly negative side of the current hyperplane. */ inline void formSum(const VecSpec& pos, const VecSpec& neg); /** * Returns the dot product of this vector with the * hyperplane currently being processed. */ inline const NLargeInteger& nextHyp() const; /** * Returns the bitmask describing which coordinate are * non-zero. */ inline const BitmaskType& mask() const; /** * Returns the sign of the dot product of this vector * with the hyperplane currently being processed. This * is simply the sign of the data member \a nextHyp_. * * @return 1, 0 or -1 according to the sign of \a nextHyp_. */ inline int sign() const; #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D /** * Returns information from the summands used to * create this vector. * * Specifically: Suppose this vector was created using * formSum(). If nextHyp() ≥ 0, then this routine returns * pos.nextHyp() where \a pos was the * positive summand passed to formSum(). * If nextHyp() < 0, then this routine returns * neg.nextHyp() where \a neg was the * negative summand passed to formSum(). * * If this vector was not created using formSum(), * or if initNextHyp() has since been called, then this * routine returns zero. * * @return the summand information as described above. */ inline const NLargeInteger& srcNextHyp() const; #endif /** * Determines if this and the given vector are identical. * * @param other the vector to compare with this. * @return \c true if this vector is identical to the * given vector, or \c false if not. */ inline bool operator == (const VecSpec& other) const; /** * Determines if every element of this vector is less * than or equal to every element of the given vector. * * @param other the vector to compare with this. * @return \c true if every element of this vector is * less than or equal to every element of \a other, or * \c false otherwise. */ inline bool operator <= (const VecSpec& other) const; using NRay::operator []; }; /** * Identical to the public routine enumerateHilbertBasis(), * except that there is an extra template parameter \a BitmaskType. * This describes what type should be used for bitmasks that * represent zero/non-zero coordinates in a vector. * * All argument are identical to those for the public routine * enumerateHilbertBasis(). * * \pre The bitmask type is one of Regina's bitmask types, such * as NBitmask, NBitmask1 or NBitmask2. * \pre The type \a BitmaskType can handle at least \a n bits, * where \a n is the number of coordinates in the underlying vectors. */ template static void enumerateUsingBitmask(OutputIterator results, const NMatrixInt& subspace, const NEnumConstraintList* constraints, NProgressTracker* tracker, unsigned initialRows); /** * Private constructor to ensure that objects of this class are * never created. */ NHilbertDual(); /** * Test whether the vector \a vec can be reduced using * any of the candidate basis vectors in \a against. * * We say that \a vec reduces against a candidate basis vector * \a b if and only if: * * - the vector vec-b is non-negative; * - if \a listSign is 0, then vec-b lies on the * hyperplane currently under investigation; * - if \a listSign is positive, then vec-b lies either * on or to the positive side of the hyperplane under investigation; * - if \a listSign is negative, then vec-b lies either * on or to the negative side of the hyperplane under investigation. * * This routine uses VecSpec::nextHyp() to determine the * relationships between vectors and the current hyperplane. * * @param vec the vector to test for reducibility. * @param against the list of candidate basis vectors to reduce * \a vec against. * @param listSign an integer indicating which sign of the * current hyperplane we are working on. * @return \c true if the given vector can be reduced, or \c false * otherwise. */ template static bool reduces(const VecSpec& vec, const std::list*>& against, int listSign); /** * Removes all vectors from \reduce that can be reduced against * any of the candidate basis vectors in \a against. * This routine might also reorder the list \a against in the * hope of detecting future reductions more quickly. * For details of what reduction means, see reduces() above. * * This routine will work even if \a reduce and \a against are * the same list. * * @param reduce the list of vectors to test for reducibility. * @param against the list of candidate basis vectors to reduce against. * @param listSign an integer indicating which sign of the * current hyperplane we are working on. */ template static void reduceBasis(std::list*>& reduce, std::list*>& against, int listSign); /** * Updates a Hilbert basis by intersecting with a new hyperplane. * * The input vectors in \a list should contain the Hilbert basis * of a cone (defined as the intersection of the non-negative * orthant with some linear subspace). This routine converts * \list into the Hilbert basis of this same cone intersected with * a new hyperplane. The new hyperplane is defined by the given * row of the \a subspace matrix. * * This routine can optionally enforce a set of validity * constraints (such as the normal surface quadrilateral * constraints), as described in enumerateHilbertBasis(). * * The set of validity constraints must be passed here as a * C-style array of bitmasks. Each bitmask contains one bit per * coordinate position, as seen in the VecSpec inner class. * Each constraint is of the form "at most one of these * coordinates can be non-zero"; the bits for these coordinates must * be set to 1 in the corresponding bitmask, and all other bits must * be set to 0. * * @param list contains the original Hilbert basis on entry to * this function, and will contain the updated Hilbert basis upon * returning. * @param subspace a matrix of hyperplanes. * @param row indicates which row of \a subspace contains the * hyperplane that we will intersect with the cone defined by * the old Hilbert basis. * @param constraintsBegin the beginning of the C-style array of * validity constraints. This should be 0 if no additional * constraints are to be imposed. * @param constraintsEnd a pointer just past the end of the * C-style array of validity constraints. This should be 0 * if no additional constraints are to be imposed. */ template static void intersectHyperplane( std::vector*>& list, const NMatrixInt& subspace, unsigned row, const BitmaskType* constraintsBegin, const BitmaskType* constraintsEnd); }; /*@}*/ // Inline functions for NHilbertDual inline NHilbertDual::NHilbertDual() { } // Inline functions for NHilbertDual::VecSpec template inline NHilbertDual::VecSpec::VecSpec(size_t dim) : NRay(dim), mask_(dim) { // All vector elements, nextHyp_ and srcNextHyp_ are initialised to // zero thanks to the NLargeInteger default constructor. } template inline NHilbertDual::VecSpec::VecSpec(size_t pos, size_t dim) : NRay(dim), mask_(dim) { // All coordinates are initialised to zero by default thanks to // the NLargeInteger constructor. setElement(pos, NLargeInteger::one); mask_.set(pos, true); } template inline NHilbertDual::VecSpec::VecSpec( const NHilbertDual::VecSpec& other) : NRay(other), nextHyp_(other.nextHyp_), #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D srcNextHyp_(other.srcNextHyp_), #endif mask_(other.mask_) { } template inline void NHilbertDual::VecSpec::initNextHyp( const NMatrixInt& subspace, unsigned row) { nextHyp_ = NLargeInteger::zero; NLargeInteger tmp; for (int i = 0; i < subspace.columns(); ++i) if (subspace.entry(row, i) != 0 && (*this)[i] != 0) { tmp = subspace.entry(row, i); tmp *= (*this)[i]; nextHyp_ += tmp; } #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D srcNextHyp_ = 0; #endif } template inline void NHilbertDual::VecSpec::formSum( const NHilbertDual::VecSpec& pos, const NHilbertDual::VecSpec& neg) { (*this) = pos; // The default assignment operator. (*this) += neg; nextHyp_ += neg.nextHyp_; mask_ |= neg.mask_; #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D if (nextHyp_ >= 0) srcNextHyp_ = pos.nextHyp_; else srcNextHyp_ = neg.nextHyp_; #endif } template inline const NLargeInteger& NHilbertDual::VecSpec::nextHyp() const { return nextHyp_; } template inline const BitmaskType& NHilbertDual::VecSpec::mask() const { return mask_; } template inline int NHilbertDual::VecSpec::sign() const { return (nextHyp_ == 0 ? 0 : nextHyp_ > 0 ? 1 : -1); } #ifdef __REGINA_HILBERT_DUAL_OPT_BI16D template inline const NLargeInteger& NHilbertDual::VecSpec::srcNextHyp() const { return srcNextHyp_; } #endif template inline bool NHilbertDual::VecSpec::operator == ( const NHilbertDual::VecSpec& other) const { // Begin with simple tests that give us a fast way of saying no. if (! (mask_ == other.mask_)) return false; return (static_cast(*this) == static_cast(other)); } template inline bool NHilbertDual::VecSpec::operator <= ( const NHilbertDual::VecSpec& other) const { // Begin with simple tests that give us a fast way of saying no. if (! (mask_ <= other.mask_)) return false; for (unsigned i = 0; i < size(); ++i) if ((*this)[i] > other[i]) return false; return true; } } // namespace regina // Template definitions #include "enumerate/nhilbertdual-impl.h" #endif regina-4.95/engine/enumerate/nhilbertdual.tcc000644 000765 000024 00000004672 12234011536 021217 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertdual.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use nhilbertdual-impl.h instead. #include "enumerate/nhilbertdual-impl.h" regina-4.95/engine/enumerate/nhilbertprimal-impl.h000644 000765 000024 00000021073 12234011536 022165 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* To be included from nhilbertprimal.h. */ #ifndef __NHILBERTPRIMAL_IMPL_H #ifndef __DOXYGEN #define __NHILBERTPRIMAL_IMPL_H #endif #include "regina-core.h" #include "regina-config.h" #include "enumerate/nenumconstraint.h" #include "enumerate/nhilbertprimal.h" #include "enumerate/nmaxadmissible.h" #include "enumerate/normaliz/cone.h" #include "maths/nray.h" #include "progress/nprogresstracker.h" #include #include #include #include namespace regina { template void NHilbertPrimal::enumerateHilbertBasis(OutputIterator results, const RayIterator& raysBegin, const RayIterator& raysEnd, const NEnumConstraintList* constraints, NProgressTracker* tracker) { if (raysBegin == raysEnd) { // No extremal rays; no Hilbert basis. return; } // Get the dimension of the space. size_t dim = (*raysBegin)->size(); if (dim == 0) return; // Choose a bitmask type that can hold dim bits. // Use a (much faster) optimised bitmask type if we can. // Then farm the work out to the real enumeration routine that is // templated on the bitmask type. if (dim <= 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); else if (dim <= 8 * sizeof(unsigned long)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); #ifdef LONG_LONG_FOUND else if (dim <= 8 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); else if (dim <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned long)) enumerateUsingBitmask >( results, raysBegin, raysEnd, constraints, tracker); else if (dim <= 16 * sizeof(unsigned long long)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); #else else if (dim <= 8 * sizeof(unsigned long) + 8 * sizeof(unsigned)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); else if (dim <= 16 * sizeof(unsigned long)) enumerateUsingBitmask >(results, raysBegin, raysEnd, constraints, tracker); #endif else enumerateUsingBitmask(results, raysBegin, raysEnd, constraints, tracker); } template void NHilbertPrimal::enumerateUsingBitmask(OutputIterator results, const RayIterator& raysBegin, const RayIterator& raysEnd, const NEnumConstraintList* constraints, NProgressTracker* tracker) { // We know at this point that the dimension is non-zero. size_t dim = (*raysBegin)->size(); // First enumerate all maximal admissible faces. if (tracker) tracker->setPercent(10); std::vector* maxFaces = NMaxAdmissible::enumerate( raysBegin, raysEnd, constraints); // Now use normaliz to process each face. if (tracker) tracker->setPercent(30); std::set > finalBasis; std::vector face; typename std::vector::const_iterator mit; RayIterator rit; unsigned i; std::vector >::const_iterator hlit; std::set >::const_iterator hsit; std::vector::const_iterator hvit; for (mit = maxFaces->begin(); mit != maxFaces->end(); ++mit) { // Locate the extremal rays that generate this face. std::vector > input; for (rit = raysBegin; rit != raysEnd; ++rit) if (inFace(**rit, *mit)) { input.push_back(std::vector()); std::vector& v(input.back()); v.reserve(dim); for (i = 0; i < dim; ++i) { if ((**rit)[i].isNative()) v.push_back(mpz_class((**rit)[i].longValue())); else v.push_back(mpz_class((**rit)[i].rawData())); } } libnormaliz::Cone cone(input, libnormaliz::Type::integral_closure); libnormaliz::ConeProperties wanted( libnormaliz::ConeProperty::HilbertBasis); cone.compute(wanted); if (! cone.isComputed(libnormaliz::ConeProperty::HilbertBasis)) { // TODO: Bail properly. std::cerr << "ERROR: Hilbert basis not computed!" << std::endl; continue; } const std::vector > basis = cone.getHilbertBasis(); for (hlit = basis.begin(); hlit != basis.end(); ++hlit) finalBasis.insert(*hlit); } if (tracker) tracker->setPercent(90); RayClass* ans; NLargeInteger tmpInt; for (hsit = finalBasis.begin(); hsit != finalBasis.end(); ++hsit) { ans = new RayClass(dim); for (i = 0, hvit = hsit->begin(); hvit != hsit->end(); ++hvit, ++i) { // We make two copies of the GMP integer instead of one. // This is because NVector/NRay does not give us direct // non-const access to its elements, and so we need a // temporary NLargeInteger to pass through setElement() instead. tmpInt.setRaw(hvit->get_mpz_t()); ans->setElement(i, tmpInt); } *results++ = ans; } // All done! delete maxFaces; if (tracker) tracker->setPercent(100); } template bool NHilbertPrimal::inFace(const NRay& ray, const BitmaskType& face) { for (unsigned i = 0; i < ray.size(); ++i) if ((! face.get(i)) && ray[i] > 0) return false; return true; } } // namespace regina #endif regina-4.95/engine/enumerate/nhilbertprimal.h000644 000765 000024 00000022735 12234011536 021234 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertprimal.h * \brief Provides a modified primal algorithm for Hilbert basis enumeration. */ #ifndef __NHILBERTPRIMAL_H #ifndef __DOXYGEN #define __NHILBERTPRIMAL_H #endif #include "regina-core.h" #include "maths/nmatrixint.h" #include #include namespace regina { class NEnumConstraintList; class NMatrixInt; class NProgressTracker; class NRay; /** * \weakgroup enumerate * @{ */ /** * Implements a modified primal algorithm for enumerating Hilbert bases. * This incorporates the primal algorithm described in * "Normaliz: Algorithms for affine monoids and rational cones", * Winfried Bruns and Bogdan Ichim, J. Algebra 324 (2010), 1098-1113, * and has been modified to allow for additional constraints (such as * the quadrilateral constraints from normal surface theory). * * To summarise: the algorithm first enumerates extremal rays of the rational * cone, and then decomposes the admissible region of the cone (where the * extra constraints are satisfied) into maximal admissible faces. * It calls Normaliz directly to enumerate the Hilbert basis for * each maximal admissible faces, and finally combines these into a basis * for the entire space. * * All routines of interest within this class are static; no object of * this class should ever be created. * * \ifacespython Not present. */ class NHilbertPrimal { public: /** * Determines the Hilbert basis that generates all integer * points in the intersection of the n-dimensional * non-negative orthant with some linear subspace. * The resulting basis elements will be of the class \a RayClass, * will be newly allocated, and will be written to the given output * iterator. Their deallocation is the responsibility of whoever * called this routine. * * The intersection of the non-negative orthant with this linear * subspace is a pointed polyhedral cone with apex at the origin, * and this routine requires the extremal rays of this cone to * be provided as input. The extremal rays can be computed * using NDoubleDescription::enumerate() in general cases, * though sometimes (such as with normal surface theory) more * efficient methods are available. * * This routine computes the Hilbert basis of all integer points in * this cone. The resulting list of basis vectors will contain * no duplicates or redundancies. * * The parameter \a constraints may contain a set of validity * constraints, in which case this routine will only return \e valid * basis elements. Each validity constraint is of the form "at * most one of these coordinates may be non-zero"; see the * NEnumConstraintList class for details. These contraints have the * important property that, although validity is not preserved under * addition, \e invalidity is. * * An optional progress tracker may be passed. If so, this routine * will update the percentage progress and poll for cancellation * requests. It will be assumed that an appropriate stage has already * been declared via NProgressTracker::newStage() before this routine * is called, and that NProgressTracker::setFinished() will be * called after this routine returns. * * \pre If \a constraints is passed, then the given list of * extremal rays contains \e only those extremal rays that satisfy * all of the given constraints. * \pre The template argument RayClass is derived from NRay (or * may possibly be NRay itself). * \pre The template argument RayIterator is a forward iterator type, * and when dereferenced can be cast to (const NRay*). * * \warning If a progress tracker is passed, be aware that the * present implementation updates percentage progress very infrequently, * and may take a very long time to honour cancellation requests. * * @param results the output iterator to which the resulting basis * elements will be written; this must accept objects of type * RayClass*. * @param raysBegin an iterator pointing to the beginning of the * list of extremal rays. * @param raysEnd an iterator pointing past the end of the * list of extremal rays. * @param constraints a set of validity constraints as described * above, or 0 if no additional constraints should be imposed. * @param tracker a progress tracker through which progress * will be reported, or 0 if no progress reporting is required. */ template static void enumerateHilbertBasis(OutputIterator results, const RayIterator& raysBegin, const RayIterator& raysEnd, const NEnumConstraintList* constraints, NProgressTracker* tracker = 0); private: /** * Identical to the public routine enumerateHilbertBasis(), * except that there is an extra template parameter \a BitmaskType. * This describes what type should be used for bitmasks that * represent maximal admissible faces. * * All arguments to this function are identical to those for the * public routine enumerateHilbertBasis(). * * \pre The bitmask type is one of Regina's bitmask types, such * as NBitmask, NBitmask1 or NBitmask2. * \pre The type \a BitmaskType can handle at least \a n bits, * where \a n is the dimension of the Euclidean space (i.e., the * number of columns in \a subspace). */ template static void enumerateUsingBitmask(OutputIterator results, const RayIterator& raysBegin, const RayIterator& raysEnd, const NEnumConstraintList* constraints, NProgressTracker* tracker); /** * Determines whether the given ray lies in the face specified * by the given bitmask. * * Faces are described using bitmasks in the same manner as described * by NMaxAdmissible::enumerate(), where \c true represents a * coordinate that is non-zero in the relative interior of the * face, and \c false represents a coordinate that is always * zero throughout the face. * * @param ray the ray to test, given as a vector of integers. * @param face the face to test, given as a bitmask. * @return \c true if the given ray lies within the given face, * or \c false otherwise. */ template static bool inFace(const NRay& ray, const BitmaskType& face); /** * Private constructor to ensure that objects of this class are * never created. */ NHilbertPrimal(); }; /*@}*/ // Inline functions for NHilbertPrimal inline NHilbertPrimal::NHilbertPrimal() { } } // namespace regina // Template definitions #include "enumerate/nhilbertprimal-impl.h" #endif regina-4.95/engine/enumerate/nhilbertprimal.tcc000644 000765 000024 00000004700 12234011536 021546 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nhilbertprimal.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use nhilbertprimal-impl.h instead. #include "enumerate/nhilbertprimal-impl.h" regina-4.95/engine/enumerate/nmaxadmissible-impl.h000644 000765 000024 00000015044 12234011536 022152 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* To be included from nmaxadmissible.h. */ #ifndef __NMAXADMISSIBLE_IMPL_H #ifndef __DOXYGEN #define __NMAXADMISSIBLE_IMPL_H #endif #include "enumerate/nenumconstraint.h" #include "enumerate/nmaxadmissible.h" #include #include namespace regina { template std::vector* NMaxAdmissible::enumerate( RayIterator beginExtremalRays, RayIterator endExtremalRays, const NEnumConstraintList* constraints) { if (beginExtremalRays == endExtremalRays) { // Input is empty, so output is empty. return new std::vector(); } size_t dim = (*beginExtremalRays)->size(); BitmaskType b(dim); int i; // Rewrite the constraints as bitmasks. std::vector constMasks; if (constraints) { NEnumConstraintList::const_iterator cit; std::set::const_iterator sit; for (cit = constraints->begin(); cit != constraints->end(); ++cit) { b.reset(); for (sit = cit->begin(); sit != cit->end(); ++sit) b.set(*sit, true); constMasks.push_back(b); } } // Create a set of bitmasks representing the admissible 1-faces of // the cone, i.e., the set of admissible extremal rays. std::vector rays; for (RayIterator rit = beginExtremalRays; rit != endExtremalRays; ++rit) { for (i = 0; i < dim; ++i) b.set(i, (**rit)[i] != 0); rays.push_back(b); } // Create a working set of admissible faces. // We initialise this to the set of all admissible 1-faces, as above. std::list faces(rays.begin(), rays.end()); // Create the final set of faces to return. std::vector* maxFaces = new std::vector(); // Keep expanding the faces using additional extremal rays until we can // expand no more. // The ith iteration of the following loop enumerates _all_ admissible // faces of dimension i+1, and identifies all _maximal_ admissible // faces of dimension i. std::list nextDim; typename std::vector::const_iterator r, c; typename std::list::const_iterator f; typename std::list::iterator n, next; bool isMax, broken; while (! faces.empty()) { for (f = faces.begin(); f != faces.end(); ++f) { // Expand this face by combining with other extremal rays. isMax = true; for (r = rays.begin(); r != rays.end(); ++r) { BitmaskType comb(*f); comb |= *r; // Ignore rays already in this face. if (comb == *f) continue; // Ignore rays that will break admissibility. broken = false; for (c = constMasks.begin(); c != constMasks.end(); ++c) { b = comb; b &= *c; if (! b.atMostOneBit()) { broken = true; break; } } if (broken) continue; // comb expands *f to a higher-dimensional superface. isMax = false; // Strip out duplicates, and also strip out superfaces of // too high a dimension (since we only want to step up one // dimension at a time). broken = false; n = nextDim.begin(); while (n != nextDim.end()) { next = n; ++next; if (*n <= comb) { // comb has too high a dimension, or is a duplicate. broken = true; break; } if (comb <= *n) { // *n has too high a dimension. nextDim.erase(n); } n = next; } if (! broken) nextDim.push_back(comb); } if (isMax) maxFaces->push_back(*f); } std::swap(nextDim, faces); nextDim.clear(); } // All done! return maxFaces; } } // namespace regina #endif regina-4.95/engine/enumerate/nmaxadmissible.h000644 000765 000024 00000015030 12234011536 021206 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nmaxadmissible.h * \brief Provides an algorithm for enumerating maximal faces of a * polyhedral cone that satisfy a set of admissibility constraints. */ #ifndef __NMAXADMISSIBLE_H #ifndef __DOXYGEN #define __NMAXADMISSIBLE_H #endif #include "regina-core.h" #include "utilities/nbitmask.h" #include namespace regina { class NEnumConstraintList; /** * \weakgroup enumerate * @{ */ /** * Used to enumerate all maximal admissible faces of a polyhedral cone * under a given set of admissibility constraints. See the routine * enumerate() for details. * * All routines of interest within this class are static; no object of * this class should ever be created. * * \ifacespython Not present. */ class NMaxAdmissible { public: /** * Enumerates all maximal admissible faces of the given polyhedral cone. * The cone must be the intersection of the non-negative orthant in some * Euclidean space R^n with a linear subspace. * * Admissibility is defined by the given set of constraints. Each * constraint requires that at most one of a given set of coordinates * can be non-zero; see the NEnumConstraintList class for details. * In particular, the quadrilateral constraints from normal surface * theory are of this type. * * It is simple to show that the admissible region of the cone is a * union of faces, which we call "admissible faces". Those admissible * faces that do not appear as a strict subface of some other * admissible face are called "maximal admissible faces". * The admissible region can therefore be expressed as the union of * all maximal admissible faces. * * The input for this routine is the set of all admissible extremal rays * of the cone. These should be computed beforehand; for instance, * using the routine NDoubleDescription::enumerateExtremalRays(). * * The return value is the set of all maximal admissible faces, stored * in a newly allocated vector. Each face \a F is described by a * bitmask. Specifically: if we are working in R^n, then each face is * described by a bitmask \a b of length n, where b[i] * is \c false if every point \a x in \a F has x[i]=0, * and b[i] is \c true if every point \a x in the relative * interior of \a F has x[i] > 0. * * \pre The template argument RayIterator should be an iterator * type that, when dereferenced, can be cast to a const NRay*. * * \pre The template argument BitmaskType is one of the bitmask * types NBitmask, NBitmask1 or NBitmask2. * * \pre Bitmasks of type BitmaskType can hold \a n bits, where * \a n is the dimension of the underlying space (i.e., the size * of the input vectors described by \a beginExtremalRays and * \a endExtremalRays). This is always true of NBitmask, but * you must be careful when using one of the fast but size-limited * types NBitmask1 or NBitmask2. * * @param beginExtremalRays an iterator that begins the set of * admissible extremal rays, as described above. Typically this would * be rays.begin() if \a rays is a standard container type. * @param endExtremalRays an iterator that is past-the-end of the set * of admissible extremal rays. Typically this would be rays.end() * if \a rays is a standard container type. * @param constraints a set of validity constraints as described * above. This may be 0 to indicate no constraints (in which * case there will be just one maximal admissible face). * @return a newly allocated list containing one bitmask * representing each maximal admissible face, as described above. */ template static std::vector* enumerate( RayIterator beginExtremalRays, RayIterator endExtremalRays, const NEnumConstraintList* constraints); }; /*@}*/ } // namespace regina // Template definitions #include "enumerate/nmaxadmissible-impl.h" #endif regina-4.95/engine/enumerate/nmaxadmissible.tcc000644 000765 000024 00000004700 12234011536 021532 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/nmaxadmissible.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use nmaxadmissible-impl.h instead. #include "enumerate/nmaxadmissible-impl.h" regina-4.95/engine/enumerate/normaliz/CMakeLists.txt000644 000765 000024 00000000634 12234011536 022434 0ustar00babstaff000000 000000 # enumerate/normaliz # Files to compile SET ( FILES HilbertSeries cone_property libnormaliz-templated ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} enumerate/normaliz/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET(SOURCES ${SOURCES} PARENT_SCOPE) # Normaliz headers should not be shipped: these are for internal use only. regina-4.95/engine/enumerate/normaliz/cone.cpp000644 000765 000024 00000105103 12234011536 021321 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include "cone.h" #include "full_cone.h" namespace libnormaliz { using namespace std; // adds the signs inequalities given by Signs to Inequalities template Matrix sign_inequalities(const vector< vector >& Signs) { if (Signs.size() != 1) { errorOutput() << "ERROR: Bad signs matrix, has " << Signs.size() << " rows (should be 1)!" << endl; throw BadInputException(); } size_t dim = Signs[0].size(); Matrix Inequ(0,dim); vector ineq(dim,0); for (size_t i=0; i Cone::Cone(const vector< vector >& Input, InputType input_type) { initialize(); if (!Input.empty()) { dim = Input.front().size(); if (input_type == Type::rees_algebra || input_type == Type::polytope) { dim++; // we add one component } if (input_type == Type::congruences) { dim--; //congruences have one extra column } } single_matrix_input(Input, input_type); } template Cone::Cone(const map< InputType, vector< vector > >& multi_input_data) { initialize(); typename map< InputType , vector< vector > >::const_iterator it; //determine dimension it = multi_input_data.begin(); for(; it != multi_input_data.end(); ++it) { if (!it->second.empty()) { dim = it->second.front().size(); if (it->first == Type::rees_algebra || it->first == Type::polytope) { dim++; // we add one component } if (it->first == Type::congruences) { dim--; //congruences have one extra column } break; } } //check for a grading it = multi_input_data.find(Type::grading); if (it != multi_input_data.end()) { vector< vector > lf = it->second; if (lf.size() != 1) { errorOutput() << "ERROR: Bad grading, has " << lf.size() << " rows (should be 1)!" << endl; throw BadInputException(); } setGrading (lf[0]); } it = multi_input_data.begin(); if (multi_input_data.size() == 1) { // use the "one-matrix-input" method single_matrix_input(it->second,it->first); } else if (multi_input_data.size() == 2 && isComputed(ConeProperty::Grading)) { if (it->first == Type::grading) it++; single_matrix_input(it->second,it->first); } else { // now we have to have constraints! Matrix Inequalities(0,dim), Equations(0,dim), Congruences(0,dim+1), Signs(0,dim); for (; it != multi_input_data.end(); ++it) { if (it->second.size() == 0) { continue; } switch (it->first) { case Type::hyperplanes: if (it->second.begin()->size() != dim) { errorOutput() << "Dimensions of hyperplanes ("<second.begin()->size()<<") do not match dimension of other constraints ("<second; break; case Type::equations: if (it->second.begin()->size() != dim) { errorOutput() << "Dimensions of equations ("<second.begin()->size()<<") do not match dimension of other constraints ("<second; break; case Type::congruences: if (it->second.begin()->size() != dim+1) { errorOutput() << "Dimensions of congruences ("<second.begin()->size()<<") do not match dimension of other constraints ("<second; break; case Type::signs: if (it->second.begin()->size() != dim) { errorOutput() << "Dimensions of hyperplanes ("<second.begin()->size()<<") do not match dimension of other constraints ("<second); break; case Type::grading: break; //skip the grading default: errorOutput() << "This InputType combination is currently not supported!" << endl; throw BadInputException(); } } if(!BC_set) compose_basis_change(Sublattice_Representation(dim)); Inequalities.append(Signs); prepare_input_type_456(Congruences, Equations, Inequalities); } } /* only used by the constructors */ template void Cone::initialize() { BC_set=false; is_Computed = bitset(); //initialized to false dim = 0; rees_primary = false; } template void Cone::single_matrix_input(const vector< vector >& Input, InputType input_type) { switch (input_type) { case Type::integral_closure: prepare_input_type_0(Input); break; case Type::normalization: prepare_input_type_1(Input); break; case Type::polytope: prepare_input_type_2(Input); break; case Type::rees_algebra: prepare_input_type_3(Input); break; case Type::signs: prepare_input_type_45(vector >(), sign_inequalities(Input)); break; case Type::hyperplanes: prepare_input_type_45(vector >(), Input); break; case Type::equations: prepare_input_type_45(Input, vector >()); break; case Type::congruences: prepare_input_type_456(Input, vector >(), vector >()); break; case Type::lattice_ideal: prepare_input_type_10(Input); break; case Type::grading: errorOutput() << "Grading as only input not supported!" << endl; // no break, go to default default: throw BadInputException(); } if(!BC_set) compose_basis_change(Sublattice_Representation(dim)); } /* check what is computed */ template bool Cone::isComputed(ConeProperty::Enum prop) const { return is_Computed.test(prop); } template bool Cone::isComputed(ConeProperties CheckComputed) const { return CheckComputed.reset(is_Computed).any(); } /* getter */ template Sublattice_Representation Cone::getBasisChange() const{ return BasisChange; } template vector< vector > Cone::getGeneratorsOfToricRing() const { return GeneratorsOfToricRing; } template vector< vector > Cone::getGenerators() const { return Generators; } template vector< vector > Cone::getExtremeRays() const { return Matrix(Generators).submatrix(ExtremeRays).get_elements(); } template vector< vector > Cone::getSupportHyperplanes() const { return SupportHyperplanes; } //TODO gehts nicht auch in der SR? template vector< vector > Cone::getEquations() const { size_t rank = BasisChange.get_rank(); size_t nr_of_equ = dim-rank; if (rank == 0) // the zero cone return Matrix(dim).get_elements(); // identity matrix Matrix Equations(nr_of_equ,dim); if (nr_of_equ > 0) { Lineare_Transformation NewLT = Transformation(Matrix(getExtremeRays())); Matrix Help = NewLT.get_right().transpose(); for (size_t i = rank; i < dim; i++) { Equations.write(i-rank,Help.read(i)); } } return Equations.get_elements(); } template vector< vector > Cone::getCongruences() const { return BasisChange.get_congruences().get_elements(); } template map< InputType , vector< vector > > Cone::getConstraints () const { map > > c; c.insert(pair< InputType,vector< vector > >(Type::hyperplanes,SupportHyperplanes)); c.insert(pair< InputType,vector< vector > >(Type::equations,getEquations())); c.insert(pair< InputType,vector< vector > >(Type::congruences,getCongruences())); return c; } template const vector< pair,Integer> >& Cone::getTriangulation() const { return Triangulation; } template const list< STANLEYDATA >& Cone::getStanleyDec() const { return StanleyDec; } template size_t Cone::getTriangulationSize() const { return TriangulationSize; } template Integer Cone::getTriangulationDetSum() const { return TriangulationDetSum; } template vector< vector > Cone::getHilbertBasis() const { return HilbertBasis; } template vector< vector > Cone::getDeg1Elements() const { return Deg1Elements; } template const HilbertSeries& Cone::getHilbertSeries() const { return HSeries; } template vector Cone::getGrading() const { return Grading; } template Integer Cone::getGradingDenom() const { return GradingDenom; } template mpq_class Cone::getMultiplicity() const { return multiplicity; } template bool Cone::isPointed() const { return pointed; } template bool Cone::isDeg1ExtremeRays() const { return deg1_extreme_rays; } template bool Cone::isDeg1HilbertBasis() const { return deg1_hilbert_basis; } template bool Cone::isIntegrallyClosed() const { return integrally_closed; } template bool Cone::isReesPrimary() const { return rees_primary; } template Integer Cone::getReesPrimaryMultiplicity() const { return ReesPrimaryMultiplicity; } //--------------------------------------------------------------------------- template void Cone::compose_basis_change(const Sublattice_Representation& BC) { if (BC_set) { BasisChange.compose(BC); } else { BasisChange = BC; BC_set = true; } } //--------------------------------------------------------------------------- template void Cone::prepare_input_type_0(const vector< vector >& Input) { Generators = Input; is_Computed.set(ConeProperty::Generators); Sublattice_Representation Basis_Change(Matrix(Input),true); compose_basis_change(Basis_Change); } //--------------------------------------------------------------------------- template void Cone::prepare_input_type_1(const vector< vector >& Input) { Generators = Input; is_Computed.set(ConeProperty::Generators); Sublattice_Representation Basis_Change(Matrix(Input),false); compose_basis_change(Basis_Change); } //--------------------------------------------------------------------------- /* polytope input */ template void Cone::prepare_input_type_2(const vector< vector >& Input) { size_t j; size_t nr = Input.size(); if (nr == 0) { Generators = Input; } else { //append a column of 1 Generators = vector< vector >(nr, vector(dim)); for (size_t i=0; i(Matrix(Generators),true)); // use the added last component as grading Grading = vector(dim,0); Grading[dim-1] = 1; is_Computed.set(ConeProperty::Grading); } //--------------------------------------------------------------------------- /* rees input */ template void Cone::prepare_input_type_3(const vector< vector >& InputV) { Matrix Input(InputV); int i,j,k,l,nr_rows=Input.nr_of_rows(), nr_columns=Input.nr_of_columns(); rees_primary=true; Integer number; Matrix Full_Cone_Generators(nr_rows+nr_columns,nr_columns+1,0); for (i = 0; i < nr_columns; i++) { Full_Cone_Generators.write(i,i,1); } for(i=0; i Prim_Test=Input; for(i=0; i=nr_rows) { rees_primary=false; break; } } is_Computed.set(ConeProperty::ReesPrimary); Generators = Full_Cone_Generators.get_elements(); is_Computed.set(ConeProperty::Generators); compose_basis_change(Sublattice_Representation(Full_Cone_Generators.nr_of_columns())); } //--------------------------------------------------------------------------- template void Cone::prepare_input_type_456(const Matrix& Congruences, const Matrix& Equations, const Matrix& Inequalities) { size_t nr_cong = Congruences.nr_of_rows(); // handle Congurences if (nr_cong > 0) { size_t i,j; //add slack variables Matrix Cong_Slack(nr_cong, dim+nr_cong); for (i = 0; i < nr_cong; i++) { for (j = 0; j < dim; j++) { Cong_Slack.write(i,j,Congruences.read(i,j)); } Cong_Slack.write(i,dim+i,Congruences.read(i,dim)); } //compute kernel Lineare_Transformation Diagonalization = Transformation(Cong_Slack); size_t rank = Diagonalization.get_rank(); Matrix H = Diagonalization.get_right(); Matrix Ker_Basis_Transpose(dim, dim+nr_cong-rank); for (i = 0; i < dim; i++) { for (j = rank; j < dim+nr_cong; j++) { Ker_Basis_Transpose.write(i, j-rank, H.read(i,j)); } } //TODO now a new linear transformation is computed, necessary?? Sublattice_Representation Basis_Change(Ker_Basis_Transpose.transpose(),false); compose_basis_change(Basis_Change); } prepare_input_type_45(Equations, Inequalities); } //--------------------------------------------------------------------------- template void Cone::prepare_input_type_45(const Matrix& Equations, const Matrix& Inequalities) { // use positive orthant if no inequalities are given if (Inequalities.nr_of_rows() == 0) { if (verbose) { verboseOutput() << "No inequalities specified in constraint mode, using non-negative space." << endl; } SupportHyperplanes = (Matrix(dim)).get_elements(); } else { SupportHyperplanes = Inequalities.get_elements(); } is_Computed.set(ConeProperty::SupportHyperplanes); if(!BC_set) compose_basis_change(Sublattice_Representation(dim)); size_t i,j; if (Equations.nr_of_rows()>0) { Lineare_Transformation Diagonalization = Transformation(BasisChange.to_sublattice_dual(Equations)); size_t rank=Diagonalization.get_rank(); Matrix Help=Diagonalization.get_right(); Matrix Ker_Basis_Transpose(dim,dim-rank); for (i = 0; i < dim; i++) { for (j = rank; j < dim; j++) { Ker_Basis_Transpose.write(i,j-rank,Help.read(i,j)); } } Sublattice_Representation Basis_Change(Ker_Basis_Transpose.transpose(),true); compose_basis_change(Basis_Change); } } //--------------------------------------------------------------------------- template void Cone::prepare_input_type_10(const vector< vector >& BinomialsV) { Matrix Binomials(BinomialsV); size_t i,j, nr_of_monoid_generators = dim; if (isComputed(ConeProperty::Grading)) { //check if binomials are homogeneous vector degrees = Binomials.MxV(Grading); for (size_t i=0; i Diagonalization=Transformation(Binomials); size_t rank=Diagonalization.get_rank(); Matrix Help=Diagonalization.get_right(); Matrix Generators(nr_of_monoid_generators,nr_of_monoid_generators-rank); for (i = 0; i < nr_of_monoid_generators; i++) { for (j = rank; j < nr_of_monoid_generators; j++) { Generators.write(i,j-rank,Help.read(i,j)); } } Full_Cone FC(Generators); //TODO verboseOutput(), what is happening here? FC.support_hyperplanes(); Matrix Supp_Hyp=FC.getSupportHyperplanes(); Matrix Selected_Supp_Hyp_Trans=(Supp_Hyp.submatrix(Supp_Hyp.max_rank_submatrix_lex())).transpose(); Matrix Positive_Embedded_Generators=Generators.multiplication(Selected_Supp_Hyp_Trans); GeneratorsOfToricRing = Positive_Embedded_Generators.get_elements(); is_Computed.set(ConeProperty::GeneratorsOfToricRing); dim = Positive_Embedded_Generators.nr_of_columns(); if (isComputed(ConeProperty::Grading)) { // solve GeneratorsOfToricRing * grading = old_grading Grading = Positive_Embedded_Generators.solve(Grading); if (Grading.size() != dim) { errorOutput() << "Grading could not be transfered!"< void Cone::setGrading (const vector& lf) { if (lf.size() != dim) { errorOutput() << "Grading linear form has wrong dimension " << lf.size() << " (should be " << dim << ")" << endl; throw BadInputException(); } //check if the linear forms are the same if (isComputed(ConeProperty::Grading) && Grading == lf) { return; } if (isComputed(ConeProperty::Generators)) { vector degrees = Matrix(Generators).MxV(lf); for (size_t i=0; i ConeProperties Cone::compute(ConeProperties ToCompute) { if (ToCompute.test(ConeProperty::DualMode)) compute_dual(); /* preparation: get generators if necessary */ compute_generators(); if (!isComputed(ConeProperty::Generators)) { errorOutput()<<"FATAL ERROR: Could not get Generators. This should not happen!"< FC(BasisChange.to_sublattice(Matrix(Generators))); /* activate bools in FC */ bool only_support_hyperplanes = true; if (ToCompute.test(ConeProperty::HilbertSeries)) { FC.do_h_vector = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::HilbertBasis)) { FC.do_Hilbert_basis = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::Triangulation)) { FC.keep_triangulation = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::Multiplicity)) { FC.do_multiplicity = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::TriangulationSize)) { FC.do_triangulation = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::Deg1Elements)) { FC.do_deg1_elements = true; only_support_hyperplanes = false; } if (ToCompute.test(ConeProperty::StanleyDec)) { FC.do_Stanley_dec = true; only_support_hyperplanes = false; } /* Give extra data to FC */ if ( isComputed(ConeProperty::ExtremeRays) ) { FC.Extreme_Rays = ExtremeRays; FC.is_Computed.set(ConeProperty::ExtremeRays); } if ( isComputed(ConeProperty::Grading) ) { FC.Grading = BasisChange.to_sublattice_dual(Grading); FC.is_Computed.set(ConeProperty::Grading); FC.set_degrees(); } /* Do the computation! */ if (only_support_hyperplanes) { // workaround for not dualizing twice if (isComputed(ConeProperty::SupportHyperplanes)) { vector< vector > vvSH = BasisChange.to_sublattice_dual(Matrix(SupportHyperplanes)).get_elements(); FC.Support_Hyperplanes = list< vector >(vvSH.begin(), vvSH.end()); FC.is_Computed.set(ConeProperty::SupportHyperplanes); } FC.support_hyperplanes(); } else { FC.compute(); } extract_data(FC); /* check if everything is computed*/ ToCompute.reset(is_Computed); //remove what is now computed if (ToCompute.any()) { errorOutput() << "Warning: Cone could not compute everything, that it was asked for!"< void Cone::compute_generators() { //create Generators from SupportHyperplanes if (!isComputed(ConeProperty::Generators) && isComputed(ConeProperty::SupportHyperplanes)) { if (verbose) { verboseOutput() < Dual_Cone(BasisChange.to_sublattice_dual(Matrix(SupportHyperplanes))); Dual_Cone.support_hyperplanes(); if (Dual_Cone.isComputed(ConeProperty::SupportHyperplanes)) { //get the extreme rays of the primal cone Matrix Extreme_Rays=Dual_Cone.getSupportHyperplanes(); Generators = BasisChange.from_sublattice(Extreme_Rays).get_elements(); is_Computed.set(ConeProperty::Generators); ExtremeRays = vector(Generators.size(),true); is_Computed.set(ConeProperty::ExtremeRays); if (Dual_Cone.isComputed(ConeProperty::ExtremeRays)) { //get minmal set of support_hyperplanes Matrix Supp_Hyp = Dual_Cone.getGenerators().submatrix(Dual_Cone.getExtremeRays()); SupportHyperplanes = BasisChange.from_sublattice_dual(Supp_Hyp).get_elements(); } Sublattice_Representation Basis_Change(Extreme_Rays,true); compose_basis_change(Basis_Change); //compute denominator of Grading if (isComputed(ConeProperty::Grading) && Generators.size() > 0) { GradingDenom = v_scalar_product(Grading,Generators[0]); GradingDenom /= v_scalar_product(BasisChange.to_sublattice_dual(Grading),BasisChange.to_sublattice(Generators[0])); //TODO in Sublattice Rep berechnen lassen } } } } template ConeProperties Cone::compute(ComputationMode mode) { if (mode == Mode::dual) { return compute_dual(); } else { ConeProperties cps; cps.set(mode); return compute(cps); } } template ConeProperties Cone::compute_dual() { if(isComputed(ConeProperty::Generators) && !isComputed(ConeProperty::SupportHyperplanes)){ if (verbose) { verboseOutput() < Tmp_Cone(BasisChange.to_sublattice(Matrix(Generators))); Tmp_Cone.support_hyperplanes(); extract_data(Tmp_Cone); } if (!isComputed(ConeProperty::SupportHyperplanes)) { errorOutput()<<"FATAL ERROR: Could not get SupportHyperplanes. This should not happen!"< Inequ_on_Ker = BasisChange.to_sublattice_dual(Matrix(SupportHyperplanes)); size_t newdim = Inequ_on_Ker.nr_of_columns(); //now sort the inequalities, hopefully this makes the computation faster Integer norm; vector< Integer > hyperplane; multimap > SortingHelp; typename multimap >::const_iterator ii; for (i = 0; i < Inequ_on_Ker.nr_of_rows() ; i++) { hyperplane=Inequ_on_Ker.read(i); norm=0; for (j = 0; j > (norm,hyperplane)); } Matrix Inequ_Ordered(Inequ_on_Ker.nr_of_rows(),newdim); i=0; for (ii=SortingHelp.begin(); ii != SortingHelp.end(); ii++) { Inequ_Ordered.write(i,(*ii).second); i++; } Cone_Dual_Mode ConeDM(Inequ_Ordered); ConeDM.hilbert_basis_dual(); //create a Full_Cone out of ConeDM if ( ConeDM.Generators.rank() < ConeDM.dim ) { Sublattice_Representation SR(ConeDM.Generators,true); ConeDM.to_sublattice(SR); compose_basis_change(SR); } Full_Cone FC(ConeDM); // Give extra data to FC if ( isComputed(ConeProperty::Grading) ) { FC.Grading = BasisChange.to_sublattice_dual(Grading); FC.is_Computed.set(ConeProperty::Grading); FC.set_degrees(); } FC.dual_mode(); extract_data(FC); is_Computed.set(ConeProperty::DualMode); return ConeProperties(); } template void Cone::extract_data(Full_Cone& FC) { //this function extracts ALL available data from the Full_Cone //even if it was in Cone already <- this may change //it is possible to delete the data in Full_Cone after extracting it if(verbose) { verboseOutput() << "transforming data..."<, Integer> >(tri_size); SHORTSIMPLEX simp; for (size_t i = 0; i 0) { GradingDenom = v_scalar_product(Grading,Generators[0]); GradingDenom /= v_scalar_product(FC.getGrading(),FC.Generators[0]); } } if (FC.isComputed(ConeProperty::IsDeg1HilbertBasis)) { deg1_hilbert_basis = FC.isDeg1HilbertBasis(); is_Computed.set(ConeProperty::IsDeg1HilbertBasis); } if (FC.isComputed(ConeProperty::IsIntegrallyClosed)) { integrally_closed = FC.isIntegrallyClosed(); is_Computed.set(ConeProperty::IsIntegrallyClosed); } if (verbose) { verboseOutput() << " done." <. * */ #ifndef CONE_H_ #define CONE_H_ #include #include #include "libnormaliz.h" #include "cone_property.h" #include "sublattice_representation.h" #include "HilbertSeries.h" namespace libnormaliz { using std::vector; using std::map; using std::pair; template class Full_Cone; template class Matrix; // type for simplex, short in contrast to class Simplex template struct SHORTSIMPLEX { vector key; // full key of simplex Integer height; // height of last vertex over opposite facet Integer vol; // volume if computed, 0 else }; template struct STANLEYDATA { vector key; Matrix offsets; }; template class Cone { //--------------------------------------------------------------------------- // public methods //--------------------------------------------------------------------------- public: //--------------------------------------------------------------------------- // Constructors, they preprocess the input //--------------------------------------------------------------------------- /* give a single matrix as input */ Cone(const vector< vector >& input_data, InputType type = Type::integral_closure); /* give multiple input */ Cone(const map< InputType , vector< vector > >& multi_input_data); //--------------------------------------------------------------------------- // give additional data //--------------------------------------------------------------------------- /* Sets the linear form which is used to grade. * It has to be an N-grading, i.e. all generators must have a value >=1. * If it is not, a subclass of NormalizException will be thrown at the * time of detection which can be in this method or later! * It will delete all data from the cone that depend on the grading! */ void setGrading (const vector& lf); //--------------------------------------------------------------------------- // make computations //--------------------------------------------------------------------------- // return what was NOT computed ConeProperties compute(ComputationMode mode = Mode::hilbertBasisSeries); //default: everything ConeProperties compute(ConeProperties ToCompute); //is done by compiler throug creation of CPies // return true iff it could be computed ConeProperties compute(ConeProperty::Enum prop); //--------------------------------------------------------------------------- // check what is computed //--------------------------------------------------------------------------- bool isComputed(ConeProperty::Enum prop) const; //returns true, when ALL properties in CheckComputed are computed bool isComputed(ConeProperties CheckComputed) const; //--------------------------------------------------------------------------- // get the results, these methods do not start a computation //--------------------------------------------------------------------------- size_t getDim() const { return dim; }; vector< vector > getGenerators() const; vector< vector > getExtremeRays() const; vector< vector > getSupportHyperplanes() const; vector< vector > getEquations() const; vector< vector > getCongruences() const; map< InputType , vector< vector > > getConstraints() const; size_t getTriangulationSize() const; Integer getTriangulationDetSum() const; vector< vector > getHilbertBasis() const; vector< vector > getDeg1Elements() const; vector getGrading() const; Integer getGradingDenom() const; mpq_class getMultiplicity() const; bool isPointed() const; bool isDeg1ExtremeRays() const; bool isDeg1HilbertBasis() const; bool isIntegrallyClosed() const; bool isReesPrimary() const; Integer getReesPrimaryMultiplicity() const; vector< vector > getGeneratorsOfToricRing() const; Sublattice_Representation getBasisChange() const; // the following methods return const refs to avoid copying of big data objects const HilbertSeries& getHilbertSeries() const; //general purpose object const vector< pair, Integer> >& getTriangulation() const; const list< STANLEYDATA >& getStanleyDec() const; //--------------------------------------------------------------------------- // private part //--------------------------------------------------------------------------- private: size_t dim; Sublattice_Representation BasisChange; //always use compose_basis_change() ! bool BC_set; ConeProperties is_Computed; vector< vector > GeneratorsOfToricRing; vector< vector > Generators; vector ExtremeRays; vector< vector > SupportHyperplanes; size_t TriangulationSize; Integer TriangulationDetSum; vector< pair, Integer> > Triangulation; list< STANLEYDATA > StanleyDec; mpq_class multiplicity; vector< vector > HilbertBasis; vector< vector > Deg1Elements; HilbertSeries HSeries; vector< vector > HilbertQuasiPolynomial; vector Grading; Integer GradingDenom; bool pointed; bool deg1_extreme_rays; bool deg1_hilbert_basis; bool integrally_closed; bool rees_primary; Integer ReesPrimaryMultiplicity; void compose_basis_change(const Sublattice_Representation& SR); // composes SR /* Progress input, depending on input_type */ void prepare_input_type_0(const vector< vector >& Input); void prepare_input_type_1(const vector< vector >& Input); void prepare_input_type_2(const vector< vector >& Input); void prepare_input_type_3(const vector< vector >& Input); void prepare_input_type_10(const vector< vector >& Binomials); void prepare_input_type_456(const Matrix& Congruences, const Matrix& Equations, const Matrix& Inequalities); void prepare_input_type_45(const Matrix& Equations, const Matrix& Inequalities); /* only used by the constructors */ void initialize(); void single_matrix_input(const vector< vector >& Input, InputType input_type); /* compute the generators using the support hyperplanes */ void compute_generators(); /* compute method for the dual_mode, used in compute(mode) */ ConeProperties compute_dual(); /* extract the data from Full_Cone, this may remove data from Full_Cone!*/ void extract_data(Full_Cone& FC); }; } //end namespace libnormaliz #endif /* CONE_H_ */ regina-4.95/engine/enumerate/normaliz/cone_dual_mode.cpp000644 000765 000024 00000056363 12234011536 023347 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include #include #include #include #include #include "cone_dual_mode.h" #include "vector_operations.h" #include "lineare_transformation.h" #include "list_operations.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- //private //--------------------------------------------------------------------------- template void Cone_Dual_Mode::splice_them(list< vector< Integer > >& Total, vector > >& Parts){ for(int i=0;i void Cone_Dual_Mode::record_order(list< vector< Integer > >& Elements, list< vector< Integer >* >& Order){ Order.clear(); typename list < vector >::iterator it=Elements.begin(); for(;it!=Elements.end();++it) { Order.push_back(&(*it)); } } //--------------------------------------------------------------------------- template bool Cone_Dual_Mode::reducible( list< vector< Integer >* >& Irred, const vector< Integer >& new_element, const size_t& size, const bool ordered){ size_t i,c=1; typename list< vector* >::iterator j; vector *reducer; for (j =Irred.begin(); j != Irred.end(); j++) { reducer=(*j); if (ordered && new_element[0]<2*(*reducer)[0]) { break; // if element is reducible, divisor of smallest degree will be found later } if (new_element[0]<=(*reducer)[0]) continue; if ((*reducer)[c]<=new_element[c]){ for (i = 1; i <=size ; i++) { if ((*reducer)[i]>new_element[i]){ c=i; break; } } if (i==size+1) { Irred.splice(Irred.begin(),Irred,j); return true; } } //new_element is reducible } return false; } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::reduce(list< vector< Integer > >& Irred, list< vector< Integer > >& Red, const size_t& size){ list< vector< Integer >* > Irred_Reorder; record_order(Irred, Irred_Reorder); typename list< vector >::iterator s; for (s = Red.begin(); s != Red.end();) if(reducible(Irred_Reorder,*s,size,true)) s=Red.erase(s); else ++s; } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::reduce_and_insert(const vector< Integer >& new_element, list >& Irred,const size_t& size){ size_t i,c=1; typename list< vector >::iterator j; for (j =Irred.begin(); j != Irred.end(); j++) { if (new_element[0]<2*(*j)[0]) { break; // if element is reducible, divisor of smallest degree will be found later } else { if ((*j)[c]<=new_element[c]){ for (i = 1; i <= size; i++) { if ((*j)[i]>new_element[i]){ c=i; break; } } if (i==size+1) { Irred.splice(Irred.begin(),Irred,j); return; } } } } Irred.push_back(new_element); } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::auto_reduce(list< vector< Integer> >& To_Reduce, const size_t& size){ To_Reduce.sort(); list > Irred; typename list < vector >::iterator c; for (c=To_Reduce.begin(); c != To_Reduce.end(); ++c) { reduce_and_insert((*c),Irred,size); } To_Reduce.clear(); To_Reduce.splice(To_Reduce.begin(),Irred); To_Reduce.sort(); } //--------------------------------------------------------------------------- //public //--------------------------------------------------------------------------- template Cone_Dual_Mode::Cone_Dual_Mode(Matrix M){ dim=M.nr_of_columns(); if (dim!=M.rank()) { errorOutput()<<"Cone_Dual_Mode error: Matrix with rank = number of columns needed in the constructor of the object Cone_Dual_Mode.\nProbable reason: The Cone is not pointed!"<(static_cast(nr_sh))) { errorOutput()<<"To many support hyperplanes to fit in range of key_t!"< gcds = SupportHyperplanes.make_prime(); vector key=v_non_zero_pos(gcds); if (key.size() < nr_sh) { SupportHyperplanes=SupportHyperplanes.submatrix(key); nr_sh=SupportHyperplanes.nr_of_rows(); } hyp_size=dim+nr_sh; GeneratorList = list< vector >(); Hilbert_Basis = list< vector >(); } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::print()const{ verboseOutput()<<"dim="< Matrix Cone_Dual_Mode::get_support_hyperplanes() const { return SupportHyperplanes; } //--------------------------------------------------------------------------- template Matrix Cone_Dual_Mode::get_generators()const{ return Generators; } //--------------------------------------------------------------------------- template Matrix Cone_Dual_Mode::read_hilbert_basis()const{ size_t s= Hilbert_Basis.size(); Matrix M(s,dim); size_t i; typename list< vector >::const_iterator l; for (i=0, l =Hilbert_Basis.begin(); l != Hilbert_Basis.end(); ++l, ++i) { M.write(i,(*l)); } return M; } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::cut_with_halfspace_hilbert_basis(const size_t& hyp_counter, const bool& lifting, vector& halfspace){ if (verbose==true) { verboseOutput()<<"cut with halfspace "< > Positive_Irred,Negative_Irred,Neutral_Irred; Integer orientation, scalar_product,diff,factor; vector hyperplane=SupportHyperplanes.read(hyp_counter-1); typename list< vector >::iterator h; if (lifting==true) { orientation=v_scalar_product(hyperplane,halfspace); if(orientation<0){ orientation=-orientation; v_scalar_multiplication(halfspace,-1); //transforming into the generator of the positive halfspace } for (h = Hilbert_Basis.begin(); h != Hilbert_Basis.end(); ++h) { //reduction modulo the generator of the positive halfspace scalar_product=v_scalar_product_unequal_vectors_end(hyperplane,(*h)); sign=1; if (scalar_product<0) { scalar_product=-scalar_product; sign=-1; } factor=scalar_product/orientation; for (i = 0; i < dim; i++) { (*h)[nr_sh+3+i]=(*h)[nr_sh+3+i]-sign*factor*halfspace[i]; } } //adding the generators of the halfspace to negative and positive vector hyp_element(hyp_size+3,0); for (i = 0; i < dim; i++) { hyp_element[nr_sh+3+i]= halfspace[i]; } hyp_element[hyp_counter]=orientation; hyp_element[0]=orientation; if (orientation==0){ //never Neutral_Irred.push_back(hyp_element); } else{ Positive_Irred.push_back(hyp_element); v_scalar_multiplication(hyp_element,-1); hyp_element[hyp_counter]=orientation; hyp_element[0]=orientation; Negative_Irred.push_back(hyp_element); } } //end lifting for (h = Hilbert_Basis.begin(); h != Hilbert_Basis.end(); ++h) { //dividing into negative and positive (*h)[hyp_counter]=v_scalar_product_unequal_vectors_end(hyperplane,(*h)); if ((*h)[hyp_counter]>0) { (*h)[nr_sh+1]=1; // generation (*h)[nr_sh+2]=0; //not sum (*h)[0]+=(*h)[hyp_counter]; Positive_Irred.push_back((*h)); } if ((*h)[hyp_counter]<0) { (*h)[hyp_counter]=-(*h)[hyp_counter]; (*h)[nr_sh+1]=1; (*h)[nr_sh+2]=0; (*h)[0]+=(*h)[hyp_counter]; Negative_Irred.push_back((*h)); } if ((*h)[hyp_counter]==0) { (*h)[nr_sh+1]=0; (*h)[nr_sh+2]=0; Neutral_Irred.push_back((*h)); } } Neutral_Irred.sort(); Positive_Irred.sort(); Negative_Irred.sort(); //long int counter=0; list < vector > New_Positive,New_Negative,New_Neutral,Pos,Neg,Neu; vector > > New_Positive_thread(omp_get_max_threads()), New_Negative_thread(omp_get_max_threads()), New_Neutral_thread(omp_get_max_threads()); typename list < vector >::const_iterator p,n; typename list < vector >::iterator c; not_done=true; while(not_done){ not_done=false; New_Positive.clear(); New_Negative.clear(); New_Neutral.clear(); //generating new elements list < vector* > Positive,Negative,Neutral; // pointer lists, used to move reducers to the front size_t psize=0; #pragma omp parallel { #pragma omp single nowait record_order(Negative_Irred,Negative); #pragma omp single nowait record_order(Positive_Irred,Positive); #pragma omp single nowait psize=Positive_Irred.size(); #pragma omp single nowait record_order(Neutral_Irred,Neutral); } // END PARALLEL if (verbose) { size_t nsize=Negative_Irred.size(); size_t zsize=Neutral_Irred.size(); if (psize*nsize>1000000) verboseOutput()<<"Positive: "< ppos; ++ppos, ++p) ; for(;i < ppos; --ppos, --p) ; for (n = Negative_Irred.begin(); n != Negative_Irred.end(); ++n){ if ((*p)[nr_sh+1]<=1 && (*n)[nr_sh+1]<=1 && ((*p)[nr_sh+1]!=0 || (*n)[nr_sh+1]!=0)) { if (((*p)[nr_sh+2]!=0&&(*p)[nr_sh+2]<=(*n)[hyp_counter]) || ((*n)[nr_sh+2]!=0&&(*n)[nr_sh+2]<=(*p)[hyp_counter])) continue; // counter++; diff=(*p)[hyp_counter]-(*n)[hyp_counter]; vector new_candidate=v_add((*p),(*n)); if (diff>0) { new_candidate[hyp_counter]=diff; new_candidate[0]-=2*(*n)[hyp_counter]; if(reducible(Positive,new_candidate,hyp_counter,false)) { continue; } if(reducible(Neutral,new_candidate,hyp_counter-1,false)) { continue; } new_candidate[nr_sh+1]=2; new_candidate[nr_sh+2]=(*p)[hyp_counter]; // #pragma omp critical(NEW_POSITIVE) New_Positive_thread[omp_get_thread_num()].push_back(new_candidate); } if (diff<0) { new_candidate[hyp_counter]=-diff; new_candidate[0]-=2*(*p)[hyp_counter]; if(reducible(Negative,new_candidate,hyp_counter,false)) { continue; } if(reducible(Neutral,new_candidate,hyp_counter-1,false)) { continue; } new_candidate[nr_sh+1]=2; new_candidate[nr_sh+2]=(*n)[hyp_counter]; // #pragma omp critical(NEW_NEGATIVE) New_Negative_thread[omp_get_thread_num()].push_back(new_candidate); } if (diff==0) { new_candidate[hyp_counter]=0; new_candidate[0]-=2*(*p)[hyp_counter]; if(reducible(Neutral,new_candidate,hyp_counter-1,false)) { continue; } new_candidate[nr_sh+1]=0; new_candidate[nr_sh+2]=0; #pragma omp critical(NEW_NEUTRAL) New_Neutral_thread[omp_get_thread_num()].push_back(new_candidate); } } } } //end generation of new elements } //END PARALLEL #pragma omp parallel { #pragma omp single nowait { splice_them(New_Neutral,New_Neutral_thread); auto_reduce(New_Neutral,hyp_counter-1); } #pragma omp single nowait { splice_them(New_Positive,New_Positive_thread); auto_reduce(New_Positive,hyp_counter-1); } #pragma omp single nowait { splice_them(New_Negative,New_Negative_thread); auto_reduce(New_Negative,hyp_counter-1); } } // END PARALLEL if (New_Neutral.size()!=0) { #pragma omp parallel { #pragma omp single nowait reduce(New_Neutral,New_Positive, hyp_counter-1); #pragma omp single nowait reduce(New_Neutral,New_Negative, hyp_counter-1); #pragma omp single nowait reduce(New_Neutral,Neutral_Irred, hyp_counter-1); #pragma omp single nowait reduce(New_Neutral,Positive_Irred, hyp_counter-1); #pragma omp single nowait reduce(New_Neutral,Negative_Irred, hyp_counter-1); } // END PARALLEL Neutral_Irred.merge(New_Neutral); } #pragma omp parallel { #pragma omp single nowait if (New_Positive.size()!=0) { not_done=true; reduce(New_Positive,Positive_Irred, hyp_counter); Positive_Irred.merge(New_Positive); } #pragma omp single nowait if (New_Negative.size()!=0) { not_done=true; reduce(New_Negative,Negative_Irred, hyp_counter); Negative_Irred.merge(New_Negative); } } // PARALLEL // adjust generation #pragma omp parallel { #pragma omp single nowait for (c = Positive_Irred.begin(); c != Positive_Irred.end(); ++c){ if((*c)[nr_sh+1]>0) { (*c)[nr_sh+1]--; } } #pragma omp single nowait for (typename list < vector >::iterator c2 = Negative_Irred.begin(); c2 != Negative_Irred.end(); ++c2){ if((*c2)[nr_sh+1]>0) { (*c2)[nr_sh+1]--; } } } // END PARALLEL // verboseOutput()< >::iterator c; list zero_list; size_t i,j,k; for (c=Hilbert_Basis.begin(); c!=Hilbert_Basis.end(); ++c){ zero_list.clear(); for (i = 0; i < nr_sh; i++) { if ((*c)[i+1]==0) { zero_list.push_back(i); } } k=zero_list.size(); if (k>=dim-1) { vector zero_vector(k); for (j = 0; j < k; j++) { zero_vector[j]=zero_list.front(); zero_list.pop_front(); } Matrix Test=SupportHyperplanes.submatrix(zero_vector); if (Test.rank()>=dim-1) { GeneratorList.push_back((*c)); } } } size_t s = GeneratorList.size(); Generators = Matrix(s,dim); typename list< vector >::const_iterator l; for (i=0, l=GeneratorList.begin(); l != GeneratorList.end(); ++l, ++i) { Generators.write( i, v_cut_front(*l, dim) ); } } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::hilbert_basis_dual(){ if(dim>0){ //correction needed to include the 0 cone; if (verbose==true) { verboseOutput()<<"\n************************************************************\n"; verboseOutput()<<"computing Hilbert basis ..."< Basis_Max_Subspace(dim); //identity matrix for (hyp_counter = 1; hyp_counter <= nr_sh; hyp_counter++) { Basis_Max_Subspace=cut_with_halfspace(hyp_counter,Basis_Max_Subspace); } extreme_rays_rank(); l_cut_front(Hilbert_Basis,dim); relevant_support_hyperplanes(); GeneratorList.clear(); } } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::relevant_support_hyperplanes(){ if (verbose) { verboseOutput() << "Find relevant support hyperplanes" << endl; } list zero_list; typename list >::iterator gen_it; vector relevant_sh; relevant_sh.reserve(nr_sh); size_t i,k; size_t realdim = Generators.rank(); for (i = 0; i < nr_sh; ++i) { Matrix Test(0,dim); k = 0; for (gen_it = GeneratorList.begin(); gen_it != GeneratorList.end(); ++gen_it) { if ((*gen_it)[i+1]==0) { Test.append( v_cut_front(*gen_it,dim) ); k++; } } if (k >= realdim-1 && Test.rank_destructive()>=realdim-1) { relevant_sh.push_back(i); } } SupportHyperplanes = SupportHyperplanes.submatrix(relevant_sh); } //--------------------------------------------------------------------------- template void Cone_Dual_Mode::to_sublattice(Sublattice_Representation SR) { assert(SR.get_dim() == dim); dim = SR.get_rank(); hyp_size = dim+nr_sh; SupportHyperplanes = SR.to_sublattice_dual(SupportHyperplanes); typename list >::iterator it; vector tmp; Generators = SR.to_sublattice(Generators); for (it = Hilbert_Basis.begin(); it != Hilbert_Basis.end(); ) { tmp = SR.to_sublattice(*it); it = Hilbert_Basis.erase(it); Hilbert_Basis.insert(it,tmp); } } } //end namespace libnormaliz regina-4.95/engine/enumerate/normaliz/cone_dual_mode.h000644 000765 000024 00000011635 12234011536 023005 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CONE_DUAL_MODE_H #define CONE_DUAL_MODE_H #include #include #include "libnormaliz.h" #include "matrix.h" #include "sublattice_representation.h" namespace libnormaliz { using std::list; using std::vector; template class Cone_Dual_Mode { public: size_t dim; size_t nr_sh; size_t hyp_size; Matrix SupportHyperplanes; Matrix Generators; list > GeneratorList; //only temporarily used list > Hilbert_Basis; /* --------------------------------------------------------------------------- * Private routines, used in the public routines * --------------------------------------------------------------------------- */ /* splices a vector of lists into a total list*/ void splice_them(list< vector< Integer > >& Total, vector > >& Parts); /* records the order of Elements in pointer list Order */ void record_order(list< vector< Integer > >& Elements, list< vector< Integer >* >& Order); /* Returns true if new_element is reducible versus the elements in Irred used for dual algorithm * ATTENTION: this is "random access" for new_element if ordered==false. * Otherrwise it is assumed that the new elements tested come in ascending total degree * after the list underlying Irred has been ordered the last time */ bool reducible(list *> & Irred, const vector & new_element, const size_t & size, const bool ordered); /* reduce Red versus Irred ATTENTION: both lists must be ordered by total degree * Irred will not be changed, Red is returned without the reducible elements, but no other * change * ATTENTION: not suitable for autoreduction */ void reduce(list > & Irred, list > & Red, const size_t & size); /* adds a new element that is irreducible w.r.t. Irred to Irred * the new elements must come from a structure sorted by total degree * used for dual algorithm */ void reduce_and_insert(const vector & new_element, list >& Irred, const size_t & size); /* reduces a list against itself * the list must be sorted sorted by total degree as used for dual algorithm * The irreducible elements are reurned in ascendingorder */ void auto_reduce(list< vector< Integer> >& To_Reduce, const size_t& size); /* computes the Hilbert basis after adding a support hyperplane with the dual algorithm */ void cut_with_halfspace_hilbert_basis(const size_t & hyp_counter, const bool & lifting, vector & halfspace); /* computes the Hilbert basis after adding a support hyperplane with the dual algorithm , general case */ Matrix cut_with_halfspace(const size_t & hyp_counter, const Matrix& Basis_Max_Subspace); /* computes the extreme rays using reduction, used for the dual algorithm */ void extreme_rays_reduction(); /* computes the extreme rays using rank test, used for the dual algorithm */ void extreme_rays_rank(); void relevant_support_hyperplanes(); Cone_Dual_Mode(Matrix M); //main constructor /*--------------------------------------------------------------------------- * Data access *--------------------------------------------------------------------------- */ void print() const; //to be modified, just for tests Matrix get_support_hyperplanes() const; Matrix get_generators() const; Matrix read_hilbert_basis() const; /*--------------------------------------------------------------------------- * Computation Methods *--------------------------------------------------------------------------- */ void hilbert_basis_dual(); /* transforms all data to the sublattice */ void to_sublattice(Sublattice_Representation SR); }; //class end ***************************************************************** } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/cone_property.cpp000644 000765 000024 00000015071 12234011536 023271 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include "cone_property.h" #include "normaliz_exception.h" namespace libnormaliz { using std::bitset; using std::vector; using std::string; /* Constructors */ ConeProperties::ConeProperties() { CPs = bitset(); } ConeProperties::ConeProperties(ConeProperty::Enum p1) { CPs = bitset(); CPs.set(p1); } ConeProperties::ConeProperties(ConeProperty::Enum p1, ConeProperty::Enum p2) { CPs = bitset(); CPs.set(p1); CPs.set(p2); } ConeProperties::ConeProperties(const bitset& props){ CPs = props; } /* set Properties */ ConeProperties& ConeProperties::set(ConeProperty::Enum p1, bool value) { CPs.set(p1, value); return *this; } ConeProperties& ConeProperties::set(ConeProperty::Enum p1, ConeProperty::Enum p2) { CPs.set(p1); CPs.set(p2); return *this; } ConeProperties& ConeProperties::set(const ConeProperties& ConeProps) { CPs ^= ConeProps.CPs; return *this; } /* reset (=unset) properties */ ConeProperties& ConeProperties::reset(ConeProperty::Enum Property) { CPs.set(Property, false); return *this; } ConeProperties& ConeProperties::reset(const ConeProperties& ConeProps) { CPs &= ~ConeProps.CPs; return *this; } /* test which/how many properties are set */ bool ConeProperties::test(ConeProperty::Enum Property) const { return CPs.test(Property); } bool ConeProperties::any() const { return CPs.any(); } bool ConeProperties::none() const { return CPs.none(); } size_t ConeProperties::count () const { return CPs.count(); } /* this method sets all fields that should be computed in that mode */ ConeProperties& ConeProperties::set(Mode::ComputationMode mode) { switch (mode) { case Mode::supportHyperplanes: set(ConeProperty::SupportHyperplanes, ConeProperty::ExtremeRays); break; case Mode::triangulationSize: set(ConeProperty::TriangulationSize); break; case Mode::triangulation: set(ConeProperty::Triangulation); break; case Mode::volumeTriangulation: set(ConeProperty::Triangulation, ConeProperty::Multiplicity); break; case Mode::volumeLarge: set(ConeProperty::Multiplicity); break; case Mode::degree1Elements: set(ConeProperty::Deg1Elements); break; case Mode::hilbertBasisTriangulation: set(ConeProperty::HilbertBasis, ConeProperty::Triangulation); break; case Mode::hilbertBasisMultiplicity: set(ConeProperty::HilbertBasis, ConeProperty::Multiplicity); break; case Mode::hilbertBasisLarge: set(ConeProperty::HilbertBasis); break; case Mode::hilbertSeries: set(ConeProperty::Triangulation); case Mode::hilbertSeriesLarge: set(ConeProperty::Deg1Elements, ConeProperty::HilbertSeries); break; case Mode::hilbertBasisSeries: set(ConeProperty::Triangulation); case Mode::hilbertBasisSeriesLarge: set(ConeProperty::HilbertSeries, ConeProperty::HilbertBasis); break; case Mode::dual: set(ConeProperty::DualMode); break; default: throw FatalException(); break; } return *this; } /* conversion */ namespace { // only to initialize the CPN in ConePropertyNames vector initializeCPN() { vector CPN(ConeProperty::EnumSize); if (ConeProperty::EnumSize != 21) { //to detect changes in size of Enum errorOutput() << "Fatal Error: ConeProperties Enum size does not fit!" << std::endl; throw FatalException(); } CPN.at(ConeProperty::Generators) = "Generators"; CPN.at(ConeProperty::ExtremeRays) = "ExtremeRays"; CPN.at(ConeProperty::SupportHyperplanes) = "SupportHyperplanes"; CPN.at(ConeProperty::TriangulationSize) = "TriangulationSize"; CPN.at(ConeProperty::TriangulationDetSum) = "TriangulationDetSum"; CPN.at(ConeProperty::Triangulation) = "Triangulation"; CPN.at(ConeProperty::Multiplicity) = "Multiplicity"; CPN.at(ConeProperty::HilbertBasis) = "HilbertBasis"; CPN.at(ConeProperty::Deg1Elements) = "Deg1Elements"; CPN.at(ConeProperty::HilbertSeries) = "HilbertSeries"; CPN.at(ConeProperty::Grading) = "Grading"; CPN.at(ConeProperty::IsPointed) = "IsPointed"; CPN.at(ConeProperty::IsDeg1Generated) = "IsDeg1Generated"; CPN.at(ConeProperty::IsDeg1ExtremeRays) = "IsDeg1ExtremeRays"; CPN.at(ConeProperty::IsDeg1HilbertBasis) = "IsDeg1HilbertBasis"; CPN.at(ConeProperty::IsIntegrallyClosed) = "IsIntegrallyClosed"; CPN.at(ConeProperty::GeneratorsOfToricRing) = "GeneratorsOfToricRing"; CPN.at(ConeProperty::ReesPrimary) = "ReesPrimary"; CPN.at(ConeProperty::ReesPrimaryMultiplicity) = "ReesPrimaryMultiplicity"; CPN.at(ConeProperty::StanleyDec) = "StanleyDec"; CPN.at(ConeProperty::DualMode) = "DualMode"; return CPN; } const vector& ConePropertyNames() { static vector CPN(initializeCPN()); return CPN; } } ConeProperty::Enum toConeProperty(const std::string& s) { const vector& CPN = ConePropertyNames(); for (size_t i=0; i(i); } errorOutput() << "Unknown ConeProperty string \"" << s << "\"" << std::endl; throw BadInputException(); } const std::string& toString(ConeProperty::Enum cp) { return ConePropertyNames()[cp]; } /* print it in a nice way */ std::ostream& operator<< (std::ostream& out, const ConeProperties& CP){ for (size_t i=0; i(i)) << " "; } return out; } } /* end namespace libnormaliz */ regina-4.95/engine/enumerate/normaliz/cone_property.h000644 000765 000024 00000005602 12234011536 022735 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef CONE_PROPERTY_H_ #define CONE_PROPERTY_H_ #include #include #include "libnormaliz.h" namespace libnormaliz { /* An enumeration of things, that can be computed for a cone. * The namespace prevents interfering with other names. */ namespace ConeProperty { enum Enum { Generators, ExtremeRays, SupportHyperplanes, TriangulationSize, TriangulationDetSum, Triangulation, Multiplicity, HilbertBasis, Deg1Elements, HilbertSeries, Grading, IsPointed, IsDeg1Generated, IsDeg1ExtremeRays, IsDeg1HilbertBasis, IsIntegrallyClosed, GeneratorsOfToricRing, ReesPrimary, ReesPrimaryMultiplicity, StanleyDec, DualMode, EnumSize // this has to be the last entry, to get the number of entries in the enum }; // remember to change also the string conversion function if you change this enum } class ConeProperties { public: /* Constructors */ ConeProperties(); ConeProperties(ConeProperty::Enum); ConeProperties(ConeProperty::Enum, ConeProperty::Enum); ConeProperties(const std::bitset&); /* set properties */ ConeProperties& set(ConeProperty::Enum, bool value=true); ConeProperties& set(ConeProperty::Enum, ConeProperty::Enum); ConeProperties& set(const ConeProperties&); ConeProperties& set(Mode::ComputationMode mode); /* reset (=unset) properties */ ConeProperties& reset(ConeProperty::Enum Property); ConeProperties& reset(const ConeProperties&); /* test which/how many properties are set */ bool test(ConeProperty::Enum Property) const; bool any() const; bool none() const; size_t count () const; /* print it in a nice way */ friend std::ostream& operator<<(std::ostream&, const ConeProperties&); private: std::bitset CPs; }; // conversion to/from strings ConeProperty::Enum toConeProperty(const std::string&); const std::string& toString(ConeProperty::Enum); std::ostream& operator<<(std::ostream&, const ConeProperties&); } #endif /* CONE_PROPERTY_H_ */ regina-4.95/engine/enumerate/normaliz/COPYING000644 000765 000024 00000104513 12234011536 020730 0ustar00babstaff000000 000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . regina-4.95/engine/enumerate/normaliz/full_cone.cpp000644 000765 000024 00000305451 12234011536 022353 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include "full_cone.h" #include "vector_operations.h" #include "lineare_transformation.h" #include "list_operations.h" #include "map_operations.h" #include "my_omp.h" #include "integer.h" //--------------------------------------------------------------------------- const size_t RecBoundTriang=1000000; // if number(supphyps)*size(triang) > RecBoundTriang // we pass to (non-recirsive) pyramids const size_t EvalBoundTriang=2500000; // if more than EvalBoundTriang simplices have been stored // evaluation is started (whenever possible) const size_t EvalBoundPyr=200000; // the same for stored pyramids const size_t EvalBoundRecPyr=20000; // the same for stored RECURSIVE pyramids //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- //private //--------------------------------------------------------------------------- template void Full_Cone::add_hyperplane(const size_t& new_generator, const FACETDATA & positive,const FACETDATA & negative, list& NewHyps){ // adds a new hyperplane found in find_new_facets to this cone (restricted to generators processed) size_t k; FACETDATA NewFacet; NewFacet.Hyp.resize(dim); NewFacet.GenInHyp.resize(nr_gen); Integer used_for_tests; if (test_arithmetic_overflow) { // does arithmetic tests for (k = 0; k void Full_Cone::find_new_facets(const size_t& new_generator){ // our Fourier-Motzkin implementation // the special treatment of simplicial facets was inserted because of line shellings. // At present these are not computed. //to see if possible to replace the function .end with constant iterator since push-back is performed. // NEW: new_generator is the index of the generator being inserted size_t i,k,nr_zero_i; size_t subfacet_dim=dim-2; // NEW dimension of subfacet size_t facet_dim=dim-1; // NEW dimension of facet const bool tv_verbose = false; //verbose && !is_pyramid; // && Support_Hyperplanes.size()>10000; //verbose in this method call // preparing the computations, the various types of facets are sorted into the deques deque Pos_Simp,Pos_Non_Simp; deque Neg_Simp,Neg_Non_Simp; deque Neutral_Simp, Neutral_Non_Simp; boost::dynamic_bitset<> Zero_Positive(nr_gen),Zero_Negative(nr_gen); bool simplex; if (tv_verbose) verboseOutput()<<"transform_values:"<::iterator ii = Facets.begin(); for (; ii != Facets.end(); ++ii) { simplex=true; nr_zero_i=0; for(size_t j=0;jGenInHyp.test(j)) nr_zero_i++; if(nr_zero_i>facet_dim){ simplex=false; break; } } if(ii->ValNewGen>0) Zero_Positive|=ii->GenInHyp; else if(ii->ValNewGen<0) Zero_Negative|=ii->GenInHyp; if (ii->ValNewGen==0) { ii->GenInHyp.set(new_generator); // Must be set explicitly !! if (simplex) { Neutral_Simp.push_back(&(*ii)); } else { Neutral_Non_Simp.push_back(&(*ii)); } } else if (ii->ValNewGen>0) { if (simplex) { Pos_Simp.push_back(&(*ii)); } else { Pos_Non_Simp.push_back(&(*ii)); } } else if (ii->ValNewGen<0) { if (simplex) { Neg_Simp.push_back(&(*ii)); } else { Neg_Non_Simp.push_back(&(*ii)); } } } boost::dynamic_bitset<> Zero_PN(nr_gen); Zero_PN=Zero_Positive & Zero_Negative; size_t nr_PosSimp = Pos_Simp.size(); size_t nr_PosNonSimp = Pos_Non_Simp.size(); size_t nr_NegSimp = Neg_Simp.size(); size_t nr_NegNonSimp = Neg_Non_Simp.size(); size_t nr_NeuSimp = Neutral_Simp.size(); size_t nr_NeuNonSimp = Neutral_Non_Simp.size(); if (tv_verbose) verboseOutput()<<" PS "<, int> > > Neg_Subfacet_Multi(omp_get_max_threads()) ; boost::dynamic_bitset<> zero_i(nr_gen); boost::dynamic_bitset<> subfacet(nr_gen); #pragma omp parallel for firstprivate(zero_i,subfacet) private(k,nr_zero_i) schedule(dynamic) for (i=0; iGenInHyp; nr_zero_i=0; for(size_t j=0;jsubfacet_dim){ break; } } if(nr_zero_i==subfacet_dim) // NEW This case treated separately Neg_Subfacet_Multi[omp_get_thread_num()].push_back(pair , int> (zero_i,i)); else{ for (k =0; k, int> (subfacet,i)); } } } } list < pair < boost::dynamic_bitset<>, int> > Neg_Subfacet_Multi_United; for(int i=0;i, int > >::iterator jj; list< pair < boost::dynamic_bitset<>, int > >::iterator del; jj =Neg_Subfacet_Multi_United.begin(); // remove negative subfacets shared while (jj!= Neg_Subfacet_Multi_United.end()) { // by two neg simpl facets del=jj++; if (jj!=Neg_Subfacet_Multi_United.end() && (*jj).first==(*del).first) { //delete since is the intersection of two negative simplicies Neg_Subfacet_Multi_United.erase(del); del=jj++; Neg_Subfacet_Multi_United.erase(del); } } size_t nr_NegSubfMult = Neg_Subfacet_Multi_United.size(); if (tv_verbose) verboseOutput() << nr_NegSubfMult << ", " << flush; vector > NewHypsSimp(nr_PosSimp); vector > NewHypsNonSimp(nr_PosNonSimp); map < boost::dynamic_bitset<>, int > Neg_Subfacet; size_t nr_NegSubf=0; #pragma omp parallel private(jj) //if(nr_NegNonSimp+nr_NegSimp>1000) { size_t i,j,k,nr_zero_i; boost::dynamic_bitset<> subfacet(dim-2); jj = Neg_Subfacet_Multi_United.begin(); size_t jjpos=0; bool found; #pragma omp for schedule(dynamic) for (size_t j=0; j jjpos; ++jjpos, ++jj) ; // by non-simpl neg or neutral facets for(;j < jjpos; --jjpos, --jj) ; subfacet=(*jj).first; found=false; for (i = 0; i GenInHyp); if(found) break; } if (!found) { for (i = 0; i GenInHyp); if(found) break; } if(!found) { for (i = 0; i GenInHyp); if(found) break; } } } if (found) { jj->second=-1; } } #pragma omp single { //remove elements that where found in the previous loop jj = Neg_Subfacet_Multi_United.begin(); map < boost::dynamic_bitset<>, int > ::iterator last_inserted=Neg_Subfacet.begin(); // used to speedup insertion into the new map for (; jj!= Neg_Subfacet_Multi_United.end(); ++jj) { if ((*jj).second != -1) { last_inserted = Neg_Subfacet.insert(last_inserted,*jj); } } nr_NegSubf=Neg_Subfacet.size(); } #pragma omp single nowait {Neg_Subfacet_Multi_United.clear();} #pragma omp single nowait if (tv_verbose) { verboseOutput()<< nr_NegSubf < zero_i(nr_gen); map , int> ::iterator jj_map; #pragma omp for schedule(dynamic) // nowait // Now matching positive and negative (sub)facets for (i =0; iGenInHyp & Zero_PN; nr_zero_i=0; for(size_t m=0;msubfacet_dim){ break; } } if (nr_zero_i==subfacet_dim) { // NEW slight change in logic. Positive simpl facet shared at most jj_map=Neg_Subfacet.find(zero_i); // one subfacet with negative simpl facet if (jj_map!=Neg_Subfacet.end()) { add_hyperplane(new_generator,*Pos_Simp[i],*Neg_Simp[(*jj_map).second],NewHypsSimp[i]); (*jj_map).second = -1; // block subfacet in further searches } } if (nr_zero_i==facet_dim){ // now there could be more such subfacets. We make all and search them. for (k =0; k jjpos; ++jjpos, ++jj_map) ; for( ; j < jjpos; --jjpos, --jj_map) ; if ( (*jj_map).second != -1 ) { // skip used subfacets if(jj_map->first.is_subset_of(Pos_Non_Simp[i]->GenInHyp)){ add_hyperplane(new_generator,*Pos_Non_Simp[i],*Neg_Simp[(*jj_map).second],NewHypsNonSimp[i]); (*jj_map).second = -1; // has now been used } } } } // P vs NS #pragma omp single nowait if (tv_verbose) { verboseOutput() << "PS vs N, " << flush; } vector key(nr_gen); size_t nr_missing; bool common_subfacet; #pragma omp for schedule(dynamic) nowait for (size_t i =0; iGenInHyp; nr_zero_i=0; for(j=0;j=subfacet_dim) { for (j=0; jGenInHyp.test(key[k])) { nr_missing++; if(nr_missing==2 || nr_zero_i==subfacet_dim) { common_subfacet=false; break; } } } if(common_subfacet){ add_hyperplane(new_generator,*Pos_Simp[i],*Neg_Non_Simp[j],NewHypsSimp[i]); if(nr_zero_i==subfacet_dim) // only one subfacet can lie in negative hyperplane break; } } } } // PS vs N #pragma omp single nowait if (tv_verbose) { verboseOutput() << "P vs N" << endl; } list AllNonSimpHyp; typename list::iterator a; for(i=0;i common_zero(nr_gen); vector common_key(nr_gen); #pragma omp for schedule(dynamic) // nowait for (size_t i =0; iGenInHyp; nr_zero_i=0; for(j=0;j=subfacet_dim) { missing_bound=nr_zero_i-subfacet_dim; // at most this number of generators can be missing // to have a chance for common subfacet for (j=0; jGenInHyp.test(key[k])) { nr_missing++; if(nr_missing>missing_bound || nr_zero_i==subfacet_dim) { common_subfacet=false; break; } } else { common_key[nr_common_zero]=key[k]; nr_common_zero++; } } if(common_subfacet){//intersection of *i and *j may be a subfacet exactly_two=true; ranktest=((nr_PosNonSimp+nr_NegNonSimp+nr_NeuNonSimp>dim*dim*nr_common_zero/3)); if (ranktest) { Matrix Test(nr_common_zero,dim); for (k = 0; k < nr_common_zero; k++) Test.write(k,Generators[common_key[k]]); if (Test.rank_destructive()GenInHyp; for (a=AllNonSimpHyp.begin();a!=AllNonSimpHyp.end();++a){ hp_t=*a; if ((hp_t!=hp_i) && (hp_t!=hp_j) && common_zero.is_subset_of(hp_t->GenInHyp)) { exactly_two=false; AllNonSimpHyp.splice(AllNonSimpHyp.begin(),AllNonSimpHyp,a); break; } } } // else if (exactly_two) { //intersection of i and j is a subfacet add_hyperplane(new_generator,*hp_i,*hp_j,NewHypsNonSimp[i]); } } } } } } //END parallel for(i=0;i void Full_Cone::extend_triangulation(const size_t& new_generator){ // extends the triangulation of this cone by including new_generator // simplicial facets save us from searching the "brother" in the existing triangulation // to which the new simplex gets attached size_t listsize = Facets.size(); vector::iterator> visible; visible.reserve(listsize); typename list::iterator i = Facets.begin(); // #pragma omp critical(VERBOSE) // verboseOutput() << "L " << pyr_level << " H " << listsize << " T " << TriangulationSize << endl; for (; i!=Facets.end(); ++i) if (i->ValNewGen < 0) // visible facet visible.push_back(i); listsize = visible.size(); // cout << "Pyr Level " << pyr_level << " Visible " << listsize << " Triang " << TriangulationSize << endl; typename list< SHORTSIMPLEX >::iterator oldTriBack = --Triangulation.end(); #pragma omp parallel private(i) if(TriangulationSize>1000) { size_t k,l; bool one_not_in_i, not_in_facet; size_t not_in_i=0; size_t facet_dim=dim-1; size_t nr_in_i=0; list< SHORTSIMPLEX > Triangulation_kk; typename list< SHORTSIMPLEX >::iterator j; vector key(dim); #pragma omp for schedule(dynamic) for (size_t kk=0; kkGenInHyp.test(m)) nr_in_i++; if(nr_in_i>facet_dim){ break; } } if (nr_in_i==facet_dim){ // simplicial l=0; for (k = 0; k GenInHyp[k]==1) { key[l]=k; l++; } } key[dim-1]=new_generator; if(parallel_inside_pyramid) { #pragma omp critical(TRIANG) // critical only on top level store_key(key,-i->ValNewGen,0,Triangulation); } else { store_key(key,-i->ValNewGen,0,Triangulation); } continue; } size_t irrelevant_vertices=0; for(size_t vertex=0;vertexGenInHyp[VertInTri[vertex]]==0) // lead vertex not in hyperplane continue; if(irrelevant_verticeskey; one_not_in_i=false; // true indicates that one gen of simplex is not in hyperplane not_in_facet=false; // true indicates that a second gen of simplex is not in hyperplane for(k=0;kGenInHyp.test(key[k])) { if(one_not_in_i){ not_in_facet=true; break; } one_not_in_i=true; not_in_i=k; } } if(not_in_facet) // simplex does not share facet with hyperplane continue; key[not_in_i]=new_generator; store_key(key,-i->ValNewGen,j->vol,Triangulation_kk); } // j } // for vertex if(parallel_inside_pyramid) { #pragma omp critical(TRIANG) Triangulation.splice(Triangulation.end(),Triangulation_kk); } else Triangulation.splice(Triangulation.end(),Triangulation_kk); } // for kk } // parallel VertInTri.push_back(new_generator); TriSectionFirst.push_back(++oldTriBack); TriSectionLast.push_back(--Triangulation.end()); } //--------------------------------------------------------------------------- template void Full_Cone::store_key(const vector& key, const Integer& height, const Integer& mother_vol, list< SHORTSIMPLEX >& Triangulation){ // stores a simplex given by key and height in Triangulation // mother_vol is the volume of the simplex to which the new one is attached SHORTSIMPLEX newsimplex; newsimplex.key=key; newsimplex.height=height; newsimplex.vol=0; #pragma omp atomic TriangulationSize++; int tn; if(omp_get_level()==0) tn=0; else tn = omp_get_ancestor_thread_num(1); if (do_only_multiplicity) { // directly compute the volume if (mother_vol==1) newsimplex.vol = height; // the multiplicity is computed in SimplexEvaluator for(size_t i=0; iSimplexEval[tn].evaluate(newsimplex); // restore the local generator numbering, needed in extend_triangulation newsimplex.key=key; } if (keep_triangulation){ Triangulation.push_back(newsimplex); return; } bool Simpl_available=true; typename list< SHORTSIMPLEX >::iterator F; if(Top_Cone->FS[tn].empty()){ #pragma omp critical(FREESIMPL) { if(Top_Cone->FreeSimpl.empty()) Simpl_available=false; else{ F=Top_Cone->FreeSimpl.begin(); // take 100 simplices from FreeSimpl size_t q; for(q=0;q<1000;++q){ // or what you can get if(F==Top_Cone->FreeSimpl.end()) break; ++F; } if(q<1000) Top_Cone->FS[tn].splice(Top_Cone->FS[tn].begin(), Top_Cone->FreeSimpl); else Top_Cone->FS[tn].splice(Top_Cone->FS[tn].begin(), Top_Cone->FreeSimpl,Top_Cone->FreeSimpl.begin(),++F); } // else } // critical } // if empty if(Simpl_available){ Triangulation.splice(Triangulation.end(),Top_Cone->FS[tn], Top_Cone->FS[tn].begin()); Triangulation.back()=newsimplex; } else Triangulation.push_back(newsimplex); } //--------------------------------------------------------------------------- template void Full_Cone::process_pyramids(const size_t new_generator,const bool recursive){ /* We distinguish two types of pyramids: (i) recursive pyramids that give their support hyperplanes back to the mother. (ii) independent pyramids that are not linked to the mother. Pyramids of type (i) are The parameter recursive indicates whether the pyramids that will be created in process_pyramid(s) are of type (i) or (ii). We must also know whether "this" is of type (i) or (ii). This is indicted by do_all_hyperplanes. recursion_allowed indicates whether recursive pyramids can be created from "this". It has the same value as do_all_hyperplanes, except possibly for the top cone. By setting recursion_allowed=false in the constructor of the top cone, one can suppress recursive pyramids completely, but do_all_hyperplanes must be set to true for the top cone. Pyramids of type (ii) set recursion_allowed=false. Pyramids of type (i) are stored in RecPyramids, those of type (ii) in Pyramids. The store_level of the created pyramids is the one of the mother +1, EXCEPT that all pyramids of type (ii) created from pyramids of type (i) are stored at level 0. Note: the top cone has pyr_level=-1. */ long store_level; if(recursion_allowed && !recursive) store_level=0; else store_level=pyr_level+1; size_t start_level=omp_get_level(); // allows us to check that we are on level 0 // outside the loop and can therefore call evaluation // NOW the level doesn't go up in the loop // because parallelization has been dropped vector Pyramid_key; Pyramid_key.reserve(nr_gen); boost::dynamic_bitset<> in_Pyramid(nr_gen); typename list< FACETDATA >::iterator l=Facets.begin(); size_t listsize=Facets.size(); Integer ov_sp; // Order_Vector scalar product bool skip_triang; // make hyperplanes but skip triangulation (recursive pyramids only) // no need to parallelize the followqing loop since ALL pyramids // are now processed in parallel (see version 2.8 for parallel version) // BUT: new hyperplanes can be added before the loop has been finished. // Therefore we must work with listsize. for (size_t kk=0; kkValNewGen>=0) // facet not visible continue; skip_triang = false; if (Top_Cone->do_partial_triangulation && l->ValNewGen>=-1) { //ht1 criterion if (!is_pyramid) { // in the topcone we always have ov_sp > 0 skip_triang = true; } else { //check if it would be an excluded hyperplane ov_sp = v_scalar_product(l->Hyp,Order_Vector); if (ov_sp > 0) { skip_triang = true; } else if (ov_sp == 0) { for (size_t i=0; iHyp[i]>0) { skip_triang = true; break; } else if (l->Hyp[i]<0) { break; } } } } if (skip_triang && !recursive) { continue; } } Pyramid_key.clear(); // make data of new pyramid Pyramid_key.push_back(new_generator); for(size_t i=0;iHyp,Generators[i])==0){ // incidence data may no longer exist Pyramid_key.push_back(i); in_Pyramid.set(i); } } in_Pyramid.set(new_generator); // now we can store the new pyramid at the right place (or finish the simplicial ones) if (recursive && skip_triang) { // mark as "do not triangulate" process_pyramid(Pyramid_key, in_Pyramid, new_generator,store_level,0, recursive); } else { //default process_pyramid(Pyramid_key, in_Pyramid, new_generator,store_level,-l->ValNewGen, recursive); } if(Top_Cone->nrRecPyrs[store_level]>EvalBoundRecPyr && start_level==0){ Top_Cone->evaluate_rec_pyramids(store_level); } if(Top_Cone->nrPyramids[store_level] > EvalBoundPyr && start_level==0){ Top_Cone->evaluate_stored_pyramids(store_level); } } } //--------------------------------------------------------------------------- template void Full_Cone::process_pyramid(const vector& Pyramid_key, const boost::dynamic_bitset<>& in_Pyramid, const size_t new_generator,const size_t store_level, Integer height, const bool recursive){ // processes simplicial pyramids directly, stores other pyramids into their depots #pragma omp atomic Top_Cone->totalNrPyr++; if(Pyramid_key.size()==dim){ // simplicial pyramid completely done here #pragma omp atomic // only for saving memory Top_Cone->nrSimplicialPyr++; if(recursive){ // the facets may be facets of the mother cone and if recursive==true must be given back Simplex S(Pyramid_key, Generators); height = S.read_volume(); //update our lower bound for the volume Matrix H=S.read_support_hyperplanes(); list > NewFacets; for (size_t i=0; i Pyramid(*this,Pyramid_key); Pyramid.Mother=this; Pyramid.in_Pyramid=in_Pyramid; // need these data to give back supphyps Pyramid.new_generator=new_generator; if (height == 0) { //indicates "do not triangulate" Pyramid.do_partial_triangulation = false; Pyramid.do_Hilbert_basis = false; } nrRecPyramidsDue++; #pragma omp critical(RECPYRAMIDS) { Top_Cone->RecPyrs[store_level].push_back(Pyramid); Top_Cone->nrRecPyrs[store_level]++; } // critical } else { //not recursive vector key_wrt_top(Pyramid_key.size()); for(size_t i=0;iPyramids[store_level].push_back(key_wrt_top); Top_Cone->nrPyramids[store_level]++; } // critical } } } //--------------------------------------------------------------------------- template void Full_Cone::find_and_evaluate_start_simplex(){ size_t i,j; Integer factor; Simplex S = find_start_simplex(); vector key=S.read_key(); // generators indexed from 0 for (i = 0; i < dim; i++) { in_triang[key[i]]=true; if (deg1_triangulation && isComputed(ConeProperty::Grading)) deg1_triangulation = (gen_degrees[i] == 1); } Matrix H=S.read_support_hyperplanes(); for (i = 0; i (dim,0); Matrix G=S.read_generators(); //srand(12345); for(i=0;i1)) { store_key(key,S.read_volume(),1,Triangulation); if(do_only_multiplicity) { #pragma omp atomic TotDet++; } } if(do_triangulation){ // we must prepare the sections of the triangulation for(i=0;i void Full_Cone::select_supphyps_from(list >& NewFacets, const size_t new_generator, const boost::dynamic_bitset<>& in_Pyr){ // the mother cone (=this) selects supphyps from the list NewFacets supplied by the daughter // the daughter provides the necessary information via the parameters size_t i; typename list >::iterator pyr_hyp = NewFacets.begin(); bool new_global_hyp; FACETDATA NewFacet; Integer test; for(;pyr_hyp!= NewFacets.end();pyr_hyp++){ if(v_scalar_product(Generators[new_generator],*pyr_hyp)>0) continue; new_global_hyp=true; for(i=0;i void Full_Cone::evaluate_rec_pyramids(const size_t level){ // evaluates the stored recursive pyramids // Note that we must call extend_cone for every new_generator added to a given pyramid, // once we have left extend_cone to make recursive pyramids. // extend_cone is locked until all subpyramids have been finished. // Therefore we need not take care of this question here. // The "skip_remaining" technique is applied here and later on since we cannot interrupt // parallelized loops to do paralleized ecaluation. (No nested parallelization) assert(omp_get_level()==0); if(RecPyrs[level].empty()) return; if (verbose){ verboseOutput() << "++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; for (size_t l=0; l0) { verboseOutput() << "level " << l << " recursive pyramids remaining: " << nrRecPyrs[l] << endl; } } if (nrRecPyrs[level]>0) { verboseOutput() << "Computing support hyperplanes of " << nrRecPyrs[level] << " level " << level << " recursive pyramids." << endl; } verboseOutput() << "++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; } RecPyrs.resize(level+2); // provide space for a new generation nrRecPyrs.resize(level+2); nrRecPyrs[level+1]=0; size_t nr_pyramids=nrRecPyrs[level]; typename list >::iterator p; size_t ppos; bool skip_remaining_tri,skip_remaining_pyr,skip_remaining_rec_pyr; do { p = RecPyrs[level].begin(); ppos=0; skip_remaining_tri=false; skip_remaining_pyr=false; skip_remaining_rec_pyr=false; #pragma omp parallel for firstprivate(p,ppos) schedule(dynamic) for(size_t i=0; i ppos; ++ppos, ++p) ; for(; i < ppos; --ppos, --p) ; p->pyr_level=level; p->extend_cone(); if(check_evaluation_buffer_size()) // we interrupt parallel execution if it is really parallel skip_remaining_tri=true; // to keep the triangulation buffer under control if(nrRecPyrs[level+1]>EvalBoundRecPyr) skip_remaining_rec_pyr=true; if(nrPyramids[0]>EvalBoundPyr) skip_remaining_pyr=true; } if (!skip_remaining_tri && !skip_remaining_pyr) evaluate_rec_pyramids(level+1); // remove done pyramids p = RecPyrs[level].begin(); for(size_t i=0; iDone) { p=RecPyrs[level].erase(p); nrRecPyrs[level]--; } else { ++p; } } nr_pyramids = nrRecPyrs[level]; if (skip_remaining_tri) { if (verbose) verboseOutput() << nr_pyramids << " recursive pyramids remaining on level " << level << ", "; Top_Cone->evaluate_triangulation(); } if(skip_remaining_pyr){ if (verbose) verboseOutput() << nr_pyramids << " recursive pyramids remaining on level " << level << endl; evaluate_stored_pyramids(0); } } while(nr_pyramids>0); // indicates: not all pyramids done /* if (verbose) { verboseOutput() << "++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; verboseOutput() << "all recursive pyramids on level "<< level << " done!"<0) { verboseOutput() << "level " << l << " recursive pyramids remaining: " << nrRecPyrs[l] << endl; } } verboseOutput() << "++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; } */ if(check_evaluation_buffer()) { Top_Cone->evaluate_triangulation(); } } //--------------------------------------------------------------------------- template void Full_Cone::evaluate_stored_pyramids(const size_t level){ // evaluates the stored non-recursive pyramids // In contrast to the the recusrive pyramids, extend_cone is called // only once for every stored pyramid since we set recursion_allowed=false. assert(omp_get_level()==0); if(Pyramids[level].empty()) return; Pyramids.resize(level+2); // provide space for a new generation nrPyramids.resize(level+2); nrPyramids[level+1]=0; size_t nr_done=0; size_t nr_pyramids=nrPyramids[level]; vector Done(nr_pyramids,0); if (verbose) { verboseOutput() << "**************************************************" << endl; for (size_t l=0; l<=level; ++l) { if (nrPyramids[l]>0) { verboseOutput() << "level " << l << " pyramids remaining: " << nrPyramids[l] << endl; } } verboseOutput() << "**************************************************" << endl; } typename list >::iterator p; size_t ppos; bool skip_remaining_tri,skip_remaining_pyr; do { p = Pyramids[level].begin(); ppos=0; skip_remaining_tri=false; skip_remaining_pyr=false; #pragma omp parallel for firstprivate(p,ppos) schedule(dynamic) for(size_t i=0; i ppos; ++ppos, ++p) ; for(; i < ppos; --ppos, --p) ; if(Done[i]) continue; Done[i]=1; #pragma omp atomic nr_done++; Full_Cone Pyramid(*this,*p); Pyramid.recursion_allowed=false; // ABSOLUTELY NECESSARY HERE Pyramid.pyr_level=level; Pyramid.do_all_hyperplanes=false; if(level>=2 && do_partial_triangulation){ // limits the descent of do_partial_triangulation Pyramid.do_triangulation=true; Pyramid.do_partial_triangulation=false; } Pyramid.extend_cone(); if(check_evaluation_buffer_size() && nr_done < nr_pyramids) // we interrupt parallel execution if it is really parallel skip_remaining_tri=true; // to keep the triangulation buffer under control if(nrPyramids[level+1]>EvalBoundPyr && nr_done < nr_pyramids) skip_remaining_pyr=true; } // remove done pyramids p = Pyramids[level].begin(); for(size_t i=0; ievaluate_triangulation(); } if(skip_remaining_pyr){ evaluate_stored_pyramids(level+1); } } while(skip_remaining_tri || skip_remaining_pyr); if (verbose) { verboseOutput() << "**************************************************" << endl; verboseOutput() << "all pyramids on level "<< level << " done!"<0) { verboseOutput() << "level " << l << " pyramids remaining: " << nrPyramids[l] << endl; } } verboseOutput() << "**************************************************" << endl; } if(check_evaluation_buffer()) { Top_Cone->evaluate_triangulation(); } Pyramids[level].clear(); nrPyramids[level]=0; evaluate_stored_pyramids(level+1); } //--------------------------------------------------------------------------- /* builds the top cone successively by inserting generators, computes all essential data except global reduction */ template void Full_Cone::build_top_cone() { if(dim==0) return; if (verbose) { verboseOutput()< void Full_Cone::extend_cone() { assert(nrRecPyramidsDone <= nrRecPyramidsDue); // cout << "In extend " << allRecPyramidsBuilt << " " << nrRecPyramidsDone << " " << nrRecPyramidsDue << endl; if(!allRecPyramidsBuilt || nrRecPyramidsDone < nrRecPyramidsDue) // must wait for completion of subpyramids return; // of recursive pyramids built from previous generator long i; typename list< FACETDATA >::iterator l; // DECIDE WHETHER TO BUILD RECURSIVE PYRAMIDS long long RecBoundSuppHyp = dim*dim; RecBoundSuppHyp *= RecBoundSuppHyp*3000; //dim^4 * 3000 // int bound_div = nr_gen-dim+1; // if(bound_div > 3* (int) dim) bound_div = 3*dim; // RecBoundSuppHyp /= bound_div; if(nextGen==-1){ // indicates the first call of extend_cone for this cone find_and_evaluate_start_simplex(); nextGen=0; last_to_be_inserted=nr_gen-1; for(long j=nr_gen-1;j>=0;--j){ if(isComputed(ConeProperty::ExtremeRays)){ if(!in_triang[j] && Extreme_Rays[j]){ last_to_be_inserted=j; break; } } else if(!in_triang[j]){ last_to_be_inserted=j; break; } } } else // in this case we have left this function last time for this cone // via the explicit return since we had to form recursive pyramids. // The steps at the end of the loop that were skipped have to be done now. { // removing the negative hyperplanes if necessary if(do_all_hyperplanes || nextGen!=last_to_be_inserted){ l=Facets.begin(); for (size_t j=0; jValNewGen<0) l=Facets.erase(l); else l++; } } if(verbose && !is_pyramid) { verboseOutput() << "gen="<< nextGen <<", "<0) verboseOutput() << ", " << nrPyramids[0] << " pyr"; if(do_triangulation||do_partial_triangulation) verboseOutput() << ", " << TriangulationSize << " simpl"; verboseOutput()<< endl; } } // else Integer scalar_product; bool is_new_generator; for (i=nextGen;i(nr_gen);++i) { if(in_triang[i] || (isComputed(ConeProperty::ExtremeRays) && !Extreme_Rays[i])) continue; if(do_triangulation && TriangulationSize > 2*RecBoundTriang) // emermergency brake tri_recursion=true; // to switch off production of simplices in favor // of non-recursive pyramids is_new_generator=false; l=Facets.begin(); long long nr_pos=0; long long nr_neg=0; vector L; old_nr_supp_hyps=Facets.size(); // Facets will be xtended in the loop size_t lpos=0; #pragma omp parallel for private(L,scalar_product) firstprivate(lpos,l) reduction(+: nr_pos, nr_neg) schedule(dynamic) if(old_nr_supp_hyps>10000) for (size_t k=0; k lpos; lpos++, l++) ; for(;k < lpos; lpos--, l--) ; L=Generators[i]; scalar_product=v_scalar_product(L,(*l).Hyp); // l->ValPrevGen=l->ValNewGen; // last new generator is now previous generator l->ValNewGen=scalar_product; if (scalar_product<0) { is_new_generator=true; nr_neg++; } if (scalar_product>0) { nr_pos++; } } //end parallel for if(!is_new_generator) continue; // the i-th generator is used in the triangulation in_triang[i]=true; if (deg1_triangulation && isComputed(ConeProperty::Grading)) deg1_triangulation = (gen_degrees[i] == 1); // First we test whether to go to recursive pyramids because of too many supphyps // Once we have done so, we must stay with it if( supphyp_recursion || (recursion_allowed && nr_neg*nr_pos>RecBoundSuppHyp)){ // go to pyramids because of supphyps if(check_evaluation_buffer()){ // cout << "Evaluation Build Mitte" << endl; Top_Cone->evaluate_triangulation(); } // cout << "In SuppHyp Rec" << endl; supphyp_recursion=true; allRecPyramidsBuilt=false; // must lock extend_cone for this cone nrRecPyramidsDue=0; nrRecPyramidsDone=0; process_pyramids(i,true); //recursive allRecPyramidsBuilt=true; nextGen=i+1; return; // in recursive mode we stop at this point and come back later // to proceed with nextGen } else{ // now we check whether to go to pyramids because of the size of triangulation if( tri_recursion || (do_triangulation && (nr_neg*TriangulationSize > RecBoundTriang || 3*omp_get_max_threads()*TriangulationSize>EvalBoundTriang ))){ // go to pyramids because of triangulation if(check_evaluation_buffer()){ Top_Cone->evaluate_triangulation(); } tri_recursion=true; process_pyramids(i,false); //non-recursive } else{ // no pyramids necesary if(do_partial_triangulation) process_pyramids(i,false); // non-recursive if(do_triangulation) extend_triangulation(i); } if(do_all_hyperplanes || i!=last_to_be_inserted) find_new_facets(i); } // removing the negative hyperplanes if necessary if(do_all_hyperplanes || i!=last_to_be_inserted){ l=Facets.begin(); for (size_t j=0; jValNewGen<0) { l=Facets.erase(l); } else ++l; } } if(verbose && !is_pyramid) { verboseOutput() << "gen="<< i+1 <<", "<0) verboseOutput() << ", " << nrPyramids[0] << " pyr"; if(do_triangulation||do_partial_triangulation) verboseOutput() << ", " << TriangulationSize << " simpl"; verboseOutput()<< endl; } } // transfer Facets --> SupportHyperplanes if (do_all_hyperplanes) { typename list::const_iterator IHV=Facets.begin(); for(;IHV!=Facets.end();IHV++){ Support_Hyperplanes.push_back(IHV->Hyp); } } Facets.clear(); is_Computed.set(ConeProperty::SupportHyperplanes); if(is_pyramid && do_all_hyperplanes){ // must give supphyps back to maother Mother->select_supphyps_from(Support_Hyperplanes,new_generator,in_Pyramid); #pragma omp atomic Mother->nrRecPyramidsDone++; } transfer_triangulation_to_top(); // transfer remaining simplices to top if(check_evaluation_buffer()){ // cout << "Evaluating in build_cone at end, pyr level " << pyr_level << endl; // cout << "Evaluation Build Ende " << is_pyramid << endl; Top_Cone->evaluate_triangulation(); } Done=true; // this cone now finished } //--------------------------------------------------------------------------- template bool Full_Cone::check_evaluation_buffer(){ return(omp_get_level()==0 && check_evaluation_buffer_size()); } //--------------------------------------------------------------------------- template bool Full_Cone::check_evaluation_buffer_size(){ return(!Top_Cone->keep_triangulation && Top_Cone->TriangulationSize > EvalBoundTriang); } //--------------------------------------------------------------------------- template void Full_Cone::transfer_triangulation_to_top(){ // NEW EVA size_t i; // cout << "Pyr level " << pyr_level << endl; if(!is_pyramid) { // we are in top cone if(check_evaluation_buffer()){ evaluate_triangulation(); } return; // no transfer necessary } // now we are in a pyramid // cout << "In pyramid " << endl; typename list< SHORTSIMPLEX >::iterator pyr_simp=Triangulation.begin(); for(;pyr_simp!=Triangulation.end();pyr_simp++) for(i=0;ikey[i]=Top_Key[pyr_simp->key[i]]; // cout << "Keys transferred " << endl; #pragma omp critical(TRIANG) { Top_Cone->Triangulation.splice(Top_Cone->Triangulation.end(),Triangulation); Top_Cone->TriangulationSize+=TriangulationSize; } TriangulationSize = 0; // cout << "Done." << endl; } //--------------------------------------------------------------------------- template void Full_Cone::evaluate_triangulation(){ assert(omp_get_level()==0); if(TriangulationSize>0) { const long VERBOSE_STEPS = 50; long step_x_size = TriangulationSize-VERBOSE_STEPS; if (verbose) { verboseOutput() << "evaluating "< >::iterator s = Triangulation.begin(); size_t spos=0; int tn = omp_get_thread_num(); #pragma omp for schedule(dynamic) for(size_t i=0; i spos; ++spos, ++s) ; for(; i < spos; --spos, --s) ; if(keep_triangulation || do_Stanley_dec) sort(s->key.begin(),s->key.end()); SimplexEval[tn].evaluate(*s); if (verbose) { #pragma omp critical(VERBOSE) while ((long)(i*VERBOSE_STEPS) >= step_x_size) { step_x_size += TriangulationSize; verboseOutput() << "|" < void Full_Cone::primal_algorithm(){ // set needed do_ vars if (do_Hilbert_basis||do_deg1_elements||do_h_vector) do_evaluation = true; // look for a grading if it is needed deg1_check(); if (!isComputed(ConeProperty::Grading) && (do_multiplicity || do_deg1_elements || do_h_vector)) { if (!isComputed(ConeProperty::ExtremeRays)) { if (verbose) { verboseOutput() << "Cannot find grading s.t. all generators have the same degree! Computing Extreme rays first:" << endl; } compute_support_hyperplanes(); extreme_rays_and_deg1_check(); if(!pointed) return; // We keep the SupportHyperplanes, so we do not need to recompute them // for the last generator, and use them to make a global reduction earlier do_all_hyperplanes = false; supphyp_recursion = false; for(size_t i=0;i >(omp_get_max_threads(),SimplexEvaluator(*this)); } /***** Main Work is done in build_cone() *****/ build_top_cone(); // evaluates if keep_triangulation==false /***** Main Work is done in build_cone() *****/ if (verbose) { verboseOutput() << "Total number of pyramids = "<< totalNrPyr << endl; // cout << "Uni "<< Unimod << " Ht1NonUni " << Ht1NonUni << " NonDecided " << NonDecided << " TotNonDec " << NonDecidedHyp<< endl; if(do_only_multiplicity) verboseOutput() << "Determinantes computed = " << TotDet << endl; } extreme_rays_and_deg1_check(); if(!pointed) return; if (keep_triangulation) { if (isComputed(ConeProperty::Grading) && !deg1_generated) { deg1_triangulation = false; } evaluate_triangulation(); } FreeSimpl.clear(); // collect accumulated data from the SimplexEvaluators if(!is_pyramid) { for (int zi=0; zi void Full_Cone::dualize_cone() { compute_support_hyperplanes(); reset_tasks(); } // check the do_* bools, they must be set in advance // this method (de)activate them according to dependencies between them template void Full_Cone::do_vars_check() { // activate implications if (do_Stanley_dec) keep_triangulation = true; if (keep_triangulation) do_triangulation = true; if (do_multiplicity) do_triangulation = true; if (do_h_vector) do_triangulation = true; if (do_deg1_elements) do_partial_triangulation = true; if (do_Hilbert_basis) do_partial_triangulation = true; // activate do_only_multiplicity = do_multiplicity; if (do_Stanley_dec || do_h_vector || do_deg1_elements || do_Hilbert_basis) { do_only_multiplicity = false; do_evaluation = true; } if (do_multiplicity) do_evaluation = true; if (do_triangulation) do_partial_triangulation = false; if (do_Hilbert_basis) do_deg1_elements = false; //they will be extracted later } // general purpose compute method // do_* bools must be set in advance, this method does sanity checks for it // if no bool is set it does support hyperplanes and extreme rays template void Full_Cone::compute() { do_vars_check(); if (!do_triangulation && !do_partial_triangulation) support_hyperplanes(); else primal_algorithm(); } // -s template void Full_Cone::support_hyperplanes() { // recursion_allowed=true; compute_support_hyperplanes(); extreme_rays_and_deg1_check(); reset_tasks(); } template void Full_Cone::dual_mode() { Support_Hyperplanes.sort(); Support_Hyperplanes.unique(); Support_Hyperplanes.remove(vector(dim,0)); if(dim>0) { //correction needed to include the 0 cone; deg1_check(); if (isComputed(ConeProperty::Grading)) { if (verbose) { verboseOutput() << "Find degree 1 elements" << endl; } select_deg1_elements(); } } else { deg1_extreme_rays = deg1_generated = true; Grading=vector(dim); is_Computed.set(ConeProperty::IsDeg1ExtremeRays); is_Computed.set(ConeProperty::IsDeg1Generated); is_Computed.set(ConeProperty::Grading); } if (isComputed(ConeProperty::Grading)) check_deg1_hilbert_basis(); check_integrally_closed(); } //--------------------------------------------------------------------------- // Checks and auxiliary algorithms //--------------------------------------------------------------------------- template void Full_Cone::extreme_rays_and_deg1_check() { check_pointed(); if(!pointed) return; compute_extreme_rays(); deg1_check(); } //--------------------------------------------------------------------------- template void Full_Cone::set_degrees() { if(gen_degrees.size()==0 && isComputed(ConeProperty::Grading)) // now we set the degrees { gen_degrees.resize(nr_gen); vector gen_degrees_Integer=Generators.MxV(Grading); for (size_t i=0; i void Full_Cone::sort_gens_by_degree() { if(gen_degrees.size()==0 || deg1_extreme_rays) return; list > genList; vector v(dim+3); vector w(dim); unsigned long i,j; for(i=0;i >::iterator g=genList.begin(); for(;g!=genList.end();++g){ v=*g; gen_degrees[i]=explicit_cast_to_long(v[0]); Extreme_Rays[i]=false; if(v[dim+2]>0) Extreme_Rays[i]=true; for(j=0;j(gen_degrees); } } //--------------------------------------------------------------------------- template void Full_Cone::compute_support_hyperplanes(){ if(isComputed(ConeProperty::SupportHyperplanes)) return; bool save_tri = do_triangulation; bool save_part_tri = do_partial_triangulation; do_triangulation = false; do_partial_triangulation = false; build_top_cone(); do_triangulation = save_tri; do_partial_triangulation = save_part_tri; } //--------------------------------------------------------------------------- template Simplex Full_Cone::find_start_simplex() const { if (isComputed(ConeProperty::ExtremeRays)) { vector marked_extreme_rays(0); for (size_t i=0; i key_extreme = Generators.submatrix(Extreme_Rays).max_rank_submatrix_lex(dim); assert(key_extreme.size() == dim); vector key(dim); for (key_t i=0; i(key, Generators); } else { // assert(Generators.rank()>=dim); return Simplex(Generators); } } //--------------------------------------------------------------------------- template Matrix Full_Cone::select_matrix_from_list(const list >& S, vector& selection){ sort(selection.begin(),selection.end()); assert(selection.back() M(selection.size(),S.front().size()); typename list >::const_iterator ll=S.begin(); for(;ll!=S.end()&&i void Full_Cone::compute_extreme_rays(){ if (isComputed(ConeProperty::ExtremeRays)) return; assert(isComputed(ConeProperty::SupportHyperplanes)); if(dim*Support_Hyperplanes.size() < nr_gen) compute_extreme_rays_rank(); else compute_extreme_rays_compare(); } //--------------------------------------------------------------------------- template void Full_Cone::compute_extreme_rays_rank(){ size_t i,j; typename list >::iterator s; vector gen_in_hyperplanes; gen_in_hyperplanes.reserve(Support_Hyperplanes.size()); Matrix M; for(i=0;i=dim-1) Extreme_Rays[i]=true; } is_Computed.set(ConeProperty::ExtremeRays); } //--------------------------------------------------------------------------- template void Full_Cone::compute_extreme_rays_compare(){ size_t i,j,k,l,t; // Matrix SH=getSupportHyperplanes().transpose(); // Matrix Val=Generators.multiplication(SH); size_t nc=Support_Hyperplanes.size(); vector > Val(nr_gen); for (i=0;i Zero(nc); vector nr_zeroes(nr_gen); typename list >::iterator s; for (i = 0; i =nr_zeroes[i]) { Extreme_Rays[i]=false; break; } } } } } is_Computed.set(ConeProperty::ExtremeRays); } //--------------------------------------------------------------------------- template void Full_Cone::select_deg1_elements() { typename list >::iterator h = Hilbert_Basis.begin(); for(;h!=Hilbert_Basis.end();h++) if(v_scalar_product(Grading,*h)==1) Deg1_Elements.push_back(*h); is_Computed.set(ConeProperty::Deg1Elements,true); } //--------------------------------------------------------------------------- template void Full_Cone::check_pointed() { assert(isComputed(ConeProperty::SupportHyperplanes)); if (isComputed(ConeProperty::IsPointed)) return; Matrix SH = getSupportHyperplanes(); pointed = (SH.rank_destructive() == dim); is_Computed.set(ConeProperty::IsPointed); } //--------------------------------------------------------------------------- template void Full_Cone::deg1_check() { if (!isComputed(ConeProperty::Grading) // we still need it and && !isComputed(ConeProperty::IsDeg1ExtremeRays)) { // we have not tried it if (isComputed(ConeProperty::ExtremeRays)) { Matrix Extreme=Generators.submatrix(Extreme_Rays); Grading = Extreme.find_linear_form(); if (Grading.size() == dim) { is_Computed.set(ConeProperty::Grading); } else { deg1_extreme_rays = false; is_Computed.set(ConeProperty::IsDeg1ExtremeRays); } } else // extreme rays not known if (!isComputed(ConeProperty::IsDeg1Generated)) { Grading = Generators.find_linear_form(); if (Grading.size() == dim) { is_Computed.set(ConeProperty::Grading); } else { deg1_generated = false; is_Computed.set(ConeProperty::IsDeg1Generated); } } } //now we hopefully have a grading if (!isComputed(ConeProperty::Grading)) { if (isComputed(ConeProperty::ExtremeRays)) { // there is no hope to find a grading later deg1_generated = false; is_Computed.set(ConeProperty::IsDeg1Generated); deg1_extreme_rays = false; is_Computed.set(ConeProperty::IsDeg1ExtremeRays); if (do_deg1_elements || do_h_vector) { errorOutput() << "No grading specified and cannot find one. " << "Disabling some computations!" << endl; do_deg1_elements = false; do_h_vector = false; } } return; // we are done } set_degrees(); if (!isComputed(ConeProperty::IsDeg1Generated)) { deg1_generated = true; for (size_t i = 0; i < nr_gen; i++) { if (gen_degrees[i] != 1) { deg1_generated = false; break; } } is_Computed.set(ConeProperty::IsDeg1Generated); if (deg1_generated) { deg1_extreme_rays = true; is_Computed.set(ConeProperty::IsDeg1ExtremeRays); } } if (!isComputed(ConeProperty::IsDeg1ExtremeRays) && isComputed(ConeProperty::ExtremeRays)) { deg1_extreme_rays = true; for (size_t i = 0; i < nr_gen; i++) { if (Extreme_Rays[i] && gen_degrees[i] != 1) { deg1_extreme_rays = false; break; } } is_Computed.set(ConeProperty::IsDeg1ExtremeRays); } } //--------------------------------------------------------------------------- template void Full_Cone::check_deg1_hilbert_basis() { if (isComputed(ConeProperty::IsDeg1HilbertBasis)) return; if ( !isComputed(ConeProperty::Grading) || !isComputed(ConeProperty::HilbertBasis)) { errorOutput() << "WARNING: unsatisfied preconditions in check_deg1_hilbert_basis()!" < >::iterator h; for (h = Hilbert_Basis.begin(); h != Hilbert_Basis.end(); ++h) { if (v_scalar_product((*h),Grading)!=1) { deg1_hilbert_basis = false; break; } } } is_Computed.set(ConeProperty::IsDeg1HilbertBasis); } //--------------------------------------------------------------------------- template void Full_Cone::check_integrally_closed() { if (isComputed(ConeProperty::IsIntegrallyClosed)) return; if ( !isComputed(ConeProperty::HilbertBasis)) { errorOutput() << "WARNING: unsatisfied preconditions in check_integrally_closed()!" < >::iterator h; for (h = Hilbert_Basis.begin(); h != Hilbert_Basis.end(); ++h) { integrally_closed = false; for (size_t i=0; i< nr_gen; i++) { if ((*h) == Generators[i]) { integrally_closed = true; break; } } if (!integrally_closed) { break; } } } is_Computed.set(ConeProperty::IsIntegrallyClosed); } //--------------------------------------------------------------------------- // Global reduction //--------------------------------------------------------------------------- // Returns true if new_element is reducible versus the elements in Irred template bool Full_Cone::is_reducible(list< vector* >& Irred, const vector< Integer >& new_element){ size_t i; size_t s=Support_Hyperplanes.size(); // new_element can be longer than dim (it has one extra entry for the norm) // the scalar product function just takes the first dim entries vector scalar_product=l_multiplication(Support_Hyperplanes,new_element); typename list< vector* >::iterator j; vector *reducer; for (j =Irred.begin(); j != Irred.end(); j++) { reducer=(*j); for (i = 0; i < s; i++) { if ((*reducer)[i]>scalar_product[i]){ break; } } if (i==s) { //found a "reducer" and move it to the front Irred.push_front(*j); Irred.erase(j); return true; } } return false; } //--------------------------------------------------------------------------- // reduce the Candidates against itself and stores the remaining elements in Hilbert_Basis */ template void Full_Cone::global_reduction() { Integer norm; list > HB; typename list >::iterator c; for (size_t i = 0; i degree_function=compute_degree_function(); c = Candidates.begin(); size_t cpos = 0; size_t csize=Candidates.size(); if(verbose) { verboseOutput()<<"computing the degrees of the candidates... "< scalar_product; for (size_t j=0; j cpos; ++cpos, ++c) ; for(;j < cpos; --cpos, --c) ; norm=v_scalar_product(degree_function,(*c)); c->reserve(dim+1); c->push_back(norm); } if(verbose) { verboseOutput()<<"sorting the list... "<); if (verbose) { verboseOutput()<< csize <<" candidate vectors sorted."< > HBtmp; Integer norm_crit; while ( !Candidates.empty() ) { //use norm criterion to find irreducible elements c=Candidates.begin(); norm_crit=(*c)[dim]*2; //candidates with smaller norm are irreducible if ( Candidates.back()[dim] < norm_crit) { //all candidates are irreducible if (verbose) { verboseOutput()<pop_back(); } Hilbert_Basis.splice(Hilbert_Basis.end(), Candidates); break; } while ( (*c)[dim] < norm_crit ) { //can't go over the end because of the previous if // remove norm c->pop_back(); // push the scalar products to the reducer list HBtmp.push_back(l_multiplication(Support_Hyperplanes, *c)); // and the candidate itself to the Hilbert basis Hilbert_Basis.splice(Hilbert_Basis.end(), Candidates, c++); } csize = Candidates.size(); if (verbose) { verboseOutput()<* > HBpointers; // used to put "reducer" to the front c = HBtmp.begin(); while (c != HBtmp.end()) { HBpointers.push_back(&(*(c++))); } long VERBOSE_STEPS = 50; //print | for 2% if (verbose && csize>50000) { //print | for 1000 candidates VERBOSE_STEPS=csize/1000; } long step_x_size = csize-VERBOSE_STEPS; long counter = 0; long steps_done = 0; if (verbose) { verboseOutput() << "---------+---------+---------+---------+---------+"; if (VERBOSE_STEPS == 50) { verboseOutput() << " (one | per 2%)" << endl; } else { verboseOutput() << " (one | per 1000 candidates)" << endl; } } #pragma omp parallel private(c,cpos) firstprivate(HBpointers) { c=Candidates.begin(); cpos=0; #pragma omp for schedule(dynamic) for (size_t k=0; k cpos; ++cpos, ++c) ; for(;k < cpos; --cpos, --c) ; if ( is_reducible(HBpointers, *c) ) { (*c)[dim]=-1; //mark as reducible } if (verbose) { #pragma omp critical(VERBOSE) { counter++; while (counter*VERBOSE_STEPS >= step_x_size) { steps_done++; step_x_size += csize; verboseOutput() << "|" < 50 && steps_done%50 == 0) { verboseOutput() << " " << (steps_done) << "000" << endl; } } } //end critical(VERBOSE) } } //end for } //end parallel if (verbose) verboseOutput() << endl; // delete reducible candidates c = Candidates.begin(); while (c != Candidates.end()) { if ((*c)[dim]==-1) { c = Candidates.erase(c); } else { ++c; } } HBtmp.clear(); } if (verbose) { verboseOutput()<0 */ template vector Full_Cone::compute_degree_function() const { size_t i; vector degree_function(dim,0); if (isComputed(ConeProperty::Grading)) { //use the grading if we have one for (i=0; i >::const_iterator h; for (h=Support_Hyperplanes.begin(); h!=Support_Hyperplanes.end(); ++h) { for (i=0; i Integer Full_Cone::primary_multiplicity() const{ size_t i,j,k; Integer primary_multiplicity=0; vector key,new_key(dim-1); Matrix Projection(nr_gen,dim-1); for (i = 0; i < nr_gen; i++) { for (j = 0; j < dim-1; j++) { Projection.write(i,j,Generators[i][j]); } } typename list< vector >::const_iterator h; typename list< SHORTSIMPLEX >::const_iterator t; for (h =Support_Hyperplanes.begin(); h != Support_Hyperplanes.end(); ++h){ if ((*h)[dim-1]!=0) { for (t =Triangulation.begin(); t!=Triangulation.end(); ++t){ key=t->key; for (i = 0; i void Full_Cone::reset_tasks(){ do_triangulation = false; do_partial_triangulation = false; do_multiplicity=false; do_Hilbert_basis = false; do_deg1_elements = false; keep_triangulation = false; do_Stanley_dec=false; do_h_vector=false; do_evaluation = false; do_only_multiplicity=false; nrSimplicialPyr=0; totalNrPyr=0; is_pyramid = false; } //--------------------------------------------------------------------------- template Full_Cone::Full_Cone(Matrix M){ // constructor of the top cone dim=M.nr_of_columns(); if (dim!=M.rank()) { error_msg("error: Matrix with rank = number of columns needed in the constructor of the object Full_Cone.\nProbable reason: Cone not full dimensional (<=> dual cone not pointed)!"); throw BadInputException(); } Generators = M; nr_gen=Generators.nr_of_rows(); if (nr_gen != static_cast(static_cast(nr_gen))) { error_msg("To many generators to fit in range of key_t!"); throw FatalException(); } //make the generators coprime, remove 0 rows and duplicates vector gcds = Generators.make_prime(); bool remove_some = false; vector key(nr_gen, true); for (size_t i = 0; i(); //initialized to false is_Computed.set(ConeProperty::Generators); pointed = false; deg1_extreme_rays = false; deg1_generated = false; deg1_hilbert_basis = false; integrally_closed = false; reset_tasks(); Extreme_Rays = vector(nr_gen,false); in_triang = vector (nr_gen,false); deg1_triangulation = true; if(dim==0){ //correction needed to include the 0 cone; multiplicity = 1; Hilbert_Series.add(vector(1,1),vector()); is_Computed.set(ConeProperty::HilbertSeries); is_Computed.set(ConeProperty::Triangulation); } pyr_level=-1; Top_Cone=this; Top_Key.resize(nr_gen); for(size_t i=0;i Full_Cone::Full_Cone(const Cone_Dual_Mode &C) { dim = C.dim; Generators = C.get_generators(); nr_gen = Generators.nr_of_rows(); multiplicity = 0; is_Computed = bitset(); //initialized to false is_Computed.set(ConeProperty::Generators); pointed = true; is_Computed.set(ConeProperty::IsPointed); deg1_extreme_rays = false; deg1_generated = false; deg1_triangulation = false; deg1_hilbert_basis = false; integrally_closed = false; reset_tasks(); Extreme_Rays = vector(nr_gen,true); //all generators are extreme rays is_Computed.set(ConeProperty::ExtremeRays); Matrix SH = C.SupportHyperplanes; for (size_t i=0; i < SH.nr_of_rows(); i++) { Support_Hyperplanes.push_back(SH[i]); } is_Computed.set(ConeProperty::SupportHyperplanes); in_triang = vector(nr_gen,false); Hilbert_Basis = C.Hilbert_Basis; is_Computed.set(ConeProperty::HilbertBasis); if(dim==0){ //correction needed to include the 0 cone; multiplicity = 1; Hilbert_Series.add(vector(1,1),vector()); is_Computed.set(ConeProperty::HilbertSeries); } pyr_level=-1; Top_Cone=this; Top_Key.resize(nr_gen); for(size_t i=0;i Full_Cone::Full_Cone(Full_Cone& C, const vector& Key) { Generators = C.Generators.submatrix(Key); dim = Generators.nr_of_columns(); nr_gen = Generators.nr_of_rows(); Top_Cone=C.Top_Cone; // relate to top cone Top_Key.resize(nr_gen); for(size_t i=0;i(nr_gen,false); is_Computed.set(ConeProperty::ExtremeRays, C.isComputed(ConeProperty::ExtremeRays)); if(isComputed(ConeProperty::ExtremeRays)) for(size_t i=0;i (nr_gen,false); deg1_triangulation = true; Grading=C.Grading; is_Computed.set(ConeProperty::Grading, C.isComputed(ConeProperty::Grading)); Order_Vector=C.Order_Vector; do_triangulation=C.do_triangulation; do_partial_triangulation=C.do_partial_triangulation; do_multiplicity=C.do_multiplicity; do_deg1_elements=C.do_deg1_elements; do_h_vector=C.do_h_vector; do_Hilbert_basis=C.do_Hilbert_basis; keep_triangulation=C.keep_triangulation; do_only_multiplicity=C.do_only_multiplicity; do_evaluation=C.do_evaluation; do_Stanley_dec=C.do_Stanley_dec; is_pyramid=true; // pyr_level set by the calling routine totalNrSimplices=0; detSum = 0; if(C.gen_degrees.size()>0){ // now we copy the degrees gen_degrees.resize(nr_gen); for (size_t i=0; i bool Full_Cone::isComputed(ConeProperty::Enum prop) const{ return is_Computed.test(prop); } //--------------------------------------------------------------------------- // Data access //--------------------------------------------------------------------------- template size_t Full_Cone::getDimension()const{ return dim; } //--------------------------------------------------------------------------- template size_t Full_Cone::getNrGenerators()const{ return nr_gen; } //--------------------------------------------------------------------------- template bool Full_Cone::isPointed()const{ return pointed; } //--------------------------------------------------------------------------- template bool Full_Cone::isDeg1ExtremeRays() const{ return deg1_extreme_rays; } template bool Full_Cone::isDeg1HilbertBasis() const{ return deg1_hilbert_basis; } template bool Full_Cone::isIntegrallyClosed() const{ return integrally_closed; } //--------------------------------------------------------------------------- template vector Full_Cone::getGrading() const{ return Grading; } //--------------------------------------------------------------------------- template mpq_class Full_Cone::getMultiplicity()const{ return multiplicity; } //--------------------------------------------------------------------------- template const Matrix& Full_Cone::getGenerators()const{ return Generators; } //--------------------------------------------------------------------------- template vector Full_Cone::getExtremeRays()const{ return Extreme_Rays; } //--------------------------------------------------------------------------- template Matrix Full_Cone::getSupportHyperplanes()const{ size_t s= Support_Hyperplanes.size(); Matrix M(s,dim); size_t i=0; typename list< vector >::const_iterator l; for (l =Support_Hyperplanes.begin(); l != Support_Hyperplanes.end(); l++) { M.write(i,(*l)); i++; } return M; } //--------------------------------------------------------------------------- template void Full_Cone::getTriangulation(list< vector >& Triang, list& TriangVol) const { Triang.clear(); TriangVol.clear(); vector key(dim); typename list< SHORTSIMPLEX >::const_iterator l; for (l =Triangulation.begin(); l != Triangulation.end(); l++) { key=l->key; Triang.push_back(key); TriangVol.push_back(l->height); } } //--------------------------------------------------------------------------- template Matrix Full_Cone::getHilbertBasis()const{ size_t s= Hilbert_Basis.size(); Matrix M(s,dim); size_t i=0; typename list< vector >::const_iterator l; for (l =Hilbert_Basis.begin(); l != Hilbert_Basis.end(); l++) { M.write(i,(*l)); i++; } return M; } //--------------------------------------------------------------------------- template Matrix Full_Cone::getDeg1Elements()const{ size_t s= Deg1_Elements.size(); Matrix M(s,dim); size_t i=0; typename list< vector >::const_iterator l; for (l =Deg1_Elements.begin(); l != Deg1_Elements.end(); l++) { M.write(i,(*l)); i++; } return M; } //--------------------------------------------------------------------------- template void Full_Cone::error_msg(string s) const{ errorOutput() <<"\nFull Cone "<< s<<"\n"; } //--------------------------------------------------------------------------- template void Full_Cone::print()const{ verboseOutput()<<"\ndim="<. * */ #ifndef FULL_CONE_H #define FULL_CONE_H #include #include //#include #include #include "libnormaliz.h" #include "cone_property.h" #include "matrix.h" #include "simplex.h" #include "cone_dual_mode.h" #include "HilbertSeries.h" namespace libnormaliz { using std::list; using std::vector; //using std::set; using std::pair; using boost::dynamic_bitset; template class Cone; template class Full_Cone { friend class Cone; friend class SimplexEvaluator; size_t dim; size_t nr_gen; size_t hyp_size; // not used at present bool pointed; bool deg1_generated; bool deg1_extreme_rays; bool deg1_triangulation; bool deg1_hilbert_basis; bool integrally_closed; // control of what to compute bool do_triangulation; bool do_partial_triangulation; bool do_multiplicity; bool do_Hilbert_basis; bool do_deg1_elements; bool do_h_vector; bool keep_triangulation; bool do_Stanley_dec; // internal helper control variables bool do_only_multiplicity; bool do_evaluation; ConeProperties is_Computed; vector Grading; mpq_class multiplicity; Matrix Generators; vector Extreme_Rays; list > Support_Hyperplanes; vector in_triang; list > Hilbert_Basis; list > Candidates; // for the Hilbert basis size_t CandidatesSize; list > Deg1_Elements; HilbertSeries Hilbert_Series; vector gen_degrees; // will contain the degrees of the generators // list< SHORTSIMPLEX > CheckTri; list < SHORTSIMPLEX > Triangulation; // triangulation of cone size_t TriangulationSize; // number of elements in Triangulation, for efficiency Integer detSum; // sum of the det vector >::iterator> TriSectionFirst; // first simplex with lead vertex i vector >::iterator> TriSectionLast; // last simplex with lead vertex i vector VertInTri; // generators in the order in which they are inserted into the triangulation list< SHORTSIMPLEX > FreeSimpl; // list of short simplices already evaluated, kept for recycling vector > > FS; // the same per thread vector< SimplexEvaluator > SimplexEval; // one per thread struct FACETDATA { vector Hyp; // linear form of the hyperplane boost::dynamic_bitset<> GenInHyp; // incidence hyperplane/generators Integer ValNewGen; // value of linear form on the generator to be added // value on last generator added }; list Facets; // contains the data for Fourier-Motzkin and extension of triangulation vector Order_Vector; // vector for the disjoint decomposition of the cone list< STANLEYDATA > StanleyDec; // Stanley decomposition Full_Cone* Top_Cone; // reference to cone on top level vector Top_Key; // indices of generators w.r.t Top_Cone // control of pyramids and recusrion int pyr_level; // -1 for top cone, increased by 1 for each level of pyramids bool is_pyramid; // false for top cone bool do_all_hyperplanes; // controls whether all support hyperplanes must be computed long last_to_be_inserted; // good to know in case of do_all_hyperplanes==false bool recursion_allowed; // to allow or block recursive formation of pytamids bool parallel_inside_pyramid; // indicates that paralleization is taking place INSIDE the pyramid bool supphyp_recursion; // true if we have gone to pyramids because of support hyperplanes bool tri_recursion; // true if we have gone to pyramids because of triangulation vector< list > > Pyramids; //storage for pyramids vector nrPyramids; // number of pyramids on the various levels long nextGen; // the next generator to be processed size_t old_nr_supp_hyps; // must be remembered since we may leave extend_cone // before discarding "negative" hyperplanes Full_Cone* Mother; // reference to the mother of the pyramid boost::dynamic_bitset<> in_Pyramid; // indicates which generators of the MOTHER are in pyramid size_t new_generator; // indicates which generator of mother cone is apex of pyramid vector > > RecPyrs; // storage for recursive pyramids vector nrRecPyrs; size_t nrRecPyramidsDue; // number of recursive pyramids created from this at the current extension size_t nrRecPyramidsDone; // number of recursive pyramids that have returned supphyps bool allRecPyramidsBuilt; // indicates that all recursive pyramids from the current generator have been built bool Done; // true if this cone has been finished // statistics size_t totalNrSimplices; // total number of simplices evaluated size_t nrSimplicialPyr; size_t totalNrPyr; /* --------------------------------------------------------------------------- * Private routines, used in the public routines * --------------------------------------------------------------------------- */ void add_hyperplane(const size_t& new_generator, const FACETDATA & positive,const FACETDATA & negative, list& NewHyps); void extend_triangulation(const size_t& new_generator); void find_new_facets(const size_t& new_generator); void process_pyramids(const size_t new_generator,const bool recursive); void process_pyramid(const vector& Pyramid_key, const boost::dynamic_bitset<>& in_Pyramid, const size_t new_generator, const size_t store_level, Integer height, const bool recursive); void select_supphyps_from(list >& NewFacets, const size_t new_generator, const boost::dynamic_bitset<>& in_Pyramid); void evaluate_stored_pyramids(const size_t level); void evaluate_rec_pyramids(const size_t level); void find_and_evaluate_start_simplex(); Simplex find_start_simplex() const; void store_key(const vector&, const Integer& height, const Integer& mother_vol, list< SHORTSIMPLEX >& Triangulation); void build_top_cone(); void extend_cone(); bool is_reducible(list *> & Irred, const vector & new_element); void global_reduction(); vector compute_degree_function() const; Matrix select_matrix_from_list(const list >& S,vector& selection); void extreme_rays_and_deg1_check(); void set_degrees(); void sort_gens_by_degree(); void compute_support_hyperplanes(); bool check_evaluation_buffer(); bool check_evaluation_buffer_size(); void evaluate_triangulation(); void transfer_triangulation_to_top(); void primal_algorithm(); void compute_extreme_rays(); void compute_extreme_rays_compare(); void compute_extreme_rays_rank(); void select_deg1_elements(); void check_pointed(); void deg1_check(); void check_deg1_extreme_rays(); void check_deg1_hilbert_basis(); void check_integrally_closed(); void compute_multiplicity(); void do_vars_check(); void reset_tasks(); void addMult(Integer& volume, const vector& key, const int& tn); // multiplicity sum over thread tn public: /*--------------------------------------------------------------------------- * Constructors *--------------------------------------------------------------------------- */ Full_Cone(Matrix M); //main constructor Full_Cone(const Cone_Dual_Mode &C); Full_Cone(Full_Cone& C, const vector& Key); // for pyramids /*--------------------------------------------------------------------------- * Data access *--------------------------------------------------------------------------- */ void print() const; //to be modified, just for tests size_t getDimension() const; size_t getNrGenerators() const; bool isPointed() const; bool isDeg1ExtremeRays() const; bool isDeg1HilbertBasis() const; bool isIntegrallyClosed() const; vector getGrading() const; mpq_class getMultiplicity() const; const Matrix& getGenerators() const; vector getExtremeRays() const; Matrix getSupportHyperplanes() const; void getTriangulation(list< vector >& Triang, list& TriangVol) const; Matrix getHilbertBasis() const; Matrix getDeg1Elements() const; vector getHVector() const; bool isComputed(ConeProperty::Enum prop) const; /*--------------------------------------------------------------------------- * Computation Methods *--------------------------------------------------------------------------- */ void dualize_cone(); void support_hyperplanes(); void compute(); /* computes the multiplicity of the ideal in case of a Rees algebra * (not the same as the multiplicity of the semigroup) */ Integer primary_multiplicity() const; void dual_mode(); void error_msg(string s) const; }; //class end ***************************************************************** //--------------------------------------------------------------------------- } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/general.h000644 000765 000024 00000002377 12234011536 021470 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef GENERAL_H_ #define GENERAL_H_ #include #include #include /* // Regina will use GMP everywhere, even on Windows. #ifdef _WIN32 //for 32 and 64 bit windows #define NMZ_MPIR //always use MPIR #endif */ #ifdef NMZ_MPIR // use MPIR #include #else // otherwise use GMP #include #endif #include "libnormaliz.h" #include "normaliz_exception.h" #include "cone_property.h" namespace libnormaliz { } /* end namespace libnormaliz */ #endif /* GENERAL_H_ */ regina-4.95/engine/enumerate/normaliz/HilbertSeries.cpp000644 000765 000024 00000045707 12234011536 023156 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include #include #include #include #include "HilbertSeries.h" #include "vector_operations.h" #include "map_operations.h" #include "integer.h" #include "matrix.h" //--------------------------------------------------------------------------- namespace libnormaliz { using std::cout; using std::endl; using std::flush; long lcm_of_keys(const map& m){ long l = 1; map::const_iterator it; for (it = m.begin(); it != m.end(); ++it) { if (it->second != 0) l = lcm(l,it->first); } return l; } //--------------------------------------------------------------------------- // Constructor, creates 0/1 HilbertSeries::HilbertSeries() { num = vector(1,0); //denom just default constructed } // Constructor, creates num/denom, see class description for format HilbertSeries::HilbertSeries(const vector& numerator, const vector& gen_degrees) { num = vector(1,0); add(numerator, gen_degrees); } // Constructor, creates num/denom, see class description for format HilbertSeries::HilbertSeries(const vector& numerator, const map& denominator) { num = numerator; denom = denominator; is_simplified = false; } void HilbertSeries::reset() { num.clear(); num.push_back(0); denom.clear(); denom_classes.clear(); is_simplified = false; } // add another HilbertSeries to this void HilbertSeries::add(const vector& num, const vector& gen_degrees) { vector sorted_gd(gen_degrees); sort(sorted_gd.begin(), sorted_gd.end()); if (gen_degrees.size() > 0) assert(sorted_gd[0]>0); //TODO InputException? poly_add_to(denom_classes[sorted_gd], num); if (denom_classes.size() > DENOM_CLASSES_BOUND) collectData(); is_simplified = false; } // add another HilbertSeries to this HilbertSeries& HilbertSeries::operator+=(const HilbertSeries& other) { // add denom_classes map< vector, vector >::const_iterator it; for (it = other.denom_classes.begin(); it != other.denom_classes.end(); ++it) { poly_add_to(denom_classes[it->first], it->second); } // add accumulated data vector num_copy(other.num); performAdd(num_copy, other.denom); return (*this); } void HilbertSeries::performAdd(const vector& numerator, const vector& gen_degrees) const { map other_denom; size_t i, s = gen_degrees.size(); for (i=0; i0); other_denom[gen_degrees[i]]++; } // convert numerator to mpz s = numerator.size(); vector other_num(s); for (i=0; i& other_num, const map& oth_denom) const { map other_denom(oth_denom); //TODO redesign, dont change other_denom // adjust denominators denom_t diff; map::iterator it; for (it = denom.begin(); it != denom.end(); ++it) { // augment other denom_t& ref = other_denom[it->first]; diff = it->second - ref; if (diff > 0) { ref += diff; poly_mult_to(other_num, it->first, diff); } } for (it = other_denom.begin(); it != other_denom.end(); ++it) { // augment this denom_t& ref = denom[it->first]; diff = it->second - ref; if (diff > 0) { ref += diff; poly_mult_to(num, it->first, diff); } } assert (denom == other_denom); // now just add the numerators poly_add_to(num,other_num); remove_zeros(num); is_simplified = false; } void HilbertSeries::collectData() const { if (verbose) verboseOutput() << "Adding " << denom_classes.size() << " denominator classes..." << flush; map< vector, vector >::iterator it; for (it = denom_classes.begin(); it != denom_classes.end(); ++it) { performAdd(it->second, it->first); } denom_classes.clear(); if (verbose) verboseOutput() << " done." << endl; } // simplify, see class description void HilbertSeries::simplify() const { if (is_simplified) return; collectData(); /* if (verbose) { cout << "Hilbert series before simplification: "<< endl << *this; }*/ vector q, r, poly; //polynomials // In denom_cyclo we collect cyclotomic polynomials in the denominator. // During this method the Hilbert series is given by num/(denom*cdenom) // where denom | cdenom are exponent vectors of (1-t^i) | i-th cyclotminc poly. map cdenom; map::reverse_iterator rit; long i; for (rit = denom.rbegin(); rit != denom.rend(); ++rit) { // check if we can divide the numerator by (1-t^i) i = rit->first; denom_t& denom_i = rit->second; poly = coeff_vector(i); while (denom_i > 0) { poly_div(q, r, num, poly); if (r.size() == 0) { // numerator is divisable by poly num = q; denom_i--; } else { break; } } if (denom_i == 0) continue; // decompose (1-t^i) into cyclotomic polynomial for(long d=1; d<=i/2; ++d) { if (i % d == 0) cdenom[d] += denom_i; } cdenom[i] += denom_i; // the product of the cyclo. is t^i-1 = -(1-t^i) if (denom_i%2 == 1) v_scalar_multiplication(num,mpz_class(-1)); } // end for denom.clear(); map::iterator it = cdenom.begin(); while (it != cdenom.end()) { // check if we can divide the numerator by i-th cyclotomic polynomial i = it->first; denom_t& cyclo_i = it->second; poly = cyclotomicPoly(i); while (cyclo_i > 0) { poly_div(q, r, num, poly); if (r.size() == 0) { // numerator is divisable by poly num = q; cyclo_i--; } else { break; } } if (cyclo_i == 0) { cdenom.erase(it++); } else { ++it; } } // done with canceling // save this representation cyclo_num = num; cyclo_denom = cdenom; // now collect the cyclotomic polynomials in (1-t^i) factors it = cdenom.find(1); if (it != cdenom.end()) dim = it->second; else dim = 0; period = lcm_of_keys(cdenom); i = period; if (period > 2000) { errorOutput() << "WARNING: Period is to big, the representation of the Hilbert series may have more than dimensional many factors in the denominator!" << endl; i = cdenom.rbegin()->first; } while (!cdenom.empty()) { //create a (1-t^i) factor out of all cyclotomic poly. denom[i]++; v_scalar_multiplication(num,mpz_class(-1)); for (long d = 1; d <= i; ++d) { if (i % d == 0) { it = cdenom.find(d); if (it != cdenom.end() && it->second>0) { it->second--; if (it->second == 0) cdenom.erase(it); } else { num = poly_mult(num, cyclotomicPoly(d)); } } } i = lcm_of_keys(cdenom); if (i > 2000) { i = cdenom.rbegin()->first; } } /* if (verbose) { cout << "Simplified Hilbert series: " << endl << *this; }*/ is_simplified = true; } long HilbertSeries::getPeriod() const { simplify(); return period; } vector< vector > HilbertSeries::getHilbertQuasiPolynomial() const { if(!is_simplified || quasi_poly.size()==0) { computeHilbertQuasiPolynomial(); } return quasi_poly; } mpz_class HilbertSeries::getHilbertQuasiPolynomialDenom() const { if(!is_simplified || quasi_poly.size()==0) { computeHilbertQuasiPolynomial(); } return quasi_denom; } void HilbertSeries::computeHilbertQuasiPolynomial() const { simplify(); if (period > 2000) { errorOutput()<<"WARNING: We skip the computation of the Hilbert-quasi-polynomial because the period "<< period <<" is to big!" < norm_num(num_size); //normalized numerator for (i = 0; i < num_size; ++i) { norm_num[i] = to_mpz(num[i]); } map::reverse_iterator rit; long d; vector factor, r; for (rit = denom.rbegin(); rit != denom.rend(); ++rit) { d = rit->first; //nothing to do if it already has the correct t-power if (d != period) { //norm_num *= (1-t^p / 1-t^d)^denom[d] poly_div(factor, r, coeff_vector(period), coeff_vector(d)); assert(r.size()==0); //assert remainder r is 0 //TODO more efficient method *= //TODO Exponentiation by squaring of factor, then *= for (i=0; i < rit->second; ++i) { norm_num = poly_mult(norm_num, factor); } } } //cut numerator into period many pieces and apply standard method quasi_poly = vector< vector >(period); long nn_size = norm_num.size(); for (j=0; j= 0; --i) { pp *= period; //p^i ok, it is p^(dim-1-i) for (j=0; j(1,dim) * pp; //substitute t by t-j for (j=0; j(quasi_poly[j], j); // replaces quasi_poly[j] } //divide by gcd //TODO operate directly on vector Matrix QP(quasi_poly); mpz_class g = QP.matrix_gcd(); g = gcd(g,quasi_denom); quasi_denom /= g; QP.scalar_division(g); quasi_poly = QP.get_elements(); } // returns the numerator, repr. as vector of coefficients, the h-vector const vector& HilbertSeries::getNum() const { return num; } // returns the denominator, repr. as a map of the exponents of (1-t^i)^e const map& HilbertSeries::getDenom() const { return denom; } // returns the numerator, repr. as vector of coefficients const vector& HilbertSeries::getCyclotomicNum() const { simplify(); return cyclo_num; } // returns the denominator, repr. as a map of the exponents of (1-t^i)^e const map& HilbertSeries::getCyclotomicDenom() const { simplify(); return cyclo_denom; } ostream& operator<< (ostream& out, const HilbertSeries& HS) { HS.collectData(); out << "("; if (HS.num.size()>0) out << " " << HS.num[0]; for (size_t i=1; i 0 ) out << " +"<::const_iterator it; for (it = HS.denom.begin(); it != HS.denom.end(); ++it) { if ( it->second != 0 ) out << " (1-t^"<< it->first <<")^" << it->second; } out << " )" << std::endl; return out; } //--------------------------------------------------------------------------- // polynomial operations, for polynomials repr. as vector of coefficients //--------------------------------------------------------------------------- // returns the coefficient vector of 1-t^i template vector coeff_vector(size_t i) { vector p(i+1,0); p[0] = 1; p[i] = -1; return p; } template void remove_zeros(vector& a) { size_t i=a.size(); while ( i>0 && a[i-1]==0 ) --i; if (i < a.size()) { a.resize(i); } } // a += b (also possible to define the += op for vector) template void poly_add_to (vector& a, const vector& b) { size_t b_size = b.size(); if (a.size() < b_size) { a.resize(b_size); } for (size_t i=0; i void poly_sub_to (vector& a, const vector& b) { size_t b_size = b.size(); if (a.size() < b_size) { a.resize(b_size); } for (size_t i=0; i vector poly_mult(const vector& a, const vector& b) { size_t a_size = a.size(); size_t b_size = b.size(); vector p( a_size + b_size - 1 ); size_t i,j; for (i=0; i void poly_mult_to(vector& a, long d, long e) { assert(d>0); assert(e>=0); long i; a.reserve(a.size() + d*e); while (e>0) { a.resize(a.size() + d); for (i=a.size()-1; i>=d; --i) { a[i] -= a[i-d]; } e--; } } // division with remainder, a = q * b + r, deg(r) < deg(b), needs |leadcoef(b)| = 1 template void poly_div(vector& q, vector& r, const vector& a, const vector&b) { assert(b.back()!=0); // no unneeded zeros assert(b.back()==1 || b.back()==-1); // then division is always possible r = a; remove_zeros(r); size_t b_size = b.size(); int degdiff = r.size()-b_size; // degree differenz if (r.size() < b_size) { q = vector(); } else { q = vector(degdiff+1); } Integer divisor; size_t i=0; while (r.size() >= b_size) { divisor = r.back()/b.back(); q[degdiff] = divisor; // r -= divisor * t^degdiff * b for (i=0; i vector cyclotomicPoly(long n) { // the static variable is initialized only once and then stored static map > CyclotomicPoly = map >(); if (CyclotomicPoly.count(n) == 0) { //it was not computed so far vector poly, q, r; for (long i = 1; i <= n; ++i) { // compute needed and uncomputed factors if( n % i == 0 && CyclotomicPoly.count(i) == 0) { // compute the i-th poly by dividing X^i-1 by the // d-th cycl.poly. with d divides i poly = vector(i+1); poly[0] = -1; poly[i] = 1; // X^i - 1 for (long d = 1; d < i; ++d) { // <= i/2 should be ok if( i % d == 0) { poly_div(q, r, poly, CyclotomicPoly[d]); assert(r.size()==0); poly = q; } } CyclotomicPoly[i] = poly; //cout << i << "-th cycl. pol.: " << CyclotomicPoly[i]; } } } assert(CyclotomicPoly.count(n)>0); return CyclotomicPoly[n]; } //--------------------------------------------------------------------------- // computing the Hilbert polynomial from h-vector //--------------------------------------------------------------------------- template vector compute_e_vector(vector Q, int dim){ int i,j; vector E_Vector(dim,0); Q.resize(dim+1); for (i = 0; i (1,i); for (j = 1; j <=dim; j++) { Q[j-1]=j*Q[j]; } } return E_Vector; } //--------------------------------------------------------------------------- template vector compute_polynomial(vector h_vector, int dim) { // handle dimension 0 if (dim == 0) return vector(dim); vector Hilbert_Polynomial = vector(dim); int i,j; Integer mult_factor; vector E_Vector=compute_e_vector(h_vector, dim); vector C(dim,0); C[0]=1; for (i = 0; i (i,dim); if (((dim-1-i)%2)==0) { for (j = 0; j (1,i+1); } return Hilbert_Polynomial; } //--------------------------------------------------------------------------- // substitutes t by (t-a), overwrites the polynomial! template void linear_substitution(vector& poly, const Integer& a) { long deg = poly.size()-1; // Iterated division by (t+a) for (long step=0; step= step; --i) { poly[i] -= a * poly[i+1]; } //the remainders are the coefficients of the transformed polynomial } } } //end namespace libnormaliz regina-4.95/engine/enumerate/normaliz/HilbertSeries.h000644 000765 000024 00000015722 12234011536 022615 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /* * HilbertSeries represents a Hilbert series of a (ZZ_+)-graded algebra * with generators of different degrees. * It is represented as a polynomial divided by a product of (1-t^i). * The numerator is represented as vector of coefficients, the h-vector * h vector repr.: sum of h[i]*t^i * and the denominator is represented as a map d of the exponents of (1-t^i) * d vector repr.: product of (1-t^i)^d[i] over i in d * Input of the denominator is also possible as a vector of degrees of the * generators. * * The class offers basic operations on the series, a simplification and * different forms of representation of the series. * * Furthermore this file include operations for the polynomials used. */ //--------------------------------------------------------------------------- #ifndef HILBERT_SERIES_H #define HILBERT_SERIES_H //--------------------------------------------------------------------------- #include #include #include #include "general.h" //--------------------------------------------------------------------------- namespace libnormaliz { using std::vector; using std::map; using std::ostream; class HilbertSeries; // write a readable representation to the stream ostream& operator<< (ostream& out, const HilbertSeries& HS); typedef long long num_t; //integer type for numerator typedef long denom_t; //integer type for denominator class HilbertSeries { public: // Constructor, creates 0/1 HilbertSeries(); // Constructor, creates num/denom, see class description for format HilbertSeries(const vector& num, const vector& gen_degrees); // Constructor, creates num/denom, see class description for format HilbertSeries(const vector& num, const map& denom); // resets to 0/1 void reset(); // add another HilbertSeries to this HilbertSeries& operator+=(const HilbertSeries& other); // add another HilbertSeries to this void add(const vector& num, const vector& gen_degrees); // simplify, see class description // it changes the representation of the series, but not the series itself // therefore it is declared const void simplify() const; // collect data from the denom_classes void collectData() const; // returns the numerator, repr. as vector of coefficients, the h-vector const vector& getNum() const; // returns the denominator, repr. as a map of the exponents of (1-t^i)^e const map& getDenom() const; // returns the numerator, repr. as vector of coefficients const vector& getCyclotomicNum() const; // returns the denominator, repr. as a map of the exponents of the cyclotomic polynomials const map& getCyclotomicDenom() const; long getPeriod() const; vector< vector > getHilbertQuasiPolynomial() const; mpz_class getHilbertQuasiPolynomialDenom() const; private: // collected data in denominator classes mutable map< vector, vector > denom_classes; // add the classes if they get too many static const size_t DENOM_CLASSES_BOUND = 50000; // the numerator, repr. as vector of coefficients, the h-vector mutable vector num; // the denominator, repr. as a map of the exponents of (1-t^i)^e mutable map denom; // the numerator, repr. as vector of coefficients mutable vector cyclo_num; // the denominator, repr. as a map of the exponents of the cyclotomic polynomials mutable map cyclo_denom; mutable bool is_simplified; mutable long dim; mutable long period; // the quasi polynomial, can have big coefficients mutable vector< vector > quasi_poly; mutable mpz_class quasi_denom; // these are only const when used properly!! void performAdd(const vector& num, const vector& gen_degrees) const; void performAdd(vector& num, const map& denom) const; void computeHilbertQuasiPolynomial() const; friend ostream& operator<< (ostream& out, const HilbertSeries& HS); }; //class end ***************************************************************** //--------------------------------------------------------------------------- // polynomial operations, for polynomials repr. as vector of coefficients //--------------------------------------------------------------------------- // a += b template void poly_add_to (vector& a, const vector& b); // a -= b template void poly_sub_to (vector& a, const vector& b); // a * b template vector poly_mult(const vector& a, const vector& b); // a *= (1-t^d)^e template void poly_mult_to(vector& a, long d, long e = 1); // division with remainder, a = q * b + r template void poly_div(vector& q, vector& r, const vector& a, const vector&b); // remove leading zero coefficients, 0 polynomial leads to empty list template void remove_zeros(vector& a); // Returns the n-th cyclotomic polynomial, all smaller are computed and stored. // The n-th cyclotomic polynomial is the product of (X-w) over all // n-th primitive roots of unity w. template vector cyclotomicPoly(long n); // returns the coefficient vector of 1-t^i template vector coeff_vector(size_t i); // substitutes t by (t-a), overwrites the polynomial! template void linear_substitution(vector& poly, const Integer& a); //--------------------------------------------------------------------------- // computing the Hilbert polynomial from h-vector //--------------------------------------------------------------------------- template vector compute_e_vector(vector h_vector, int dim); template vector compute_polynomial(vector h_vector, int dim); } //end namespace libnormaliz //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/integer.cpp000644 000765 000024 00000006400 12234011536 022032 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include "integer.h" #include "limits.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; bool fits_long_range(long long a) { //TODO make it return true without check when ranges are the same return ( a <= LONG_MAX && a >= LONG_MIN); } mpz_class to_mpz(long a) { return mpz_class(a); } mpz_class to_mpz(long long a) { if (fits_long_range(a)) { return mpz_class(long(a)); } else { static long long mod = LONG_MAX; // to ensure the following % and / are done in long long return mpz_class(long (a % mod)) + mpz_class(LONG_MAX) * to_mpz(long(a/mod)); } } template Integer gcd(const Integer& a, const Integer& b){ if (a==0) { return Iabs(b); } if (b==0) { return Iabs(a); } Integer q0,q1,r; q0=Iabs(a); r=Iabs(b); do { q1=r; r=q0%q1; q0=q1; } while (r!=0); return q1; } template<> mpz_class gcd(const mpz_class& a, const mpz_class& b) { mpz_class g; mpz_gcd (g.get_mpz_t(), a.get_mpz_t(), b.get_mpz_t()); return g; } //--------------------------------------------------------------------------- template Integer lcm(const Integer& a, const Integer& b){ if ((a==0)||(b==0)) { return 0; } else return Iabs(a*b/gcd(a,b)); } template<> mpz_class lcm(const mpz_class& a, const mpz_class& b) { mpz_class g; mpz_lcm (g.get_mpz_t(), a.get_mpz_t(), b.get_mpz_t()); return g; } //--------------------------------------------------------------------------- template size_t decimal_length(Integer a){ size_t l=1; if (a<0) { a=-a; l++; } while((a/=10)!=0) l++; return l; } //--------------------------------------------------------------------------- template Integer permutations(const size_t& a, const size_t& b){ unsigned long i; Integer P=1; for (i = a+1; i <= b; i++) { P*=i; } return P; } //--------------------------------------------------------------------------- template Integer permutations_modulo(const size_t& a, const size_t& b, long m) { unsigned long i; Integer P=1; for (i = a+1; i <= b; i++) { P*=i; P%=m; } return P; } //--------------------------------------------------------------------------- } regina-4.95/engine/enumerate/normaliz/integer.h000644 000765 000024 00000006310 12234011536 021477 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef INTEGER_H_ #define INTEGER_H_ #include "general.h" // Integer should (may) support: // Integer abs(Integer); here implemented as Iabs // Integer min(Integer, Integer); here we use the template min in // It provides abs, gcd and lcm //--------------------------------------------------------------------------- namespace libnormaliz { //--------------------------------------------------------------------------- bool fits_long_range(long long a); mpz_class to_mpz(long a); mpz_class to_mpz(long long a); inline mpz_class to_mpz(const mpz_class& a) {return a;} template inline long explicit_cast_to_long(const Integer& a) { // check for overflow if (!fits_long_range(a)) { throw ArithmeticException(); } return (long)a; } template<> inline long explicit_cast_to_long(const long& a) { return a; } template<> inline long explicit_cast_to_long (const mpz_class& a) { // check for overflow if (!a.fits_slong_p()) { throw ArithmeticException(); } return a.get_si(); } //--------------------------------------------------------------------------- // Basic functions //--------------------------------------------------------------------------- // returns the absolute value of a template inline Integer Iabs(const Integer& a) { return (a>=0) ? (a) : Integer(-a); } //returns gcd of a and b, if one is 0 returns the other integer template Integer gcd(const Integer& a, const Integer& b); template<> mpz_class gcd(const mpz_class& a, const mpz_class& b); //returns lcm of a and b, returns 0 if one is 0 template Integer lcm(const Integer& a, const Integer& b); template<> mpz_class lcm(const mpz_class& a, const mpz_class& b); //--------------------------------------------------------------------------- // Special functions //--------------------------------------------------------------------------- //return the number of decimals, needed to write the Integer a template size_t decimal_length(Integer a); //returns b!/a! template Integer permutations(const size_t& a, const size_t& b); template Integer permutations_modulo(const size_t& a, const size_t& b, long m); } //--------------------------------------------------------------------------- #endif /* INTEGER_H_ */ //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/libnormaliz-templated.cpp000644 000765 000024 00000005516 12234011536 024703 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "libnormaliz.cpp" #include "integer.cpp" #include "vector_operations.cpp" #include "matrix.cpp" #include "simplex.cpp" #include "list_operations.cpp" #include "lineare_transformation.cpp" #include "sublattice_representation.cpp" #include "full_cone.cpp" #include "cone_dual_mode.cpp" #include "cone.cpp" namespace libnormaliz { template class Cone; template class Cone; template class Cone; template class Matrix; template class Matrix; template class Matrix; template class Sublattice_Representation; template class Sublattice_Representation; template class Sublattice_Representation; template class Lineare_Transformation; template class Lineare_Transformation; template class Lineare_Transformation; template Lineare_Transformation Transformation(const Matrix& M); template Lineare_Transformation Transformation(const Matrix& M); template Lineare_Transformation Transformation(const Matrix& M); template size_t decimal_length(long); template size_t decimal_length(long long int); template size_t decimal_length(mpz_class); template long gcd(const long& a, const long& b); template long lcm(const long& a, const long& b); template long permutations(const size_t& a, const size_t& b); template long long gcd(const long long& a, const long long& b); template long long lcm(const long long& a, const long long& b); template long long permutations(const size_t& a, const size_t& b); //template mpz_class gcd(const mpz_class& a, const mpz_class& b); //template mpz_class lcm(const mpz_class& a, const mpz_class& b); template mpz_class permutations(const size_t& a, const size_t& b); template ostream& operator<< (ostream& out, const vector& v); template ostream& operator<< (ostream& out, const vector& v); template ostream& operator<< (ostream& out, const vector& v); } regina-4.95/engine/enumerate/normaliz/libnormaliz.cpp000644 000765 000024 00000004773 12234011536 022732 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "libnormaliz.h" #include "general.h" namespace libnormaliz { bool verbose = false; bool test_arithmetic_overflow = false; long overflow_test_modulus = 15401; namespace { std::ostream* verbose_ostream_ptr = &std::cout; std::ostream* error_ostream_ptr = &std::cerr; } // end anonymous namespace, only accessible in this file (and when it is included) void setVerboseOutput(std::ostream& v_out) { verbose_ostream_ptr = &v_out; } void setErrorOutput(std::ostream& e_out) { error_ostream_ptr = &e_out; } std::ostream& verboseOutput() { return *verbose_ostream_ptr; } std::ostream& errorOutput() { return *error_ostream_ptr; } InputType to_type(const std::string& type_string) { if (type_string=="0"||type_string=="integral_closure") { return Type::integral_closure; } if (type_string=="1"||type_string=="normalization") { return Type::normalization; } if (type_string=="2"||type_string=="polytope") { return Type::polytope; } if (type_string=="3"||type_string=="rees_algebra") { return Type::rees_algebra; } if (type_string=="4"||type_string=="hyperplanes") { return Type::hyperplanes; } if (type_string=="5"||type_string=="equations") { return Type::equations; } if (type_string=="6"||type_string=="congruences") { return Type::congruences; } if (type_string=="signs") { return Type::signs; } if (type_string=="10"||type_string=="lattice_ideal") { return Type::lattice_ideal; } if (type_string=="grading") { return Type::grading; } std::cerr<<"ERROR: Unknown type \""<. * */ #ifndef LIBNORMALIZ_H_ #define LIBNORMALIZ_H_ #include #include namespace libnormaliz { namespace Type { enum InputType { integral_closure, normalization, polytope, rees_algebra, hyperplanes, signs, equations, congruences, inhomogeneous_hyperplanes, inhomogeneous_equations, inhomogeneous_congruences, lattice_ideal, grading }; } //end namespace Type namespace Mode { enum ComputationMode { supportHyperplanes, triangulationSize, triangulation, volumeTriangulation, volumeLarge, degree1Elements, hilbertBasisTriangulation, hilbertBasisMultiplicity, hilbertBasisLarge, hilbertSeries, hilbertSeriesLarge, hilbertBasisSeries, hilbertBasisSeriesLarge, dual }; } //end namespace Mode using Type::InputType; using Mode::ComputationMode; /* converts a string to an InputType * throws an BadInputException if the string cannot be converted */ InputType to_type(const std::string& type_string); /* this type is used in the entries of keys * it has to be able to hold number of generators */ typedef unsigned int key_t; extern bool verbose; /* if test_arithmetic_overflow is true, many operations are also done * modulo overflow_test_modulus to ensure the correctness of the calculations */ extern bool test_arithmetic_overflow; extern long overflow_test_modulus; /* methods to set and use the output streams */ void setVerboseOutput(std::ostream&); void setErrorOutput(std::ostream&); std::ostream& verboseOutput(); std::ostream& errorOutput(); } /* end namespace libnormaliz */ #endif /* LIBNORMALIZ_H_ */ regina-4.95/engine/enumerate/normaliz/lineare_transformation.cpp000644 000765 000024 00000015350 12234011536 025146 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include #include #include #include "lineare_transformation.h" #include "integer.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- template Lineare_Transformation::Lineare_Transformation(){ rk=0; status="non initialized"; index=1; } //--------------------------------------------------------------------------- template Lineare_Transformation::Lineare_Transformation(const Matrix& M){ rk=0; status="initialized, before transformation"; index=1; Center = Matrix(M); Right = Matrix(M.nr_of_columns()); Right_Inv = Matrix(M.nr_of_columns()); } //--------------------------------------------------------------------------- template void Lineare_Transformation::read() const{ cout<<"\nRank="< long Lineare_Transformation::get_rank() const{ return rk; } //--------------------------------------------------------------------------- template string Lineare_Transformation::get_status() const{ return status; } //--------------------------------------------------------------------------- template Integer Lineare_Transformation::get_index() const{ return index; } //--------------------------------------------------------------------------- template Matrix Lineare_Transformation::get_center()const{ return Center; } //--------------------------------------------------------------------------- template Matrix Lineare_Transformation::get_right() const{ return Right; } //--------------------------------------------------------------------------- template Matrix Lineare_Transformation::get_right_inv() const{ return Right_Inv; } //--------------------------------------------------------------------------- template void Lineare_Transformation::set_rank(const size_t rank) { rk = rank; } //--------------------------------------------------------------------------- template void Lineare_Transformation::set_center(const Matrix& M){ Center=M; } //--------------------------------------------------------------------------- template void Lineare_Transformation::set_right(const Matrix& M){ Right=M; } //--------------------------------------------------------------------------- template void Lineare_Transformation::set_right_inv(const Matrix& M){ Right_Inv=M; } //--------------------------------------------------------------------------- template void Lineare_Transformation::exchange_rows(size_t row1, size_t row2){ Center.exchange_rows(row1,row2); } //--------------------------------------------------------------------------- template void Lineare_Transformation::exchange_columns(size_t col1, size_t col2){ Center.exchange_columns(col1,col2); Right.exchange_columns(col1,col2); Right_Inv.exchange_rows(col1,col2); } //--------------------------------------------------------------------------- template void Lineare_Transformation::reduce_row(size_t corner){ Center.reduce_row(corner); } //--------------------------------------------------------------------------- template void Lineare_Transformation::reduce_column(size_t corner){ Center.reduce_column(corner, Right, Right_Inv); } //--------------------------------------------------------------------------- template void Lineare_Transformation::transformation(){ long r; long rk_max=min(Center.nr_of_rows(),Center.nr_of_columns()); vector piv(2,0); for (r = 0; r < rk_max; r++) { piv=Center.pivot(r); if (piv[0]>=0) { do { exchange_rows (r,piv[0]); exchange_columns (r,piv[1]); reduce_row (r); reduce_column (r); piv=Center.pivot(r); } while ((piv[0]>r)||(piv[1]>r)); } else break; } rk=r; for (r = 0; r < rk; r++) { index*=Center.read(r,r); } index=Iabs(index); status="initialized, after transformation"; } //--------------------------------------------------------------------------- template bool Lineare_Transformation::test_transformation(const Matrix& M,const size_t& m) const{ size_t nc=Center.nr_of_columns(); Matrix N=Right.multiplication(Right_Inv, m); Matrix I(nc); return I.equal(N,m); } //--------------------------------------------------------------------------- template Lineare_Transformation Transformation(const Matrix& M) { Lineare_Transformation LT(M); LT.transformation(); if (test_arithmetic_overflow==true) { bool test=LT.test_transformation(M,overflow_test_modulus); if (test==false) { errorOutput()<<"Arithmetic failure in linear transformation. Most likely overflow.\n"; throw ArithmeticException(); } } return LT; } //--------------------------------------------------------------------------- } regina-4.95/engine/enumerate/normaliz/lineare_transformation.h000644 000765 000024 00000012611 12234011536 024610 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- // This class implements the elementary divisor algoritm. // An object Lineare_transformation consists of four matrices. // Given a matrix M we want to obtain matrices U and V such that // UMV=the diagonal form of M. // In this class U is called Left, M Center and V Right. // The inverse of V is also computed as Right_Inv. // A Lineare_transormation should be initialized with a matrix M by the main // constructor. After using the procedure transformation the result should be // interpreted as follows: // Left=U; Center=the diagonal form of M; Right=V; // Right_Inv=the inverse of V; rk=rank of M. // We recommend using the non-member function Transformation, which also test // for possible arithmetic overflows //--------------------------------------------------------------------------- #ifndef LINEARE_TRANSFORMATION_H #define LINEARE_TRANSFORMATION_H #include #include "libnormaliz.h" #include "matrix.h" //--------------------------------------------------------------------------- namespace libnormaliz { template class Lineare_Transformation { long rk; string status; Integer index; Matrix Center; Matrix Right; Matrix Right_Inv; //--------------------------------------------------------------------------- public: //--------------------------------------------------------------------------- // Construction and destruction //--------------------------------------------------------------------------- Lineare_Transformation(); Lineare_Transformation(const Matrix& M); //main constructor //--------------------------------------------------------------------------- // Data acces //--------------------------------------------------------------------------- void read() const; // to be modified, just for tests long get_rank() const; //returns rank if status is // "initialized, after transformation" string get_status()const; //returns status, may be: // "non initialized" // "initialized, before transformation" // "initialized, after transformation" Integer get_index() const; Matrix get_center() const; //read center matrix Matrix get_right() const; //read right matrix Matrix get_right_inv() const; //read the inverse of the right matrix void set_rank(const size_t rank); void set_center(const Matrix& M); //write center matrix void set_right(const Matrix& M); //write right matrix void set_right_inv(const Matrix& M); //write the inverse of the right matrix //--------------------------------------------------------------------------- // Rows and columns exchange //--------------------------------------------------------------------------- void exchange_rows(size_t row1, size_t row2); //similar to Matrix::exchange_rows void exchange_columns(size_t col1, size_t col2); //similar to Matrix::exchange_columns //--------------------------------------------------------------------------- // Rows and columns reduction //--------------------------------------------------------------------------- void reduce_row(size_t corner); //similar to Matrix::reduce_row void reduce_column(size_t corner); //similar to Matrix::reduce_column //--------------------------------------------------------------------------- // Algorithms //--------------------------------------------------------------------------- void transformation(); //makes the main computation //no tests for errors //--------------------------------------------------------------------------- //Tests //--------------------------------------------------------------------------- /* test the main computation for arithmetic overflow * uses multiplication mod m */ bool test_transformation(const Matrix& M, const size_t& m) const; }; //class end ***************************************************************** //--------------------------------------------------------------------------- //makes the main computation, test for errors template Lineare_Transformation Transformation(const Matrix& M); } /* end namespace libnormaliz */ //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/list_operations.cpp000644 000765 000024 00000005354 12234011536 023622 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include "integer.h" #include "vector_operations.h" #include "matrix.h" #include "simplex.h" #include "list_operations.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- template vector l_multiplication(const list< vector >& l,const vector& v){ int s=l.size(); vector p(s); typename list< vector >::const_iterator i; s=0; for (i =l.begin(); i != l.end(); ++i, ++s) { p[s]=v_scalar_product(*i,v); //maybe we loose time here? } return p; } //--------------------------------------------------------------------------- template list< vector > l_list_x_matrix(const list< vector >& l,const Matrix& M){ list< vector > result; vector p; typename list< vector >::const_iterator i; for (i =l.begin(); i != l.end(); i++) { p=M.VxM(*i); result.push_back(p); } return result; } //--------------------------------------------------------------------------- template void l_cut(list< vector >& l, int size){ typename list< vector >::iterator i; for (i =l.begin(); i != l.end(); i++) { (*i).resize(size); } } //--------------------------------------------------------------------------- template void l_cut_front(list< vector >& l, int size){ typename list< vector >::iterator i; vector tmp; for (i =l.begin(); i != l.end(); ) { tmp=v_cut_front(*i, size); i=l.erase(i); //important to decrease memory consumption l.insert(i,tmp); } } //--------------------------------------------------------------------------- } regina-4.95/engine/enumerate/normaliz/list_operations.h000644 000765 000024 00000005230 12234011536 023260 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #ifndef LIST_OPERATIONS_H #define LIST_OPERATIONS_H //--------------------------------------------------------------------------- #include #include #include "libnormaliz.h" #include "simplex.h" namespace libnormaliz { //--------------------------------------------------------------------------- // Data access //--------------------------------------------------------------------------- template ostream& operator<< (ostream& out, const list& l) { typename list< vector >::const_iterator i; for (i =l.begin(); i != l.end(); i++) { out << *i <<" "; } out << std::endl; return out; } //--------------------------------------------------------------------------- // List operations //--------------------------------------------------------------------------- template vector l_multiplication(const list< vector >& l,const vector& v); //the list shall contain only vectors of size=v.size(). Returns a vector //containing all the scalar products (we see l as as matrix and return l*v). template list< vector > l_list_x_matrix(const list< vector >& l,const Matrix& M); //the list shall contain only vectors of size=M.nr_of_rows(). Returns a list //containing the product (we see l as as matrix and return l*M). template void l_cut(list< vector >& l,int size ); //cuts all the vectors in l to a given size. template void l_cut_front(list< vector >& l,int size ); //cuts all the vectors in l to a given size, maintaining the back } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/map_operations.h000644 000765 000024 00000004101 12234011536 023056 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #ifndef MAP_OPERATIONS_H #define MAP_OPERATIONS_H //--------------------------------------------------------------------------- #include #include namespace libnormaliz { using std::ostream; template ostream& operator<< (ostream& out, const map M) { typename map::const_iterator it; for (it = M.begin(); it != M.end(); ++it) { out << it->first << ": " << it-> second << " "; } out << std::endl; return out; } //--------------------------------------------------------------------------- template map count_in_map (const vector v) { map m; T size = v.size(); for (T i = 0; i < size; ++i) { m[v[i]]++; } return m; } template vector to_vector (const map M) { vector v; typename map::const_iterator it; for (it = M.begin(); it != M.end(); ++it) { for (T i = 0; i < it-> second; i++) { v.push_back(it->first); } } return v; } } //end namespace //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/matrix.cpp000644 000765 000024 00000106111 12234011536 021701 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include "matrix.h" #include "vector_operations.h" #include "lineare_transformation.h" #include "normaliz_exception.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- //Private //--------------------------------------------------------------------------- template void Matrix::max_rank_submatrix_lex(vector& v, const size_t& rank) const{ size_t level=v.size(); if (level==rank) { return; } if (level==0) { v.push_back(0); } else{ v.push_back(v[level-1]); } for (; v[level] < nr; v[level]++) { Matrix S=submatrix(v); if (S.rank()==S.nr_of_rows()) { max_rank_submatrix_lex(v,rank); return; } } } //--------------------------------------------------------------------------- //Public //--------------------------------------------------------------------------- template Matrix::Matrix(){ nr=0; nc=0; } //--------------------------------------------------------------------------- template Matrix::Matrix(size_t dim){ assert(dim>=0); nr=dim; nc=dim; elements = vector< vector >(dim, vector(dim)); for (size_t i = 0; i < dim; i++) { elements[i][i]=1; } } //--------------------------------------------------------------------------- template Matrix::Matrix(size_t row, size_t col){ assert(row>=0); assert(col>=0); nr=row; nc=col; elements = vector< vector >(row, vector(col)); } //--------------------------------------------------------------------------- template Matrix::Matrix(size_t row, size_t col, Integer value){ assert(row>=0); assert(col>=0); nr=row; nc=col; elements = vector< vector > (row, vector(col,value)); } //--------------------------------------------------------------------------- template Matrix::Matrix(const vector< vector >& elem){ nr=elem.size(); if (nr>0) { nc=elem[0].size(); elements=elem; //check if all rows have the same length for (size_t i=1; i Matrix::Matrix(const list< vector >& elem){ nr = elem.size(); elements = vector< vector > (nr); nc = 0; size_t i=0; typename list< vector >::const_iterator it=elem.begin(); for(; it!=elem.end(); ++it, ++i) { if(i == 0) { nc = (*it).size(); } else { if ((*it).size() != nc) { throw BadInputException(); } } elements[i]=(*it); } } //--------------------------------------------------------------------------- template void Matrix::write(istream& in){ size_t i,j; for(i=0; i> elements[i][j]; } } } //--------------------------------------------------------------------------- template void Matrix::write(size_t row, const vector& data){ assert(row >= 0); assert(row < nr); assert(nc == data.size()); elements[row]=data; } //--------------------------------------------------------------------------- template void Matrix::write(size_t row, const vector& data){ assert(row >= 0); assert(row < nr); assert(nc == data.size()); for (size_t i = 0; i < nc; i++) { elements[row][i]=data[i]; } } //--------------------------------------------------------------------------- template void Matrix::write_column(size_t col, const vector& data){ assert(col >= 0); assert(col < nc); assert(nr == data.size()); for (size_t i = 0; i < nr; i++) { elements[i][col]=data[i]; } } //--------------------------------------------------------------------------- template void Matrix::write(size_t row, size_t col, Integer data){ assert(row >= 0); assert(row < nr); assert(col >= 0); assert(col < nc); elements[row][col]=data; } //--------------------------------------------------------------------------- template void Matrix::print(const string& name,const string& suffix) const{ string file_name = name+"."+suffix; const char* file = file_name.c_str(); ofstream out(file); print(out); out.close(); } //--------------------------------------------------------------------------- template void Matrix::print(ostream& out) const{ size_t i,j; out< void Matrix::pretty_print(ostream& out, bool with_row_nr) const{ size_t i,j,k; size_t max_length = maximal_decimal_length(); size_t max_index_length = decimal_length(nr); for (i = 0; i < nr; i++) { if (with_row_nr) { for (k= 0; k <= max_index_length - decimal_length(i); k++) { out<<" "; } out << i << ": "; } for (j = 0; j < nc; j++) { for (k= 0; k <= max_length - decimal_length(elements[i][j]); k++) { out<<" "; } out< void Matrix::read() const{ //to overload for files size_t i,j; for(i=0; i vector Matrix::read(size_t row) const{ assert(row >= 0); assert(row < nr); return elements[row]; } //--------------------------------------------------------------------------- template Integer Matrix::read (size_t row, size_t col) const{ assert(row >= 0); assert(row < nr); assert(col >= 0); assert(col < nc); return elements[row][col]; } //--------------------------------------------------------------------------- template size_t Matrix::nr_of_rows () const{ return nr; } //--------------------------------------------------------------------------- template size_t Matrix::nr_of_columns () const{ return nc; } //--------------------------------------------------------------------------- template void Matrix::random (int mod) { size_t i,j; int k; for (i = 0; i < nr; i++) { for (j = 0; j < nc; j++) { k = rand(); elements[i][j]=k % mod; } } } //--------------------------------------------------------------------------- template Matrix Matrix::submatrix(const vector& rows) const{ size_t size=rows.size(), j; Matrix M(size, nc); for (size_t i=0; i < size; i++) { j=rows[i]; assert(j >= 0); assert(j < nr); M.elements[i]=elements[j]; } return M; } //--------------------------------------------------------------------------- template Matrix Matrix::submatrix(const vector& rows) const{ size_t size=rows.size(), j; Matrix M(size, nc); for (size_t i=0; i < size; i++) { j=rows[i]; assert(j >= 0); assert(j < nr); M.elements[i]=elements[j]; } return M; } //--------------------------------------------------------------------------- template Matrix Matrix::submatrix(const vector& rows) const{ assert(rows.size() == nr); size_t size=0; for (size_t i = 0; i M(size, nc); size_t j = 0; for (size_t i = 0; i < nr; i++) { if (rows[i]) { M.elements[j++] = elements[i]; } } return M; } //--------------------------------------------------------------------------- template vector Matrix::diagonale() const{ assert(nr == nc); vector diag(nr); for(size_t i=0; i size_t Matrix::maximal_decimal_length() const{ size_t i,j,maxim=0; for (i = 0; i void Matrix::append(const Matrix& M) { assert (nc == M.nc); elements.reserve(nr+M.nr); for (size_t i=0; i void Matrix::append(const vector& V) { assert (nc == V.size()); elements.push_back(V); nr++; } //--------------------------------------------------------------------------- template void Matrix::cut_columns(size_t c) { assert (c >= 0); assert (c <= nc); for (size_t i=0; i Matrix Matrix::add(const Matrix& A) const{ assert (nr == A.nr); assert (nc == A.nc); Matrix B(nr,nc); size_t i,j; for(i=0; i Matrix Matrix::multiplication(const Matrix& A) const{ assert (nc == A.nr); Matrix B(nr,A.nc,0); //initialized with 0 size_t i,j,k; for(i=0; i Matrix Matrix::multiplication_cut(const Matrix& A, const size_t& c) const{ assert (nc == A.nr); assert(c<= A.nc); Matrix B(nr,c,0); //initialized with 0 size_t i,j,k; for(i=0; i Matrix Matrix::multiplication(const Matrix& A, long m) const{ assert (nc == A.nr); Matrix B(nr,A.nc,0); //initialized with 0 size_t i,j,k; for(i=0; i bool Matrix::equal(const Matrix& A) const{ if ((nr!=A.nr)||(nc!=A.nc)){ return false; } size_t i,j; for (i=0; i < nr; i++) { for (j = 0; j < nc; j++) { if (elements[i][j]!=A.elements[i][j]) { return false; } } } return true; } //--------------------------------------------------------------------------- template bool Matrix::equal(const Matrix& A, long m) const{ if ((nr!=A.nr)||(nc!=A.nc)){ return false; } size_t i,j; for (i=0; i < nr; i++) { for (j = 0; j < nc; j++) { if (((elements[i][j]-A.elements[i][j]) % m)!=0) { return false; } } } return true; } //--------------------------------------------------------------------------- template Matrix Matrix::transpose()const{ Matrix B(nc,nr); size_t i,j; for(i=0; i void Matrix::scalar_multiplication(const Integer& scalar){ size_t i,j; for(i=0; i void Matrix::scalar_division(const Integer& scalar){ size_t i,j; assert(scalar != 0); for(i=0; i void Matrix::reduction_modulo(const Integer& modulo){ size_t i,j; for(i=0; i Integer Matrix::matrix_gcd() const{ Integer g=0,h; for (size_t i = 0; i (g, h); if (g==1) return g; } return g; } //--------------------------------------------------------------------------- template vector Matrix::make_prime() { vector g(nr); for (size_t i = 0; i Matrix Matrix::multiply_rows(const vector& m) const{ //row i is multiplied by m[i] Matrix M = Matrix(nr,nc); size_t i,j; for (i = 0; i vector Matrix::MxV(const vector& v) const{ assert (nc == v.size()); vector w(nr); for(size_t i=0; i vector Matrix::VxM(const vector& v) const{ assert (nr == v.size()); vector w(nc,0); size_t i,j; for (i=0; i void Matrix::exchange_rows(const size_t& row1, const size_t& row2){ if (row1 == row2) return; assert(row1 >= 0); assert(row1 < nr); assert(row2 >= 0); assert(row2 < nr); elements[row1].swap(elements[row2]); } //--------------------------------------------------------------------------- template void Matrix::exchange_columns(const size_t& col1, const size_t& col2){ if (col1 == col2) return; assert(col1 >= 0); assert(col1 < nc); assert(col2 >= 0); assert(col2 < nc); for(size_t i=0; i void Matrix::reduce_row (size_t corner) { assert(corner >= 0); assert(corner < nc); assert(corner < nr); size_t i,j; Integer help; for (i = corner+1; i < nr; i++) { if (elements[i][corner]!=0) { help=elements[i][corner] / elements[corner][corner]; for (j = corner; j < nc; j++) { elements[i][j] -= help*elements[corner][j]; } } } } //--------------------------------------------------------------------------- template void Matrix::reduce_row (size_t corner, Matrix& Left) { assert(corner >= 0); assert(corner < nc); assert(corner < nr); assert(Left.nr == nr); size_t i,j; Integer help1, help2=elements[corner][corner]; for ( i = corner+1; i < nr; i++) { help1=elements[i][corner] / help2; if (help1!=0) { for (j = corner; j < nc; j++) { elements[i][j] -= help1*elements[corner][j]; } for (j = 0; j < Left.nc; j++) { Left.elements[i][j] -= help1*Left.elements[corner][j]; } } } } //--------------------------------------------------------------------------- template void Matrix::reduce_column (size_t corner) { assert(corner >= 0); assert(corner < nc); assert(corner < nr); size_t i,j; Integer help1, help2=elements[corner][corner]; for ( j = corner+1; j < nc; j++) { help1=elements[corner][j] / help2; if (help1!=0) { for (i = corner; i < nr; i++) { elements[i][j] -= help1*elements[i][corner]; } } } } //--------------------------------------------------------------------------- template void Matrix::reduce_column (size_t corner, Matrix& Right, Matrix& Right_Inv) { assert(corner >= 0); assert(corner < nc); assert(corner < nr); assert(Right.nr == nc); assert(Right.nc == nc); assert(Right_Inv.nr == nc); assert(Right_Inv.nc ==nc); size_t i,j; Integer help1, help2=elements[corner][corner]; for ( j = corner+1; j < nc; j++) { help1=elements[corner][j] / help2; if (help1!=0) { for (i = corner; i < nr; i++) { elements[i][j] -= help1*elements[i][corner]; } for (i = 0; i < nc; i++) { Right.elements[i][j] -= help1*Right.elements[i][corner]; Right_Inv.elements[corner][i] += help1*Right_Inv.elements[j][i]; } } } } //--------------------------------------------------------------------------- template vector Matrix::pivot(size_t corner){ assert(corner >= 0); assert(corner < nc); assert(corner < nr); size_t i,j; Integer help=0; vector v(2,-1); for (i = corner; i < nr; i++) { for (j = corner; j < nc; j++) { if (elements[i][j]!=0) { if ((help==0)||(Iabs(elements[i][j]) long Matrix::pivot_column(size_t col){ assert(col >= 0); assert(col < nc); assert(col < nr); size_t i,j=-1; Integer help=0; for (i = col; i < nr; i++) { if (elements[i][col]!=0) { if ((help==0)||(Iabs(elements[i][col]) size_t Matrix::diagonalize(){ long rk; long rk_max=min(nr,nc); vector piv(2,-1); for (rk = 0; rk < rk_max; rk++) { piv=pivot(rk); if (piv[0]>=0) { do { exchange_rows (rk,piv[0]); exchange_columns (rk,piv[1]); reduce_row (rk); reduce_column (rk); piv=pivot(rk); } while ((piv[0]>rk)||(piv[1]>rk)); } else break; } return rk; } //--------------------------------------------------------------------------- template size_t Matrix::rank() const{ Matrix N(*this); return N.rank_destructive(); } //--------------------------------------------------------------------------- template size_t Matrix::rank_destructive(){ size_t rk,i,j,Min_Row, rk_max=min(nr,nc); bool empty; Integer Test, Min; for (rk = 0; rk < rk_max; rk++) { for (i = rk; i < nr; i++) { //TODO exchange i,j loops? for (j = rk; j < nc; j++) if (elements[i][j]!=0) break; if (j < nc) break; } if (i >= nr) break; //no element != 0 left if (rk!=i) exchange_rows (rk,i); if (rk!=j) exchange_columns (rk,j); do { Min=Iabs(elements[rk][rk]); Min_Row=rk; empty=true; for (i = rk+1; i < nr; i++) { Test=Iabs(elements[i][rk]); empty = empty && (Test==0); if (Test!=0 && (Test Integer Matrix::vol_destructive(){ size_t rk,i,j,Min_Row, rk_max=nr; // we assume nr==nc bool empty; Integer Test, Min; for (rk = 0; rk < rk_max; rk++) { for (i = rk; i < nr; i++) { for (j = rk; j < nc; j++) if (elements[i][j]!=0) break; if (j=nr) break; if (rk!=i) exchange_rows (rk,i); if (rk!=j) exchange_columns (rk,j); do { Min=Iabs(elements[rk][rk]); Min_Row=rk; empty=true; for (i = rk+1; i < nr; i++) { Test=Iabs(elements[i][rk]); empty=empty && (Test==0); if (Test!=0&& (Test vector Matrix::max_rank_submatrix() const{ //may be optimized in two ways //first only a triangular matrix is realy needed, no full diagonalization is necesary //second the matrix Rows_Exchanges may be computed by Lineare_transformation::transformation size_t i,j,k; long rk, rk_max=min(nr,nc); vector piv(2); Matrix M(*this); Matrix Rows_Exchanges(nr); for (rk = 0; rk < rk_max; rk++) { piv=M.pivot(rk); if (piv[0]>=0) { do { M.exchange_rows (rk,piv[0]); Rows_Exchanges.exchange_columns(rk,piv[0]); M.exchange_columns (rk,piv[1]); M.reduce_row (rk); M.reduce_column (rk); //optimization posible here piv=M.pivot(rk); } while ((piv[0]>rk)||(piv[1]>rk)); } else break; } M=Rows_Exchanges.multiplication(M); vector simplex(rk); k=0; for (i = 0; i < nr; i++) { for (j = 0; j < nc; j++) { if (M.elements[i][j]!=0) { simplex[k]=i; k++; //TODO break } } } return simplex; } //--------------------------------------------------------------------------- template vector Matrix::max_rank_submatrix_lex() const{ size_t rk=rank(); vector v(0); max_rank_submatrix_lex(v,rk); return v; } //--------------------------------------------------------------------------- template vector Matrix::max_rank_submatrix_lex(const size_t& rank) const { vector v(0); max_rank_submatrix_lex(v,rank); return v; } //--------------------------------------------------------------------------- template void Matrix::solve_destructive_Sol_inner(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom, Matrix& Solution) { size_t dim=Right_side.nr; size_t nr_sys=Right_side.nc; // cout << endl << "Sol.nc " << Solution.nc << " Sol.nr " << Solution.nr << " " << nr_sys << endl; assert(nr == nc); assert(nc == dim); assert(dim == diagonal.size()); assert(Solution.nc>=nr_sys); assert(Solution.nr==dim); Integer S; size_t i; long rk, piv; for (rk = 0; rk < (long)dim; rk++) { piv=(*this).pivot_column(rk); if (piv>=0) { do { (*this).exchange_rows (rk,piv); Right_side.exchange_rows (rk,piv); (*this).reduce_row(rk, Right_side); piv=(*this).pivot_column(rk); } while (piv>rk); } } denom = 1; for (i = 0; i < dim; i++) { denom *= (*this).elements[i][i]; diagonal[i] = (*this).elements[i][i]; } if (denom==0) { throw BadInputException(); //TODO welche Exception? } denom=Iabs(denom); long j; size_t k; for (i = 0; i < nr_sys; i++) { for (j = dim-1; j >= 0; j--) { S=denom*Right_side.elements[j][i]; for (k = j+1; k < dim; k++) { S-=(*this).elements[j][k]*Solution.elements[k][i]; } Solution.elements[j][i]=S/(*this).elements[j][j]; } } } //--------------------------------------------------------------------------- template void Matrix::solve_destructive_Sol(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom, Matrix& Solution) { if(!test_arithmetic_overflow){ solve_destructive_Sol_inner(Right_side,diagonal,denom,Solution); return; } // now with test_arithmetic_overflow Matrix LS_Copy=*this; Matrix RS_x_denom=Right_side; solve_destructive_Sol_inner(Right_side,diagonal,denom,Solution); RS_x_denom.scalar_multiplication(denom); // cout << endl; // cout << RS_x_denom.nr << " " << RS_x_denom.nc << endl; // RS_x_denom.pretty_print(cout); // cout << endl; Matrix RS_test=LS_Copy.multiplication_cut(Solution,RS_x_denom.nc); // cout << RS_test.nr << " " << RS_test.nc << endl; // RS_test.pretty_print(cout); if (!RS_x_denom.equal(RS_test)) { errorOutput()<<"Arithmetic failure in solving a linear system. Most likely overflow.\n"; throw ArithmeticException(); } } //--------------------------------------------------------------------------- template Matrix Matrix::solve_destructive(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom) { Matrix Solution(Right_side.nr,Right_side.nc); solve_destructive_Sol(Right_side,diagonal,denom,Solution); return Solution; } //--------------------------------------------------------------------------- template Matrix Matrix::solve(Matrix Right_side, Integer& denom) const { Matrix Left_side(*this); vector dummy_diag(nr); return Left_side.solve_destructive(Right_side, dummy_diag, denom); } //--------------------------------------------------------------------------- template Matrix Matrix::solve(Matrix Right_side, vector< Integer >& diagonal, Integer& denom) const { Matrix Left_side(*this); return Left_side.solve_destructive(Right_side, diagonal, denom); } //--------------------------------------------------------------------------- template Matrix Matrix::invert(vector< Integer >& diagonal, Integer& denom) const{ assert(nr == nc); assert(nr == diagonal.size()); Matrix Left_side(*this); Matrix Right_side(nr); return Left_side.solve_destructive(Right_side,diagonal,denom); } //--------------------------------------------------------------------------- template vector Matrix::solve(vector v) const { if (nc == 0 || nr == 0) { //return zero-vector as solution return vector(nc,0); } size_t i; Integer denom; vector rows=max_rank_submatrix_lex(); Matrix Left_Side=submatrix(rows); assert(nc == Left_Side.nr); //otherwise input hadn't full rank //TODO Matrix Right_Side(v.size(),1); Right_Side.write_column(0,v); Right_Side = Right_Side.submatrix(rows); Matrix Solution=Left_Side.solve(Right_Side, denom); vector Linear_Form(nc); for (i = 0; i test = MxV(Linear_Form); denom = test[0]/v[0]; //cout << denom << " v= " << v; //cout << denom << " t= " << test; for (i = 0; i (); } } return Linear_Form; } template vector Matrix::find_linear_form() const { return solve(vector(nr,1)); } //--------------------------------------------------------------------------- template vector Matrix::find_linear_form_low_dim () const{ size_t rank=(*this).rank(); if (rank == 0) { //return zero-vector as linear form return vector(nc,0); } if (rank == nc) { // basis change not necessary return (*this).find_linear_form(); } // prepare basis change vector key = max_rank_submatrix_lex(rank); Matrix Full_Rank_Matrix = submatrix(key); // has maximal number of linear independent lines Lineare_Transformation Basis_Change = Transformation(Full_Rank_Matrix); rank=Basis_Change.get_rank(); Matrix V=Basis_Change.get_right(); Matrix Change_To_Full_Emb(nc,rank); size_t i,j; for (i = 0; i Full_Cone_Generators = Full_Rank_Matrix.multiplication(Change_To_Full_Emb); //compute linear form vector Linear_Form = Full_Cone_Generators.find_linear_form(); if (Linear_Form.size()==nc) { //lift linear form back Change_To_Full_Emb = Change_To_Full_Emb.transpose(); // preparing the matrix for transformation on the dual space vector v; Linear_Form = Change_To_Full_Emb.VxM(Linear_Form); v_make_prime(Linear_Form); } return Linear_Form; } //--------------------------------------------------------------------------- template Matrix solve(const Matrix& Left_side, const Matrix& Right_side,Integer& denom){ return Left_side.solve(Right_side,denom); } //--------------------------------------------------------------------------- template Matrix invert(const Matrix& Left_side, vector< Integer >& diagonal, Integer& denom){ return Left_side.invert(diagonal,denom); } //--------------------------------------------------------------------------- } // namespace regina-4.95/engine/enumerate/normaliz/matrix.h000644 000765 000024 00000027765 12234011536 021367 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #ifndef MATRIX_HPP #define MATRIX_HPP //--------------------------------------------------------------------------- #include #include #include #include #include "libnormaliz.h" //--------------------------------------------------------------------------- namespace libnormaliz { using std::list; using std::vector; using std::string; template class Matrix { size_t nr; size_t nc; vector< vector > elements; //--------------------------------------------------------------------------- // Private routines, used in the public routines //--------------------------------------------------------------------------- void max_rank_submatrix_lex(vector& v, const size_t& rank) const; //v will be a vector with entries the indices of the first rows in lexicographic //order of this forming a submatrix of maximal rank. //v shoud be a vector of size 0 by call!!! // Does the computation for the solution of linear systems void solve_destructive_Sol_inner(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom, Matrix& Solution); //--------------------------------------------------------------------------- public: //--------------------------------------------------------------------------- // Construction and destruction //--------------------------------------------------------------------------- Matrix(); Matrix(size_t dim); //constructor of identity matrix Matrix(size_t row, size_t col); //main constructor, all entries 0 Matrix(size_t row, size_t col, Integer value); //constructor, all entries set to value Matrix(const vector< vector >& elem); //constuctor, elements=elem Matrix(const list< vector >& elems); //--------------------------------------------------------------------------- // Data access //--------------------------------------------------------------------------- void write(std::istream& in = std::cin); // to be modified, just for tests void write(size_t row, const vector& data); //write a row void write(size_t row, const vector& data); //write a row void write_column(size_t col, const vector& data); //write a column void write(size_t row, size_t col, Integer data); // write data at (row,col) void print(const string& name, const string& suffix) const; // writes matrix into name.suffix void print(std::ostream& out) const; // writes matrix to the stream void pretty_print(std::ostream& out, bool with_row_nr=false) const; // writes matrix in a nice format to the stream void read() const; // to be modified, just for tests vector read(size_t row) const; // read a row Integer read (size_t row, size_t col) const; // read data at (row,col) size_t nr_of_rows() const; // returns nr size_t nr_of_columns() const; // returns nc /* generates a pseudo random matrix for tests, entries form 0 to mod-1 */ void random(int mod=3); /* returns a submatrix with rows corresponding to indices given by * the entries of rows, Numbering from 0 to n-1 ! */ Matrix submatrix(const vector& rows) const; Matrix submatrix(const vector& rows) const; Matrix submatrix(const vector& rows) const; vector diagonale() const; //returns the diagonale of this //this should be a quadratic matrix size_t maximal_decimal_length() const; //return the maximal number of decimals //needed to write an entry void append(const Matrix& M); // appends the rows of M to this void append(const vector& v); // append the row v to this void cut_columns(size_t c); // remove columns, only the first c columns will survive inline const Integer& get_elem(size_t row, size_t col) const { return elements[row][col]; } inline const vector< vector >& get_elements() const { return elements; } inline vector const& operator[] (size_t row) const { return elements[row]; } inline vector& operator[] (size_t row) { return elements[row]; } //--------------------------------------------------------------------------- // Basic matrices operations //--------------------------------------------------------------------------- Matrix add(const Matrix& A) const; // returns this+A Matrix multiplication(const Matrix& A) const; // returns this*A Matrix multiplication(const Matrix& A, long m) const;// returns this*A (mod m) Matrix multiplication_cut(const Matrix& A, const size_t& c) const; // returns // this*(first c columns of A) bool equal(const Matrix& A) const; // returns this==A bool equal(const Matrix& A, long m) const; // returns this==A (mod m) Matrix transpose() const; // returns the transpose of this //--------------------------------------------------------------------------- // Scalar operations //--------------------------------------------------------------------------- void scalar_multiplication(const Integer& scalar); //this=this*scalar void scalar_division(const Integer& scalar); //this=this div scalar, all the elements of this must be divisible with the scalar void reduction_modulo(const Integer& modulo); //this=this mod scalar Integer matrix_gcd() const; //returns the gcd of all elements vector make_prime(); //each row of this is reduced by its gcd //return a vector containing the gcd of the rows Matrix multiply_rows(const vector& m) const; //returns matrix were row i is multiplied by m[i] //--------------------------------------------------------------------------- // Vector operations //--------------------------------------------------------------------------- vector MxV(const vector& v) const;//returns this*V vector VxM(const vector& v) const;//returns V*this //--------------------------------------------------------------------------- // Rows and columns exchange //--------------------------------------------------------------------------- void exchange_rows(const size_t& row1, const size_t& row2); //row1 is exchanged with row2 void exchange_columns(const size_t& col1, const size_t& col2); // col1 is exchanged with col2 //--------------------------------------------------------------------------- // Rows and columns reduction in respect to // the right-lower submatrix of this described by an int corner //--------------------------------------------------------------------------- void reduce_row(size_t corner); //reduction by the corner-th row void reduce_row(size_t corner, Matrix& Left);//row reduction, Left used //for saving or copying the linear transformations void reduce_column(size_t corner); //reduction by the corner-th column void reduce_column(size_t corner, Matrix& Right, Matrix& Right_Inv); //column reduction, Right used for saving or copying the linear //transformations, Right_Inv used for saving the inverse linear transformations //--------------------------------------------------------------------------- // Pivots for rows/columns operations //--------------------------------------------------------------------------- vector pivot(size_t corner); //Find the position of an element x with //0 max_rank_submatrix() const; //returns a vector with entries the //indices of the rows of this forming a submatrix of maximal rank vector max_rank_submatrix_lex() const; //returns a vector with entries //the indices of the first rows in lexicographic order of this forming //a submatrix of maximal rank. vector max_rank_submatrix_lex(const size_t& rank) const; //returns a vector with entries the indices of the first rows in lexicographic //order of this forming a submatrix of maximal rank, assuming that //the rank of this is known. // In the following routines denom is the absolute value of the determinant of the // left side matrix ( =this). Matrix solve(Matrix Right_side, Integer& denom) const;// solves the system //this*Solution=denom*Right_side. this should be a quadratic matrix with nonzero determinant. Matrix solve(Matrix Right_side, vector< Integer >& diagonal, Integer& denom) const;// solves the system //this*Solution=denom*Right_side. this should be a quadratic /matrix with nonzero determinant. //The diagonal of this after transformation into an upper triangular matrix //is saved in diagonal // Right_side and this get destroyed! Matrix solve_destructive(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom); // Returns the solution of the system in Solution (for efficiency) void solve_destructive_Sol(Matrix& Right_side, vector< Integer >& diagonal, Integer& denom, Matrix& Solution); Matrix invert(vector< Integer >& diagonal, Integer& denom) const;// solves the system //this*Solution=denom*I. this should be a quadratic matrix with nonzero determinant. //The diagonal of this after transformation into an upper triangular matrix //is saved in diagonal vector find_linear_form () const; // Tries to find a linear form which gives the same value an all rows of this // this should be a m x n matrix (m>=n) of maxinal rank // returns an empty vector if there does not exist such a linear form vector find_linear_form_low_dim () const; //same as find_linear_form but also works with not maximal rank //uses a linear transformation to get a full rank matrix vector solve(vector v) const; // like find_linear_form, but for right side v }; //class end ***************************************************************** } // namespace //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/my_omp.h000644 000765 000024 00000002323 12234011536 021342 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2012,2013 Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /* * This header provide some dummy replacements of OpenMP functions. We use it * to compile Normaliz without OpenMP. */ #ifndef MY_OMP_H_ #define MY_OMP_H_ #ifdef _OPENMP #include #else inline int omp_get_level(){ return 0; } inline int omp_get_active_level() { return 0; } inline int omp_get_thread_num() { return 0; } inline int omp_get_max_threads() { return 1; } inline int omp_get_ancestor_thread_num(int level) { return 0; } #endif /* ifndef _OPENMP */ #endif /* MY_OMP_H_ */ regina-4.95/engine/enumerate/normaliz/normaliz_exception.h000644 000765 000024 00000003071 12234011536 023754 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef NORMALIZ_EXEPTION_H_ #define NORMALIZ_EXEPTION_H_ #include //#include "libnormaliz.h" namespace libnormaliz { class NormalizException: public std::exception { public: virtual const char* what() const throw() = 0; }; class ArithmeticException: public NormalizException { public: virtual const char* what() const throw() { return "Arithmetic Overflow detected, try a bigger integer type!"; } }; class BadInputException: public NormalizException { public: virtual const char* what() const throw() { return "Some error in the normaliz input data detected!"; } }; class FatalException: public NormalizException { public: virtual const char* what() const throw() { return "Fatal error! This should not happen, please contact the developers."; } }; } /* end namespace */ #endif /* LIBNORMALIZ_H_ */ regina-4.95/engine/enumerate/normaliz/README.txt000644 000765 000024 00000001033 12234011536 021364 0ustar00babstaff000000 000000 Normaliz Library ---------------- This directory contains a copy of the Normaliz kernel libnormaliz. This is currently synced with Normaliz version 2.10.1. Normaliz is distributed under the terms of the GNU General Public License, version 3. The full text of this license is included in the file COPYING in this directory. Normaliz was written by Winfried Bruns (wbruns@uos.de), Bogdan Ichim (bogdan_ichim@yahoo.com) and Christof Söger (csoeger@uos.de), and can be downloaded from http://www.mathematik.uni-osnabrueck.de/normaliz/ . regina-4.95/engine/enumerate/normaliz/regina.patch000644 000765 000024 00000001145 12234011536 022160 0ustar00babstaff000000 000000 --- general.h (revision 7811) +++ general.h (working copy) @@ -21,11 +21,15 @@ #include +#include #include +/* +// Regina will use GMP everywhere, even on Windows. #ifdef _WIN32 //for 32 and 64 bit windows #define NMZ_MPIR //always use MPIR #endif +*/ #ifdef NMZ_MPIR // use MPIR #include --- list_operations.h +++ list_operations.h @@ -41,7 +41,7 @@ ostream& operator<< (ostream& out, const list& l) { for (i =l.begin(); i != l.end(); i++) { out << *i <<" "; } - out << endl; + out << std::endl; return out; } regina-4.95/engine/enumerate/normaliz/simplex.cpp000644 000765 000024 00000046075 12234011536 022072 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include #include #include "integer.h" #include "vector_operations.h" #include "matrix.h" #include "simplex.h" #include "list_operations.h" #include "HilbertSeries.h" #include "cone.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- template Simplex::Simplex(const Matrix& Map){ dim=Map.nr_of_columns(); key=Map.max_rank_submatrix_lex(dim); Generators=Map.submatrix(key); diagonal = vector< Integer >(dim); Support_Hyperplanes=invert(Generators, diagonal, volume); //test for arithmetic //overflow performed v_abs(diagonal); Support_Hyperplanes = Support_Hyperplanes.transpose(); multiplicators = Support_Hyperplanes.make_prime(); } //--------------------------------------------------------------------------- template Simplex::Simplex(const vector& k, const Matrix& Map){ key=k; Generators=Map.submatrix(k); dim=k.size(); diagonal = vector< Integer >(dim); Support_Hyperplanes=invert(Generators, diagonal, volume); //test for arithmetic //overflow performed v_abs(diagonal); Support_Hyperplanes=Support_Hyperplanes.transpose(); multiplicators=Support_Hyperplanes.make_prime(); } //--------------------------------------------------------------------------- template size_t Simplex::read_dimension() const{ return dim; } //--------------------------------------------------------------------------- template void Simplex::write_volume(const Integer& vol){ volume=vol; } //--------------------------------------------------------------------------- template Integer Simplex::read_volume() const{ return volume; } //--------------------------------------------------------------------------- template vector Simplex::read_key() const{ return key; } //--------------------------------------------------------------------------- template Matrix Simplex::read_generators() const{ return Generators; } //--------------------------------------------------------------------------- template vector Simplex::read_diagonal() const{ return diagonal; } //--------------------------------------------------------------------------- template vector Simplex::read_multiplicators() const{ return multiplicators; } //--------------------------------------------------------------------------- template Matrix Simplex::read_support_hyperplanes() const{ return Support_Hyperplanes; } //--------------------------------------------------------------------------- // SimplexEvaluator //--------------------------------------------------------------------------- template SimplexEvaluator::SimplexEvaluator(Full_Cone& fc) : C_ptr(&fc), dim(fc.dim), det_sum(0), mult_sum(0), collected_elements_size(0), Generators(dim,dim), TGenerators(dim,dim), GenCopy(dim,dim), InvGenSelRows(dim,dim), InvGenSelCols(dim,dim), Sol(dim,dim+1), InvSol(dim,dim+1), GDiag(dim), TDiag(dim), Excluded(dim), Indicator(dim), gen_degrees(dim), RS(dim,1) { if (C_ptr->do_h_vector) { // we need the generators to be sorted by degree size_t hv_max=0; for (size_t i=C_ptr->nr_gen-dim; inr_gen; i++) hv_max += C_ptr->gen_degrees[i]; hvector.resize(hv_max); } } //--------------------------------------------------------------------------- size_t Unimod=0, Ht1NonUni=0, Gcd1NonUni=0, NonDecided=0, NonDecidedHyp=0; size_t TotDet=0; /* evaluates a simplex in regard to all data */ template Integer SimplexEvaluator::evaluate(SHORTSIMPLEX& s) { Integer& volume = s.vol; vector& key = s.key; Full_Cone& C = *C_ptr; bool do_only_multiplicity = C.do_only_multiplicity; // || (s.height==1 && C.do_partial_triangulation); size_t i,j; //degrees of the generators according to the Grading of C if(C.isComputed(ConeProperty::Grading)) for (i=0; i Ind0_key; //contains the indices i as above Ind0_key.reserve(dim-1); if(potentially_unimodular) for(i=0;i0){ for(i=0; i0){ Matrix RSmult(dim,Ind0_key.size()); for(i=0;i RSmult(dim,Ind0_key.size()); Generators.solve_destructive_Sol(RSmult,GDiag,volume,InvSol); v_abs(GDiag); GDiag_computed=true; } } addMult(volume); // now we must compute the matrix InvGenSelRows (selected rows of InvGen) // for those i for which Gdiag[i]>1 combined with computation // of Indicator in case of potentially_unimodular==false (uses transpose of Gen) vector Last_key; Last_key.reserve(dim); if (!unimodular) { for(i=0; i1) Last_key.push_back(i); } size_t RScol; if(potentially_unimodular) RScol=Last_key.size(); else RScol=Last_key.size()+1; Matrix RSmult(dim,RScol); for(i=0;i0 (if there are any) if(!potentially_unimodular){ for(i=0;i0){ Generators=GenCopy; Matrix RSmult(dim,Ind0_key.size()); for(i=0;i0){ #pragma omp atomic NonDecided++; #pragma omp atomic NonDecidedHyp+=Ind0_key.size(); } for(i=0;i0) // facet included break; } } } if(C.do_h_vector) { // count the 0-vector in h-vector with the right shift hvector[Deg]++; } Matrix* StanleyMat=&GenCopy; //just to initialize it and make GCC happy if(C.do_Stanley_dec){ STANLEYDATA SimplStanley; SimplStanley.key=key; Matrix offsets(explicit_cast_to_long(volume),dim); SimplStanley.offsets=offsets; #pragma omp critical(STANLEY) { C.StanleyDec.push_back(SimplStanley); StanleyMat= &C.StanleyDec.back().offsets; } for(i=0;i > Candidates; typename list >::iterator c; size_t last; vector point(dim,0); Matrix elements(dim,dim); //all 0 matrix vector help; //now we need to create the candidates while (true) { last = dim; for (int k = dim-1; k >= 0; k--) { if (point[k] < GDiag[k]-1) { last = k; break; } } if (last >= dim) { break; } point[last]++; v_add_to_mod(elements[last], InvGenSelRows[last], volume); for (i = last+1; i (normG/volume); for(i=0;i=2 // if (!C.do_partial_triangulation || s.height >= 2) { if (C.do_Hilbert_basis) { Candidates.push_back(v_merge(elements[last],norm)); continue; } if(C.do_deg1_elements && normG==volume && !isDuplicate(elements[last])) { help=GenCopy.VxM(elements[last]); v_scalar_division(help,volume); Collected_Elements.push_back(help); collected_elements_size++; } // } } if(C.do_h_vector) { // #pragma omp critical(HSERIES) Hilbert_Series.add(hvector,gen_degrees); } if(!C.do_Hilbert_basis) return volume; // no local reduction in this case Candidates.sort(compare_last); typename list >::iterator cand=Candidates.begin(); while(cand != Candidates.end()) { if (is_reducible_interior(*cand)) // erase the candidate cand = Candidates.erase(cand); else // move it to the Hilbert basis Hilbert_Basis.splice(Hilbert_Basis.end(), Candidates, cand++); } //inverse transformation //some test for arithmetic overflow may be implemented here typename list< vector >::iterator jj = Hilbert_Basis.begin(); while (jj != Hilbert_Basis.end()) { if (isDuplicate(*jj)) { //delete the element jj = Hilbert_Basis.erase(jj); } else { jj->pop_back(); //remove the norm entry at the end *jj = GenCopy.VxM(*jj); v_scalar_division(*jj,volume); ++jj; collected_elements_size++; } } Collected_Elements.splice(Collected_Elements.end(),Hilbert_Basis); return volume; } //--------------------------------------------------------------------------- template bool SimplexEvaluator::isDuplicate(const vector& cand) const { for (size_t i=0; i void SimplexEvaluator::addMult(const Integer& volume) { if (volume==0) throw volume; det_sum += volume; if (!C_ptr->isComputed(ConeProperty::Grading)) return; if (C_ptr->deg1_triangulation) { mult_sum += to_mpz(volume); } else { mpz_class deg_prod=gen_degrees[0]; for (size_t i=1; i bool SimplexEvaluator::is_reducible_interior(const vector< Integer >& new_element){ // the norm is at position dim if (new_element[dim]==0) { return true; // new_element=0 } else { size_t i,c=0; typename list< vector >::iterator j; for (j =Hilbert_Basis.begin(); j != Hilbert_Basis.end(); ++j) { if (new_element[dim]<2*(*j)[dim]) { break; //new_element is not reducible; } else { if ((*j)[c]<=new_element[c]){ for (i = 0; i < dim; i++) { if ((*j)[i]>new_element[i]){ c=i; break; } } if (i==dim) { // move the reducer to the begin Hilbert_Basis.splice(Hilbert_Basis.begin(), Hilbert_Basis, j); return true; } //new_element is not in the Hilbert Basis } } } return false; } } //--------------------------------------------------------------------------- template void SimplexEvaluator::transfer_candidates() { if ( (C_ptr->do_deg1_elements || C_ptr->do_Hilbert_basis) && collected_elements_size > 0 ) { #pragma omp critical(CANDIDATES) C_ptr->Candidates.splice(C_ptr->Candidates.begin(),Collected_Elements); #pragma omp atomic C_ptr->CandidatesSize += collected_elements_size; collected_elements_size = 0; } } template Integer SimplexEvaluator::getDetSum() const { return det_sum; } template mpq_class SimplexEvaluator::getMultiplicitySum() const { return mult_sum; } template const HilbertSeries& SimplexEvaluator::getHilbertSeriesSum() const { return Hilbert_Series; } } /* end namespace */ regina-4.95/engine/enumerate/normaliz/simplex.h000644 000765 000024 00000012342 12234011536 021525 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- // The matrix Map is assumed to be such that the rank of Map equals // the number of columns of Map and no test is performed in the constructor //--------------------------------------------------------------------------- #ifndef SIMPLEX_H #define SIMPLEX_H //--------------------------------------------------------------------------- #include #include #include "libnormaliz.h" #include "cone.h" #include "HilbertSeries.h" //--------------------------------------------------------------------------- namespace libnormaliz { using std::list; using std::vector; template class Full_Cone; template class Simplex { size_t dim; Integer volume; vector key; Matrix Generators; vector< Integer > diagonal; vector< Integer > multiplicators; Matrix Support_Hyperplanes; //--------------------------------------------------------------------------- public: // Construction and destruction Simplex(const Matrix& Map); //contructor of the first in lexicographic //order simplex inside Map, the rank of Map is assumed to equal the number of //columns of Map Simplex(const vector& k, const Matrix& Map); //main constuctor //the rank of M is assumed to equal the number of columns of M // Data acces size_t read_dimension() const; // returns dim void write_volume(const Integer& vol); // writes volume Integer read_volume() const; // returns volume vector read_key() const; // returns key Matrix read_generators() const; // returns generators vector read_diagonal() const; // returns diagonal vector read_multiplicators() const; // returns multiplicators Matrix read_support_hyperplanes() const; // returns the support hyperplanes }; //class Simplex end template class SimplexEvaluator { Full_Cone * C_ptr; size_t dim; //Integer volume; Integer det_sum; // sum of the determinants of all evaluated simplices mpq_class mult_sum; // sum of the multiplicities of all evaluated simplices size_t collected_elements_size; Matrix Generators; Matrix TGenerators; Matrix GenCopy; Matrix InvGenSelRows; // selected rows of inverse of Gen Matrix InvGenSelCols; // selected columns of inverse of Gen Matrix Sol; Matrix InvSol; vector< Integer > GDiag; // diagonal of generator matrix after trigonalization vector< Integer > TDiag; // diagonal of transpose of generaor matrix after trigonalization vector< bool > Excluded; vector< Integer > Indicator; vector< long > gen_degrees; vector< num_t > hvector; //h-vector of the current evaluation HilbertSeries Hilbert_Series; //this is the summed Hilbert Series list< vector > Candidates; list< vector > Hilbert_Basis; list< vector > Collected_Elements; //temporary objects are kept to prevent repeated alloc and dealloc Matrix RS; // right hand side to hold order vector // Matrix RSmult; // for multiple right hand sides //checks whether a new element is reducible by the local Hilbert basis bool is_reducible_interior(const vector< Integer >& new_element); bool isDuplicate(const vector& cand) const; void addMult(const Integer& volume); //--------------------------------------------------------------------------- public: SimplexEvaluator(Full_Cone& fc); // full evaluation of the simplex, writes data back to the cone, // returns volume Integer evaluate(SHORTSIMPLEX& s); // moves the union of Hilbert basis / deg1 elements to the cone // for partial triangulation it merges the sorted list void transfer_candidates(); // returns sum of the determinants of all evaluated simplices Integer getDetSum() const; // returns sum of the multiplicities of all evaluated simplices mpq_class getMultiplicitySum() const; // returns sum of the Hilbert Series of all evaluated simplices const HilbertSeries& getHilbertSeriesSum() const; }; //class SimplexEvaluater end } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/sublattice_representation.cpp000644 000765 000024 00000022616 12234011536 025665 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /** * The class Sublattice_Representation represents a sublattice of Z^n as Z^r. * To transform vectors of the sublattice use: * Z^r --> Z^n and Z^n --> Z^r * v |-> vA u |-> (uB)/c * A r x n matrix * B n x r matrix * c Integer */ #include "sublattice_representation.h" #include "lineare_transformation.h" #include "vector_operations.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; /** * creates a representation of Z^n as a sublattice of itself */ template Sublattice_Representation::Sublattice_Representation(size_t n) { dim = n; rank = n; index = 1; A = Matrix(n); B = Matrix(n); c = 1; } //--------------------------------------------------------------------------- /** * Main Constructor * creates a representation of a sublattice of Z^n * if direct_summand is false the sublattice is generated by the rows of M * otherwise it is a direct summand of Z^n containing the rows of M */ template Sublattice_Representation::Sublattice_Representation(const Matrix& M, bool direct_summand) { Lineare_Transformation Basis_Change = Transformation(M); initialize(Basis_Change, direct_summand); } template Sublattice_Representation::Sublattice_Representation(const Lineare_Transformation& Basis_Change, bool direct_summand) { initialize(Basis_Change, direct_summand); } template void Sublattice_Representation::initialize(const Lineare_Transformation& Basis_Change, bool direct_summand) { size_t i,j; rank = Basis_Change.get_rank(); if (rank==0) { errorOutput()<<"warning: matrix has rank 0. Please check input data."< R = Basis_Change.get_right_inv(); Matrix R_Inv = Basis_Change.get_right(); dim = R.nr_of_columns(); c = 1; index = 1; Matrix D = Basis_Change.get_center(); //use the identity matrix when possible bool is_identity = (dim==rank); if (is_identity && !direct_summand) { for (i = 0; i < rank; i++) { if (Iabs(D.read(i,i)) != 1) { is_identity = false; break; } } } if (is_identity) { A = B = Matrix(dim); } else { A = Matrix(rank, dim); B = Matrix(dim, rank); for (i = 0; i < rank; i++) { for (j = 0; j < dim; j++) { A.write(i,j, R.read(i,j)); B.write(j,i, R_Inv.read(j,i)); } } } if ( direct_summand ) { for (i = 0; i < rank; i++) { index *= D.read(i,i); } index = Iabs(index); } else if(!is_identity) { Matrix Diagonal(rank); for (i = 0; i < rank; i++) { Diagonal.write(i,i,D.read(i,i)); } A = Diagonal.multiplication(A); vector c_vector = Diagonal.diagonale(); c = v_lcm(c_vector); //invert Diagonal, multiply c to maintain integer coefficients for (i = 0; i < rank; i++) { Diagonal.write(i,i,c/c_vector[i]); } B = B.multiplication(Diagonal); } } //--------------------------------------------------------------------------- // Manipulation operations //--------------------------------------------------------------------------- /* first this then SR when going from Z^n to Z^r */ template void Sublattice_Representation::compose(const Sublattice_Representation& SR) { assert(rank == SR.dim); //TODO vielleicht doch exception? rank = SR.rank; index = index * SR.index; // A = SR.A * A A = SR.A.multiplication(A); // B = B * SR.B B = B.multiplication(SR.B); c = c * SR.c; //check if a factor can be extraced from B //TODO necessary? Integer g = B.matrix_gcd(); g = gcd(g,c); //TODO necessary?? if (g > 1) { c /= g; B.scalar_division(g); } } //--------------------------------------------------------------------------- // Transformations //--------------------------------------------------------------------------- template Matrix Sublattice_Representation::to_sublattice (const Matrix& M) const { Matrix N = M.multiplication(B); if (c!=1) N.scalar_division(c); return N; } template Matrix Sublattice_Representation::from_sublattice (const Matrix& M) const { Matrix N = M.multiplication(A); return N; } template Matrix Sublattice_Representation::to_sublattice_dual (const Matrix& M) const { Matrix N = M.multiplication(A.transpose()); N.make_prime(); return N; } template Matrix Sublattice_Representation::from_sublattice_dual (const Matrix& M) const { Matrix N = M.multiplication(B.transpose()); N.make_prime(); return N; } template vector Sublattice_Representation::to_sublattice (const vector& V) const { vector N = B.VxM(V); if (c!=1) v_scalar_division(N,c); return N; } template vector Sublattice_Representation::from_sublattice (const vector& V) const { vector N = A.VxM(V); return N; } template vector Sublattice_Representation::to_sublattice_dual (const vector& V) const { vector N = A.MxV(V); v_make_prime(N); return N; } template vector Sublattice_Representation::from_sublattice_dual (const vector& V) const { vector N = B.MxV(V); v_make_prime(N); return N; } template vector Sublattice_Representation::to_sublattice_dual_no_div (const vector& V) const { vector N = A.MxV(V); return N; } //--------------------------------------------------------------------------- // Data access //--------------------------------------------------------------------------- /* returns the dimension of the ambient space */ template size_t Sublattice_Representation::get_dim() const { return dim; } //--------------------------------------------------------------------------- /* returns the rank of the sublattice */ template size_t Sublattice_Representation::get_rank() const { return rank; } //--------------------------------------------------------------------------- /* returns the index of the sublattice */ template Integer Sublattice_Representation::get_index() const { return index; } //--------------------------------------------------------------------------- template Matrix Sublattice_Representation::get_A() const { return A; } //--------------------------------------------------------------------------- template Matrix Sublattice_Representation::get_B() const { return B; } //--------------------------------------------------------------------------- template Integer Sublattice_Representation::get_c() const { return c; } //--------------------------------------------------------------------------- /* returns the congruences defining the sublattice */ template Matrix Sublattice_Representation::get_congruences() const { if ( c == 1 ) { // no congruences then return Matrix(0,dim+1); } // Cong is B transposed and with an extra column for the modul m Matrix Cong = B; Cong.append(Matrix(1,rank)); Cong = Cong.transpose(); vector gcds = Cong.make_prime(); Integer m; //the modul Integer rowgcd; Matrix Cong2(0,dim+1); //only the relavant congruences vector new_row; for (size_t j=0; j. * */ /** * The class Sublattice_Representation represents a sublattice of Z^n as Z^r. * To transform vectors of the sublattice use: * Z^r --> Z^n and Z^n --> Z^r * v |-> vA u |-> (uB)/c * A r x n matrix * B n x r matrix * c Integer */ #ifndef SUBLATTICE_REPRESENTATION_H #define SUBLATTICE_REPRESENTATION_H #include #include "libnormaliz.h" #include "matrix.h" //--------------------------------------------------------------------------- namespace libnormaliz { template class Matrix; template class Lineare_Transformation; using std::vector; template class Sublattice_Representation { size_t dim, rank; Matrix A; Matrix B; Integer c; Integer index; //--------------------------------------------------------------------------- public: //--------------------------------------------------------------------------- // Construction and destruction //--------------------------------------------------------------------------- /** * creates a dummy object */ Sublattice_Representation() {} /** * creates a representation of Z^n as a sublattice of itself */ Sublattice_Representation(size_t n); /** * Main Constructor * creates a representation of a sublattice of Z^n * if direct_summand is false the sublattice is generated by the rows of M * otherwise it is a direct summand of Z^n containing the rows of M */ Sublattice_Representation(const Matrix& M, bool direct_summand); Sublattice_Representation(const Lineare_Transformation& LT, bool direct_summand); //--------------------------------------------------------------------------- // Manipulation operations //--------------------------------------------------------------------------- /* like the matching constructor */ void initialize(const Lineare_Transformation& LT, bool direct_summand); /* first this then SR when going from Z^n to Z^r */ void compose(const Sublattice_Representation& SR); //--------------------------------------------------------------------------- // Transformations //--------------------------------------------------------------------------- Matrix to_sublattice (const Matrix& M) const; Matrix from_sublattice (const Matrix& M) const; Matrix to_sublattice_dual (const Matrix& M) const; Matrix from_sublattice_dual (const Matrix& M) const; vector to_sublattice (const vector& V) const; vector from_sublattice (const vector& V) const; vector to_sublattice_dual (const vector& M) const; vector from_sublattice_dual (const vector& V) const; vector to_sublattice_dual_no_div (const vector& M) const; //--------------------------------------------------------------------------- // Data acces //--------------------------------------------------------------------------- /* returns the dimension of the ambient space */ size_t get_dim() const; /* returns the rank of the sublattice */ size_t get_rank() const; /* returns the index of the sublattice */ Integer get_index() const; Matrix get_A() const; Matrix get_B() const; Integer get_c() const; /* returns the congruences defining the sublattice */ Matrix get_congruences() const; }; } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/normaliz/unused/libnormaliz-all.cpp000644 000765 000024 00000002133 12234011536 024767 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "libnormaliz.cpp" #include "integer.cpp" #include "vector_operations.cpp" #include "matrix.cpp" #include "simplex.cpp" #include "list_operations.cpp" #include "lineare_transformation.cpp" #include "sublattice_representation.cpp" #include "full_cone.cpp" #include "cone_dual_mode.cpp" #include "cone.cpp" #include "cone_property.cpp" #include "HilbertSeries.cpp" regina-4.95/engine/enumerate/normaliz/unused/libnormaliz-impl.cpp000644 000765 000024 00000004263 12234011536 025166 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "libnormaliz.cpp" namespace libnormaliz { template class Cone; template class Cone; template class Cone; template class Matrix; template class Matrix; template class Matrix; template class Sublattice_Representation; template class Sublattice_Representation; template class Sublattice_Representation; template class Lineare_Transformation; template class Lineare_Transformation; template class Lineare_Transformation; template Lineare_Transformation Transformation(const Matrix& M); template Lineare_Transformation Transformation(const Matrix& M); template Lineare_Transformation Transformation(const Matrix& M); template size_t decimal_length(long); template size_t decimal_length(long long int); template size_t decimal_length(mpz_class); template mpz_class gcd(const mpz_class& a, const mpz_class& b); template mpz_class lcm(const mpz_class& a, const mpz_class& b); template mpz_class permutations(const size_t& a, const size_t& b); template ostream& operator<< (ostream& out, const vector& v); template ostream& operator<< (ostream& out, const vector& v); template ostream& operator<< < vector >(ostream& out, const vector< vector >& v); } regina-4.95/engine/enumerate/normaliz/vector_operations.cpp000644 000765 000024 00000024200 12234011536 024140 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #include #include #include #include "integer.h" #include "vector_operations.h" //--------------------------------------------------------------------------- namespace libnormaliz { using namespace std; //--------------------------------------------------------------------------- template Integer v_scalar_product(const vector& av,const vector& bv){ //loop stretching ; brings some small speed improvement Integer ans = 0; size_t i,n=av.size(); typename vector::const_iterator a=av.begin(), b=bv.begin(); if( n >= 16 ) { for( i = 0; i < ( n >> 4 ); ++i, a += 16, b +=16 ){ ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; ans += a[4] * b[4]; ans += a[5] * b[5]; ans += a[6] * b[6]; ans += a[7] * b[7]; ans += a[8] * b[8]; ans += a[9] * b[9]; ans += a[10] * b[10]; ans += a[11] * b[11]; ans += a[12] * b[12]; ans += a[13] * b[13]; ans += a[14] * b[14]; ans += a[15] * b[15]; } n -= i<<4; } if( n >= 8) { ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; ans += a[4] * b[4]; ans += a[5] * b[5]; ans += a[6] * b[6]; ans += a[7] * b[7]; n -= 8; a += 8; b += 8; } if( n >= 4) { ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; n -= 4; a += 4; b += 4; } if( n >= 2) { ans += a[0] * b[0]; ans += a[1] * b[1]; n -= 2; a += 2; b += 2; } if(n>0) ans += a[0]*b[0]; return ans; } //--------------------------------------------------------------------------- template Integer v_scalar_product_unequal_vectors_end(const vector& a,const vector& b){ Integer ans = 0; size_t i,n=a.size(),m=b.size(); for (i = 1; i <= n; i++) { ans+=a[n-i]*b[m-i]; } return ans; } //--------------------------------------------------------------------------- template vector v_add(const vector& a,const vector& b){ assert(a.size() == b.size()); size_t i,s=a.size(); vector d(s); for (i = 0; i vector& v_add_to_mod(vector& a, const vector& b, const Integer& m) { // assert(a.size() == b.size()); size_t i, s=a.size(); for (i = 0; i = m) { a[i] -= m; } } return a; } //--------------------------------------------------------------------------- template vector& v_abs(vector& v){ size_t i, size=v.size(); for (i = 0; i < size; i++) { if (v[i]<0) v[i] = Iabs(v[i]); } return v; } //--------------------------------------------------------------------------- template Integer v_gcd(const vector& v){ size_t i, size=v.size(); Integer g=0; for (i = 0; i < size; i++) { g=gcd(g,v[i]); if (g==1) { return 1; } } return g; } //--------------------------------------------------------------------------- template Integer v_lcm(const vector& v){ size_t i,size=v.size(); Integer g=1; for (i = 0; i < size; i++) { g=lcm(g,v[i]); if (g==0) { return 0; } } return g; } //--------------------------------------------------------------------------- template Integer v_make_prime(vector& v){ size_t i, size=v.size(); Integer g=v_gcd(v); if (g!=0) { for (i = 0; i < size; i++) { v[i] /= g; } } return g; } //--------------------------------------------------------------------------- template vector v_scalar_multiplication_two(const vector& v, const Integer& scalar){ size_t i,size=v.size(); vector w(size); for (i = 0; i void v_scalar_division(vector& v, const Integer& scalar){ size_t i,size=v.size(); for (i = 0; i void v_reduction_modulo(vector& v, const Integer& modulo){ size_t i,size=v.size(); for (i = 0; i bool v_test_scalar_product(const vector& av,const vector& bv, const Integer& result, const long& m){ Integer ans = 0; size_t i,n=av.size(); typename vector::const_iterator a=av.begin(),b=bv.begin(); if( n >= 16 ) { for( i = 0; i < ( n >> 4 ); ++i, a += 16, b += 16 ){ ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; ans %= m; ans += a[4] * b[4]; ans += a[5] * b[5]; ans += a[6] * b[6]; ans += a[7] * b[7]; ans %= m; ans += a[8] * b[8]; ans += a[9] * b[9]; ans += a[10] * b[10]; ans += a[11] * b[11]; ans %= m; ans += a[12] * b[12]; ans += a[13] * b[13]; ans += a[14] * b[14]; ans += a[15] * b[15]; ans %= m; } n -= i << 4; } if( n >= 8) { ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; ans %= m; ans += a[4] * b[4]; ans += a[5] * b[5]; ans += a[6] * b[6]; ans += a[7] * b[7]; ans %= m; n -= 8; a += 8; b += 8; } if( n >= 4) { ans += a[0] * b[0]; ans += a[1] * b[1]; ans += a[2] * b[2]; ans += a[3] * b[3]; ans %= m; n -= 4; a += 4; b += 4; } if( n >= 2) { ans += a[0] * b[0]; ans += a[1] * b[1]; n -= 2; a += 2; b += 2; } if(n>0) ans += a[0]*b[0]; ans %= m; if (((result-ans) % m)!=0) { return false; } return true; } //--------------------------------------------------------------------------- template vector v_merge(const vector& a, const T& b) { size_t s=a.size(); vector c(s+1); for (size_t i = 0; i < s; i++) { c[i]=a[i]; } c[s] = b; return c; } //--------------------------------------------------------------------------- template vector v_merge(const vector& a,const vector& b){ size_t s1=a.size(), s2=b.size(), i; vector c(s1+s2); for (i = 0; i < s1; i++) { c[i]=a[i]; } for (i = 0; i < s2; i++) { c[s1+i]=b[i]; } return c; } //--------------------------------------------------------------------------- template vector v_cut_front(const vector& v, size_t size){ size_t s,k; vector tmp(size); s=v.size()-size; for (k = 0; k < size; k++) { tmp[k]=v[s+k]; } return tmp; } //--------------------------------------------------------------------------- template vector v_non_zero_pos(vector v){ vector key; size_t size=v.size(); key.reserve(size); for (key_t i = 0; i void v_el_trans(const vector& av,vector& bv, const Integer& F, const size_t& start){ size_t i,n=av.size(); typename vector::const_iterator a=av.begin(); typename vector::iterator b=bv.begin(); a += start; b += start; n -= start; if( n >= 8 ) { for( i = 0; i < ( n >> 3 ); ++i, a += 8, b += 8 ){ b[0] += F*a[0]; b[1] += F*a[1]; b[2] += F*a[2]; b[3] += F*a[3]; b[4] += F*a[4]; b[5] += F*a[5]; b[6] += F*a[6]; b[7] += F*a[7]; } n -= i << 3; } if( n >= 4) { b[0] += F*a[0]; b[1] += F*a[1]; b[2] += F*a[2]; b[3] += F*a[3]; n -=4; a +=4; b +=4; } if( n >= 2) { b[0] += F*a[0]; b[1] += F*a[1]; n -=2; a +=2; b +=2; } if(n>0) b[0] += F*a[0]; } } // end namespace libnormaliz regina-4.95/engine/enumerate/normaliz/vector_operations.h000644 000765 000024 00000012605 12234011536 023613 0ustar00babstaff000000 000000 /* * Normaliz * Copyright (C) 2007-2013 Winfried Bruns, Bogdan Ichim, Christof Soeger * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ //--------------------------------------------------------------------------- #ifndef VECTOR_OPERATIONS_H #define VECTOR_OPERATIONS_H //--------------------------------------------------------------------------- #include #include #include "libnormaliz.h" namespace libnormaliz { using std::vector; using std::ostream; //--------------------------------------------------------------------------- // Data access //--------------------------------------------------------------------------- template ostream& operator<< (ostream& out, const vector& vec) { for (size_t i=0; i Integer v_scalar_product(const vector& a,const vector& b); //returns the scalar product of the vector a with the end of the vector b template Integer v_scalar_product_unequal_vectors_end(const vector& a,const vector& b); //returns the addition a + b, vectors must be of equal size template vector v_add(const vector& a,const vector& b); //adds b to a reduces the result modulo m, a and b must be reduced modulo m! template vector& v_add_to_mod(vector& a, const vector& b, const Integer& m); //--------------------------------------------------------------------------- // abs, gcd and lcm //--------------------------------------------------------------------------- //takes the absolute value of the elements and returns a reference to the changed vector template vector& v_abs(vector& v); //returns gcd of the elements of v template Integer v_gcd(const vector& v); //returns lcm of the elements of v template Integer v_lcm(const vector& v); //divides the elements by their gcd and returns the gcd template Integer v_make_prime(vector& v); //--------------------------------------------------------------------------- // Scalar operations //--------------------------------------------------------------------------- //v = v * scalar template void v_scalar_multiplication(vector& v, const Integer& scalar){ size_t i,size=v.size(); for (i = 0; i vector v_scalar_multiplication_two(const vector& v, const Integer& scalar); template void v_scalar_division(vector& v, const Integer& scalar); //v = v / scalar, all the elements of v must be divisible with the scalar template void v_reduction_modulo(vector& v, const Integer& modulo); //v = v mod modulo //--------------------------------------------------------------------------- // Test //--------------------------------------------------------------------------- template bool v_test_scalar_product(const vector& a,const vector& b, const Integer& result, const long& m); // test the main computation for arithmetic overflow // uses multiplication mod m //--------------------------------------------------------------------------- // General vector operations //--------------------------------------------------------------------------- //returns a new vector with the content of a extended by b template vector v_merge(const vector& a, const T& b); //returns a new vector with the content of a and b template vector v_merge(const vector& a, const vector& b); //returns a new vector with the last size entries of v template vector v_cut_front(const vector& v, size_t size); //the input vectors must be ordered of equal size //if u is different from v by just one element, it returns that element //else returns 0 (the elements of u and v are >0) //int v_difference_ordered_fast(const vector& u,const vector& v); template bool compare_last (const vector& a, const vector& b) { return a.back() < b.back(); } //returns a key vector containing the positions of non-zero entrys of v template vector v_non_zero_pos(vector v); } //--------------------------------------------------------------------------- #endif //--------------------------------------------------------------------------- regina-4.95/engine/enumerate/ntreeconstraint.cpp000644 000765 000024 00000023516 12234011536 021773 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/ntreeconstraint.h" #include "snappea/nsnappeatriangulation.h" #include "triangulation/ntriangulation.h" namespace regina { bool LPConstraintEuler::addRows( LPInitialTableaux::Col* col, const int* columnPerm, NTriangulation* tri) { int* obj = new int[7 * tri->getNumberOfTetrahedra()]; unsigned tet, i; NPerm4 p; for (i = 0; i < 7 * tri->getNumberOfTetrahedra(); ++i) obj[i] = 1; for (i = 0; i < tri->getNumberOfTriangles(); ++i) { tet = tri->tetrahedronIndex( tri->getTriangle(i)->getEmbedding(0).getTetrahedron()); p = tri->getTriangle(i)->getEmbedding(0).getVertices(); --obj[7 * tet + p[0]]; --obj[7 * tet + p[1]]; --obj[7 * tet + p[2]]; --obj[7 * tet + 4]; --obj[7 * tet + 5]; --obj[7 * tet + 6]; } for (i = 0; i < tri->getNumberOfEdges(); ++i) { tet = tri->tetrahedronIndex( tri->getEdge(i)->getEmbedding(0).getTetrahedron()); p = tri->getEdge(i)->getEmbedding(0).getVertices(); ++obj[7 * tet + p[0]]; ++obj[7 * tet + p[1]]; ++obj[7 * tet + 4 + vertexSplitMeeting[p[0]][p[1]][0]]; ++obj[7 * tet + 4 + vertexSplitMeeting[p[0]][p[1]][1]]; } for (i = 0; i < 7 * tri->getNumberOfTetrahedra(); ++i) col[i].euler = obj[columnPerm[i]]; col[7 * tri->getNumberOfTetrahedra()].euler = -1; delete[] obj; return true; } #ifndef EXCLUDE_SNAPPEA bool LPConstraintNonSpun::addRows( LPInitialTableaux::Col* col, const int* columnPerm, NTriangulation* tri) { // Regardless of whether the constraints are broken, // we need to ensure that the matrix has full rank. // Therefore add the coefficients for the two new variables now. col[3 * tri->getNumberOfTetrahedra()].meridian = -1; col[3 * tri->getNumberOfTetrahedra() + 1].longitude = -1; // For the time being we insist on one vertex, which must be // ideal with torus link. if (tri->getNumberOfVertices() != 1 || (! tri->getVertex(0)->isIdeal()) || (! tri->getVertex(0)->isLinkOrientable()) || tri->getVertex(0)->getLinkEulerCharacteristic() != 0) return false; // Compute the two slope equations for the torus cusp, if we can. NSnapPeaTriangulation snapPea(*tri, false); NMatrixInt* coeffs = snapPea.slopeEquations(); if (! coeffs) return false; // Check that SnapPea hasn't changed the triangulation on us. if (! snapPea.verifyTriangulation(*tri)) { delete coeffs; return false; } // All good! Add the two slope equations as extra rows to // our constraint matrix. // // The coefficients here are differences of terms from // SnapPy's get_cusp_equation(), which works in native // integers; therefore we will happily convert them back to // native integers now. for (int i = 0; i < 3 * tri->getNumberOfTetrahedra(); ++i) { col[i].meridian = coeffs->entry(0, columnPerm[i]).longValue(); col[i].longitude = coeffs->entry(1, columnPerm[i]).longValue(); } delete coeffs; return true; } #endif // EXCLUDE_SNAPPEA BanConstraintBase::BanConstraintBase(NTriangulation* tri, int coords) : tri_(tri), coords_(coords) { unsigned nCols = (coords == NS_QUAD || coords == NS_AN_QUAD_OCT ? 3 * tri->getNumberOfTetrahedra() : 7 * tri->getNumberOfTetrahedra()); banned_ = new bool[nCols]; marked_ = new bool[nCols]; std::fill(banned_, banned_ + nCols, false); std::fill(marked_, marked_ + nCols, false); } void BanBoundary::init(const int* columnPerm) { unsigned n = tri_->getNumberOfTetrahedra(); unsigned tet, type, i, k; bool quadOnly = (coords_ == NS_QUAD || coords_ == NS_AN_QUAD_OCT); // The implementation here is a little inefficient (we repeat tests // three or four times over), but this routine is only called at // the beginning of the enumeration process so no need to worry. // Ban quadrilaterals in tetrahedra that meet the boundary // (every such quadrilateral meets a boundary face). for (i = 0; i < 3 * n; ++i) { if (quadOnly) tet = columnPerm[i] / 3; else tet = columnPerm[i] / 7; for (k = 0; k < 4; ++k) if (! tri_->getTetrahedron(tet)->adjacentTetrahedron(k)) { banned_[i] = true; break; } } // Ban normal triangles in tetrahedra that meet the boundary (but // only those normal triangles that meet the boundary faces). if (! quadOnly) for (i = 3 * n; i < 7 * n; ++i) { tet = columnPerm[i] / 7; type = columnPerm[i] % 7; for (k = 0; k < 4; ++k) if (k != type && ! tri_->getTetrahedron(tet)-> adjacentTetrahedron(k)) { banned_[i] = true; break; } } } void BanTorusBoundary::init(const int* columnPerm) { unsigned n = tri_->getNumberOfTetrahedra(); unsigned tet, type, i, k; // Which boundary faces are we banning? unsigned nTriangles = tri_->getNumberOfTriangles(); bool* banTriangle = new bool[nTriangles]; std::fill(banTriangle, banTriangle + nTriangles, false); // Which vertex links are we marking normal triangles around? unsigned nVertices = tri_->getNumberOfVertices(); bool* markVtx = new bool[nVertices]; std::fill(markVtx, markVtx + nVertices, false); NBoundaryComponent* bc; for (i = 0; i < tri_->getNumberOfBoundaryComponents(); ++i) { bc = tri_->getBoundaryComponent(i); if ((! bc->isIdeal()) && bc->isOrientable() && bc->getEulerCharacteristic() == 0) { // We've found a real torus boundary. for (k = 0; k < bc->getNumberOfTriangles(); ++k) banTriangle[bc->getTriangle(k)->markedIndex()] = true; for (k = 0; k < bc->getNumberOfVertices(); ++k) markVtx[bc->getVertex(k)->markedIndex()] = true; } } bool quadOnly = (coords_ == NS_QUAD || coords_ == NS_AN_QUAD_OCT); // The implementation here is a little inefficient (we repeat tests // three or four times over), but this routine is only called at // the beginning of the enumeration process so no need to worry. // Ban quadrilaterals that touch torus boundaries. for (i = 0; i < 3 * n; ++i) { if (quadOnly) tet = columnPerm[i] / 3; else tet = columnPerm[i] / 7; for (k = 0; k < 4; ++k) if (banTriangle[tri_->getTetrahedron(tet)->getTriangle(k)-> markedIndex()]) { banned_[i] = true; break; } } // Ban normal triangles that touch torus boundaries, and mark all // normal triangles that surround vertices on torus boundaries // (even if the normal triangles do not actually touch the boundary). if (! quadOnly) for (i = 3 * n; i < 7 * n; ++i) { tet = columnPerm[i] / 7; type = columnPerm[i] % 7; if (markVtx[tri_->getTetrahedron(tet)->getVertex(type)-> markedIndex()]) marked_[i] = true; for (k = 0; k < 4; ++k) if (k != type && banTriangle[tri_->getTetrahedron(tet)->getTriangle(k)-> markedIndex()]) { banned_[i] = true; break; } } delete[] banTriangle; delete[] markVtx; } } // namespace regina regina-4.95/engine/enumerate/ntreeconstraint.h000644 000765 000024 00000116501 12234011536 021435 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntreeconstraint.h * \brief Constraint classes for use with tree traversal enumeration methods. */ #ifndef __NTREECONSTRAINT_H #ifndef __DOXYGEN #define __NTREECONSTRAINT_H #endif #include "regina-config.h" // For EXCLUDE_SNAPPEA #include "enumerate/ntreelp.h" #include "maths/ninteger.h" #include "surfaces/nnormalsurfacelist.h" namespace regina { class NNormalSurface; class NTriangulation; template class LPMatrix; /** * \weakgroup enumerate * @{ */ /** * A base class for additional linear constraints that we can add to the * tableaux of normal surface matching equations. This is used with * NTreeEnumeration, NTreeSingleSoln and related algorithms for enumerating and * locating normal surfaces in a 3-manifold triangulation. See the * LPInitialTableaux class notes for details on how these constraints * interact with the tableaux of matching equations. * * The linear constraints may be equalities or inequalities, and there * may be more than one such constraint. If all constraints are * homogeneous equalities, the class should derive from LPConstraintSubspace * instead (not this base class). * * This base class provides no functionality. For documentation's sake * only, the notes here describe the functionality that any subclass * \e must implement. We note again that LPConstraintBase does not * provide any implementations at all, and subclasses are completely * responsible for their own implementations. * * \apinotfinal * * \ifacespython Not present. */ class LPConstraintBase { #ifdef __DOXYGEN public: enum { /** * The number of additional linear constraints that we impose. * Each constraint will generate one new variable (column) * and one new equation (row) in the tableaux. */ nConstraints }; /** * Stores the extra coefficients in a single column for the * \a nConstraints additional rows that we add to the tableaux * to describe the \a nConstraints additional linear equations * or inequalities. * * Subclasses may store these coefficients however they like * (in particular, they may optimise for sparse coefficients, * binary coefficients, and so on). They will only ever be * accessed through the member functions of this Coefficients class. */ struct Coefficients { /** * Creates an uninitialised set of coefficients for a single * column. These cofficients must be initialised through a * call to addRows() before they can be used. */ Coefficients(); /** * Explicitly fills the final row(s) of the given tableaux matrix * with the coefficients stored in this Coefficients structure. * In essence, this routine simply copies this sparse and/or * specialised representation of the final row(s) into a * more standard dense matrix representation. * * This routine should only affect the final \a nConstraints * entries in the given column of the matrix. It may assume * that these final row(s) have already been initialised to zero. * * \pre The given matrix has at least \a nConstraints rows * and at least \a col + 1 columns. * \pre The final \a nConstraints entries in column \a col * of the given matrix have already been set to zero. * * @param m the matrix in which to place these column * coefficients. * @param col the column of the given matrix in which to * place these coefficients. */ template void fillFinalRows(LPMatrix& m, unsigned col) const; /** * Computes the inner product of (i) the final \a nConstraints * entries in the given row of the given matrix with (ii) the * \a nConstraints column coefficients stored in this data * structure. * * \pre The given matrix has at least \a nConstraints columns * and at least \a mRow + 1 rows. * * @param m the matrix whose row we will use in the inner product. * @param mRow the row of the matrix \a m to use in the inner * product. * @return the resulting portion of the inner product. */ template Integer innerProduct(const LPMatrix& m, unsigned mRow) const; /** * A variant of innerProduct() that takes into account any * adjustments to these linear constraint(s) that are required when * this is a quadrilateral column being used to represent an * octagon type. * * The LPData class offers support for octagonal almost normal * surfaces, in which exactly one tetrahedron is allowed to have * exactly one octagon type. We represent such an octagon as a * \e pair of incompatible quadrilaterals within the same * tetrahedron. See the LPData class notes for details on how * this works. * * In some settings, our extra linear constraints must behave * differently in the presence of octagons (i.e., the coefficient * of the octagon type is not just the sum of coefficients of the * two constituent quadrilateral types). This routine effectively * allows us to adjust the tableaux accordingly. * * Specifically: this routine computes the inner product of (i) the * final \a nConstraints entries in the given row of the given * matrix with (ii) the \a nConstraints column coefficients * stored in this data structure. We assume that this column * in the underlying tableaux describes one of the two * quadrilateral coordinates in some tetrahedron that together * form an octagon type, and if necessary we implicitly adjust * the coefficients stored in this data structure accordingly. * * \pre The given matrix has at least \a nConstraints columns * and at least \a mRow + 1 rows. * * \pre This column of the underlying tableaux describes one * of the two quadrilateral coordinates that are being * combined to form an octagon type within some tetrahedron. * * @param m the matrix whose row we will use in the inner product. * @param mRow the row of the matrix \a m to use in the inner * product. * @return the resulting portion of the inner product. */ template Integer innerProductOct(const LPMatrix& m, unsigned mRow) const; }; /** * Explicitly constructs equations for the linear function(s) * constrained by this class. Specifically, this routine takes an * array of Coefficients objects (one for each column of the initial * tableaux) and fills in the necessary coefficient data. * * The precise form of the linear function(s) will typically * depend upon the underlying triangulation. For this reason, * the triangulation is explicitly passed, along with the * permutation that indicates which columns of the initial tableaux * correspond to which normal coordinates. * * More precisely: recall that, for each linear function, the initial * tableaux acquires one new variable \a x_i that evaluates this linear * function f(x). This routine must create the corresponding row that * sets f(x) - x_i = 0. Thus it must construct the * coefficients of f(x) in the columns corresponding to normal * coordinates, and it must also set a coefficient of -1 in the * column for the corresponding new variable. * * For each subclass \a S of LPConstraintBase, the array \a col * must be an array of objects of type LPInitialTableaux::Col. * The class LPInitialTableaux::Col is itself a larger subclass of * the Coefficients class. This exact type must be used because the * compiler must know how large each column object is in * order to correct access each element of the given array. * * As described in the LPInitialTableaux class notes, it might * not be possible to construct the linear functions (since the * triangulation might not satisfy the necessary requirements). * In this case, this routine should ensure that the linear * functions are in fact the zero functions, and should return * \c false (but it must still set -1 coefficients for the new * variables as described above). Otherwise (if the linear function * were successfully constructed) this routine should return \c true. * * \pre For all coefficients in the array \a col, the * Coefficients substructures have all been initialised with the * default constructor and not modified since. * * @param col the array of columns as stored in the initial * tableaux (i.e., the data member LPInitialTableaux::col_). * @param columnPerm the corresponding permutation of columns * that describes how columns of the tableaux correspond to * normal coordinates in the underlying triangulation (i.e., the * data member LPInitialTableaux::columnPerm_). * @param tri the underlying triangulation. * @return \c true if the linear functions were successfully * constructed, or \c false if not (in which case they will be * replaced with the zero functions instead). */ static bool addRows(LPInitialTableaux::Col* col, const int* columnPerm, NTriangulation* tri); /** * Explicitly constraints each of these linear functions to an * equality or inequality in the underlying tableaux. This will * typically consist of a series of calls to LPData::constrainZero() * and/or LPData::constrainPositive(). * * The variables for these extra linear functions are stored in * columns numCols - nConstraints, ..., numCols - 1 * of the given tableaux, and so your calls to LPData::constrainZero() * and/or LPData::constrainPositive() should operate on these * (and only these) columns. * * \pre These column coefficients belong to the initial starting * tableaux (LPInitialTableaux) from which the given tableaux is * derived. * * @param lp the tableaux in which to constrain these linear * functions. * @param numCols the number of columns in the given tableaux. */ template static void constrain(LPData& lp, unsigned numCols); /** * Ensures that the given normal surface satisfies the extra * constraints described by this class. * * Ideally this test is not based on explicitly recomputing the * linear function(s), but instead runs independent tests. * For instance, if this class is used to constraint Euler * characteristic, then ideally this routine would call * s->getEulerCharacteristic() and test the return value of that * routine instead. * * @param s the surface to test. * @return \c true if the given surface satisfies these linear * constraints, or \c false if it does not. */ static bool verify(const NNormalSurface* s); /** * Indicates whether the given coordinate system is supported by * this constraint class. * * This routine assumes that the given system is already known to be * supported by the generic tree traversal infrastructure, and only * returns \c false if there are additional prerequisites * imposed by this particular constraint class that the given * system does not satisfy. If this constraint class does not impose * any of its own additional conditions, this routine may * simply return \c true. * * @param coords the coordinate system being queried; this must * be one of the coordinate systems known to be supported by the * generic NTreeTraversal infrastructure. * @return \c true if and only if this coordinate system is * also supported by this specific constraint class. */ static bool supported(NormalCoords coords); #endif }; /** * A subclass of LPConstraintBase used for constraints defined entirely * by homogeneous linear equations. * * Any set of constraints defined entirely by homogeneous linear * equations should derive from LPConstraintSubspace, not LPConstraintBase. * In other words, any set of constraints derived from LPConstraintSubspace * should simply restrict our attention to a vector subspace of the * normal surface coordinate system. * * This class does not provide any additional functionality. It is * merely a convenience to help describe and enforce preconditions. * * \apinotfinal * * \ifacespython Not present. */ class LPConstraintSubspace : public LPConstraintBase { }; /** * A do-nothing class that imposes no additional linear constraints on * the tableaux of normal surface matching equations. * * See the LPConstraintBase class notes for details on all member * functions and structs. * * \ifacespython Not present. */ class LPConstraintNone : public LPConstraintSubspace { public: enum { nConstraints = 0 }; /** * Stores the extra coefficients in the tableaux associated * with this constraint class (which for this class is a no-op, * since in this case there are no extra coefficients). * * See the LPConstraintBase::Coefficients notes for further details. */ struct Coefficients { Coefficients(); template void fillFinalRows(LPMatrix& m, unsigned col) const; template Integer innerProduct(const LPMatrix&, unsigned) const; template Integer innerProductOct(const LPMatrix&, unsigned) const; }; static bool addRows(LPInitialTableaux::Col*, const int*, NTriangulation*); template static void constrain( LPData&, unsigned); static bool verify(const NNormalSurface*); static bool supported(NormalCoords coords); }; /** * A class that constraints the tableaux of normal surface matching equations * to ensure that Euler characteristic is strictly positive. * * There are many ways of writing Euler characteritic as a linear * function. The function constructed here has integer coefficients, * but otherwise has no special properties of note. * * This constraint can work with either normal or almost normal * coordinates. In the case of almost normal coordinates, the function * is modified to measure Euler characteristic minus the number of * octagons (a technique of Casson, also employed by Jaco and Rubinstein, that * is used to ensure we do not have more than two octagons when searching for * a normal or almost normal sphere in the 3-sphere recognition algorithm). * * See the LPConstraintBase class notes for details on all member * functions and structs. * * \pre We are working in standard normal or almost normal coordinates * (not quadrilateral or quadrilateral-octagon coordinates). In * particular, the coordinate system passed to the corresponding * LPInitialTableaux class constructor must be NS_STANDARD. * * \apinotfinal * * \ifacespython Not present. */ class LPConstraintEuler : public LPConstraintBase { public: enum { nConstraints = 1 }; /** * Stores the extra coefficients in the tableaux associated with this * constraint class (in this case, one extra integer per column). * * See the LPConstraintBase::Coefficients notes for further details. */ struct Coefficients { int euler; /**< The coefficient of the Euler characteristic function for the corresponding column of the matching equation matrix. */ Coefficients(); template void fillFinalRows(LPMatrix& m, unsigned col) const; template Integer innerProduct(const LPMatrix& m, unsigned mRow) const; template Integer innerProductOct(const LPMatrix& m, unsigned mRow) const; }; static bool addRows( LPInitialTableaux::Col* col, const int* columnPerm, NTriangulation* tri); template static void constrain( LPData& lp, unsigned numCols); static bool verify(const NNormalSurface* s); static bool supported(NormalCoords coords); }; #ifndef EXCLUDE_SNAPPEA /** * A class that constraints the tableaux of normal surface matching equations * to ensure that normal surfaces in an ideal triangulation are compact * (thereby avoiding spun normal surfaces with infinitely many triangles). * * At present this class can only work with oriented triangulations that have * precisely one vertex, which is ideal with torus link. These * constraints are explicitly checked by addRows(), which returns \c false * if they are not satisfied. Moreover, this constraint calls on * SnapPea for some calculations: in the unexpected situation where * SnapPea retriangulates, the linear function cannot be constructed and * addRows() will again return \c false. You should always test * LPInitialTableaux::constraintsBroken() to verify that the linear * functions have been constructed correctly. * * Also, at present this class can only work with quadrilateral normal * coordinates (and cannot handle almost normal coordinates at all). * This is \e not explicitly checked; instead it appears as a * precondition (see below). * * See the LPConstraintBase class notes for details on all member * functions and structs. * * \pre We are working in quadrilateral normal coordinates. In particular, * the coordinate system passed to the corresponding LPInitialTableaux class * must be NS_QUAD, and constrainOct() must never be * called on any of the corresponding LPData tableaux. * * \apinotfinal * * \ifacespython Not present. */ class LPConstraintNonSpun : public LPConstraintSubspace { public: enum { nConstraints = 2 }; /** * Stores the extra coefficients in the tableaux associated with this * constraint class (in this case, two extra integers per column). * * See the LPConstraintBase::Coefficients notes for further details. */ struct Coefficients { int meridian; /**< The coefficient of the meridian equation for the corresponding column of the matching equation matrix. */ int longitude; /**< The coefficient of the longitude equation for the corresponding column of the matching equation matrix. */ Coefficients(); template void fillFinalRows(LPMatrix& m, unsigned col) const; template Integer innerProduct(const LPMatrix& m, unsigned mRow) const; template Integer innerProductOct(const LPMatrix& m, unsigned mRow) const; }; static bool addRows( LPInitialTableaux::Col* col, const int* columnPerm, NTriangulation* tri); template static void constrain( LPData& lp, unsigned numCols); static bool verify(const NNormalSurface* s); static bool supported(NormalCoords coords); }; #endif // EXCLUDE_SNAPPEA /** * A base class for additional banning and marking constraints that we * can place on tree traversal algorithms. This is used with * NTreeEnumeration, NTreeSingleSoln and related algorithms for * enumerating and locating normal surfaces in a 3-manifold triangulation. * * This class adds constraints of two types: * * - \e Banning constraints, which ensure that certain normal coordinates * are set to zero; * * - \e Marking constraints, which are more flexible and can be used in * different ways by different algorithms. * * All of these constraints operate only on normal coordinates in the * underlying tableaux (and in particular not the additional variables * introduced by additional linear constraints, as described by * LPConstraintBase and its subclasses). * * Currently marking is used in the following ways: * * - The NTreeEnumeration algorithm does not use marking at all. * * - In the NTreeSingleSoln algorithm, marking affects what is considered * a non-trivial surface. Normally, a non-trivial surface is defined * to be one in which some triangle coordinate is zero. With marking, * a non-trivial surface is redefined to be one in which some \e unmarked * triangle coordinate is zero. In other words, marked triangle types * are effectively ignored when determining whether a surface is non-trivial * or not. * * At present, marking is not used at all for quadrilateral coordinates. * Howver, marking is a very new feature, and this concept may be expanded * in future versions of Regina. * * This class does not record disc types in the order of their normal * coordinates; instead it records them in the order of their columns in * a tableaux for linear programming (as used in LPInitialTableaux). * This means that there is a little more work required in setting up * the initial lists of banned and marked columns, but then these lists are * easy to use on the fly during tree traversal algorithms. * * This base class provides limited functionality (as documented below). * Subclasses \e must implement a constructor (which, like this base * class, takes a triangulation and a coordinate system), must implement * init() which determines which normal coordinates are banned and/or marked, * and must implement supported(), which indicates which normal * coordinate system this constraint class can work with. * * \apinotfinal * * \ifacespython Not present. */ class BanConstraintBase { protected: NTriangulation* tri_; /**< The triangulation with which we are working. */ int coords_; /**< The normal or almost normal coordinate system in which we are working. This must be one of NS_QUAD, NS_STANDARD, NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ bool* banned_; /**< Indicates which columns of a tableaux correspond to banned disc types. The size of this array is the number of normal coordinates (so we explicitly exclude extra columns that arise from the template parameter LPConstraint. */ bool* marked_; /**< Indicates which columns of a tableaux correspond to marked disc types. The size of this array is the number of normal coordinates (so we explicitly exclude extra columns that arise from the template parameter LPConstraint. */ protected: /** * Constructs and initialises the \a banned_ and \a marked_ arrays * to be entirely \c false. The only purpose of passing the * triangulation and coordinate system is to determine how many * normal coordinates we are dealing with. * * \warning Before you use this object, the routine init() must be * called to fill in the \a banned_ and \a marked_ arrays with the * correct data. Otherwise you will have no banned or marked disc * types at all. * * @param tri the triangulation with which we are working. * @param coords the normal or almost normal coordinate system in * which we are working. This must be one of NS_QUAD, * NS_STANDARD, NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ BanConstraintBase(NTriangulation* tri, int coords); /** * Destroys this object and all associated data. */ ~BanConstraintBase(); /** * Enforces all bans described by this class in the given * tableaux. Specifically, for each banned disc type, this * routine calls LPData::constrainZero() on the corresponding * normal coordinate column. * * @param lp the tableaux in which to enforce the bans. */ template void enforceBans(LPData& lp) const; #ifdef __DOXYGEN /** * Idetifies which disc types to ban and mark, and records the * corresponding tableaux columns in the \a banned_ and \a marked_ * arrays respectively. * * @param columnPerm the permutation of columns that describes how * columns of the tableaux correspond to normal coordinates in * the underlying triangulation. Specifically, this permutation must * be the same permutation returned by LPInitialTableaux::columnPerm(). */ void init(const int* columnPerm); /** * Indicates whether the given coordinate system is supported by * this constraint class. * * This routine assumes that the given system is already known to be * supported by the generic tree traversal infrastructure, and only * returns \c false if there are additional prerequisites * imposed by this particular constraint class that the given * system does not satisfy. If this constraint class does not impose * any of its own additional conditions, this routine may * simply return \c true. * * @param coords the coordinate system being queried; this must * be one of the coordinate systems known to be supported by the * generic NTreeTraversal infrastructure. * @return \c true if and only if this coordinate system is * also supported by this specific constraint class. */ static bool supported(NormalCoords coords); #endif }; /** * A do-nothing class that bans no disc types and marks no disc types. * * See the BanConstraintBase class notes for details on all member * functions and structs. * * \apinotfinal * * \ifacespython Not present. */ class BanNone : public BanConstraintBase { protected: /** * Constructs and initialises the \a banned_ and \a marked_ arrays * to be entirely \c false, as described in the BanConstraintBase * superclass constructor. * * Although one should normally call the routine init() before * using this object, for BanNone this is not strictly necessary * since there are no disc types to ban or mark. * * @param tri the triangulation with which we are working. * @param coords the normal or almost normal coordinate system in * which we are working. This must be one of NS_QUAD, * NS_STANDARD, NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ BanNone(NTriangulation* tri, int coords); void init(const int*); static bool supported(NormalCoords coords); }; /** * A class that bans normal disc types that meet the boundary of the * underlying triangulation. No disc types are marked at all. * * \warning This class only works as expected in \e standard normal or * almost normal coordinates. In quadrilateral or quadrilateral-octagon * coordinates it will only ban quadrilaterals or octagons that touch * the boundary, but it will still allow \e triangles that meet the boundary * (since triangle types are not counted in these coordinate systems). * The supported() routine will only return \c true in standard normal or * almost normal coordinates. * * See the BanConstraintBase class notes for details on all member * functions and structs. * * \apinotfinal * * \ifacespython Not present. */ class BanBoundary : public BanConstraintBase { protected: /** * Constructs and initialises the \a banned_ and \a marked_ arrays * to be entirely \c false, as described in the BanConstraintBase * superclass constructor. * * \warning Before you use this object, the routine init() must be * called to fill in the \a banned_ and \a marked_ arrays with the * correct data. Otherwise you will have no banned or marked disc * types at all. * * @param tri the triangulation with which we are working. * @param coords the normal or almost normal coordinate system in * which we are working. This must be one of NS_QUAD, * NS_STANDARD, NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ BanBoundary(NTriangulation* tri, int coords); void init(const int* columnPerm); static bool supported(NormalCoords coords); }; /** * A class that bans and marks disc types associated with torus boundary * components. Here we refer exclusively to real torus boundary * components (not ideal vertices with torus cusps). Specifically: * * - this class bans any normal triangle or quadrilateral that meets a * torus boundary; * * - this class marks any normal triangle in the link of a vertex on a * torus boundary. * * \warning As with BanBoundary, this class only works as expected in * \e standard normal or almost normal coordinates. In quadrilateral or * quadrilateral-octagon coordinates it will only ban quadrilaterals or * octagons that touch torus boundaries, but it will still allow \e triangles * that meet torus boundaries (since triangle types are not counted in these * coordinate systems). The supported() routine will only return \c true * in standard normal or almost normal coordinates. * * See the BanConstraintBase class notes for details on all member * functions and structs. * * \apinotfinal * * \ifacespython Not present. */ class BanTorusBoundary : public BanConstraintBase { protected: /** * Constructs and initialises the \a banned_ and \a marked_ arrays * to be entirely \c false, as described in the BanConstraintBase * superclass constructor. * * \warning Before you use this object, the routine init() must be * called to fill in the \a banned_ and \a marked_ arrays with the * correct data. Otherwise you will have no banned or marked disc * types at all. * * @param tri the triangulation with which we are working. * @param coords the normal or almost normal coordinate system in * which we are working. This must be one of NS_QUAD, * NS_STANDARD, NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ BanTorusBoundary(NTriangulation* tri, int coords); void init(const int* columnPerm); static bool supported(NormalCoords coords); }; // Inline functions inline LPConstraintNone::Coefficients::Coefficients() { } template inline void LPConstraintNone::Coefficients::fillFinalRows( LPMatrix& m, unsigned col) const { } template inline Integer LPConstraintNone::Coefficients::innerProduct( const LPMatrix&, unsigned) const { return 0; } template inline Integer LPConstraintNone::Coefficients::innerProductOct( const LPMatrix&, unsigned) const { return 0; } inline bool LPConstraintNone::addRows( LPInitialTableaux::Col*, const int*, NTriangulation*) { return true; } template inline void LPConstraintNone::constrain( LPData&, unsigned) { } inline bool LPConstraintNone::verify(const NNormalSurface*) { return true; } inline bool LPConstraintNone::supported(NormalCoords) { return true; } inline LPConstraintEuler::Coefficients::Coefficients() : euler(0) {} template inline void LPConstraintEuler::Coefficients::fillFinalRows( LPMatrix& m, unsigned col) const { m.entry(m.rows() - 1, col) = euler; } template inline Integer LPConstraintEuler::Coefficients::innerProduct( const LPMatrix& m, unsigned mRow) const { Integer ans(m.entry(mRow, m.rows() - 1)); ans *= euler; return ans; } template inline Integer LPConstraintEuler::Coefficients::innerProductOct( const LPMatrix& m, unsigned mRow) const { // This is called for *two* quad columns (the two quads // that combine to give a single octagon). // // The adjustment in this case is to subtract two from // the overall Euler characteristic coefficient for this // octagon type (-1 because an octagon has lower Euler // characteristic than two quads, and -1 again because // we are measuring Euler - #octagons. // // Happily we can do this by subtracting one from the // coefficient in each of the two columns, as // implemented below. Integer ans(m.entry(mRow, m.rows() - 1)); ans *= (euler - 1); return ans; } template inline void LPConstraintEuler::constrain( LPData& lp, unsigned numCols) { lp.constrainPositive(numCols - 1); } inline bool LPConstraintEuler::verify(const NNormalSurface* s) { return (s->getEulerCharacteristic() > 0); } inline bool LPConstraintEuler::supported(NormalCoords coords) { return (coords == NS_STANDARD || coords == NS_AN_STANDARD); } #ifndef EXCLUDE_SNAPPEA inline LPConstraintNonSpun::Coefficients::Coefficients() : meridian(0), longitude(0) { } template inline void LPConstraintNonSpun::Coefficients::fillFinalRows( LPMatrix& m, unsigned col) const { m.entry(m.rows() - 2, col) = meridian; m.entry(m.rows() - 1, col) = longitude; } template inline Integer LPConstraintNonSpun::Coefficients::innerProduct( const LPMatrix& m, unsigned mRow) const { Integer ans1(m.entry(mRow, m.rows() - 2)); ans1 *= meridian; Integer ans2(m.entry(mRow, m.rows() - 1)); ans2 *= longitude; ans1 += ans2; return ans1; } template inline Integer LPConstraintNonSpun::Coefficients::innerProductOct( const LPMatrix& m, unsigned mRow) const { // This should never be called, since we never use this // constraint with almost normal surfaces. // For compilation's sake though, just return the usual // inner product. return innerProduct(m, mRow); } template inline void LPConstraintNonSpun::constrain( LPData& lp, unsigned numCols) { lp.constrainZero(numCols - 2); lp.constrainZero(numCols - 1); } inline bool LPConstraintNonSpun::verify(const NNormalSurface* s) { return s->isCompact(); } inline bool LPConstraintNonSpun::supported(NormalCoords coords) { return (coords == NS_QUAD); } #endif // EXCLUDE_SNAPPEA inline BanConstraintBase::~BanConstraintBase() { delete[] banned_; delete[] marked_; } template inline void BanConstraintBase::enforceBans(LPData& lp) const { for (unsigned i = 0; i < lp.coordinateColumns(); ++i) if (banned_[i]) lp.constrainZero(i); } inline BanNone::BanNone(NTriangulation* tri, int coords) : BanConstraintBase(tri, coords) { } inline void BanNone::init(const int*) { } inline bool BanNone::supported(NormalCoords) { return true; } inline BanBoundary::BanBoundary(NTriangulation* tri, int coords) : BanConstraintBase(tri, coords) { } inline bool BanBoundary::supported(NormalCoords coords) { return (coords == NS_STANDARD || NS_AN_STANDARD); } inline BanTorusBoundary::BanTorusBoundary(NTriangulation* tri, int coords) : BanConstraintBase(tri, coords) { } inline bool BanTorusBoundary::supported(NormalCoords coords) { return (coords == NS_STANDARD || NS_AN_STANDARD); } } // namespace regina #endif regina-4.95/engine/enumerate/ntreelp-impl.h000644 000765 000024 00000131611 12234011536 020622 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* Definitions of template member functions from ntreelp.h. If your template arguments are classes from ntreeconstraint.h, then you never need to include this file (since all templates will already be instantiated for you). You will, however, need to include this file if you provide your own custom classes as template arguments. */ #ifndef __NTREELP_IMPL_H #ifndef __DOXYGEN #define __NTREELP_IMPL_H #endif /** * Optimisation flags: * Define any combination of the following flags to switch \e off * various optimisations. * This is for diagnostic purposes only. */ // #define REGINA_NOOPT_REORDER_COLUMNS #include "enumerate/ntreeconstraint.h" #include "enumerate/ntreelp.h" #include "maths/matrixops.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" #include "utilities/nbitmask.h" #include namespace regina { template void LPMatrix::combRow(const Integer& destCoeff, unsigned dest, const Integer& srcCoeff, unsigned src, const Integer& div) { Integer* ps = dat_ + src * cols_; Integer* pd = dat_ + dest * cols_; Integer tmp; // Use this to avoid spurious temporary Integers. for (unsigned i = 0; i < cols_; ++i) { *pd *= destCoeff; tmp = srcCoeff; tmp *= *ps++; *pd -= tmp; (*pd++).divByExact(div); } } template Integer LPMatrix::combRowAndNorm(const Integer& destCoeff, unsigned dest, const Integer& srcCoeff, unsigned src) { Integer gcdRow; // Initialised to zero. Integer* ps = dat_ + src * cols_; Integer* pd = dat_ + dest * cols_; Integer tmp; // Use this to avoid spurious temporary Integers. unsigned i; for (i = 0; i < cols_; ++i, ++pd, ++ps) { *pd *= destCoeff; tmp = srcCoeff; tmp *= *ps; *pd -= tmp; if (gcdRow != 1) gcdRow.gcdWith(*pd); // gcd() guarantees to be >= 0. } if (gcdRow > 1) { pd = dat_ + dest * cols_; for (i = 0; i < cols_; ++i) (*pd++).divByExact(gcdRow); } return gcdRow; } template void LPMatrix::dump(std::ostream& out) const { out << "---------------------------------" << std::endl; unsigned r, c; for (r = 0; r < rows_; ++r) { for (c = 0; c < cols_; ++c) out << entry(r, c) << ' '; out << std::endl; } out << "---------------------------------" << std::endl; } template LPInitialTableaux::LPInitialTableaux( NTriangulation* tri, NormalCoords coords, bool enumeration) : tri_(tri), coords_(coords) { // Fetch the original (unadjusted) matrix of matching equations. eqns_ = regina::makeMatchingEquations(tri, coords); // Compute the rank of the matrix, and reorder its rows so // the first \a rank_ rows are full rank. rank_ = regina::rowBasis(*eqns_); // Reorder the columns using a good heuristic. cols_ = eqns_->columns() + LPConstraint::nConstraints; columnPerm_ = new int[cols_]; reorder(enumeration); // Create and fill the sparse columns. col_ = new Col[cols_]; unsigned r, c; for (c = 0; c < eqns_->columns(); ++c) for (r = 0; r < rank_; ++r) if (eqns_->entry(r, c) != 0) col_[c].push(r, eqns_->entry(r, c).longValue()); // Add in the final row(s) for any additional constraints. constraintsBroken_ = ! LPConstraint::addRows(col_, columnPerm_, tri); rank_ += LPConstraint::nConstraints; } #ifdef REGINA_NOOPT_REORDER_COLUMNS template void LPInitialTableaux::reorder(bool) { // This is a "do-nothing" version of reorder(). int i, j; if (coords_ == NS_QUAD) { // Leave the columns exactly as they were. for (i = 0; i < cols_; ++i) columnPerm_[i] = i; } else { // Keep the tetrahedra in the same order, but move // quadrilaterals to the front and triangles to the back // as required by columnPerm(). int n = tri_->getNumberOfTetrahedra(); for (i = 0; i < n; ++i) { columnPerm_[3 * i] = 7 * i + 4; columnPerm_[3 * i + 1] = 7 * i + 5; columnPerm_[3 * i + 2] = 7 * i + 6; columnPerm_[3 * n + 4 * i] = 7 * i; columnPerm_[3 * n + 4 * i + 1] = 7 * i + 1; columnPerm_[3 * n + 4 * i + 2] = 7 * i + 2; columnPerm_[3 * n + 4 * i + 3] = 7 * i + 3; } } // This fills the columnPerm_ array; now we need to move the // columns of eqns_ around accordingly, and then finish off // columnPerm_ with the columns for additional constraints // from LPConstraint (if we have any). // // From here on we copy code directly from the "real" reorder() // below. int* tmp = new int[eqns_->columns()]; std::copy(columnPerm_, columnPerm_ + eqns_->columns(), tmp); for (i = 0; i < eqns_->columns(); ++i) { // Column tmp[i] of the matrix should be moved to // column i. if (tmp[i] == i) continue; eqns_->swapColumns(i, tmp[i]); // Adjust links to the old column i, which is now column tmp[i]. for (j = i + 1; j < eqns_->columns(); ++j) if (tmp[j] == i) break; // This is the link we need to change. #ifdef REGINA_VERIFY_LPDATA if (j == eqns_->columns()) { std::cerr << "ERROR: Sorting error." << std::endl; ::exit(1); } #endif tmp[j] = tmp[i]; tmp[i] = i; } delete[] tmp; // If we have extra variables for additional constraints or // objectives, append the corresponding entries to the end of // the permutation for completeness. for (i = 0; i < LPConstraint::nConstraints; ++i) columnPerm_[cols_ - i - 1] = cols_ - i - 1; } #else template void LPInitialTableaux::reorder(bool enumeration) { int n = tri_->getNumberOfTetrahedra(); int i, j, k; // Fill the columnPerm_ array according to what kind of // problem we're trying to solve. if (coords_ == NS_STANDARD && enumeration) { // We're doing vertex enumeration in standard coordinates. // // Use exactly the same ordering of quadrilaterals that we // use in quadrilateral coordinates, and then just fill // in the triangles at the end. LPInitialTableaux quad(tri_, NS_QUAD, true /* enumeration */); for (i = 0; i < n; ++i) { k = quad.columnPerm()[3 * i] / 3; columnPerm_[3 * i] = 7 * k + 4; columnPerm_[3 * i + 1] = 7 * k + 5; columnPerm_[3 * i + 2] = 7 * k + 6; columnPerm_[3 * n + 4 * i] = 7 * k; columnPerm_[3 * n + 4 * i + 1] = 7 * k + 1; columnPerm_[3 * n + 4 * i + 2] = 7 * k + 2; columnPerm_[3 * n + 4 * i + 3] = 7 * k + 3; } } else { // We're doing vertex enumeration in quad coordinates, // or we're in standard coordinates but just searching // for a single solution under some constraints. // // Process the rows in increasing order by number of tetrahedra // touched, and place the columns for each tetrahedron in the // order that we see them. We place columns at the "back" of // the matrix, so that we fill the matrix in "reverse" order // from the last column to the first. // Track which rows have been processed so far. bool* used = new bool[rank_]; std::fill(used, used + rank_, false); // Also track which tetrahedra have been used so far. bool* touched = new bool[n]; std::fill(touched, touched + n, false); int nTouched = 0; // Off we go, one row at a time. int bestRow, best, curr; for (i = 0; i < rank_; ++i) { // Seek out the ith row to process. // Because the first rank_ rows of the matrix are full rank, // we are guaranteed that this row will be non-zero. best = n + 1; // No row touches more than n tetrahedra. for (j = 0; j < rank_; ++j) { if (used[j]) continue; curr = 0; for (k = 0; k < n; ++k) { if (touched[k]) continue; if (coords_ == NS_QUAD) { // We're in quadrilateral coordinates. if (eqns_->entry(j, 3 * k) != 0 || eqns_->entry(j, 3 * k + 1) != 0 || eqns_->entry(j, 3 * k + 2) != 0) ++curr; } else { // We're in standard coordinates. if (eqns_->entry(j, 7 * k + 4) != 0 || eqns_->entry(j, 7 * k + 5) != 0 || eqns_->entry(j, 7 * k + 6) != 0) ++curr; } if (curr >= best) break; // We cannot beat best from here. } if (curr < best) { bestRow = j; best = curr; } } // The next row to process is bestRow. // Find all the tetrahedra that it touches that we // haven't already used yet, and place the corresponding // columns at the end of the matrix. used[bestRow] = true; for (k = 0; k < n; ++k) { if (touched[k]) continue; if (coords_ == NS_QUAD) { // We're in quadrilateral coordinates. if ((eqns_->entry(bestRow, 3 * k) != 0 || eqns_->entry(bestRow, 3 * k + 1) != 0 || eqns_->entry(bestRow, 3 * k + 2) != 0)) { touched[k] = true; columnPerm_[3 * (n - nTouched) - 3] = 3 * k; columnPerm_[3 * (n - nTouched) - 2] = 3 * k + 1; columnPerm_[3 * (n - nTouched) - 1] = 3 * k + 2; ++nTouched; } } else { // We're in standard coordinates. if ((eqns_->entry(bestRow, 7 * k + 4) != 0 || eqns_->entry(bestRow, 7 * k + 5) != 0 || eqns_->entry(bestRow, 7 * k + 6) != 0)) { touched[k] = true; // The quadrilateral columns... columnPerm_[3 * (n - nTouched) - 3] = 7 * k + 4; columnPerm_[3 * (n - nTouched) - 2] = 7 * k + 5; columnPerm_[3 * (n - nTouched) - 1] = 7 * k + 6; // ... and the triangle columns. columnPerm_[3 * n + 4 * (n - nTouched) - 4] = 7 * k; columnPerm_[3 * n + 4 * (n - nTouched) - 3] = 7 * k + 1; columnPerm_[3 * n + 4 * (n - nTouched) - 2] = 7 * k + 2; columnPerm_[3 * n + 4 * (n - nTouched) - 1] = 7 * k + 3; ++nTouched; } } } } // We have now processed all rows. However, there may be some // tetrahedra that appear in no rows at all. // Make sure we catch these tetrahedra as well. for (k = 0; k < n; ++k) { if (touched[k]) continue; touched[k] = true; if (coords_ == NS_QUAD) { // We're in quadrilateral coordinates. columnPerm_[3 * (n - nTouched) - 3] = 3 * k; columnPerm_[3 * (n - nTouched) - 2] = 3 * k + 1; columnPerm_[3 * (n - nTouched) - 1] = 3 * k + 2; } else { // We're in standard coordinates. columnPerm_[3 * (n - nTouched) - 3] = 7 * k + 4; columnPerm_[3 * (n - nTouched) - 2] = 7 * k + 5; columnPerm_[3 * (n - nTouched) - 1] = 7 * k + 6; columnPerm_[3 * n + 4 * (n - nTouched) - 4] = 7 * k; columnPerm_[3 * n + 4 * (n - nTouched) - 3] = 7 * k + 1; columnPerm_[3 * n + 4 * (n - nTouched) - 2] = 7 * k + 2; columnPerm_[3 * n + 4 * (n - nTouched) - 1] = 7 * k + 3; } ++nTouched; } delete[] touched; delete[] used; } // At this point we have filled the columnPerm_ array // (except for the final columns for additional constraints // from LPConstraint, which we will deal with later). // // Now go ahead and actually move the columns around accordingly. int* tmp = new int[eqns_->columns()]; std::copy(columnPerm_, columnPerm_ + eqns_->columns(), tmp); for (i = 0; i < eqns_->columns(); ++i) { // Column tmp[i] of the matrix should be moved to // column i. if (tmp[i] == i) continue; eqns_->swapColumns(i, tmp[i]); // Adjust links to the old column i, which is now column tmp[i]. for (j = i + 1; j < eqns_->columns(); ++j) if (tmp[j] == i) break; // This is the link we need to change. #ifdef REGINA_VERIFY_LPDATA if (j == eqns_->columns()) { std::cerr << "ERROR: Sorting error." << std::endl; ::exit(1); } #endif tmp[j] = tmp[i]; tmp[i] = i; } delete[] tmp; // If we have extra variables for additional constraints or // objectives, append the corresponding entries to the end of // the permutation for completeness. for (i = 0; i < LPConstraint::nConstraints; ++i) columnPerm_[cols_ - i - 1] = cols_ - i - 1; } #endif template void LPData::initStart() { // In this routine we rely on the fact that the // LPInitialTableaux constructor ensures that the original // tableaux has full rank. // Begin at the original tableaux, with no row operations performed // and with all equations having a right-hand side of zero. rowOps_.initIdentity(origTableaux_->rank()); std::fill(rhs_, rhs_ + origTableaux_->rank(), 0); rank_ = origTableaux_->rank(); octPrimary_ = -1; // From here, find any feasible basis. findInitialBasis(); // Since RHS = 0, this basis is already feasible. feasible_ = true; // Finally, enforce our additional linear constraints. // This might break feasibility. LPConstraint::constrain(*this, origTableaux_->columns()); } template void LPData::initClone(const LPData& parent) { // If the parent tableaux is infeasible, mark this tableaux as // infeasible also and abort. feasible_ = parent.feasible_; if (! feasible_) return; // The parent tableaux is feasible: clone all of its data. std::copy(parent.rhs_, parent.rhs_ + parent.rank_, rhs_); rowOps_.initClone(parent.rowOps_); rank_ = parent.rank_; memcpy(basis_, parent.basis_, parent.rank_ * sizeof(int)); memcpy(basisRow_, parent.basisRow_, origTableaux_->columns() * sizeof(int)); octPrimary_ = parent.octPrimary_; octSecondary_ = parent.octSecondary_; } template void LPData::constrainZero(unsigned pos) { // If the variable has already been deactivated, there is // nothing to do. if (! isActive(pos)) return; // If the system is infeasible beforehand, it will certainly // be infeasible afterwards. In this case, abort. if (! feasible_) return; // If we ever do something that *might* make the basis // infeasible, we will set perhapsInfeasible to true as a // reminder to fix things later. bool perhapsInfeasible = false; // Is the variable currently in the basis? If so, get it out. if (basisRow_[pos] >= 0) { int r = basisRow_[pos]; int c; if (rhs_[r].isZero()) { // We can pivot in any other variable that appears in // this basis row. Choose the one with largest index. for (c = origTableaux_->columns() - 1; c >= 0; --c) if (basisRow_[c] < 0 /* c is active and non-basic */ && entrySign(r, c)) break; if (c >= 0) { pivot(pos, c); // Because rhs_[r] == 0, this pivot can never create // infeasibility. } else { // There are no other variables in this basis row! // Our equation just looks like x_pos = 0. // // This means that, if we deactivate pos, we lose rank // and we need to delete the corresponding row entirely. --rank_; // "Delete" the row by moving it to index rank_, which // is now outside our scope of interest (since we are // now only interested in rows 0,...,rank_-1). if (r != rank_) { std::swap(rhs_[r], rhs_[rank_]); rowOps_.swapRows(r, rank_); basis_[r] = basis_[rank_]; basisRow_[basis_[r]] = r; } // This column is already filled with zeroes // from row 0 to rank_-1, because pos was in the basis. // If we're in paranoid mode, check this. #ifdef REGINA_VERIFY_LPDATA for (r = 0; r < rank_; ++r) if (! entry(r, pos).isZero()) { std::cerr << "VERIFY: Drop error." << std::endl; ::exit(1); } #endif } } else { // Because the system is feasible, we have rhs_[r] > 0. // This means we can only pivot in a variable with positive // coefficient in this basis row. If there is one, // choose the one with largest index. If there is // no such variable, the entire system becomes infeasible. for (c = origTableaux_->columns() - 1; c >= 0; --c) if (basisRow_[c] < 0 /* c is active and non-basic */ && entrySign(r, c) > 0) break; if (c < 0) { // There is no possible variable to pivot in. // The system must be infeasible. feasible_ = false; return; } pivot(pos, c); // The pivot *might* have made the new basis infeasible. // Remember this so we can fix things afterwards. perhapsInfeasible = true; } } // The variable is out of the basis. Deactivate the column // (which simply means setting basisRow to some non-negative // integer). basisRow_[pos] = 0; #ifdef REGINA_VERIFY_LPDATA verify(); #endif // The variable is gone, but we might have pivoted to an // infeasible basis. If this is a possibility, then move to a // feasible basis if we can. If we cannot, then makeFeasible() // will detect this and set \a feasible_ to \c false. if (perhapsInfeasible) makeFeasible(); #ifdef REGINA_VERIFY_LPDATA verify(); #endif } template void LPData::constrainPositive(unsigned pos) { // If the variable has already been deactivated, it cannot // be positive. if (! isActive(pos)) { feasible_ = false; return; } // If the system is infeasible beforehand, it will certainly // be infeasible afterwards. In this case, abort. if (! feasible_) return; // Just replace x with (1+x'), where now x' must be non-negative. // This corresponds to subtracting column pos of this tableaux // from the right-hand side. // If there is any possibility that some entry on the // right-hand side could become negative, we must remember to // pivot back to feasibility. int r = basisRow_[pos]; Integer tmp; if (r >= 0) { // This variable is in the basis, and so there is only // one non-zero entry in column pos. // This makes subtracting column pos from rhs_ very easy // (just a single operation): entry(r, pos, tmp); if ((rhs_[r] -= tmp) < 0) makeFeasible(); } else { // This variable is not in the basis. // We know nothing about the column, so just do a full // element-by-element column subtraction. for (r = 0; r < rank_; ++r) { entry(r, pos, tmp); rhs_[r] -= tmp; } makeFeasible(); } } template void LPData::constrainOct( unsigned quad1, unsigned quad2) { // If either variable has already been deactivated, it cannot // be positive. if (! (isActive(quad1) && isActive(quad2))) { feasible_ = false; return; } // If the system is infeasible beforehand, it will certainly // be infeasible afterwards. In this case, abort. if (! feasible_) return; // Suppose we choose to count octagons using column i, and // to zero out and deactivate column j (where i and j are // quad1 and quad2 in some order). Then our tasks are to: // // (i) Set x_i = x_j, by replacing the variable x_j with // x_j' = x_j - x_i; // (ii) If we have any additional linear constraints through // the template parameter LPConstraints, adjust the // coefficients in columns i and/or j if necessary to // reflect the presence of octagons (recalling that the // coefficients for an octagon type need not be the sum // of coefficients for the corresponding two // quadrilateral types); // (iii) Set x_i >= 1 and x_j' = 0. // // We do this as follows: // // (i) Add column j to column i; // (ii) Add or subtract further multiples of the final column(s) // to/from column i to reflect any change in coefficients; // (iii) Call constrainZero(j) and constrainPositive(i). // // We perform steps (i) and (ii) just by setting octPrimary_ and // octSecondary_ (this works because the implementation of entry() // adjusts its results according to the current values of // octPrimary_ and octSecondary_). However, if we change a // column corresponding to a basic variable then we must // remember to reorganise the tableaux so that the column once // again contains all zeroes except for a single positive entry. int row1 = basisRow_[quad1]; int row2 = basisRow_[quad2]; if (row1 < 0) { if (row2 < 0) { // Both variables are non-basic. // We will use quad1 to count octagons. // First adjust the columns in the tableaux... octPrimary_ = quad1; octSecondary_ = quad2; // ... and then constrain variables as required. // Since quad2 is non-basic, it is already zero so // we can simply deactivate it. basisRow_[quad2] = 0; constrainPositive(quad1); } else { // quad1 is non-basic, but quad2 is basic. // Once again we will use quad1 to count octagons. // First adjust the columns in the tableaux... octPrimary_ = quad1; octSecondary_ = quad2; // ... and then constrain variables as required. // This time quad2 might be non-zero, so we need to // call the more expensive constrainZero(quad2). constrainZero(quad2); constrainPositive(quad1); } } else if (row2 < 0) { // quad2 is non-basic, but quad1 is basic. // This time we will use quad2 to count octagons. // Do what we did in the previous case, but the other way // around. // First adjust the columns in the tableaux... octPrimary_ = quad2; octSecondary_ = quad1; // ... and then constrain variables as required. constrainZero(quad1); constrainPositive(quad2); } else { // Both quad1 and quad2 are basic. // // Because we might need to adjust columns to reflect changes // in our additional linear constraints, whichever column we // keep could change in any crazy way. We will need to adjust // things to make sure it looks like a basis column once again. // For no particular reason, let's choose to count // octagons using quad1, and eventually drop quad2. // It's going to be messy whichever we choose. // Adjust column quad1 now. octPrimary_ = quad1; octSecondary_ = quad2; // Although quad1 is in the basis, its column could now // look like anything. We need to repair it so it // contains all zeroes except for cell (row1, quad1), // which must be strictly positive. Integer e1; entry(row1, quad1, e1); if (! e1.isZero()) { // The (row1, quad1) entry is non-zero. // It's clear what to do from here: make sure // this entry is positive, perform row operations to // clear out the rest of column quad1, and then restore // feasibility. if (e1 < 0) { e1.negate(); rhs_[row1].negate(); rowOps_.negateRow(row1); } Integer coeff, gcdRow; for (int r = 0; r < rank_; ++r) { if (r == row1) continue; // We will reuse coeff, to avoid too many temporary Integers. // We first set coeff here, and then we reuse and alter it // within the IF block below. entry(r, quad1, coeff); if (! coeff.isZero()) { gcdRow = rowOps_.combRowAndNorm(e1, r, coeff, row1); // As usual, we already know in advance that // gcdRow must divide into rhs_[r]. rhs_[r] *= e1; coeff *= rhs_[row1]; rhs_[r] -= coeff; rhs_[r].divByExact(gcdRow); } } makeFeasible(); // Right: that takes care of the column adjustments. // Now constrain the variables as required. constrainZero(quad2); constrainPositive(quad1); } else { // The (row1, quad1) entry is now zero. // Our solution here is to get quad1 out of the basis. // Try to find some other non-zero coefficient in row1; // note that the only possible locations for another // non-zero coefficient are in non-basic columns. // Choose the column with largest index. int c; for (c = origTableaux_->columns() - 1; c >= 0; --c) if (basisRow_[c] < 0 /* active and non-basic */ && entrySign(row1, c)) break; if (c >= 0) { // We've found an alternative. // Pivot quad1 out of the basis, and put column c // in its place. pivot(quad1, c); // We now have a basis again with a corresponding // tableaux, but the pivot may have broken feasibility. makeFeasible(); // This takes care of the column adjustments. // Now constrain the variables as required. constrainZero(quad2); constrainPositive(quad1); } else { // Every single entry in this row is zero! // // If rhs_[row1] == 0, this is a tautology. // If rhs_[row1] != 0, this is an impossibility. if (rhs_[row1] != 0) { feasible_ = false; } else { // Just pull quad1 out of the basis. Since // the rank drops, we don't need another // variable to replace it. basisRow_[quad1] = -1; // Move the empty row out of the active area // of the matrix. --rank_; if (row1 != rank_) { std::swap(rhs_[row1], rhs_[rank_]); rowOps_.swapRows(row1, rank_); basis_[row1] = basis_[rank_]; basisRow_[basis_[row1]] = row1; } // Since the RHS did not change, the system // is still feasible. // Constrain the variables as required. constrainZero(quad2); constrainPositive(quad1); } } } } } template void LPData::dump(std::ostream& out) const { unsigned r, c; out << "========================" << std::endl; for (r = 0; r < rank_; ++r) out << basis_[r] << ' '; out << std::endl; out << "========================" << std::endl; for (r = 0; r < rank_; ++r) { for (c = 0; c < origTableaux_->columns(); ++c) out << entry(r, c) << ' '; out << std::endl; } out << "========================" << std::endl; } template void LPData::extractSolution( NRay& v, const char* type) const { // Fetch details on how to undo the column permutation. const int* columnPerm = origTableaux_->columnPerm(); // We will multiply the solution vector by // lcm(basis coefficients in the tableaux), which will // ensure that the variables will all be integers. // This multiple might be too large, but we will shrink the // vector down again at the end of this routine. // // First compute this lcm. unsigned i; NLargeInteger lcm(1); for (i = 0; i < rank_; ++i) lcm = lcm.lcm(NLargeInteger(entry(i, basis_[i]))); // Now compute (lcm * the solution vector). We do not yet // take into account the change of variables x_i -> x_i - 1 // that occurred each time we called constrainPositive(), // or the more complex changes of variables that occurred // each time we called constrainOct(). // // All non-basic variables will be zero (and so we do // nothing, since the precondition states that they are // already zero in \a v). // // For basic variables, compute the values from the tableaux. // Because we are multiplying everything by lcm, the // divisions in the following code are all perfectly safe // (and give precise integer results). NLargeInteger coord; for (i = 0; i < rank_; ++i) { if (basis_[i] >= v.size()) continue; coord = lcm; coord *= NLargeInteger(rhs_[i]); coord /= NLargeInteger(entry(i, basis_[i])); v.setElement(columnPerm[basis_[i]], coord); } // Now we take into account the changes of variable due // to past calls to constrainPositive(), as described above. // Since we have multiplied everything by lcm, instead of // adding +1 to each relevant variable we must add +lcm. size_t pos; const unsigned long nTets = origTableaux_->tri()->getNumberOfTetrahedra(); // First take into account the quadrilateral types... for (i = 0; i < nTets; ++i) if (type[i] && type[i] < 4) { pos = columnPerm[3 * i + type[i] - 1]; v.setElement(pos, v[pos] + lcm); } // ... and then the triangle types. for (i = 3 * nTets; i < v.size(); ++i) if (type[i - 2 * nTets]) { pos = columnPerm[i]; v.setElement(pos, v[pos] + lcm); } // Next take into account the changes of variable due to // past calls to constrainOct(). if (octPrimary_ >= 0) { pos = columnPerm[octPrimary_]; v.setElement(pos, v[pos] + lcm); v.setElement(columnPerm[octSecondary_], v[pos]); } // To finish, divide through by the gcd so we have the // smallest multiple that is an integer vector. v.scaleDown(); } template void LPData::pivot(unsigned outCol, unsigned inCol) { unsigned defRow = basisRow_[outCol]; basisRow_[outCol] = -1; basisRow_[inCol] = defRow; basis_[defRow] = inCol; // Make sure that inCol has a positive coefficient in row defRow. Integer base; entry(defRow, inCol, base); if (base < 0) { base.negate(); rhs_[defRow].negate(); rowOps_.negateRow(defRow); } // Walk through the entire tableaux and perform row operations // to ensure that the only non-zero entry in column \a inCol // is the entry base in row defRow (as extracted above). Integer coeff, gcdRow; unsigned r; for (r = 0; r < rank_; ++r) { if (r == defRow) continue; // We will reuse coeff, to avoid too many temporary Integers. // We first set coeff here, and then we reuse and alter it within the // IF block below. entry(r, inCol, coeff); if (! coeff.isZero()) { // Perform the row operation on the matrix... gcdRow = rowOps_.combRowAndNorm(base, r, coeff, defRow); // ... and on the right-hand side also. // We already know that gcdRow must divide into rhs_[r], // since rhs_ is obtained by multiplying the integer // matrix rowOps_ with an integer vector. rhs_[r] *= base; coeff *= rhs_[defRow]; rhs_[r] -= coeff; rhs_[r].divByExact(gcdRow); } } } template void LPData::findInitialBasis() { // Start with all variables active but non-basic. std::fill(basisRow_, basisRow_ + origTableaux_->columns(), -1); // We find our initial basis using Gauss-Jordan elimination. // Until we sit down and prove some results about the magnitude of // the intermediate integers that appear, we will need to do this // entire process using the arbitrary-precision NInteger class. // We do not touch rhs_ at all, since our preconditions ensure that // rhs_ is the zero vector. // Temporary matrices: // tab = begins as starting tableaux, becomes identity in the basis columns. // ops = begins as identity matrix, becomes the final row operation matrix. // Build a dense copy of the starting tableaux, which we // will work with as we perform our Gauss-Jordan elimination. LPMatrix tab(rank_, origTableaux_->columns()); origTableaux_->fillInitialTableaux(tab); LPMatrix ops(rank_, rank_); ops.initIdentity(rank_); // Off we go with our Gauss-Jordan elimination. // Since the original tableaux is full rank, we know in // advance that every row will define some basic variable. unsigned row; unsigned r, c; NInteger base, coeff; NInteger gcdRow; for (row = 0; row < rank_; ++row) { // Find the first non-zero entry in this row. // The corresponding column will become our next basic variable. for (c = 0; c < origTableaux_->columns(); ++c) if (basisRow_[c] < 0 /* non-basic variable */ && ! tab.entry(row, c).isZero()) break; // Since the original tableaux has full rank, we must // have found a non-zero entry. However, for sanity, // add some code to deal with the situation where we did not. if (c == origTableaux_->columns()) { // Impossible, assuming the matrix had the correct rank... #ifdef REGINA_VERIFY_LPDATA std::cerr << "ERROR: No initial basis, bad rank." << std::endl; ::exit(1); #endif // ... but deal with it anyway by just dropping rank. --rank_; if (row != rank_) { tab.swapRows(row, rank_); ops.swapRows(row, rank_); } --row; // We will ++row again for the next loop iteration. continue; } // Here is our non-zero entry. // Make this a basis variable. basis_[row] = c; basisRow_[c] = row; // Make the corresponding non-zero entry positive. base = tab.entry(row, c); if (base < 0) { base.negate(); tab.negateRow(row); ops.negateRow(row); } // Make sure this basis variable has zero coefficients // in all other rows. for (r = 0; r < rank_; ++r) { if (r == row) continue; coeff = tab.entry(r, c); if (! coeff.isZero()) { gcdRow = ops.combRowAndNorm(base, r, coeff, row); tab.combRow(base, r, coeff, row, gcdRow); } } } // Copy the final tableaux into our own rowOps_ matrix. for (r = 0; r < rank_; ++r) for (c = 0; c < rank_; ++c) rowOps_.entry(r, c) = Integer(ops.entry(r, c)); } template void LPData::makeFeasible() { int r, c, outCol, outRow; Integer outEntry, tmp, v1, v2; // Variables for detecting cycling. // // The bits in oldBasis are a snapshot of which variables were in // the basis at some point in the past, and the bits in currBasis // indicate which variables are in the basis right now. // // We use Brent's method for detecting cycles: // We store a snapshot in oldBasis after 2^k pivots, for all k. // This means that, regardless of the length of the cycle or // the number of pivots that precede the cycle, we will // detect oldBasis == currBasis shortly after cycling occurs // (in particular, the total number of pivots that we take // overall is at most three times the total number of pivots // before the first repeated basis). unsigned nCols = origTableaux_->columns(); NBitmask currBasis(nCols); for (r = 0; r < rank_; ++r) currBasis.set(basis_[r], true); NBitmask oldBasis(currBasis); unsigned long pow2 = 1; unsigned long nPivots = 0; while (true) { // Locate a variable in the basis with negative value. // If there are many, choose the variable with largest // magnitude negative value. outCol = -1; for (r = 0; r < rank_; ++r) if (rhs_[r] < 0) { if (outCol < 0) { // First candidate we've seen. // Use it until we find something better. outRow = r; outCol = basis_[r]; entry(r, outCol, outEntry); continue; } // Compare which variable is most negative. entry(r, basis_[r], tmp); v1 = rhs_[r]; v1 *= outEntry; // Avoid spurious temporaries. v2 = rhs_[outRow]; v2 *= tmp; // Avoid spurious temporaries. if (v1 < v2) { outRow = r; outCol = basis_[r]; outEntry = tmp; } } if (outCol < 0) { // All basis variables are non-negative! // This is a feasible basis; we're done. return; } // Fix this bad variable by pivoting it out. // The pivot-in variable must be the largest-index // column with negative coefficient in this row. for (c = nCols - 1; c >= 0; --c) if (basisRow_[c] < 0 /* active, non-basic variable */ && entrySign(outRow, c) < 0) break; if (c < 0) { // There is no possible variable to pivot in. // The system is infeasible. feasible_ = false; return; } pivot(outCol, c); // Run our cycle-detection machinery. currBasis.set(outCol, false); currBasis.set(c, true); if (currBasis == oldBasis) { // We've cycled! // Switch to a slower but cycle-free pivot rule. makeFeasibleAntiCycling(); return; } if (++nPivots == pow2) { oldBasis = currBasis; pow2 <<= 1; // On a modern (64-bit) system, pow2 will only overflow // after something like 10^19 pivots, and the human // will have given up in frustration long before this. // Nevertheless, make sure things work even in this case: if (! pow2) { makeFeasibleAntiCycling(); return; } } } } template void LPData::makeFeasibleAntiCycling() { int r, c, outCol; while (true) { // Locate a variable in the basis with negative value. // If there are many, choose the one with largest index. outCol = -1; for (r = 0; r < rank_; ++r) if (rhs_[r] < 0) { if (basis_[r] > outCol) outCol = basis_[r]; } if (outCol < 0) { // All basis variables are non-negative! // This is a feasible basis; we're done. return; } // Fix this bad variable by pivoting it out. // The pivot-in variable must be the largest-index // column with negative coefficient in this row. for (c = origTableaux_->columns() - 1; c >= 0; --c) if (basisRow_[c] < 0 /* active, non-basic variable */ && entrySign(basisRow_[outCol], c) < 0) break; if (c < 0) { // There is no possible variable to pivot in. // The system is infeasible. feasible_ = false; return; } pivot(outCol, c); } } template void LPData::verify() const { unsigned r, c; for (r = 0; r < rank_; ++r) { // Check that rowOps_ is an inverse matrix. for (c = 0; c < rank_; ++c) if (r != c && entrySign(r, basis_[c])) { std::cerr << "VERIFY: Inverse error" << std::endl; ::exit(1); } // Check that each row has gcd = 1. Integer g; // Initialised to zero. for (c = 0; c < rowOps_.columns(); ++c) g.gcdWith(rowOps_.entry(r, c)); if (g != 1) { std::cerr << "VERIFY: GCD error" << std::endl; ::exit(1); } } } } // namespace regina #endif regina-4.95/engine/enumerate/ntreelp.cpp000644 000765 000024 00000006204 12234011536 020215 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/ntreelp-impl.h" namespace regina { // Instantiate templates: template class LPInitialTableaux; template class LPInitialTableaux; #ifndef EXCLUDE_SNAPPEA template class LPInitialTableaux; #endif template class LPData; template class LPData; template class LPData; template class LPData; #ifndef EXCLUDE_SNAPPEA template class LPData; template class LPData; #endif #ifdef INT128_AVAILABLE template class LPData >; template class LPData >; #ifndef EXCLUDE_SNAPPEA template class LPData >; #endif #endif } // namespace regina regina-4.95/engine/enumerate/ntreelp.h000644 000765 000024 00000213463 12234011536 017671 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntreelp.h * \brief Linear programming code for tree traversal enumeration methods. */ #ifndef __NTREELP_H #ifndef __DOXYGEN #define __NTREELP_H #endif #include "maths/ninteger.h" #include "maths/nmatrixint.h" #include "surfaces/normalcoords.h" #include /** * Define REGINA_VERIFY_LPDATA to check invariants as the algorithm runs. * This checking is slow and can increase the running time significantly. */ // #define REGINA_VERIFY_LPDATA namespace regina { class NMatrixInt; class NRay; class NTriangulation; /** * \weakgroup enumerate * @{ */ /** * A matrix class for use with linear programming. * * This class is used in the tree traversal algorithms for enumerating * and locating vertex normal surfaces, as described in "A tree traversal * algorithm for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3, * and "A fast branching algorithm for unknot recognition with * experimental polynomial-time behaviour", Burton and Ozlen, arXiv:1211.1079. * * The operations on this matrix class are tailored and optimised * specifically for use with the dual simplex method in the context * of a repetitive backtracking search. As a result, the API is * cumbersome and highly specialised, which makes this matrix class * inappropriate for general use. * * It is \e critical that, before using such a matrix, you reserve space * for its elements, and then fix a specific size. A matrix for which * both tasks have been done will be called \a initialised. You can * initialise a matrix in one of two ways: * * - by using the (\a rows, \a columns) constructor, which does * everything for you; * * - by using the default (no-arguments) constructor, then calling reserve(), * and then calling one of the initialisation routines initClone() or * initIdentity(). * * You may call the initialisation initClone() and initIdentity() routines * more than once (e.g., during a backtracking search), and you may use * different matrix sizes each time. However, you may never use more * elements than you originally reserved space for. * * This matrix is stored in dense form. All elements are of the integer class * \a Integer, which is supplied as a template argument. * * \apinotfinal * * \ifacespython Not present. */ template class LPMatrix { private: Integer* dat_; /**< The elements of this matrix as a single long array, stored in row-major order. This array stores as many elements as were originally reserved, which might be more than (but can never be less than) the current size of the matrix according to \a rows_ and \a cols_. */ unsigned rows_; /**< The number of rows in this matrix. */ unsigned cols_; /**< The number of columns in this matrix. */ public: /** * Creates an uninitialised matrix with no memory storage. * * You \e must call reserve() and then either initClone() or * initIdentity() before this matrix will become initialised. */ inline LPMatrix(); /** * Creates a fully initialised \a rows by \a cols matrix * with all elements set to zero. * * This routine reserves space for precisely \a rows * \a cols * elements. In other words, you may later re-initialise the matrix * to become smaller if you like, but you cannot re-initialise the * matrix to become larger. * * @param rows the number of rows in the new matrix. This must * be strictly positive. * @param cols the number of columns in the new matrix. This must * be strictly positive. */ inline LPMatrix(unsigned rows, unsigned cols); /** * Destroys this matrix and all of the data it contains. * * You can safely destroy a matrix that is uninitialised * or only partially initialised (i.e., space has been reserved but * the matrix size is not set). */ inline ~LPMatrix(); /** * Reserves enough space to store the elements of a * \a maxRows by \a maxCols matrix. This is just an upper * bound: your matrix may end up using fewer elements than this, * but it cannot use more. * * This matrix will still not be initialised until you call * either initClone() or initIdentity(). See the class notes for * details. * * \pre This matrix was created using the default (no-argument) * constructor, and you have not called any other routines on * this matrix since. * * \warning To elaborate on the precondition above: you can only * call reserve() once, and if you did not use the default * LPMatrix constructor then you cannot call it at all. * Any additional calls to reserve() will result in a memory leak. * * @param maxRows an upper bound on the number of rows that you * will need for this matrix. This must be strictly positive. * @param maxCols an upper bound on the number of columns that * you will need for this matrix. This must be strictly positive. */ inline void reserve(unsigned maxRows, unsigned maxCols); /** * Initialises this matrix to a copy of the given matrix. * * This matrix does not yet need to be initialised, but it does * need to have enough space reserved. * * You may call this routine on an already-initialised matrix, * and you may use this routine to assign it a different size * (as long as enough space was originally reserved). * * \pre If this matrix has not been initialised before, then * reserve() must have already been called. * * \pre This matrix has enough space reserved for at least * clone.rows() * clone.columns() elements. * * @param clone the matrix to copy. */ inline void initClone(const LPMatrix& clone); /** * Initialises this matrix to the identity matrix of the given size. * * This matrix does not yet need to be initialised, but it does * need to have enough space reserved. * * You may call this routine on an already-initialised matrix, * and you may use this routine to assign it a different size * (as long as enough space was originally reserved). * * \pre If this matrix has not been initialised before, then * reserve() must have already been called. * * \pre This matrix has enough space reserved for at least * \a size * \a size elements. * * @param size the number of rows, and also the number of * columns, that will be assigned to this matrix. * This must be strictly positive. */ void initIdentity(unsigned size); /** * Returns a read-write reference to the given element of this * matrix. * * @param row the row of the requested element. This must be * between 0 and rows()-1 inclusive. * @param col the column of the requested element. This must be * between 0 and columns()-1 inclusive. */ inline Integer& entry(unsigned row, unsigned col); /** * Returns a read-only reference to the given element of this * matrix. * * @param row the row of the requested element. This must be * between 0 and rows()-1 inclusive. * @param col the column of the requested element. This must be * between 0 and columns()-1 inclusive. */ inline const Integer& entry(unsigned row, unsigned col) const; /** * Returns the number of rows in this matrix. This relates to * the currently assigned matrix size, not the total amount of * memory that was originally reserved. * * @return the number of rows. */ inline unsigned rows() const; /** * Returns the number of columns in this matrix. This relates to * the currently assigned matrix size, not the total amount of * memory that was originally reserved. * * @return the number of columns. */ inline unsigned columns() const; /** * Swaps the two given rows of this matrix. * The two arguments \a r1 and \a r2 may be equal (in which case * the matrix will be left unchanged). * * @param r1 the index of the first row to swap. This must be * between 0 and rows()-1 inclusive. * @param r2 the index of the second row to swap. This must be * between 0 and rows()-1 inclusive. */ inline void swapRows(unsigned r1, unsigned r2); /** * Applies a particular row operation to this matrix. * * Specifically, row \a dest will be replaced with the linear * combination: * (\a destCoeff * row \a dest - \a srcCoeff * row \a src) / \a div. * * \pre \a dest and \a src are not equal. * \pre It is known in advance that every integer in * (\a destCoeff * row \a dest - \a srcCoeff * row \a src) * will be divisible by \a div. In other words, it is known in * advance that we can use exact integer division without remainders. * * @param destCoeff the coefficient applied to row \a dest in * the linear combination. * @param dest the index of the row to replace. This must be * between 0 and rows()-1 inclusive. * @param srcCoeff the coefficient applied to row \a src in * the linear combination. * @param src the index of the other row used in this linear * combination. This must be between 0 and rows()-1 inclusive. * @param div the integer to divide the final row by. This must * be non-zero. */ inline void combRow(const Integer& destCoeff, unsigned dest, const Integer& srcCoeff, unsigned src, const Integer& div); /** * Applies a particular row operation to this matrix, and then * normalises. * * Specifically, row \a dest will be replaced with the linear * combination: * (\a destCoeff * row \a dest - \a srcCoeff * row \a src); * then, if row \a dest is non-zero, it will be normalised by * dividing through by the gcd of its elements. Note that this gcd * is always taken to be positive (i.e., the final normalisation * will never change the signs of the elements in the row). * * \pre \a dest and \a src are not equal. * * @param destCoeff the coefficient applied to row \a dest in * the linear combination. * @param dest the index of the row to replace. This must be * between 0 and rows()-1 inclusive. * @param srcCoeff the coefficient applied to row \a src in * the linear combination. * @param src the index of the other row used in this linear * combination. This must be between 0 and rows()-1 inclusive. * @return the positive gcd that row \a dest was scaled down by, * or 0 if row \a dest is entirely zero. */ inline Integer combRowAndNorm(const Integer& destCoeff, unsigned dest, const Integer& srcCoeff, unsigned src); /** * Negates all elements in the given row of this matrix. * * @param row the row whose elements should be negated. * This must be between 0 and rows()-1 inclusive. */ inline void negateRow(unsigned row); /** * Writes this matrix to the given output stream. * The output is "rough" and wasteful, and is intended for * debugging purposes only. The precise output format is * subject to change in future versions of Regina. * * @param out the output stream to write to. */ void dump(std::ostream& out) const; }; /** * Stores an adjusted matrix of matching equations from the * underlying triangulation, in sparse form. * * This class forms part of the tree traversal algorithms for enumerating * and locating normal surfaces, as described in "A tree traversal algorithm * for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3, * and "A fast branching algorithm for unknot recognition with * experimental polynomial-time behaviour", Burton and Ozlen, arXiv:1211.1079. * * The adjustments (which are all carried out in the LPInitialTableaux * class constructor) are as follows: * * - the rows of the matching equation matrix have been reordered so * that the first rank() rows are full rank; * - the columns of the matching equation matrix have been reordered * according to the permutation returned by columnPerm(). This is a * heuristic reordering designed to improve the performance of the tree * traversal algorithm; see columnPerm() for a list of constraints that * such a reordering must satisfy. * * There is also optional support for adding extra linear constraints * (such as a constraint on Euler characteristic). These extra * constraints are supplied by the template parameter \a LPConstraint, * and will generate LPConstraint::nConstraints additional rows and columns * (used by the additional variables that evaluate the corresponding linear * functions). If there are no additional constraints, simply use the * template parameter LPConstraintNone. * * In some cases, it may be impossible to add the extra linear constraints * that you would like (for instance, the constraints might require some * preconditions on the underlying triangulation that are not met). If this * is a possibility in your setting, you should call constraintsBroken() to * test this as soon as the LPInitialTableaux has been constructed. Even if * the constraints could not be added correctly, the tableaux will be left in a * consistent state (the constraints will just be treated as zero functions * instead). * * This class is optimised for working with \e columns of the matrix * (in particular, multiplying columns of this matrix by rows of some * other matrix). * * This class can only work in quadrilateral normal coordinates * (NS_QUAD) or standard normal coordinates (NS_STANDARD). * No other coordinate systems are supported. * * \warning The implementation of this class relies on the fact that the * sum of absolute values of all coefficients in each column is * at most four (not counting the rows for any optional extra constraints). * If you are extending this class to work with more general matching * equation matrices, you may need to change the implementation accordingly. * * \pre The template parameter LPConstraint must be one of the subclasses of * LPConstraintBase. See the LPConstraintBase class notes for further details. * * \apinotfinal * * \ifacespython Not present. */ template class LPInitialTableaux { public: /** * Stores a single column of the adjusted matching equation matrix * in sparse form. * * Specifically, this stores the location of each +1 entry, * and the location of each -1 entry. If some entry in the matrix * is greater than +1 or less than -1, we represent it using * multiple +1 or -1 entries in the same matrix location. * * For any additional rows that represent extra linear constraints, * we inherit the coefficients directly from LPConstraint::Coefficients. */ struct Col : public LPConstraint::Coefficients { unsigned nPlus; /**< The total number of +1 entries in this column. */ unsigned plus[4]; /**< The rows containing these +1 entries, in any order. The same row may appear in this list more than once (indicating a +2, +3 or +4 entry in the matrix). */ unsigned nMinus; /**< The total number of -1 entries in this column. */ unsigned minus[4]; /**< The rows containing these -1 entries, in any order. The same row may appear in this list more than once (indicating a -2, -3 or -4 entry in the matrix). */ /** * Initialises an empty column. */ inline Col(); /** * Adds the given entry in the given row to this column. * * \pre No entry in the given row has been added to this column * yet. * * \pre The sum of absolute values of all entries in this * column must never exceed 4. * * @param row the row containing the given value. * @param val the value at this location in the matrix. */ inline void push(unsigned row, int val); }; private: NTriangulation* tri_; /**< The underlying triangulation. */ NormalCoords coords_; /**< The coordinate system used for the matrix of matching equations; this must be one of NS_QUAD or NS_STANDARD. */ NMatrixInt* eqns_; /**< The adjusted matching equation matrix, in dense form. The precise adjustments that we make are described in the LPInitialTableaux class notes. */ unsigned rank_; /**< The rank of this tableaux, taking into account any additional constraints from the template parameter LPConstraint. */ unsigned cols_; /**< The number of columns in this tableaux, taking into account any additional constraints from the template parameter LPConstraint. */ Col* col_; /**< An array of size \a cols_, storing the individual columns of this adjusted matrix in sparse form. */ int* columnPerm_; /**< A permutation of 0,...,cols_ - 1 that maps column numbers in the adjusted matrix to column numbers in the original (unmodified) matrix of matching equations that was originally derived from the triangulation. See columnPerm() for more details on what this permutation means and what constraints it must adhere to. */ bool constraintsBroken_; /**< Indicates whether or not the extra constraints from the template parameter \a LPConstraints were added successfully. See the LPInitialTableaux class notes for details. */ public: /** * Construts this adjusted sparse matrix of matching equations. * * \pre The given triangulation is non-empty. * * @param tri the underlying 3-manifold triangulation. * @param coords the coordinate system to use for the matrix of * matching equations; this must be one of NS_QUAD or NS_STANDARD. * @param enumeration \c true if we should optimise the tableaux * for a full enumeration of vertex surfaces, or \c false if we * should optimise the tableaux for an existence test (such as * searching for a non-trivial normal disc or sphere). */ LPInitialTableaux(NTriangulation* tri, NormalCoords coords, bool enumeration); /** * Destroys this matrix. */ inline ~LPInitialTableaux(); /** * Returns the underlying 3-manifold triangulation from which the * matching equations were derived. * * @return the underlying triangulation. */ inline NTriangulation* tri() const; /** * Returns the rank of this matrix. * * Note that, if we are imposing extra constraints through the * template parameter LPConstraint, then there will be extra variables * to enforce these, and so the rank will be larger than the rank of * the original matching equation matrix. * * @return the matrix rank. */ inline unsigned rank() const; /** * Returns the number of columns in this matrix. * * Note that, if we are imposing extra constraints through the * template parameter LPConstraint, then there will be extra variables * to enforce these, and so the number of columns will be larger than * in the original matching equation matrix. * * @return the number of columns. */ inline unsigned columns() const; /** * Returns the number of columns that correspond to normal * coordinates. This is precisely the number of columns in the * original matrix of matching equations. * * @return the number of normal coordinate columns. */ inline unsigned coordinateColumns() const; /** * Indicates whether or not the extra constraints from the template * parameter \a LPConstraints were added successfully. * This query function is important because some constraints require * additional preconditions on the underlying triangulation, and * cannot be added if these preconditions are not satisfied. * * Even if the extra constraints were not added successfully, this * tableaux will be left in a consistent state (the extra constraints * will be treated as zero functions). See the LPInitialTableaux class * notes for further details. * * @return \c true if the constraints were \e not added * successfully, or \c false if the constraints were added successfully. */ inline bool constraintsBroken() const; /** * Returns the permutation that describes how the columns of * the matching equation matrix were reordered. This permutation maps * column numbers in this adjusted matching equation matrix to * column numbers in the original (unmodified) matching equation * matrix that was originally derived from the triangulation. * * The permutation is returned as an array of columns() * integers, such that column \a i of this adjusted matrix corresponds * to column columnPerm()[i] of the original matrix. * * If you are imposing additional constraints through the * template parameter LPConstraint, then the corresponding extra * variables will be included in the permutation; however, these are * never moved and will always remain the rightmost variables in * this system (i.e., the columns of highest index). * * As well as the requirement that this is a genuine permutation of * 0,...,columns()-1, this array will also adhere to the * following constraints. In the following discussion, \a n refers * to the number of tetrahedra in the underlying triangulation. * * - The quadrilateral coordinate columns must appear as the * first 3n columns of the adjusted matrix. * In particular, when working in the 7n-dimensional * standard coordinate system, the remaining 4n triangle * coordinate columns must appear last. * * - The quadrilateral coordinate columns must be grouped by * tetrahedron and ordered by quadrilateral type. In other * words, for each \a i = 0,...,\a n-1, there will be some * tetrahedron \a j for which the three columns * 3i, 3i+1 and 3i+2 refer to the * quadrilaterals in tetrahedron \a j of types 0, 1 and 2 * respectively. Phrased loosely, we are allowed to reorder * the tetrahedra, but not the quadrilateral coordinates * within each tetrahedron. * * - The triangle coordinate columns (if we have them) must likewise * be grouped by tetrahedron, and these tetrahedra must appear in * the same order as for the quadrilateral types. In other * words, for each \a i = 0,...,\a n-1, the quadrilateral columns * 3i, 3i+1 and 3i+2 and the triangle columns * 3n+4i, 3n+4i+1, 3n+4i+2 * and 3n+4i+3 all refer to the same tetrahedron. * * @return details of the permutation describing how columns * were reordered. */ inline const int* columnPerm() const; /** * Computes the inner product of (i) the given row of the given * matrix with (ii) the given column of this matrix. * * This routine is optimised to use the sparse representation of * columns in this matrix. * * \pre The given matrix \a m has precisely rank() columns. * * @param m the matrix whose row we will use in the inner product. * @param mRow the row of the matrix \a m to use in the inner product. * @param thisCol the column of this matrix to use in the inner product. * @return the resulting inner product. */ template inline Integer multColByRow(const LPMatrix& m, unsigned mRow, unsigned thisCol) const; /** * A variant of multColByRow() that takes into account any adjustments * to the tableaux that are required when this is a quadrilateral * column being used to represent an octagon type. * * The LPData class offers support for octagonal almost normal * surfaces, in which exactly one tetrahedron is allowed to have * exactly one octagon type. We represent such an octagon as a * \e pair of incompatible quadrilaterals within the same tetrahedron. * See the LPData class notes for details on how this works. * * In some settings where we are using additional constraints * through the template parameter LPConstraint, these extra * constraints behave differently in the presence of octagons * (i.e., the coefficient of the octagon type is not just the * sum of coefficients of the two constituent quadrilateral types). * This routine effectively allows us to adjust the tableaux * accordingly. * * Specifically: this routine computes the inner product of * (i) the given row of the given matrix with (ii) the given * column of this matrix. We assume that the given column of * this matrix describes one of the two quadrilateral coordinates * in some tetrahedron that together form an octagon type, and * (via the helper routine LPConstraint::Coefficients::innerProductOct) * we implicitly adjust the coefficients of our extra constraints * accordingly. * * This routine is optimised to use the sparse representation of * columns in this matrix. * * \pre The given matrix \a m has precisely rank() columns. * * \pre Column \a thisCol of this matrix describes one of the * two quadrilateral coordinates that are being combined to form * an octagon type within some tetrahedron. * * @param m the matrix whose row we will use in the adjusted * inner product. * @param mRow the row of the matrix \a m to use in the adjusted * inner product. * @param thisCol the column of this matrix to use in the adjusted * inner product. * @return the resulting adjusted inner product. */ template inline Integer multColByRowOct(const LPMatrix& m, unsigned mRow, unsigned thisCol) const; /** * Fills the given matrix with the contents of this matrix. * This effectively copies this sparse * but highly specialised matrix representation into a dense * but more flexible matrix representation. * * \pre The given matrix has already been initialised to size * rank() * columns(), and all of its elements have already been * set to zero. Note that this can all be arranged by calling * the constructor LPMatrix::LPMatrix(unsigned, unsigned). * * @param m the matrix to fill. */ template void fillInitialTableaux(LPMatrix& m) const; private: /** * Reorders the columns of the matching equation matrix. * This is a heuristic reordering that aims to reduce the number * of dead ends in the tree traversal algorithm, and thereby * improve the running time. * * Details of the reordering will be stored in the array * columnPerm_; see the columnPerm() notes for more information * on the constraints that this reordering is required to satisfy. * * This routine is called before any additional constraints are * added from the template parameter LPConstraint; that is, the * rows of the matrix are just the matching equations. However, * we do already have the extra placeholder columns for the new * variables that correspond to these extra constraint(s). * * @param enumeration \c true if we should optimise the ordering * for a full enumeration of vertex surfaces, or \c false if we * should optimise the ordering for an existence test (such as * searching for a non-trivial normal disc or sphere). */ void reorder(bool enumeration); }; /** * Stores an intermediate tableaux for the dual simplex method, and * contains all of the core machinery for using the dual simplex method. * * This class forms part of the tree traversal algorithms for enumerating * and locating normal surfaces, as described in "A tree traversal algorithm * for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3, * and "A fast branching algorithm for unknot recognition with * experimental polynomial-time behaviour", Burton and Ozlen, arXiv:1211.1079. * * This class is designed to represent a state partway through the tree * traversal algorithm, where the tableaux has been altered to * constrain some variables: * * - Some variables have been "deactivated". This means we fix them to * zero permanently, and pretend that the corresponding columns do not * exist in the matrix. As a result, the rank of the matrix may * smaller than it was when we began the tree traversal. * * - Some variables have been constrained to be positive; as described * in Burton and Ozlen, it is safe to do this using the non-strict * inequality x_i >= 1 (instead of the strict inequality x_i > 0, which * is more difficult to enforce). We enforce this constraing using a * change of variable: we replace the variable x_i with (x_i - 1), * which is then constrained to be non-negative as usual. The new * variable (x_i - 1) uses the same column in the tableaux (we perform * the actual change of variable by editing the tableaux itself using * column operations). Be warned: as a result, when we arrive at a * final solution and collect the values of the variables, we must * remember to \e increment the values of any such variables by one. * * We do not store the full tableaux (which is dense and slow to work * with). Instead we store the matrix of row operations that were * applied to the original starting tableaux (in the notation of Burton * and Ozlen, we store the matrix M_beta^{-1}, where M is the original * matrix stored in the class LPInitialTableaux, and beta is the current * basis). * * If the system is infeasible (because the constraints on variables as * described above are too severe), then the contents of the internal * data members are undefined (other than the data member \a feasible_, * which is guaranteed to be \c false). This is because the code * is optimised to abort any operation as soon as infeasibility is detected, * which may leave the data members in a broken state. If you are not sure, * you should always call isFeasible() before performing any other query * or operation on this tableaux. * * This class is designed to be used in a backtracking search, which * means the API is cumbersome but we can quickly rewrite and copy data. * The rules are as follows: * * - Before using an LPData object, you must reserve the necessary memory * by calling reserve() and passing the original starting tableaux. * * - After this, you can reset the data by calling one of the initialisation * routines initStart() or initClone(), and you can call these * initialisation routines as often as you like. * * Like LPInitialTableaux, this class can enforce additional linear * constraints (such as positive Euler characteristic) through the template * parameter LPConstraint. If there are no such constraints, simply use * the template parameter LPConstraintNone. * * Although the underlying coordinate system is based on quadrilaterals * and (optionally) triangles, this class has elementary support for * octagons also, as seen in \e almost normal surface theory. For the * purposes of this class, an octagon is represented as a pair of * quadrilaterals of different types in the same tetrahedron: these meet * the boundary of the tetrahedron in the same arcs as a single octagon, * and therefore interact with the matching equations in the same way. * * To declare that you will be using octagons in some tetrahedron, you * must call constrainOct(quad1, quad2), where \a quad1 and \a quad2 are the * two corresponding quadrilateral columns. This will have the following * effects, all of which may alter the tableaux: * * - There will be some changes of variable. One of the two variables * x_i will be replaced with (x_i - 1), forcing the number of octagons * to be positive. The other variable x_j will be replaced with * (x_j - x_i), which will be set to zero and deactivated. There is no * guarantee as to which of the two variables \a quad1 and \a quad2 * will be kept and which will be deactivated: this will depend on the * layout of the tableaux when constrainOct() is called. * * - If you are imposing additional constraints through the \a LPConstraint * template parameter, the corresponding linear constraint functions * may change their values (since the coefficients they use for * octagon types need not be related to the coefficients for the two * corresponding quadrilateral columns). Any such changes are managed * through the function LPConstraint::Coefficients::innerProductOct. * * This class has been optimised to ensure that you only have one * octagon type declared at any given time (which is consistent with the * constraints of almost normal surface theory). * * All tableaux elements are of the integer class \a Integer, which is * supplied as a template argument. This same integer class will be * used as a template argument for \a LPConstraint. * * \pre The template parameter LPConstraint must be one of the subclasses of * LPConstraintBase. See the LPConstraintBase class notes for further details. * * \apinotfinal * * \ifacespython Not present. */ template class LPData { private: const LPInitialTableaux* origTableaux_; /**< The original starting tableaux that holds the adjusted matrix of matching equations, before the tree traversal algorithm began. */ Integer* rhs_; /**< An array of length origTableaux_->rank() that stores the column vector of constants on the right-hand side of the current tableaux. In the notation of Burton and Ozlen, this is the column vector M_beta^{-1} * b. If \a rank_ is smaller than origTableaux_->rank() then the "extra" entries rhs_[rank_, rank_+1, ...] may have undefined values, and should simply be ignored. */ LPMatrix rowOps_; /**< The matrix of row operations that we apply to the original starting tableaux, as described in the class notes. In the notation of Burton and Ozlen, this is the matrix M_beta^{-1}. This is a square matrix of side length origTableaux_->rank(). */ unsigned rank_; /**< The rank of the current tableaux, taking into account any changes such as deactivation of variables. This will be at most (but quite possibly less than) origTableaux_->rank(). We guarantee that the first \a rank_ rows of the current tableaux are full rank (and so any subsequent rows should simply be ignored from here on). */ int* basis_; /**< An array of length origTableaux_->rank() that stores the \a rank_ variables that form the current basis. In particular, for each i = 0,...,rank_-1, basis_[i] is the basis variable whose defining row is row i. If \a rank_ is smaller than origTableaux_->rank() then any trailing entries in this array have undefined values, and should simply be ignored. */ int* basisRow_; /**< An array of length origTableaux_->columns() that indicates which row of the current tableaux holds the defining equation for each basis variable. Specifically: - if column \a i corresponds to a basic variable, then the defining row for this basis variable is row basisRow_[i]; - if column \a i corresponds to an active non-basic variable, then basisRow_[i] will be strictly negative; - if column \a i has been deactivated, then basisRow_[i] will be zero. For each i = 0,...,rank_-1, basisRow_[basis_[i]] == i. */ bool feasible_; /**< Indicates whether or not the current system of constraints is feasible. */ int octPrimary_; /**< If we have declared an octagon type, this stores the column that we use to count the octagons. This will be one of the two quadrilateral columns that together "represent" the octagon type, as described in the class notes. If we have not declared an octagon type, this is -1. */ int octSecondary_; /**< If we have declared an octagon type, this stores the second of the two quadrilateral columns that together "represent" the octagon type, as described in the class notes. This is the quadrilateral column that we set to zero and deactivate (as opposed to \a octPrimary_, which we keep to count the number of octagons). If we have not declared an octagon type, this variable is undefined. */ public: /** * Constructs a new tableaux. You \e must call reserve() before * doing anything else with this tableaux. */ inline LPData(); /** * Destroys this tableaux. This is safe even if reserve() was * never called. */ inline ~LPData(); /** * Reserves enough memory for this tableaux to work with. * You \e must call this routine before doing anything else with * this tableaux. * * The data in this tableaux will not be initialised, and the * contents and behaviour of this tableaux will remain undefined * until you call one of the initialisation routines initStart() * or initClone(). * * @param origTableaux the original starting tableaux that holds the * adjusted matrix of matching equations, before the tree traversal * algorithm began. */ void reserve(const LPInitialTableaux* origTableaux); /** * Initialises this tableaux by beginning at the original * starting tableaux and working our way to any feasible basis. * * This routine also explicitly enforces the additional constraints * from the template parameter LPConstraint (i.e., this routine * is responsible for forcing the corresponding linear * function(s) to be zero or strictly positive as appropriate). * * It is possible that a feasible basis cannot be found; you * should test isFeasible() after running this routine to see * whether this is the case. * * \pre reserve() has already been called. */ void initStart(); /** * Initialises this tableaux to be a clone of the given tableaux. * This is used in the tree traversal algorithm as we work our way * down the search tree, and child nodes "inherit" tableaux from * their parent nodes. * * \pre reserve() has already been called. * * @param parent the tableaux to clone. */ void initClone(const LPData& parent); /** * Returns the number of columns in this tableaux. * * Note that, if we are imposing extra constraints through the * template parameter LPConstraint, then there will be extra variables * to enforce these, and so the number of columns will be larger than * in the original matching equation matrix. * * @return the number of columns. */ inline unsigned columns() const; /** * Returns the number of columns in this tableaux that correspond to * normal coordinates. This is precisely the number of columns in the * original matrix of matching equations. * * @return the number of normal coordinate columns. */ inline unsigned coordinateColumns() const; /** * Returns whether or not this system is feasible. * * A system may become infeasible when we add too many extra * constraints on the variables (such as forcing them to be * positive, or setting them to zero); see the LPData class * notes for details on these constraints. * * \warning As explained in the class notes, if this system is * infeasible then any queries or operations (other than calling * isFeasible() itself) are undefined. * * @return \c true if this system is feasible, or \c false if it * is infeasible. */ inline bool isFeasible() const; /** * Determines whether the given variable is currently active. * See the LPData class notes for details. * * @param pos the index of the variable to query. * This must be between 0 and origTableaux_->columns()-1 inclusive. */ inline bool isActive(unsigned pos) const; /** * Returns the sign of the given variable under the current * basis. This does \e not attempt to "undo" any changes of variable * caused by prior calls to constrainPositive() or constrainOct(); * it simply tests the sign of the variable in the given column * of the tableaux in its current form. * * Specifically: if the given variable is inactive or non-basic, * this routine returns zero. If the given variable is in the * basis, this routine returns the sign of the corresponding * integer on the right-hand side of the tableaux. * * @param pos the index of the variable to query. * This must be between 0 and origTableaux_->columns()-1 inclusive. * @return the sign of the variable as described above; * this will be either 1, 0 or -1. */ inline int sign(unsigned pos) const; /** * Constrains this system further by setting the given variable * to zero and deactivating it. See the LPData class notes for * details. * * This routine will work even if the given variable has already * been deactivated (and it will do nothing in this case). * * \warning If you have previously called constrainPositive() * or constrainOct() on this variable, then these prior routines * will have performed a change of variable. Any new call to * constraintZero() on this same variable will constraint the * \e new variable, not the original, and so might not have the * intended effect. * * @param pos the index of the variable that is to be set to * zero. This must be between 0 and origTableaux_->columns()-1 * inclusive. */ void constrainZero(unsigned pos); /** * Constrains this system further by constraining the given variable * to be strictly positive. We do this using a change of variable * that effectively replaces x_pos with the new variable * x'_pos = x_pos - 1 (which we simply constrain to be non-negative * as usual). See the LPData class notes for details. * * This routine will work even if the given variable has already * been deactivated, but in this case the routine will * immediately set the system to infeasible and return. * * \warning If you have previously called constrainPositive() * or constrainOct() on this variable, then these prior routines * will have performed a change of variable. Any new call to * constrainPositive() on this same variable will constrain the * \e new variable, not the original, and so might not have the * intended effect. * * @param pos the index of the variable that is to be constrained as * positive. This must be between 0 and origTableaux_->columns()-1 * inclusive. */ void constrainPositive(unsigned pos); /** * Declares that two quadrilateral coordinates within a tetrahedron * are to be combined into a single octagon coordinate, for use * with almost normal surfaces, and constrains the system accordingly. * * This constrains the system in several ways, as discussed in detail * in the LPData class notes. In theory, we set the two quadrilateral * coordinates to be equal, and also insist that the number of octagons * be strictly positive. In practice, we do this through several * changes of variable; see the LPData class notes for a detailed * discussion of precisely how the variables and tableaux will change. * * This routine will work even if one of the given quadrilateral * variables has already been deactivated, but in this case the * routine will immediately set the system to infeasible and return. * * \pre This is the first time constrainOct() has been called on * this tableaux. This is because this class can only handle one * octagon type in the entire system. * * \pre Variables \a quad1 and \a quad2 represent different * quadrilateral coordinates in the same tetrahedron of the * underlying triangulation. * * \warning If you have previously called constrainPositive() or * constrainOct() on one of the given variables, then these prior * routines will have performed a change of variable. Any new call * to constrainOct() involving this same variable will constrain the * \e new variable, not the original, and so might not have the * intended effect. * * @param quad1 one of the two quadrilateral types that we * combine to form the new octagon type. * @param quad2 the other of the two quadrilateral types that we * combine to form the new octagon type. */ void constrainOct(unsigned quad1, unsigned quad2); /** * Writes details of this tableaux to the given output stream. * The output is "rough" and wasteful, and is intended for * debugging purposes only. * * The precise output is subject to change in future versions * of Regina. * * @param out the output stream to write to. */ void dump(std::ostream& out) const; /** * Extracts the values of the individual variables from the * current basis, with some modifications (as described below). * The values of the variables are store in the given vector \a v. * * The modifications are as follows: * * - We extract variables that correspond to the original * matching equations obtained from the underlying * triangulation, \e not the current tableaux and \e not even * the original starting tableaux stored in origTableaux_. * In other words, when we fill the vector \a v we undo the * column permutation described by LPInitialTableaux::columnPerm(), * and we undo any changes of variable that were caused by * calls to constrainPositive() and/or constrainOct(). * * - To ensure that the variables are all integers, we scale the * final vector by the smallest positive rational multiple * for which all elements of the vector are integers. * (This is why the output class is NRay and not NVector.) * * This routine is not used as an internal part of the tree traversal * algorithm; instead it is offered as a helper routine for * reconstructing the normal surfaces that result. * * \pre The given vector \a v has been initialised to the zero vector * of length origTableaux_->columns(). Note that the NRay constructor * will automatically initialise all elements to zero as required. * * \pre No individual coordinate column has had more than one call * to either of constrainPositive() or constrainOct() (otherwise * the coordinate will not be correctly reconstructed). Any * additional columns arising from LPConstraint are exempt from * this requirement. * * @param v the vector into which the values of the variables * will be placed. * @param type the type vector corresponding to the current state of * this tableaux, indicating which variables were previously fixed as * positive via calls to constrainPositive(). This is necessary * because LPData does not keep such historical data on its own. */ void extractSolution(NRay& v, const char* type) const; private: /** * Returns the given entry in this tableaux. * * Since we do not store the full tableaux, this entry is * computed on the fly. However, this computation is fast * because the computations use sparse vector multiplication. * * There is an alternate version of this function that avoids * creating spurious temporaries (which may help with performance). * * @param the row of the requested entry; this must be between 0 * and rank_-1 inclusive. * @param the column of the requested entry; this must be between 0 * and origTableaux_->columns()-1 inclusive. * @return the requested entry in this tableaux. */ inline Integer entry(unsigned row, unsigned col) const; /** * Sets \a ans to the given entry in this tableaux. * * Since we do not store the full tableaux, this entry is * computed on the fly. However, this computation is fast * because the computations use sparse vector multiplication. * * There is an alternate version of this function that is more * natural (it returns its answer), but creates an additional * temporary variable (which may hinder performance). * * @param the row of the requested entry; this must be between 0 * and rank_-1 inclusive. * @param the column of the requested entry; this must be between 0 * and origTableaux_->columns()-1 inclusive. * @param ans an integer that will be set to the requested entry * in this tableaux. */ inline void entry(unsigned row, unsigned col, Integer& ans) const; /** * Determines the sign of the given entry in this tableaux. * * Since we do not store the full tableaux, the entry is * computed on the fly. However, this computation is fast * because the computations use sparse vector multiplication. * * @param the row of the requested entry; this must be between 0 * and rank_-1 inclusive. * @param the column of the requested entry; this must be between 0 * and origTableaux_->columns()-1 inclusive. * @return +1, -1 or 0 according to whether the requested entry * is positive, negative or zero. */ inline int entrySign(unsigned row, unsigned col) const; /** * Performs a pivot in the dual simplex method. * * The column \a outCol is pivoted out of the current basis, and * the column \a inCol is pivoted in. * * No assumptions are made about the current state of column \a outCol; * in particular, it may be in a state "under construction" * whereby it has more than the one expected non-zero element. * However, assumptions \a are made about the current state of * column \a inCol, as noted in the preconditions below. * * \pre \a outCol represents an active basic variable, and * \a inCol represents an active non-basic variable. * * \pre The non-basic variable \a inCol has a non-zero entry in the * row of the tableaux that defines the basic variable \a outCol. * * @param outCol the index of the variable to pivot out of the basis. * @param inCol the index of the variable to pivot into the basis. */ void pivot(unsigned outCol, unsigned inCol); /** * Finds an initial basis for the system using Gauss-Jordan * elimination. * * This routine is only ever called from initStart(), and * assumes that the current tableaux is just the original * starting tableaux (i.e., no changes have been made to the * tableaux at all). * * The implementation of this routine is a little naive and * heavy-handed, but since we only call it once in the entire * tree traversal algorithm, this does not really matter. * * In particular, it performs the entire Gauss-Jordan elimination * using the arbitrary-precision NInteger class, so there is no need * to worry about the magnitudes of any intermediate matrix * entries that might appear during the process. The final * row operation matrix will of course be copied into rowOps_ * using the Integer class specified in the template arguments. * * \pre The current tableaux is precisely the original starting * tableaux; in particular, rhs_ is the zero vector, rowOps_ is the * identity matrix, and rank_ is precisely origTableaux_->rank(). */ void findInitialBasis(); /** * Pivots from the current basis to a feasible basis, or else * marks the entire system as infeasible if this is not possible. * This uses a heuristic pivot rule with good performance but * also includes safety checks to break cycling; see below for details. * * This routine is called after we take a feasible basis and make * some modification that might (or might not) cause some basis * variables to become negative. * * The pivot rule that we use is greedy: the variable that exits * the basis is the one with largest magnitude negative value. * This rule is fast to test and in most cases leads to a small * number of pivots, giving good performance overall. However, * this rule can lead to cycling, and so we include cycle-detection * code that falls back to Bland's rule to break cycling in the * rare cases when it occurs. * * \pre feasible_ is currently marked as \c true (as a leftover * from the feasible basis before our recent modification). */ void makeFeasible(); /** * Pivots from the current basis to a feasible basis, or else * marks the entire system as infeasible if this is not possible. * The pivot rule has poor performance but guarantees to avoid * cycling; see below for details. * * This routine is called after we take a feasible basis and make * some modification that might (or might not) cause some basis * variables to become negative. * * This routine uses a variant of Bland's rule (but without an * objective function) to avoid cycling. The rule to decide on * each individual pivot is fast to run, but the total number of * pivots required to reach feasibility is often very large, leading * to a poor performance overall. It is recommended that you * use a different pivoting rule in general, and only call this * function to break cycles when they occur. * * \pre feasible_ is currently marked as \c true (as a leftover * from the feasible basis before our recent modification). */ void makeFeasibleAntiCycling(); /** * Runs some tests to ensure that the tableaux is in a * consistent state. This is for use in debugging only, since * running these tests might have a severe impact on performance. * * If any tests fail, this routine reports the error and exits * the program immediately. */ void verify() const; }; // Inline functions for LPMatrix template inline LPMatrix::LPMatrix() : dat_(0) { } template inline LPMatrix::LPMatrix(unsigned rows, unsigned cols) : dat_(new Integer[rows * cols]), rows_(rows), cols_(cols) { } template inline LPMatrix::~LPMatrix() { delete[] dat_; } template inline void LPMatrix::reserve(unsigned maxRows, unsigned maxCols) { dat_ = new Integer[maxRows * maxCols]; } template inline void LPMatrix::initClone(const LPMatrix& clone) { rows_ = clone.rows_; cols_ = clone.cols_; std::copy(clone.dat_, clone.dat_ + clone.rows_ * clone.cols_, dat_); } template inline void LPMatrix::initIdentity(unsigned size) { // Don't fuss about optimising this, since we only call it once // in the entire tree traversal algorithm. rows_ = cols_ = size; unsigned r, c; for (r = 0; r < rows_; ++r) for (c = 0; c < cols_; ++c) entry(r, c) = (r == c ? 1 : long(0)); } template inline Integer& LPMatrix::entry(unsigned row, unsigned col) { return dat_[row * cols_ + col]; } template inline const Integer& LPMatrix::entry(unsigned row, unsigned col) const { return dat_[row * cols_ + col]; } template inline unsigned LPMatrix::rows() const { return rows_; } template inline unsigned LPMatrix::columns() const { return cols_; } template inline void LPMatrix::swapRows(unsigned r1, unsigned r2) { if (r1 != r2) std::swap_ranges(dat_ + r1 * cols_, dat_ + r1 * cols_ + cols_, dat_ + r2 * cols_); } template inline void LPMatrix::negateRow(unsigned row) { Integer *p = dat_ + row * cols_; for (unsigned i = 0; i < cols_; ++p, ++i) p->negate(); } // Inline functions for LPInitialTableaux template inline LPInitialTableaux::Col::Col() : nPlus(0), nMinus(0) { } template inline void LPInitialTableaux::Col::push(unsigned row, int val) { #ifdef REGINA_VERIFY_LPDATA if ((val > 0 && val + nPlus > 4) || (val < 0 && val - nMinus < -4)) { std::cerr << "BAD MATRIX" << std::endl; ::exit(1); } #endif for (; val > 0; --val) plus[nPlus++] = row; for (; val < 0; ++val) minus[nMinus++] = row; } template inline LPInitialTableaux::~LPInitialTableaux() { delete eqns_; delete[] col_; delete[] columnPerm_; } template inline NTriangulation* LPInitialTableaux::tri() const { return tri_; } template inline unsigned LPInitialTableaux::rank() const { return rank_; } template inline unsigned LPInitialTableaux::columns() const { return cols_; } template inline unsigned LPInitialTableaux::coordinateColumns() const { return eqns_->columns(); } template inline bool LPInitialTableaux::constraintsBroken() const { return constraintsBroken_; } template inline const int* LPInitialTableaux::columnPerm() const { return columnPerm_; } template template inline Integer LPInitialTableaux::multColByRow( const LPMatrix& m, unsigned mRow, unsigned thisCol) const { Integer ans = col_[thisCol].innerProduct(m, mRow); unsigned i; for (i = 0; i < col_[thisCol].nPlus; ++i) ans += m.entry(mRow, col_[thisCol].plus[i]); for (i = 0; i < col_[thisCol].nMinus; ++i) ans -= m.entry(mRow, col_[thisCol].minus[i]); return ans; } template template inline Integer LPInitialTableaux::multColByRowOct( const LPMatrix& m, unsigned mRow, unsigned thisCol) const { Integer ans = col_[thisCol].innerProductOct(m, mRow); unsigned i; for (i = 0; i < col_[thisCol].nPlus; ++i) ans += m.entry(mRow, col_[thisCol].plus[i]); for (i = 0; i < col_[thisCol].nMinus; ++i) ans -= m.entry(mRow, col_[thisCol].minus[i]); return ans; } template template inline void LPInitialTableaux::fillInitialTableaux( LPMatrix& m) const { unsigned c, i; for (c = 0; c < cols_; ++c) { for (i = 0; i < col_[c].nPlus; ++i) ++m.entry(col_[c].plus[i], c); for (i = 0; i < col_[c].nMinus; ++i) --m.entry(col_[c].minus[i], c); // Don't forget any additional constraints that we added // as final rows to the matrix. col_[c].fillFinalRows(m, c); } } // Template functions for LPData template inline LPData::LPData() : rhs_(0), basis_(0), basisRow_(0) { } template inline LPData::~LPData() { delete[] rhs_; delete[] basis_; delete[] basisRow_; } template inline void LPData::reserve( const LPInitialTableaux* origTableaux) { origTableaux_ = origTableaux; rhs_ = new Integer[origTableaux->rank()]; rowOps_.reserve(origTableaux->rank(), origTableaux->rank()); basis_ = new int[origTableaux->rank()]; basisRow_ = new int[origTableaux->columns()]; } template inline unsigned LPData::columns() const { return origTableaux_->columns(); } template inline unsigned LPData::coordinateColumns() const { return origTableaux_->coordinateColumns(); } template inline bool LPData::isFeasible() const { return feasible_; } template inline bool LPData::isActive(unsigned pos) const { // If basisRow_[pos] < 0, the variable is active and non-basic. // If basisRow_[pos] > 0, the variable is active and basic. // If basisRow_[pos] == 0, then: // - if rank_ > 0 and basis_[0] == pos, then the variable // is active and basic; // - otherwise the variable is not active. return ! (basisRow_[pos] == 0 && (rank_ == 0 || basis_[0] != pos)); } template inline int LPData::sign(unsigned pos) const { // If basisRow_[pos] < 0, the variable is active and non-basic. // If basisRow_[pos] > 0, the variable is active and basic. // If basisRow_[pos] == 0, then: // - if rank_ > 0 and basis_[0] == pos, then the variable // is active and basic; // - otherwise the variable is not active. return ((basisRow_[pos] > 0 || (rank_ > 0 && basis_[0] == pos)) ? rhs_[basisRow_[pos]].sign() : 0); } template inline Integer LPData::entry(unsigned row, unsigned col) const { // Remember to take into account any changes of variable due // to previous calls to constrainOct(). if (octPrimary_ != col) return origTableaux_->multColByRow(rowOps_, row, col); else { Integer ans = origTableaux_->multColByRowOct(rowOps_, row, col); ans += origTableaux_->multColByRowOct(rowOps_, row, octSecondary_); return ans; } } template inline void LPData::entry(unsigned row, unsigned col, Integer& ans) const { // Remember to take into account any changes of variable due // to previous calls to constrainOct(). if (octPrimary_ != col) ans = origTableaux_->multColByRow(rowOps_, row, col); else { ans = origTableaux_->multColByRowOct(rowOps_, row, col); ans += origTableaux_->multColByRowOct(rowOps_, row, octSecondary_); } } template inline int LPData::entrySign(unsigned row, unsigned col) const { // Remember to take into account any changes of variable due // to previous calls to constrainOct(). if (octPrimary_ != col) return origTableaux_->multColByRow(rowOps_, row, col).sign(); else { Integer ans = origTableaux_->multColByRowOct(rowOps_, row, col); ans += origTableaux_->multColByRowOct(rowOps_, row, octSecondary_); return ans.sign(); } } } // namespace regina #endif regina-4.95/engine/enumerate/ntreelp.tcc000644 000765 000024 00000004653 12234011536 020212 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntreelp.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use ntreelp-impl.h instead. #include "enumerate/ntreelp-impl.h" regina-4.95/engine/enumerate/ntreetraversal-impl.h000644 000765 000024 00000126341 12234011536 022216 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* Definitions of template member functions from ntreetraversal.h. For common combinations of template arguments, the templates are already instatiated and so you will not need to include this file. For more exotic combinations of template arguments, or if the template arguments are your own classes, then you will need to include this file. */ #ifndef __NTREETRAVERSAL_IMPL_H #ifndef __DOXYGEN #define __NTREETRAVERSAL_IMPL_H #endif #include "enumerate/ntreetraversal.h" #include "progress/nprogresstracker.h" #include "surfaces/nsanstandard.h" #include "surfaces/nsquad.h" #include "surfaces/nsquadoct.h" #include "surfaces/nsstandard.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" /** * Optimisation flags: * Define any combination of the following flags to switch \e off * various optimisations. * This is for diagnostic purposes only. */ // #define REGINA_NOOPT_MIN_FEASIBLE /** * Define REGINA_TREE_TRACE to output details of the type vectors as we * walk through the search tree. This is for diagnostic purposes only. */ // #define REGINA_TREE_TRACE /** * Define REGINA_SURFACE_TRACE to output details of full normal surfaces * (possibly immersed, or branched), as well as their corresponding * type vectors, as we walk through the search tree. * This is for diagnostic purposes only. */ // #define REGINA_SURFACE_TRACE namespace regina { template NNormalSurface* NTreeTraversal:: buildSurface() const { // Note that the vector constructors automatically set all // elements to zero, as required by LPData::extractSolution(). NNormalSurfaceVector* v; if (coords_ == NS_QUAD || coords_ == NS_AN_QUAD_OCT) v = new NNormalSurfaceVectorQuad(3 * nTets_); else if (coords_ == NS_STANDARD || coords_ == NS_AN_STANDARD) v = new NNormalSurfaceVectorStandard(7 * nTets_); else return 0; lpSlot_[nTypes_]->extractSolution(*v, type_); if (coords_ == NS_QUAD || coords_ == NS_STANDARD) return new NNormalSurface(origTableaux_.tri(), v); // We have an almost normal surface: restore the octagon // coordinates. NNormalSurfaceVector* an; unsigned i, j; if (coords_ == NS_AN_QUAD_OCT) { an = new NNormalSurfaceVectorQuadOct(6 * nTets_); for (i = 0; i < nTets_; ++i) for (j = 0; j < 3; ++j) an->setElement(6 * i + j, (*v)[3 * i + j]); if (octLevel_ >= 0) { unsigned octTet = (origTableaux_.columnPerm()[ 3 * typeOrder_[octLevel_]] / 3); unsigned octType = type_[typeOrder_[octLevel_]] - 4; an->setElement(6 * octTet + 3 + octType, (*v)[3 * octTet + (octType + 1) % 3]); for (j = 0; j < 3; ++j) an->setElement(6 * octTet + j, 0); } } else { an = new NNormalSurfaceVectorANStandard(10 * nTets_); for (i = 0; i < nTets_; ++i) for (j = 0; j < 7; ++j) an->setElement(10 * i + j, (*v)[7 * i + j]); if (octLevel_ >= 0) { unsigned octTet = (origTableaux_.columnPerm()[ 3 * typeOrder_[octLevel_]] / 7); unsigned octType = type_[typeOrder_[octLevel_]] - 4; an->setElement(10 * octTet + 7 + octType, (*v)[7 * octTet + 4 + (octType + 1) % 3]); for (j = 0; j < 3; ++j) an->setElement(10 * octTet + 4 + j, 0); } } delete v; return new NNormalSurface(origTableaux_.tri(), an); } template bool NTreeTraversal::verify( const NNormalSurface* s, const NMatrixInt* matchingEqns) const { // Rebuild the matching equations if necessary. NMatrixInt* tmpEqns = 0; if (! matchingEqns) { tmpEqns = regina::makeMatchingEquations( origTableaux_.tri(), coords_); matchingEqns = tmpEqns; } // Verify the matching equations. unsigned row, col; for (row = 0; row < matchingEqns->rows(); ++row) { NLargeInteger ans; // Initialised to zero. for (col = 0; col < matchingEqns->columns(); ++col) ans += (matchingEqns->entry(row, col) * (*s->rawVector())[col]); if (ans != 0) { delete tmpEqns; return false; } } delete tmpEqns; // Verify any additional constraints. return LPConstraint::verify(s); } template NTreeTraversal::NTreeTraversal( NTriangulation* tri, NormalCoords coords, int branchesPerQuad, int branchesPerTri, bool enumeration) : BanConstraint(tri, coords), origTableaux_(tri, (coords == NS_QUAD || coords == NS_AN_QUAD_OCT ? NS_QUAD : NS_STANDARD), enumeration), coords_(coords), nTets_(tri->getNumberOfTetrahedra()), nTypes_(coords == NS_QUAD || coords == NS_AN_QUAD_OCT ? nTets_ : 5 * nTets_), /* Each time we branch, one LP can be solved in-place: therefore we use branchesPerQuad-1 and branchesPerTri-1. The final +1 is for the root node. */ nTableaux_(coords == NS_QUAD || coords == NS_AN_QUAD_OCT ? (branchesPerQuad - 1) * nTets_ + 1 : (branchesPerQuad - 1) * nTets_ + (branchesPerTri - 1) * nTets_ * 4 + 1), type_(new char[nTypes_ + 1]), typeOrder_(new int[nTypes_]), level_(0), octLevel_(coords == NS_AN_STANDARD || coords == NS_AN_QUAD_OCT ? -1 : nTypes_), lp_(new LPData[nTableaux_]), lpSlot_(new LPData*[nTypes_ + 1]), nextSlot_(new LPData*[nTypes_ + 1]), nVisited_(0) { // Initialise the type vector to the zero vector. std::fill(type_, type_ + nTypes_ + 1, 0); // Set a default type order. unsigned i; for (i = 0; i < nTypes_; ++i) typeOrder_[i] = i; // Reserve space for all the tableaux that we will ever need. for (i = 0; i < nTableaux_; ++i) lp_[i].reserve(&origTableaux_); // Mark the location of the initial tableaux at the root node. lpSlot_[0] = lp_; nextSlot_[0] = lp_ + 1; // Set up the ban list. BanConstraint::init(origTableaux_.columnPerm()); // Reserve space for our additional temporary tableaux. tmpLP_[0].reserve(&origTableaux_); tmpLP_[1].reserve(&origTableaux_); tmpLP_[2].reserve(&origTableaux_); tmpLP_[3].reserve(&origTableaux_); } /** * Destroys this object. */ template NTreeTraversal::~NTreeTraversal() { delete[] type_; delete[] typeOrder_; delete[] lp_; delete[] lpSlot_; delete[] nextSlot_; } template void NTreeTraversal::setNext( int nextType) { int* pos = std::find(typeOrder_ + level_ + 1, typeOrder_ + nTypes_, nextType); if (pos != typeOrder_ + level_ + 1) { // Use memmove(), which is safe when the source and // destination ranges overlap. memmove(typeOrder_ + level_ + 2 /* dest */, typeOrder_ + level_ + 1 /* src */, (pos - (typeOrder_ + level_ + 1)) * sizeof(int)); typeOrder_[level_ + 1] = nextType; } } template int NTreeTraversal::feasibleBranches( int quadType) { // Spin off clones for the new linear programs (reusing as much // work as possible). tmpLP_[0].initClone(*lpSlot_[level_ + 1]); tmpLP_[1].initClone(tmpLP_[0]); tmpLP_[1].constrainZero(3 * quadType + 1); tmpLP_[1].constrainZero(3 * quadType + 2); tmpLP_[1].constrainPositive(3 * quadType); tmpLP_[0].constrainZero(3 * quadType); if (! tmpLP_[0].isFeasible()) { // Branches 0, 2 and 3 will all be infeasible. // Save some work and jump straight to the solution. return (tmpLP_[1].isFeasible() ? 1 : 0); } tmpLP_[2].initClone(tmpLP_[0]); tmpLP_[2].constrainZero(3 * quadType + 2); tmpLP_[2].constrainPositive(3 * quadType + 1); tmpLP_[0].constrainZero(3 * quadType + 1); if (! tmpLP_[0].isFeasible()) { // Branches 0 and 3 will both be infeasible. return (tmpLP_[1].isFeasible() ? 1 : 0) + (tmpLP_[2].isFeasible() ? 1 : 0); } tmpLP_[3].initClone(tmpLP_[0]); tmpLP_[3].constrainPositive(3 * quadType + 2); tmpLP_[0].constrainZero(3 * quadType + 2); // Determine which of these systems are feasible. return ((tmpLP_[0].isFeasible() ? 1 : 0) + (tmpLP_[1].isFeasible() ? 1 : 0) + (tmpLP_[2].isFeasible() ? 1 : 0) + (tmpLP_[3].isFeasible() ? 1 : 0)); } template double NTreeTraversal::percent() const { double percent = 0.0; double range = 100.0; unsigned den; unsigned quadsRemaining = nTets_; // Just check the first few types, until the margin of // error is sufficiently small. for (unsigned i = 0; range > 0.01 && i < nTypes_; ++i) { if (typeOrder_[i] >= nTets_) { // Triangle coordinate. range /= 2.0; percent += (range * type_[typeOrder_[i]]); } else { // Quadrilateral or octagon coordinate. if (octLevel_ == nTypes_ || octLevel_ < i) { // Octagons have already been used, or were never available. range /= 4.0; percent += (range * type_[typeOrder_[i]]); } else if (octLevel_ == i) { // This coordinate is an octagon coordinate. den = 3 * quadsRemaining + 4; range /= den; percent += range * ((den - 3) + (type_[typeOrder_[i]] - 4)); } else { // This is a quad coordinate, but octagons are still // available for use either here or deeper in the tree. den = 3 * quadsRemaining + 4; range = (range * (den - 3)) / (4 * den); // Floating pt division percent += (range * type_[typeOrder_[i]]); } --quadsRemaining; } } return percent; } template bool NTreeEnumeration::next( NProgressTracker* tracker) { if (lastNonZero_ < 0) { // Our type vector is the zero vector. // This means we are starting the search from the very // beginning. // // Prepare the root node by finding an initial basis // from the original starting tableaux. lp_[0].initStart(); BanConstraint::enforceBans(lp_[0]); ++nVisited_; // Is the system feasible at the root node? // If not, there can be no solutions at all. if (! lp_[0].isFeasible()) return false; /** * If we ever need to reorder the search tree at the very * beginning, here is where we do it. * * Note that, since setNext() works on typeOrder_[level_ + 1], * we must temporarily set level_ = -1 before we call it. * level_ = -1; setNext(...); level_ = 0; */ } else { // We are starting the search from a previous solution. // Make the next incremental change to the type vector // to continue the search. // // We *should* increment type_[typeOrder_[nTypes_-1]]. // However, if type_[typeOrder_[nTypes_-1]] is zero then this // will fail the domination test. In fact, incrementing *any* // trailing zeroes in our type vector will likewise fail // the domination test (since it will dominate the // previous solution that we are now stepping away from). // We can therefore shortcut the search and wind our way // back to the last *non-zero* element in the type // vector, and increment that instead. // // Note: we must have *some* non-zero element in the // type vector, because we know that lastNonZero_ >= 0. level_ = lastNonZero_; ++type_[typeOrder_[level_]]; } // And... continue the search! unsigned idx; /* Index of the type we are currently choosing. */ bool outOfRange; while (true) { // Update the state of progress and test for cancellation. if (tracker && ! tracker->setPercent(percent())) break; #ifdef REGINA_TREE_TRACE dumpTypes(std::cout); std::cout << std::endl; #endif /* INVARIANTS: * * - 0 <= level_ < nTypes_. * * - We have explicitly set type_[typeOrder_[0,...,level_]], * though it is possible that type_[typeOrder_[level_]] is * out of range (too large). All later elements * type_[typeOrder_[level_+1,...,nTypes_-1]] are 0. * * - The parent node in the tree (where * type_[typeOrder_[level_]] == 0) passes the feasibility * and domination tests; however we do not yet know whether * this node in the tree (with our new value for * type_[typeOrder_[level_]]) passes these tests. */ idx = typeOrder_[level_]; // Check whether type_[idx] is out of range, // and if so then backtrack further up the tree. outOfRange = false; if (type_[idx] == 4) { // This quadrilateral column is out of range. if (octLevel_ < 0) { // But... we can make it an octagon column instead. octLevel_ = level_; } else outOfRange = true; } else if (type_[idx] == 7) { // This octagon column is out of range. // Clear octLevel_ again so that some other level can // claim the one and only octagon column if it likes. octLevel_ = -1; outOfRange = true; } else if (idx >= nTets_ && type_[idx] == 2) { // This triangle column is out of range. outOfRange = true; } if (outOfRange) { // Backtrack. type_[idx] = 0; --level_; if (level_ < 0) { // Out of options: the tree traversal is finished. return false; } ++type_[typeOrder_[level_]]; lastNonZero_ = level_; continue; } // This is a node in the search tree that we need to // examine. ++nVisited_; // Check the domination test. // If this fails then abandon this subtree, increment the // type at the current level, and continue searching. // // Note that if type_[idx] == 0 then we do not // need to check the domination test, since we know the parent // node passed the domination test and setting // type_[idx] = 0 will not change the result. if (type_[idx] && solns_.dominates(type_, nTypes_)) { ++type_[idx]; lastNonZero_ = level_; continue; } // Leave the zero test until a bit later; there is some // dual simplex work we need to do with the zero vector // even though we know we don't want it as a solution. // All that's left is the feasibility test. // Bring on the linear programming. // First, prepare the tableaux for our new type at this level // of the tree. if (! type_[idx]) { // This is the first time we have visited this node. // Since the parent node already passes the // feasibility test, we will simply overwrite the // parent tableaux "in place", avoiding the need for // an expensive copy operation. lpSlot_[level_ + 1] = lpSlot_[level_]; // Since type_[idx] = 0, we will be adding // up to three new constraints x_i = x_{i+1} = x_{i+2} = 0. // So that we can reuse as much work as possible, we // will gradually spin off clones of this tableaux // that we can later use with // type_[idx] == 1, 2 or 3 (and 4, 5 or 6 if we allow // octagons). // // How we add these constraints and spin off clones // depends on whether are working with quadrilateral // columns or triangle columns. if (idx < nTets_) { // We're working with a quadrilateral column. if (octLevel_ < 0) { // We must support both quadrilaterals and octagons. nextSlot_[level_ + 1] = nextSlot_[level_] + 6; // Here we have three constraints // x_{3k} = x_{3k+1} = x_{3k+2} = 0, // and later we will be trying quadrilateral types // 1, 2 and 3, and octagon types 4, 5 and 6. // First spin off clones that we will later use with // type_[idx] = 1, 5 and 6. These clones inherit // no extra constraints: for type 1 we will need to // fix x_{3k} >= 1 and x_{3k+1} = x_{3k+2} = 0 // later, and for types 5 and 6 we must likewise // fix all constraints later on. nextSlot_[level_]->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 4)->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 5)->initClone(*lpSlot_[level_]); // Now we can fix x_{3k} = 0. lpSlot_[level_]->constrainZero(3 * idx); // Next spin off clones that we will later use with // type_[idx] = 2 and 4. These clones already // inherit the constraint x_{3k} = 0, and we // will add the other constraints later (for // instance, for type 2 we will add x_{3k+1} >= 1 // and x_{3k+2} = 0 later). (nextSlot_[level_] + 1)->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 3)->initClone(*lpSlot_[level_]); // Now we can fix x_{3k+1} = 0. lpSlot_[level_]->constrainZero(3 * idx + 1); // Finally spin off a clone that we will later use // with type_[idx] = 3. This clone already // inherits the constraint x_{3k} = x_{3k+1} = 0, // which only leaves us x_{3k+2} >= 1 to add later. (nextSlot_[level_] + 2)->initClone(*lpSlot_[level_]); // At last we add the final constraint x_{3k+2} = 0 // for this node. lpSlot_[level_]->constrainZero(3 * idx + 2); // This node now has all of the necessary // constraints x_{3k} = x_{3k+1} = x_{3k+2} = 0. } else { // We are supporting quadrilaterals only. // As above, but with types 1, 2 and 3 only. nextSlot_[level_ + 1] = nextSlot_[level_] + 3; nextSlot_[level_]->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx); (nextSlot_[level_] + 1)->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 1); (nextSlot_[level_] + 2)->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 2); } } else { // We're working with a triangle column. nextSlot_[level_ + 1] = nextSlot_[level_] + 1; // Here we only have one constraint x_k = 0, // and later we only have type 1 to try. // Spin off a clone that we will later use with // type_[idx] = 1. This clone inherits // no extra constraints. nextSlot_[level_]->initClone(*lpSlot_[level_]); // Now add the one and only constraint x_k = 0 for // this node. lpSlot_[level_]->constrainZero(2 * nTets_ + idx); } } else { // This is not the first time we have visited this node. // Find the appropriate clone that we spun off earlier when // type_[idx] was 0, and add the missing // constraints that we did not enforce during the // cloning process. // // We always enforce constraints of the form x_i = 0 // before constraints of the form x_i >= 1. // This is in the hope that x_i = 0 is easier to // deal with, and if we break feasibility earlier then // this saves us some work later on. // // (Note that we did indeed spin off clones earlier, // since if we pass the domination test now for // type_[idx] != 0 then we must have passed // it earlier for type_[idx] == 0.) if (idx < nTets_) { // Quadrilateral columns (type is 1, 2 or 3, // or 4, 5 or 6 if we allow octagons): lpSlot_[level_ + 1] = nextSlot_[level_] + type_[idx] - 1; switch (type_[idx]) { case 1: lpSlot_[level_ + 1]->constrainZero(3 * idx + 1); lpSlot_[level_ + 1]->constrainZero(3 * idx + 2); lpSlot_[level_ + 1]->constrainPositive(3 * idx); break; case 2: lpSlot_[level_ + 1]->constrainZero(3 * idx + 2); lpSlot_[level_ + 1]->constrainPositive(3 * idx + 1); break; case 3: lpSlot_[level_ + 1]->constrainPositive(3 * idx + 2); break; case 4: lpSlot_[level_ + 1]->constrainOct( 3 * idx + 1, 3 * idx + 2); break; case 5: lpSlot_[level_ + 1]->constrainZero(3 * idx + 1); lpSlot_[level_ + 1]->constrainOct(3 * idx, 3 * idx + 2); break; case 6: lpSlot_[level_ + 1]->constrainZero(3 * idx + 2); lpSlot_[level_ + 1]->constrainOct(3 * idx, 3 * idx + 1); break; } } else { // Triangle columns (type is 1): lpSlot_[level_ + 1] = nextSlot_[level_]; lpSlot_[level_ + 1]->constrainPositive(2 * nTets_ + idx); } } // *Now* we can enforce the zero test. // We could not do this earlier because, even if we have // the zero vector, we still needed to spin off clones // for type_[idx] = 1, 2 and 3. if (lastNonZero_ < 0 && level_ == nTypes_ - 1) { // We failed the zero test. // Abandon this subtree, increment the type at the // current level, and continue searching. ++type_[idx]; lastNonZero_ = level_; continue; } // Now all our constraints are enforced, and we can // simply test the tableaux for feasibility. if (lpSlot_[level_ + 1]->isFeasible()) { if (level_ < nTypes_ - 1) { // We pass the feasibility test, but we're // not at a leaf node. // Head deeper into the tree. #if 0 if (level_ < nTets_) { // The next level is a quadrilateral type. // See if we can't find a better quadrilateral // type to branch on instead. We will choose the // quadrilateral type that branches into the // fewest possible feasible subtrees. // Here we measure the old types 0, 1, 2, 3 as // four separate branches (not the three merged // branches 0=1, 2, 3 that we use in the actual // search). int bestQuad = -1; int minBranches = 5; // Greater than any soln. int tmp; for (int i = level_ + 1; i < nTypes_; ++i) { if (typeOrder_[i] < nTets_) { // It's an available quad type. tmp = feasibleBranches(typeOrder_[i]); if (tmp < minBranches) { minBranches = tmp; bestQuad = typeOrder_[i]; if (tmp == 0) break; // Can't get any better! } } } if (bestQuad >= 0) setNext(bestQuad); } #endif ++level_; } else { // We pass the feasibility test, *and* we're at // a leaf node. This means we've found a solution! solns_.insert(type_, nTypes_); ++nSolns_; return true; } } else { // We failed the feasibility test. // Abandon this subtree, increment the type at the // current level, and continue searching. ++type_[idx]; lastNonZero_ = level_; } } // If we ever make it out here, it's because some other // thread cancelled the search. The result should be ignored. return false; } template bool NTreeSingleSoln::find() { // This code is similar to next(), but makes some changes to // account for the facts that: // - we only need a single solution that satisfies our // constraints, not all solutions; // - this single solution does not need to be a vertex solution. // // Amongst other things, we make the following key changes: // - there is no domination test (since we stop at the first // solution); // - we insist that at least one unmarked triangle coordinate is // zero at all stages of the search, so that we can avoid surfaces // with trivial (and unwanted) vertex linking components. // // Furthermore, we only take three branches for each quadrilateral // type, not four. We do this by combining types 0 and 1, // so the three branches are: // ( >= 0, 0, 0 ) --> type 1 // ( 0, >= 1, 0 ) --> type 2 // ( 0, 0, >= 1 ) --> type 3 // Start the search from the very beginning. // // Prepare the root node by finding an initial basis from // the original starting tableaux. lp_[0].initStart(); BanConstraint::enforceBans(lp_[0]); ++nVisited_; if (! lp_[0].isFeasible()) return false; // To kick off our vertex-link-avoiding regime, make // nextZeroLevel_ the first level in the search tree, and // choose an appropriate triangle type to branch on. We will // return to the quadrilateral types as soon as some triangle // coordinate is safely constrained to zero. // // Since setNext() works on typeOrder_[level_ + 1], we must // temporarily set level_ = -1 before we call it. int useTriangle = nextUnmarkedTriangleType(nTets_); if (useTriangle < 0) { // There are no triangle types available to set to zero! return false; } level_ = -1; setNext(useTriangle); level_ = 0; // Run the search! unsigned idx; /* Index of the type we are currently choosing. */ bool outOfRange; while (! cancelled()) { // We can safely return from this point. #ifdef REGINA_TREE_TRACE dumpTypes(std::cout); std::cout << std::endl; #endif // This code is based on NTreeEnumeration::next(). // For details on how it works, see the implementation of // NTreeEnumeration::next(), which is very thoroughly // documented. idx = typeOrder_[level_]; // Check whether type_[idx] is out of range, // and if so then backtrack further up the tree. outOfRange = false; if (type_[idx] == 4) { // Quadrilateral column if (octLevel_ < 0) octLevel_ = level_; // Make it an octagon column else outOfRange = true; } else if (type_[idx] == 7) { // Octagon column octLevel_ = -1; outOfRange = true; } else if (idx >= nTets_ && type_[idx] == 2) // Triangle column outOfRange = true; if (outOfRange) { // Backtrack. type_[idx] = 0; --level_; if (level_ < 0) { // Out of options: there is no solution. return false; } ++type_[typeOrder_[level_]]; continue; } ++nVisited_; // Skip the domination test and the zero test (which // are irrelevant here). Note in particular that, if we // are searching for a surface with positive Euler // characteristic, the zero vector will not be a solution. // Prepare the tableaux for our new type at this level // of the tree. if (! type_[idx]) { // This is the first time we have visited this node. // Spin off clones and add some preliminary x_i = 0 // constraints as we go, reusing as much work as possible. if (idx < nTets_) { // We're working with a quadrilateral column. // // First, ignore type 0; instead step directly to // type 1 (which merges the old types 0 and 1 together). ++type_[idx]; // Now spin off clones for type 2 and 3, and // overwrite the parent tableaux in-place for type 1. // // As we go, we add the following constraints: // - type 1: x_{3k+1} = x_{3k+2} = 0 // - type 2: x_{3k+2} = 0 // - type 3: none // // If we allow octagons, we also spin off: // - type 4: none // - type 5: none // - type 6: x_{3k+2} = 0 if (octLevel_ < 0) { // We must support both quadrilaterals and octagons. nextSlot_[level_ + 1] = nextSlot_[level_] + 5; (nextSlot_[level_] + 1)->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 2)->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 3)->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 2); nextSlot_[level_]->initClone(*lpSlot_[level_]); (nextSlot_[level_] + 4)->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 1); } else { // We only support quadrilaterals. nextSlot_[level_ + 1] = nextSlot_[level_] + 2; (nextSlot_[level_] + 1)->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 2); nextSlot_[level_]->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(3 * idx + 1); } } else { // We're working with a triangle column. // // Since we are going to process type 0 right now, // mark which tableaux we will use. lpSlot_[level_ + 1] = lpSlot_[level_]; // Spin off a clone for type 1, and overwrite // the parent tableaux in-place for type 0. // // As we go, we add the following constraints: // - type 0: x_k = 0 // - type 1: none nextSlot_[level_ + 1] = nextSlot_[level_] + 1; nextSlot_[level_]->initClone(*lpSlot_[level_]); lpSlot_[level_]->constrainZero(2 * nTets_ + idx); } } if (type_[idx]) { // Find the appropriate clone that we spun off earlier when // type_[idx] was 0, and add any missing constraints. if (idx < nTets_) { // Quadrilateral columns (type is 1, 2 or 3, // or 4, 5 or 6 if we allow octagons): lpSlot_[level_ + 1] = (type_[idx] == 1 ? lpSlot_[level_] : nextSlot_[level_] + type_[idx] - 2); switch (type_[idx]) { // Nothing required for type 1, which already // has all necessary constraints. Since we merge // types 0 and 1 together, there is not even a // positivity constraint to add. case 2: lpSlot_[level_ + 1]->constrainZero(3 * idx); lpSlot_[level_ + 1]->constrainPositive(3 * idx + 1); break; case 3: lpSlot_[level_ + 1]->constrainZero(3 * idx); lpSlot_[level_ + 1]->constrainZero(3 * idx + 1); lpSlot_[level_ + 1]->constrainPositive(3 * idx + 2); break; case 4: lpSlot_[level_ + 1]->constrainZero(3 * idx); lpSlot_[level_ + 1]->constrainOct( 3 * idx + 1, 3 * idx + 2); break; case 5: lpSlot_[level_ + 1]->constrainZero(3 * idx + 1); lpSlot_[level_ + 1]->constrainOct(3 * idx, 3 * idx + 2); break; case 6: lpSlot_[level_ + 1]->constrainOct(3 * idx, 3 * idx + 1); break; } } else { // Triangle columns (type is 1): lpSlot_[level_ + 1] = nextSlot_[level_]; lpSlot_[level_ + 1]->constrainPositive(2 * nTets_ + idx); } } // Now all our constraints are enforced, and we can // simply test the tableaux for feasibility. if (lpSlot_[level_ + 1]->isFeasible()) { #ifdef REGINA_SURFACE_TRACE { dumpTypes(std::cout); std::cout << " (" << idx << " -> " << (int)type_[idx] << ")" << std::endl; NNormalSurfaceVector* v = new NNormalSurfaceVectorStandard(7 * nTets_); lpSlot_[level_ + 1]->extractSolution(*v, type_); NNormalSurface* f = new NNormalSurface( origTableaux_.tri(), v); std::cout << f->str() << std::endl; delete f; } #endif if (level_ < nTypes_ - 1) { // We pass the feasibility test, but we're // not at a leaf node. // Head deeper into the tree. if (level_ == nextZeroLevel_) { // We're avoiding vertex links, and we're // still in the upper region of the search // tree where we force triangles to be zero // as early as possible in the search. // // Either we've just started setting the current // triangle type to 0, or we've just finished // setting the current triangle type to 0. // Either way, we need to do some rearrangement // of the search tree now. if (! type_[idx]) { // We've just started setting this triangle // type to 0. // This means that we're happy, and we // can move onto quadrilaterals. // Make sure the type we process next is the // corresponding quadrilateral type. setNext((idx - nTets_) / 4); } else { // We've just finished setting this // triangle type to 0, and so we need to try // setting a new triangle type to 0 instead. // Find the next candidate triangle type, // and make sure we process it next. useTriangle = nextUnmarkedTriangleType(idx + 1); if (useTriangle >= 0) { setNext(useTriangle); ++nextZeroLevel_; } else { // There are no more triangles types left // to try setting to 0. // The only solutions remaining have all // unmarked triangle coordinates // positive, i.e., they involve multiples // of undesirable vertex links. We don't // want such solutions, // so abandon the search now. return false; } } } else if (typeOrder_[level_ + 1] < nTets_) { // The next level is a quadrilateral type. // See if we can't find a better quadrilateral // type to branch on instead. We will choose the // quadrilateral type that branches into the // fewest possible feasible subtrees. // Here we measure the old types 0, 1, 2, 3 as // four separate branches (not the three merged // branches 0=1, 2, 3 that we use in the actual // search). int bestQuad = -1; int minBranches = 5; // Greater than any soln. int tmp; for (int i = level_ + 1; i < nTypes_; ++i) { if (typeOrder_[i] < nTets_) { // It's an available quad type. #ifdef REGINA_NOOPT_MIN_FEASIBLE bestQuad = typeOrder_[i]; break; #else tmp = feasibleBranches(typeOrder_[i]); if (tmp < minBranches) { minBranches = tmp; bestQuad = typeOrder_[i]; if (tmp == 0) break; // Can't get any better! } #endif } } if (bestQuad >= 0) setNext(bestQuad); } ++level_; } else { // We pass the feasibility test, *and* we're at // a leaf node. This means we've found a solution! // However: we have no guarantee that it's a // vertex solution, since we merged quad types 0/1 // together. Explicitly try setting each type 1 // quadrilateral coordinate to zero, and change // the type to 0 if the system is still feasible. // If the system is not feasible, call // constrainPositive() to enact the change of // variable so that we can reconstruct the // surface correctly. for (int i = 0; i < nTets_; ++i) { if (type_[i] == 1) { tmpLP_[0].initClone(*lpSlot_[level_ + 1]); tmpLP_[0].constrainZero(3 * i); // TODO: Use initClone() here instead? if (tmpLP_[0].isFeasible()) { lpSlot_[level_ + 1]->constrainZero(3 * i); type_[i] = 0; } else { lpSlot_[level_ + 1]->constrainPositive( 3 * i); } } } return true; } } else { // We failed the feasibility test. // Abandon this subtree, increment the type at the // current level, and continue searching. ++type_[idx]; } } // If we ever make it out here, it's because some other // thread cancelled the search. The result should be ignored. return false; } } // namespace regina #endif regina-4.95/engine/enumerate/ntreetraversal.cpp000644 000765 000024 00000006405 12234011536 021610 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/ntreetraversal-impl.h" namespace regina { // Instantiate templates: template class NTreeTraversal; template class NTreeTraversal; template class NTreeEnumeration; template class NTreeEnumeration; template class NTreeTraversal; template class NTreeTraversal; template class NTreeSingleSoln; template class NTreeSingleSoln; #ifdef INT128_AVAILABLE template class NTreeTraversal >; template class NTreeEnumeration >; template class NTreeTraversal >; template class NTreeSingleSoln >; #endif } // namespace regina regina-4.95/engine/enumerate/ntreetraversal.h000644 000765 000024 00000155513 12234011536 021262 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntreetraversal.h * \brief Tree traversal methods for normal surface enumeration and * optimisation. */ #ifndef __NTREETRAVERSAL_H #ifndef __DOXYGEN #define __NTREETRAVERSAL_H #endif #include "enumerate/ntreeconstraint.h" #include "enumerate/ntreelp.h" #include "enumerate/ntypetrie.h" #include "utilities/nthread.h" namespace regina { class NProgressTracker; class NTriangulation; /** * \weakgroup enumerate * @{ */ /** * A base class for searches that employ the tree traversal algorithm for * enumerating and locating vertex normal surfaces. Users should not use this * base class directly; instead use one of the subclasses NTreeEnumeration (for * enumerating all vertex normal surfaces) or NTreeSingleSoln (for * locating a single non-trivial solution under additional constraints, * such as positive Euler characteristic). * * The full algorithms are described respectively in "A tree traversal * algorithm for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3, * and "A fast branching algorithm for unknot recognition with * experimental polynomial-time behaviour", Burton and Ozlen, arXiv:1211.1079. * * This base class provides the infrastructure for the search tree, and the * subclasses handle the mechanics of the moving through the tree according * to the backtracking search. The domination test is handled separately by * the class NTypeTrie, and the feasibility test is handled separately by the * class LPData. * * This class holds the particular state of the tree traversal * at any point in time, as described by the current \e level (indicating * our current depth in the search tree) and type vector * (indicating which branches of the search tree we have followed). * For details on these concepts, see the Algorithmica paper cited above. * The key details are summarised below; throughout this discussion * \a n represents the number of tetrahedra in the underlying triangulation. * * - In quadrilateral coordinates, the type vector is a sequence of \a n * integers, each equal to 0, 1, 2 or 3, where the ith integer * describes the choice of quadrilateral location in the ith * tetrahedron. * * - In standard coordinates, the type vector begins with the \a n * quadrilateral choices outlined above. This is then followed by an * additional 4n integers, each equal to 0 or 1; these * correspond to the 4n triangle coordinates, and indicate * whether each coordinate is zero or non-zero. * * In the original Algorithmica paper, we choose types in the order * type_[0], type_[1] and so on, working from the root of the tree down * to the leaves. Here we support a more flexible system: there is an * internal permutation \a typeOrder_, and we choose types in the order * type_[typeOrder_[0]], type_[typeOrder_[1]] and so on. This * permutation may mix quadrilateral and triangle processing, and may * even change as the algorithm runs. * * This class can also support octagon types in almost normal surfaces. * However, we still do our linear programming in standard or quadrilateral * coordinates, where we represent an octagon using two conflicting * quadrilaterals in the same tetrahedron (which meet the tetrahedron boundary * in the same set of arcs as a single octagon would). As with the almost * normal coordinate systems in NNormalSurfaceList, we allow multiple octagons * of the same type, but only one octagon type in the entire tetrahedron. * In the type vector, octagons are indicated by setting a quadrilateral * type to 4, 5 or 6. * * There is optional support for adding extra linear constraints * (such as a constraint on Euler characteristic), supplied by the * template parameter \a LPConstraint. If there are no additional * constraints, simply use the template parameter LPConstraintNone. * * Also, there is optional support for banning normal disc types (so the * corresponding coordinates must be set to zero), and/or marking normal * disc types (which currently affects what is meant by a "non-trivial" * surface for the NTreeSingleSoln algorithm, though this concept may be * expanded in future versions of Regina). These options are supplied * by the template parameter \a BanConstraint. If there are no disc * types to ban or mark, simply use the template parameter \a BanNone. * * In some cases, it is impossible to add the extra linear constraints * that we would like (for instance, if they require additional * preconditions on the underlying triangulation). If this is a possibility * in your setting, you should call constraintsBroken() to test for this * once the NTreeTraversal object has been constructed. * * The template argument \a Integer indicates the integer type that * will be used throughout the underlying linear programming machinery. * Unless you have a good reason to do otherwise, you should use the * arbitrary-precision NInteger class (in which integers can grow * arbitrarily large, and overflow can never occur). * * \pre The parameters LPConstraint and BanConstraint must be subclasses of * LPConstraintBase and BanConstraintBase respectively. See the * LPConstraintBase and BanConstraintBase class notes for further details. * * \apinotfinal * * \ifacespython Not present. */ template class REGINA_API NTreeTraversal : public BanConstraint { protected: // Global information about the search: const LPInitialTableaux origTableaux_; /**< The original starting tableaux that holds the adjusted matrix of matching equations, before the tree traversal algorithm begins. */ const NormalCoords coords_; /**< The coordinate system in which we are enumerating or searching for normal or almost normal surfaces. This must be one of NS_QUAD or NS_STANDARD if we are only supporting normal surfaces, or one of NS_AN_QUAD_OCT or NS_AN_STANDARD if we are allowing octagons in almost normal surfaces. */ const int nTets_; /**< The number of tetrahedra in the underlying triangulation. */ const int nTypes_; /**< The total length of a type vector. */ const int nTableaux_; /**< The maximum number of tableaux that we need to keep in memory at any given time during the backtracking search. */ // Details of the current state of the backtracking search: char* type_; /**< The current working type vector. As the search runs, we modify this type vector in-place. Any types beyond the current level in the search tree will always be set to zero. */ int* typeOrder_; /**< A permutation of 0,...,\a nTypes_-1 that indicates in which order we select types: the first type we select (at the root of the tree) is type_[typeOrder_[0]], and the last type we select (at the leaves of the tree) is type_[typeOrder_[nTypes_-1]]. This permutation is allowed to change as the algorithm runs (though of course you can only change sections of the permutation that correspond to types not yet selected). */ int level_; /**< The current level in the search tree. As the search runs, this holds the index into typeOrder_ corresponding to the last type that we chose. */ int octLevel_; /**< The level at which we are enforcing an octagon type (with a strictly positive number of octagons). If we are working with normal surfaces only (and so we do not allow any octagons at all), then \a octLevel_ = \a nTypes_. If we are allowing almost normal surfaces but we have not yet chosen an octagon type, then \a octLevel_ = -1. */ LPData* lp_; /**< Stores tableaux for linear programming at various nodes in the search tree. We only store a limited number of tableaux at any given time, and as the search progresses we overwrite old tableaux with new tableaux. More precisely, we store a linear number of tableaux, essentially corresponding to the current node in the search tree and all of its ancestores, all the way up to the root node. In addition to these tableaux, we also store other immediate children of these ancestores that we have pre-prepared for future processing. See the documentation within NTreeEnumeration::next() for details of when and how these tableaux are constructed. */ LPData** lpSlot_; /**< Recall from above that the array \a lp_ stores tableaux for the current node in the search tree and all of its ancestors. This means we have one tableaux for the root node, as well as additional tableaux at each level 0,1,...,\a level_. The array lpSlot_ indicates which element of the array \a lp_ holds each of these tableaux. Specifically: lpSlot_[0] points to the tableaux for the root node, and for each level \a i in the range 0,...,\a level_, the corresponding tableaux is *lpSlot_[i+1]. Again, see the documentation within NTreeEnumeration::next() for details of when and how these tableaux are constructed and later overwritten. */ LPData** nextSlot_; /**< Points to the next available tableaux in lp_ that is free to use at each level of the search tree. Specifically: nextSlot_[0] points to the next free tableaux at the root node, and for each level \a i in the range 0,...,\a level_, the corresponding next free tableaux is *nextSlot_[i+1]. The precise layout of the nextSlot_ array depends on the order in which we process quadrilateral and triangle types. */ unsigned long nVisited_; /**< Counts the total number of nodes in the search tree that we have visited thus far. This may grow much faster than the number of solutions, since it also counts traversals through "dead ends" in the search tree. */ LPData tmpLP_[4]; /**< Temporary tableaux used by the function feasibleBranches() to determine which quadrilateral types have good potential for pruning the search tree. Other routines are welcome to use these temporary tableaux also (as "scratch space"); however, be aware that any call to feasibleBranches() will overwrite them. */ public: /** * Indicates whether the given coordinate system is supported by * this tree traversal infrastructure. * * Currently this is true only for NS_STANDARD and NS_QUAD (for * normal surfaces), and NS_AN_STANDARD and NS_AN_QUAD_OCT (for * almost normal surfaces). Any additional restrictions imposed * by LPConstraint and BanConstraint will also be taken into account. * * @param coords the coordinate system being queried. * @return \c true if and only if this coordinate system is * supported. */ static bool supported(NormalCoords coords); /** * Indicates whether or not the extra constraints from the template * parameter \a LPConstraints were added successfully to the * infrastructure for the search tree. * * This query function is important because some constraints require * additional preconditions on the underlying triangulation, and * so these constraints cannot be added in some circumstances. * If it is possible that the constraints * might not be added successfully, this function should be * tested as soon as the NTreeTraversal object has been created. * * If the extra constraints were not added successfully, the search * tree will be left in a consistent state but will give incorrect * results (specifically, the extra constraints will be treated as * zero functions). * * @return \c true if the constraints were \e not added * successfully, or \c false if the constraints were added successfully. */ bool constraintsBroken() const; /** * Returns the total number of nodes in the search tree that we * have visited thus far in the tree traversal. * This figure might grow much faster than the number of solutions, * since it also counts traversals through "dead ends" in the * search tree. * * This counts all nodes that we visit, including those that fail * any or all of the domination, feasibility and zero tests. * The precise way that this number is calculated is subject to * change in future versions of Regina. * * If you called NTreeEnumeration::run() or NTreeSingleSoln::find(), * then this will be the total number of nodes that were visited * in the entire tree traversal. If you are calling * NTreeEnumeration::next() one surface at time, this will be the * partial count of how many nodes have been visited so far. * * @return the number of nodes visited so far. */ unsigned long nVisited() const; /** * Writes the current type vector to the given output stream. * There will be no spaces between the types, and there will be * no final newline. * * @param out the output stream to which to write. */ void dumpTypes(std::ostream& out) const; /** * Reconstructs the full normal surface that is represented by * the type vector at the current stage of the search. * * The surface that is returned will be newly constructed, and * it is the caller's responsibility to destroy it when it is no * longer required. * * If the current type vector does not represent a \e vertex * normal surface (which may be the case when calling * NTreeSingleSoln::find()), then there may be many normal surfaces * all represented by the same type vector; in this case there are * no further guarantees about \e which of these normal surfaces * you will get. * * \pre This tree traversal is at a point in the search where * it has found a feasible solution that represents a normal surface * (though this need not be a vertex surface). * This condition is always true after NTreeEnumeration::next() * or NTreeSingleSoln::find() returns \c true, or any time that * NTreeEnumeration::run() calls its callback function. * * @return a normal surface that has been found at the current stage * of the search. */ NNormalSurface* buildSurface() const; /** * Ensures that the given normal or almost normal surface satisfies * the matching equations, as well as any additional constraints * from the template parameter LPConstraint. * * This routine is provided for diagnostic, debugging and verification * purposes. * * Instead of using the initial tableaux to verify the matching * equations, this routine goes back to the original matching * equations matrix as constructed by regina::makeMatchingEquations(). * This ensures that the test is independent of any potential * problems with the tableaux. You are not required to pass * your own matching equations (if you don't, they will be temporarily * reconstructed for you); however, you may pass your own if you * wish to use a non-standard matching equation matrix, and/or * reuse the same matrix to avoid the overhead of reconstructing it * every time this routine is called. * * \pre The normal or almost normal surface \a s uses the same * coordinate system as was passed to the NTreeTraversal constructor. * * \pre If \a matchingEqns is non-null, then the number of columns * in \a matchingEqns is equal to the number of coordinates in the * underlying normal or almost normal coordinate system. * * @param s the normal surface to verify. * @param matchingEqns the matching equations to check against * the given surface; this may be 0, in which case the matching * equations will be temporarily reconstructed for you using * regina::makeMatchingEquations(). * @return \c true if the given surfaces passes all of the test * described above, or \c false if it fails one or more tests * (indicating a problem or error). */ bool verify(const NNormalSurface* s, const NMatrixInt* matchingEqns = 0) const; protected: /** * Initialises a new base object for running the tree traversal * algorithm. This routine may only be called by subclass constructors; * for more information on how to create and run a tree * traversal, see the subclasses NTreeEnumeration and * NTreeSingleSoln instead. * * \pre The given triangulation is non-empty. * * @param tri the triangulation in which we wish to search for * normal surfaces. * @param coords the coordinate system in which wish to search for * normal surfaces. This must be one of NS_QUAD, NS_STANDARD, * NS_AN_QUAD_OCT, or NS_AN_STANDARD. * @param branchesPerQuad the maximum number of branches we * spawn in the search tree for each quadrilateral type * (e.g., 4 for a vanilla normal surface tree traversal algorithm). * @param branchesPerTri the maximum number of branches we * spawn in the search tree for each triangle type * (e.g., 2 for a vanilla normal surface tree traversal algorithm). * @param enumeration \c true if we should optimise the tableaux * for a full enumeration of vertex surfaces, or \c false if we * should optimise the tableaux for an existence test (such as * searching for a non-trivial normal disc or sphere). */ NTreeTraversal(NTriangulation* tri, NormalCoords coords, int branchesPerQuad, int branchesPerTri, bool enumeration); /** * Destroys this object. */ ~NTreeTraversal(); /** * Rearranges the search tree so that \a nextType becomes the next * type that we process. * * Specifically, this routine will set typeOrder_[level_ + 1] * to \a nextType_, and will move other elements of typeOrder_ * back by one position to make space as required. * * \pre \a nextType is in the range 0,...,\a nTypes-1 inclusive. * \pre \a nextType is still waiting to be processed; that is, * \a nextType does not appear in the list * typeOrder_[0,...,level_]. * * @param nextType the next type to process. */ void setNext(int nextType); /** * Returns the next unmarked triangle type from a given starting * point. Specifically, this routine returns the first unmarked * triangle type whose type number is greater than or equal to * \a startFrom. For more information on marking, see the * BanConstraintBase class notes. * * This routine simply searches through types by increasing index * into the type vector; in particular, it does \e not make any use * of the reordering defined by the \a typeOrder_ array. * * \pre We are working in standard normal or almost normal * coordinates. That is, the coordinate system passed to the * NTreeTraversal constructor was one of NS_STANDARD * or NS_AN_STANDARD. * * \pre The argument \a startFrom is at least \a nTets_ (i.e., * it is at least as large as the index of the first triangle type). * * @param startFrom the index into the type vector of the triangle type * from which we begin searching. * @return the index into the type vector of the next unmarked * triangle type from \a startFrom onwards, or -1 if there are no * more remaining. */ int nextUnmarkedTriangleType(int startFrom); /** * Determines how many different values we could assign to the given * quadrilateral type and still obtain a feasible system. * * This will involve solving four linear programs, all based on * the current state of the tableaux at the current level of the * search tree. These assign 0, 1, 2 and 3 to the given * quadrilateral type and enforce the corresponding constraints; * here types 0 and 1 are counted separately as in NTreeEnumeration, * not merged together as in NTreeSingleSoln. * * \pre The given quadrilateral type has not yet been processed in * the search tree (i.e., it has not had an explicit value selected). * * @param quadType the quadrilateral type to examine. * @return the number of type values 0, 1, 2 or 3 that yield a * feasible system; this will be between 0 and 4 inclusive. */ int feasibleBranches(int quadType); /** * Gives a rough estimate as to what percentage of the way the * current type vector is through a full enumeration of the * search tree. This is useful for progress tracking. * * This routine only attemps to determine the percentage within * a reasonable range of error (at the time of writing, 0.01%). * This allows it to be more efficient (in particular, by only * examining the branches closest to the root of the search tree). * * @return the percentage, as a number between 0 and 100 inclusive. */ double percent() const; }; /** * The main entry point for the tree traversal algorithm to enumerate all * vertex normal or almost normal surfaces in a 3-manifold triangulation. * * This class essentially implements the algorithm from "A tree traversal * algorithm for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3. * * To enumerate all vertex surfaces for a given 3-manifold * triangulation, simply construct a NTreeTraversal object and call run(). * * Alternatively, you can have more fine-grained control over the search. * Instead of calling run(), you can construct a NTreeTraversal object and * repeatedly call next() to step through each vertex surface one at * a time. This allows you to pause and resume the search as you please. * * If you simply wish to detect a single non-trivial solution under * additional constraints (such as positive Euler characteristic), then * use the class NTreeSingleSoln instead, which is optimised for this purpose. * * This tree traversal can only enumerate surfaces in quadrilateral normal * coordinates (NS_QUAD), standard normal coordinates (NS_STANDARD), * quadrilateral-octagon almost normal coordinates (NS_AN_QUAD_OCT), or * standard almost normal coordinates (NS_AN_STANDARD). For almost * normal surfaces, we allow any number of octagons (including zero), * but we only allow at most one octagon \e type in the entire triangulation. * No coordinate systems other than these are supported. * * By using appropriate template parameters \a LPConstraint and/or * \a BanConstraint, it is possible to impose additional linear * constraints on the normal surface solution cone, and/or explicitly force * particular normal coordinates to zero. In this case, the notion of * "vertex surface" is modified to mean a normal surface whose coordinates * lie on an extreme ray of the restricted solution cone under these additional * constraints (and whose coordinates are integers with no common divisor). * See the LPConstraintBase and BanConstraintBase class notes for * details. * * The template argument \a Integer indicates the integer type that * will be used throughout the underlying linear programming machinery. * Unless you have a good reason to do otherwise, you should use the * arbitrary-precision NInteger class (in which integers can grow * arbitrarily large, and overflow can never occur). * * \pre The parameters LPConstraint and BanConstraint must be subclasses of * LPConstraintSubspace and BanConstraintBase respectively. Note in * particular that the base class LPConstraintBase is not enough here. * See the LPConstraintBase, LPConstraintSubspace and BanConstraintBase * class notes for further details. * * \warning Although the tree traversal algorithm can run in standard * normal or almost normal coordinates, this is not recommended: it is likely * to be \e much slower than in quadrilateral or quadrilateral-octagon * coordinates respectively. Instead you should enumerate vertex * solutions using quadrilateral or quadrilateral-octagon coordinates, and * then run the conversion procedure NNormalSurfaceList::quadToStandard() * or NNormalSurfaceList::quadOctToStandardAN(). * * \apinotfinal * * \ifacespython Not present. */ template class REGINA_API NTreeEnumeration : public NTreeTraversal { public: using NTreeTraversal::dumpTypes; protected: // Since we have a template base class, we need to explicitly // list the inherited member variables that we use. // Note that these are all protected in the base class, and so // we are not changing any access restrictions here. using NTreeTraversal::level_; using NTreeTraversal::lp_; using NTreeTraversal::lpSlot_; using NTreeTraversal::nextSlot_; using NTreeTraversal::nTets_; using NTreeTraversal::nTypes_; using NTreeTraversal::nVisited_; using NTreeTraversal::octLevel_; using NTreeTraversal::type_; using NTreeTraversal::typeOrder_; using NTreeTraversal:: feasibleBranches; using NTreeTraversal::percent; using NTreeTraversal::setNext; private: NTypeTrie<7> solns_; /**< A trie that holds the type vectors for all vertex surfaces found so far. We wastefully allow for 7 possible types always (which are required for almost normal surfaces); the performance loss from changing 4 to 7 is negligible. */ unsigned long nSolns_; /**< The number of vertex surfaces found so far. */ int lastNonZero_; /**< The index into typeOrder_ corresponding to the last non-zero type that was selected, or -1 if we still have the zero vector. Here "last" means "last chosen"; that is, the highest index into typeOrder_ that gives a non-zero type. */ public: /** * Creates a new object for running the tree traversal algorithm. * * This prepares the algorithm; in order to run the algorithm * and enumerate vertex surfaces, you can either: * * - call run(), which enumerates all vertex surfaces * with a single function call; * * - repeatedly call next(), which will step to the next vertex * surface each time you call it. * * \warning Although it is supported, it is highly recommended that you * do \e not run a full vertex enumeration in standard normal * or almost normal coordinates (this is for performance reasons). * See the class notes for further discussion and better alternatives. * In normal circumstances you should run a full vertex enumeration * in quadrilateral or quadrilateral-octagon coordinates only. * * \pre The given triangulation is non-empty. * * \pre Both the trianglation and the given coordinate system * adhere to any preconditions required by the template * parameters LPConstraint and BanConstraint. * * @param tri the triangulation in which we wish to enumerate * vertex surfaces. * @param coords the coordinate system in which wish to enumerate * vertex surfaces. This must be one of NS_QUAD, NS_STANDARD, * NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ NTreeEnumeration(NTriangulation* tri, NormalCoords coords); /** * Returns the total number of vertex normal or almost normal surfaces * found thus far in the tree traversal search. * * If you called run(), then this will simply be the total number of * vertex surfaces. If you are calling next() one * surface at time, this will be the partial count of how many * vertex surfaces have been found until now. * * @return the number of solutions found so far. */ unsigned long nSolns() const; /** * Runs the complete tree traversal algorithm to enumerate * vertex normal or almost normal surfaces. * * For each vertex surface that is found, this routine * will call the function \a useSoln. It will pass two * arguments to this function: (i) this tree enumeration object, * and (ii) an arbitrary piece of data that you can supply via * the argument \a arg. * * You can extract details of the solution directly from the * tree enumeration object: for instance, you can dump the type * vector using dumpTypes(), or you can reconstruct the full normal or * almost normal surface using buildSurface() and perform some other * operations upon it. If you do call buildSurface(), remember * to delete the normal surface once you are finished with it. * * The tree traversal will block until your callback function * \a useSoln returns. If the callback function returns \c true, * then run() will continue the tree traversal. If it returns * \c false, then run() will abort the search and return immediately. * * The usual way of using this routine is to construct a * NTreeEnumeration object and then immediately call run(). However, * if you prefer, you may call run() after one or more calls to next(). * In this case, run() will continue the search from the current point * and run it to its completion. In other words, run() will locate * and call \a useSoln for all vertex surfaces that had not yet * been found, but it will not call \a useSoln on those surfaces * that had previously been found during earlier calls to next(). * * \pre The tree traversal algorithm has not yet finished. * That is, you have not called run() before, and if you have * called next() then it has always returned \c true (indicating * that it has not yet finished the search). * * @param useSoln a callback function that will be called each * time we locate a vertex surface, as described above. * @param arg the second argument to pass to the callback * function; this may be any type of data that you like. */ void run(bool (*useSoln)(const NTreeEnumeration&, void*), void* arg = 0); /** * An incremental step in the tree traversal algorithm that * runs forward until it finds the next solution. * Specifically: this continues the tree traversal from the * current point until either it finds the next vertex normal * or almost normal surface (in which case it returns \c true), or * until the tree traversal is completely finished with no more * solutions to be found (in which case it returns \c false). * * If you simply wish to find and process all vertex surfaces, * you may wish to consider the all-in-one routine * run() instead. By using next() to step through one solution * at a time however, you obtain more fine-grained control: for * instance, you can "pause" and restart the search, or have * tighter control over multithreading. * * If next() does return \c true because it found a solution, * you can extract details of the solution directly from this * tree enumeration object: for instance, you can dump the type * vector using dumpTypes(), or you can reconstruct the full normal or * almost normal surface using buildSurface() and perform some other * operations upon it. If you do call buildSurface(), remember * to delete the normal surface once you are finished with it. * * An optional progress tracker may be passed. If so, this routine * will update the percentage progress and poll for cancellation * requests. It will be assumed that an appropriate stage has already * been declared via NProgressTracker::newStage() before this routine * is called, and that NProgressTracker::setFinished() will be * called after this routine returns (and presumably not until * the entire search tree is exhausted). * The percentage progress will be given in the context of a complete * enumeration of the entire search tree (i.e., it will typically * start at a percentage greater than 0, and end at a percentage less * than 100). * * \pre The tree traversal algorithm has not yet finished. * That is, you have not called run() before, and if you have * called next() then it has always returned \c true (indicating * that it has not yet finished the search). * * @param tracker a progress tracker through which progress * will be reported, or 0 if no progress reporting is required. * @return \c true if we found another vertex surface, or * \c false if the search has now finished and no more vertex * surfaces were found. */ bool next(NProgressTracker* tracker = 0); /** * A callback function that writes to standard output the type vector * at the current point in the given tree traversal search. * You can use this as the callback function \a useSoln that is * passed to run(). * * The type vector will be written on a single line, with no * spaces between types, with a prefix indicating which solution * we are up to, and with a final newline appended. * This output format is subject to change in future versions of Regina. * * The second (void*) argument is ignored. It is only present * for compatibility with run(). * * \pre The given tree traversal is at a point in the search where * it has reached the deepest level of the search tree and found a * feasible solution that represents a vertex normal or almost * normal surface. This is always the case any time after next() * returns \c true, or any time that run() calls its callback function. * * @param tree the tree traversal object from which we are * extracting the current type vector. * @return \c true (which indicates to run() that we should * continue the tree traversal). */ static bool writeTypes(const NTreeEnumeration& tree, void*); /** * A callback function that writes to standard output the full * triangle-quadrilateral coordinates of the vertex normal * or almost normal surface at the current point in the given * tree traversal search. You can use this as the callback function * \a useSoln that is passed to run(). * * The normal surface coordinates will be written on a single line, with * spaces and punctuation separating them, a prefix indicating which * solution we are up to, and a final newline appended. * This output format is subject to change in future versions of Regina. * * The second (void*) argument is ignored. It is only present * for compatibility with run(). * * \pre The given tree traversal is at a point in the search where * it has reached the deepest level of the search tree and found a * feasible solution that represents a vertex normal or almost * normal surface. This is always the case any time after next() * returns \c true, or any time that run() calls its callback function. * * @param tree the tree traversal object from which we are * extracting the current vertex normal or almost normal surface. * @return \c true (which indicates to run() that we should * continue the tree traversal). */ static bool writeSurface(const NTreeEnumeration& tree, void*); }; /** * The main entry point for the tree traversal / branching algorithm to locate * a single non-trivial normal surface satisfying given constraints within * a 3-manifold triangulation. The constraints are passed using a * combination of the template arguments LPConstraint and BanConstraint. * * A common application of this algorithm is to find a surface of positive * Euler characteristic, using the template argument LPConstraintEuler. * This is useful for tasks such as 0-efficiency testing * and prime decomposition (when this is done in standard normal coordinates), * and also 3-sphere recognition (when this is done in standard almost normal * coordinates). Indeed, the underlying algorithm is optimised for * precisely this application. * * By a "non-trivial" surface, we mean that at least one triangle coordinate * is zero. Philosophically this is to avoid vertex linking surfaces, * though if the triangulation has more than one vertex then this takes * on a different meaning. See the warning on this matter below. * * Be warned that this routine does not eliminate the zero vector, and so * the template argument LPConstraint should include at least one * constraint that eliminates the zero vector (e.g., positive Euler * characteristic). Otherwise this algorithm may simply return the zero * vector, and the information gained will not be very useful. * * For any given normal coordinate, this routine will always try setting * that coordinate to zero before it tries setting it to non-zero. In * other words, if it does find a surface satisfying the given constraints, * then it is guaranteed that the set of non-zero coordinate positions * will be minimal (though not necessary a global \e minimum). * In many settings (such as when using LPConstraintEuler), this guarantees * that the final surface (if it exists) will be a vertex normal or * almost normal surface. * * The underlying algorithm is described in "A fast branching algorithm for * unknot recognition with experimental polynomial-time behaviour", * Burton and Ozlen, arXiv:1211.1079, and uses significant material from * "A tree traversal algorithm for decision problems in knot theory and * 3-manifold topology", Burton and Ozlen, Algorithmica (to appear), * DOI 10.1007/s00453-012-9645-3. * * To use this class, i.e., to locate a non-trivial normal or almost normal * surface under the given constraints or to prove that no such surface exists, * you can simply construct a NTreeSingleSoln object and call find(). You can * then call buildSurface() to extract the details of the surface that was * found. * * If you wish to enumerate \e all vertex surfaces in a 3-manifold * triangulation (instead of finding just one), you should use the class * NTreeEnumeration instead. * * This tree traversal can only enumerate surfaces in quadrilateral normal * coordinates (NS_QUAD), standard normal coordinates (NS_STANDARD), * quadrilateral-octagon almost normal coordinates (NS_AN_QUAD_OCT), or * standard almost normal coordinates (NS_AN_STANDARD). For almost * normal surfaces, we allow any number of octagons (including zero), * but we only allow at most one octagon \e type in the entire triangulation. * No coordinate systems other than these are supported. * * The template argument \a Integer indicates the integer type that * will be used throughout the underlying linear programming machinery. * Unless you have a good reason to do otherwise, you should use the * arbitrary-precision NInteger class (in which integers can grow * arbitrarily large, and overflow can never occur). * * \warning Typically one should only use this class with \e one-vertex * triangulations (since otherwise, setting at least one triangle coordinate * to zero is not enough to rule out trivial vertex linking surfaces). * Of course there may be settings in which multiple vertices make sense * (for instance, in ideal triangulations with multiple cusps, or when * using ban constraints), and in such settings this class will still work * precisely as described. * * \warning If you examine the type vector (for instance, by calling * dumpTypes()), be aware that this class merges the old types 0 and 1 * together into a single branch of the search tree. This means that * type 0 never appears, and that type 1 could indicate \e either positive * quadrilaterals in the first position, or else no quadrilaterals at all. * * \pre The parameters LPConstraint and BanConstraint must be subclasses of * LPConstraintBase and BanConstraintBase respectively. See the * LPConstraintBase and BanConstraintBase class notes for further details. * * \apinotfinal * * \ifacespython Not present. */ template class REGINA_API NTreeSingleSoln : public NTreeTraversal { public: using NTreeTraversal::dumpTypes; protected: using NTreeTraversal::level_; using NTreeTraversal::lp_; using NTreeTraversal::lpSlot_; using NTreeTraversal::nextSlot_; using NTreeTraversal::nTets_; using NTreeTraversal::nTypes_; using NTreeTraversal::nVisited_; using NTreeTraversal::octLevel_; using NTreeTraversal:: origTableaux_; using NTreeTraversal::tmpLP_; using NTreeTraversal::type_; using NTreeTraversal::typeOrder_; using NTreeTraversal:: feasibleBranches; using NTreeTraversal:: nextUnmarkedTriangleType; using NTreeTraversal::percent; using NTreeTraversal::setNext; private: int nextZeroLevel_; /**< The next level in the search tree at which we will force some triangle coordinate to zero. We use this to avoid vertex links by dynamically reorganising the search tree as we run to ensure that at least one relevant triangle coordinate is set to zero at all stages and all levels of the search. */ bool cancelled_; /**< Has the search been cancelled by another thread? See the cancel() and cancelled() routines for details. */ regina::NMutex mCancel_; /**< A mutex used to serialise cancellation tests and requests. */ public: /** * Creates a new object for running the tree traversal / branching * algorithm to locate a non-trivial surface that satisfies the * chosen constraints. * * This constructor prepares the algorithm; in order to run the * algorithm you should call find(), which returns \c true or \c false * according to whether or not such a surface was found. * * \pre The given triangulation is non-empty. * * \pre Both the trianglation and the given coordinate system * adhere to any preconditions required by the template * parameters LPConstraint and BanConstraint. * * @param tri the triangulation in which we wish to search for a * non-trivial surface. * @param coords the normal or almost normal coordinate system in * which to work. This must be one of NS_QUAD, NS_STANDARD, * NS_AN_QUAD_OCT, or NS_AN_STANDARD. */ NTreeSingleSoln(NTriangulation* tri, NormalCoords coords); /** * Runs the tree traversal algorithm until it finds some non-trivial * surface that satisfies the chosen constraints, or else proves that * no such solution exists. * * Note that, if a solution is found, it will have a maximal * (but not necessarily maximum) set of zero coordinates, which * in some settings is enough to guarantee a vertex normal surface. * See the NTreeSingleSoln class notes for details. * * If find() does return \c true, you can extract details of the * corresponding surface directly from this tree enumeration * object: for instance, you can dump the type vector using * dumpTypes(), or you can reconstruct the full surface using * buildSurface(). Be warned that this class defines the type * vector in an unusual way (see the NTreeSingleSoln class notes * for details). If you call buildSurface(), remember * to delete the surface once you are finished with it. * * \pre The algorithm has not yet been run, i.e., you have not called * find() before. * * @return \c true if we found a non-trivial solution as described * in the class notes, or \c false if no such solution exists. */ bool find(); /** * Cancels the current find() operation. * * This may be called from another thread (it is thread-safe). * If called, it signals that if find() is currently running * then it should be cancelled at the earliest convenient opportunity. */ void cancel(); private: /** * Returns whether some thread has requested that the current * search operation be cancelled. This routine is thread-safe. * * See cancel() for details on how cancellation works. * * @return \c true if some thread has called cancel() on this object. */ bool cancelled() const; }; // Inline functions template inline bool NTreeTraversal::supported( NormalCoords coords) { return (coords == NS_STANDARD || coords == NS_AN_STANDARD || coords == NS_QUAD || coords == NS_AN_QUAD_OCT) && LPConstraint::supported(coords) && BanConstraint::supported(coords); } template inline bool NTreeTraversal:: constraintsBroken() const { return origTableaux_.constraintsBroken(); } template inline unsigned long NTreeTraversal:: nVisited() const { return nVisited_; } template inline void NTreeTraversal::dumpTypes( std::ostream& out) const { for (unsigned i = 0; i < nTypes_; ++i) out << static_cast(type_[i]); } template inline int NTreeTraversal:: nextUnmarkedTriangleType(int startFrom) { while (startFrom < nTypes_ && BanConstraint::marked_[2 * nTets_ + startFrom]) ++startFrom; return (startFrom == nTypes_ ? -1 : startFrom); } template inline NTreeEnumeration::NTreeEnumeration( NTriangulation* tri, NormalCoords coords) : NTreeTraversal(tri, coords, (coords == NS_AN_QUAD_OCT || coords == NS_AN_STANDARD ? 7 : 4) /* branches per quad */, 2 /* branches per triangle */, true /* enumeration */), nSolns_(0), lastNonZero_(-1) { } template inline unsigned long NTreeEnumeration:: nSolns() const { return nSolns_; } template inline void NTreeEnumeration::run( bool (*useSoln)(const NTreeEnumeration&, void*), void* arg) { while (next()) if (! useSoln(*this, arg)) return; } template inline bool NTreeEnumeration::writeTypes( const NTreeEnumeration& tree, void*) { std::cout << "SOLN #" << tree.nSolns() << ": "; tree.dumpTypes(std::cout); std::cout << std::endl; return true; } template inline bool NTreeEnumeration:: writeSurface(const NTreeEnumeration& tree, void*) { std::cout << "SOLN #" << tree.nSolns() << ": "; NNormalSurface* f = tree.buildSurface(); std::cout << f->str() << std::endl; delete f; return true; } template inline NTreeSingleSoln::NTreeSingleSoln( NTriangulation* tri, NormalCoords coords) : NTreeTraversal(tri, coords, (coords == NS_AN_QUAD_OCT || coords == NS_AN_STANDARD ? 6 : 3) /* branches per quad */, 2 /* branches per triangle */, false /* enumeration */), nextZeroLevel_(0), cancelled_(false) { } template inline void NTreeSingleSoln::cancel() { regina::NMutex::MutexLock lock(mCancel_); cancelled_ = true; } template inline bool NTreeSingleSoln::cancelled() const { regina::NMutex::MutexLock lock(mCancel_); return cancelled_; } } // namespace regina #endif regina-4.95/engine/enumerate/ntreetraversal.tcc000644 000765 000024 00000004700 12234011536 021573 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntreetraversal.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use ntreetraversal-impl.h instead. #include "enumerate/ntreetraversal-impl.h" regina-4.95/engine/enumerate/ntypetrie.cpp000644 000765 000024 00000012254 12234011536 020571 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/ntypetrie.h" namespace regina { template void NTypeTrie::insert(const char* entry, unsigned len) { // Strip off trailing zeroes. while (len > 0 && ! entry[len - 1]) --len; // Insert this type vector, creating new nodes only when required. NTypeTrie* node = this; const char* next = entry; for (int pos = 0; pos < len; ++pos, ++next) { if (! node->child_[*next]) node->child_[*next] = new NTypeTrie(); node = node->child_[*next]; } node->elementHere_ = true; } template bool NTypeTrie::dominates(const char* vec, unsigned len) const { // Strip off trailing zeroes. while (len > 0 && ! vec[len - 1]) --len; // At worst we have a recursive O(2^len) search on our hands. // Create a stack of options that describe which branch of the // trie we follow at each stage of the search. // // Here node[i] will store the next candidate node to try at // depth i in the tree (where the root is at depth 0), or 0 // if we have exhausted our options at that level of the search. const NTypeTrie** node = new const NTypeTrie*[len + 2]; int level = 0; node[0] = this; while (level >= 0) { if ((! node[level]) || level > len) { // If node[level] is 0, then we ran out of siblings // at this level. // If level > len, then any vector in this subtree // must have non-zero elements where vec only has zeros. // Either way, we need to backtrack. // Move back up one level... --level; // ... and then move to the next sibling at this (higher) // level. if (level > 0 && node[level] == node[level - 1]->child_[0] && vec[level - 1]) node[level] = node[level - 1]->child_[vec[level - 1]]; else if (level >= 0) node[level] = 0; continue; } // Process the node at the current level. if (node[level]->elementHere_) { // This node (padded with trailing zeroes) is // dominated by the given type vector. delete[] node; return true; } // Descend further into the tree. // // If vec[level] == 0, we must descend to child_[0]. // Otherwise we try child_[0] and then child_[type]. // // The following code sets node[level + 1] to the first non-zero // child in this selection, or to 0 if all such children are 0. if (node[level]->child_[0]) node[level + 1] = node[level]->child_[0]; else node[level + 1] = node[level]->child_[vec[level]]; ++level; } delete[] node; return false; } // Instantiate the templates! template class NTypeTrie<7>; } // namespace regina regina-4.95/engine/enumerate/ntypetrie.h000644 000765 000024 00000016526 12234011536 020244 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 2011-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ntypetrie.h * \brief A supporting data structure for tree traversal enumeration methods. */ #ifndef __NTYPETRIE_H #ifndef __DOXYGEN #define __NTYPETRIE_H #endif #include namespace regina { /** * \weakgroup enumerate * @{ */ /** * A trie that stores a set of type vectors of a fixed length. * * This class forms part of the tree traversal algorithm for enumerating * vertex normal surfaces, as described in "A tree traversal algorithm * for decision problems in knot theory and 3-manifold topology", * Burton and Ozlen, Algorithmica (to appear), DOI 10.1007/s00453-012-9645-3. * * A type vector is a sequence of digits, each between 0 and \a nTypes-1 * inclusive. Type vectors are represented as arrays of characters: * these are not strings, but simply sequences of one-byte integers. * In particular, you cannot print them (since they use raw integer * values, not ASCII digits). The length of a type vector must be * passed alongside it (i.e., there is no special terminating character). * * A type vector \a v is said to \e dominate \a u if, for each position * \a i, either v[i] == u[i] or else u[i] == 0. So, for instance, * (1,0,2,3) dominates (1,0,2,0), which in turn dominates (1,0,0,0). * Domination is a partial order, not a total order: for instance, * neither of (1,0,2,0) or (1,0,3,0) dominates the other. * * We assume that all type vectors used in this trie have the same * length. This is important, since we optimise the implementation by * ignoring trailing zeroes, which means that this trie cannot distinguish * between a vector \a v and the same vector with additional zeroes * appended to its end. * * Internally, each node of the trie is represented by a separate * NTypeTrie object, each of which is responsible for managing the * lifespan of its descendant nodes. Externally, a user only needs * to create and manage a single NTypeTrie object (which becomes * the root of the trie). * * \pre \a nTypes is at most 256. The typical value for \a nTypes for * normal surface enumeration is \a nTypes = 4. * * \ifacespython Not present. */ template class NTypeTrie { private: NTypeTrie* child_[nTypes]; /**< If this node is \a k levels deeper than the root of the trie (that is, it corresponds to the \a kth position in the type vector), then child_[i] stores the subtrie of type vectors \a v for which v[k] == i. */ bool elementHere_; /**< \c true if the path from the root of the trie to this node precisely describes the elements of some type vector in the set, ignoring any trailing zeroes. (In particular, the zero vector is in the set if and only if \a elementHere_ is \c true at the root node.) If this is \c false at a non-root node, then the fact that the node was ever constructed means that the path from the root to this node describes some \e prefix of a longer type vector in the set that has additional subsequent non-zero elements. */ public: /** * Initialises an empty trie. */ inline NTypeTrie(); /** * Destroys this trie. */ inline ~NTypeTrie(); /** * Resets this to the empty trie. */ inline void clear(); /** * Inserts the given type vector into this trie. * * \pre The given length \a len is non-zero, and is fixed throughout * the life of this trie; that is, it is the same every time * insert() or dominates() is called. * * @param entry the type vector to insert. * @param len the number of elements in the given type vector. */ void insert(const char* entry, unsigned len); /** * Determines whether the given type vector dominates any vector * in this trie. * * \pre The given length \a len is non-zero, and is fixed throughout * the life of this trie; that is, it is the same every time * insert() or dominates() is called. * * @param vec the type vector to test. * @param len the number of elements in the given type vector. * @return \c true if and only if \a vec dominates some type * vector stored in this trie. */ bool dominates(const char* vec, unsigned len) const; }; // Inline functions for NTypeTrie template inline NTypeTrie::NTypeTrie() : elementHere_(false) { ::memset(child_, 0, sizeof(NTypeTrie*) * nTypes); } /** * Destroys this trie. */ template inline NTypeTrie::~NTypeTrie() { for (int i = 0; i < nTypes; ++i) delete child_[i]; } /** * Resets this to the empty trie. */ template inline void NTypeTrie::clear() { for (int i = 0; i < nTypes; ++i) { delete child_[i]; child_[i] = 0; } elementHere_ = false; } } // namespace regina #endif regina-4.95/engine/enumerate/ordering.h000644 000765 000024 00000012226 12234011536 020023 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file enumerate/ordering.h * \brief Provides different ways of sorting hyperplanes (or matching * equations) when performing normal surface enumeration. */ #ifndef __ORDERING_H #ifndef __DOXYGEN #define __ORDERING_H #endif #include "regina-core.h" #include "maths/nmatrixint.h" namespace regina { /** * \weakgroup enumerate * @{ */ /** * A comparison object that sorts hyperplanes by position vectors. * This ordering is described in "Optimizing the double description * method for normal surface enumeration", B.A. Burton, * Mathematics of Computation 79 (2010), 453-484. * * This comparison object is used to sort hyperplanes into a good * order before enumerating vertex or fundamental normal surfaces * A hyperplane is described by a row of the \a subspace matrix, * as passed to an enumeration routine such as * NDoubleDescription::enumerateExtremalRays() or * NHilbertDual::enumerateHilbertBasis(). * * The ordering is defined as follows. For each hyperplane, we * create a position vector (h_1, ..., h_f), where h_i is 0 if the * hyperplane contains the ith coordinate axis, or 1 if not. * We then compare these position vectors lexicographically. * * \ifacespython Not present. */ class NPosOrder { private: const NMatrixInt& matrix_; /**< The \a subspace matrix as passed to the enumeration routine. */ public: /** * Creates a new helper object for comparing hyperplanes. * * @param matrix the \a subspace matrix as passed to * the normal surface enumeration routine. */ inline NPosOrder(const NMatrixInt& matrix); /** * Determines whether the hyperplane described by * row \a i of the matrix is smaller * than the hyperplane described by row \a j. * Here "smaller" is defined by position vectors; * see the NPosOrder class notes for details. * * @param i the first matrix row index; this must be between * 0 and matrix.rows()-1 inclusive, where \a matrix is * the matrix passed to the class constructor. * @param j the second matrix row index; this must also be * between 0 and matrix.rows()-1 inclusive. * @return \c true if and only if the hyperplane described by * row \a i is smaller than the hyperplane described by row \a j. */ inline bool operator () (long i, long j) const; }; /*@}*/ // Inline functions for NPosOrder inline NPosOrder::NPosOrder(const NMatrixInt& matrix) : matrix_(matrix) { } inline bool NPosOrder::operator () ( long i, long j) const { for (unsigned long c = 0; c < matrix_.columns(); ++c) { if (matrix_.entry(i, c) == 0 && matrix_.entry(j, c) != 0) return true; if (matrix_.entry(i, c) != 0 && matrix_.entry(j, c) == 0) return false; } return false; } } // namespace regina #endif regina-4.95/engine/file/CMakeLists.txt000644 000765 000024 00000000742 12234011536 017533 0ustar00babstaff000000 000000 # file # Files to compile SET ( FILES nfileinfo nglobaldirs nxmlcallback nxmlfile) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} file/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nfileinfo.h nglobaldirs.h nxmlcallback.h nxmlelementreader.h nxmlfile.h DESTINATION ${INCLUDEDIR}/file COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/file/nfileinfo.cpp000644 000765 000024 00000014702 12234011536 017451 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "file/nfileinfo.h" #include "utilities/zstream.h" namespace regina { #define STARTS_FALSE 0 #define STARTS_TRUE 1 #define STARTS_COULD_NOT_OPEN 2 // const int NFileInfo::TYPE_BINARY = 1; // OBSOLETE as of Regina 4.94. const int NFileInfo::TYPE_XML = 2; namespace { /** * Does the given file begin with the given set of characters? * * Returns STARTS_FALSE, STARTS_TRUE or STARTS_COULD_NOT_OPEN. */ int fileStartsWith(const char* file, const char* prefix) { FILE* f = fopen(file, "rb"); if (! f) return STARTS_COULD_NOT_OPEN; int ans = STARTS_FALSE; size_t len = strlen(prefix); char* buf = new char[len]; if (fread(buf, sizeof(char), len, f) == len) if (strncmp(buf, prefix, len) == 0) ans = STARTS_TRUE; delete[] buf; fclose(f); return ans; } } NFileInfo* NFileInfo::identify(const std::string& idPathname) { // Check for an old-style binary file. int starts = fileStartsWith(idPathname.c_str(), "compressed = false; } else { regina::DecompressionStream in(idPathname.c_str()); if (in) { std::string s; in >> s; if ((! in.eof()) && (s == "compressed = true; } } } if (ans) { ans->pathname = idPathname; ans->type = NFileInfo::TYPE_XML; ans->typeDescription = "XML Regina data file"; // Make it an invalid file until we know otherwise. ans->invalid = true; regina::DecompressionStream in(idPathname.c_str()); if (! in) return ans; std::string s; // Start by slurping in the opening "> s; if (s != "". // Try skipping through several strings in case there are extra // arguments in the XML prologue (such as encoding or standalone // declarations). int i; for (i = 0; ; i++) { if (in.eof()) return ans; in >> s; if (s.length() >= 2 && s[s.length() - 2] == '?' && s[s.length() - 1] == '>') break; // If we can't find it after enough tries, just give up. // Ten tries should be more than sufficient, since the current XML // spec supports only version, encoding and standalone arguments // at present. if (i >= 10) return ans; } // The next thing we see should be the element. if (in.eof()) return ans; in >> s; if (s != "> s; if (s.length() < 8) return ans; if (s.substr(0, 8).compare("engine=\"") != 0) return ans; // We've found the engine attribute; extract its value. std::string::size_type pos = s.find('"', 8); if (pos == std::string::npos) return ans; ans->engine = s.substr(8, pos - 8); // That's as far as we need to go; we've extracted everything we want. ans->invalid = false; return ans; } // Unknown format. return 0; } void NFileInfo::writeTextShort(std::ostream& out) const { out << "File information: " << typeDescription; if (compressed) out << " (compressed)"; } void NFileInfo::writeTextLong(std::ostream& out) const { out << "Regina data\n" << typeDescription; if (compressed) out << " (compressed)"; out << '\n'; if (invalid) out << "File contains invalid metadata.\n"; else out << "Engine " << engine << '\n'; } } // namespace regina regina-4.95/engine/file/nfileinfo.h000644 000765 000024 00000016031 12234011536 017113 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file file/nfileinfo.h * \brief Deals with determining information about Regina data files. */ #ifndef __NFILEINFO_H #ifndef __DOXYGEN #define __NFILEINFO_H #endif #include "regina-core.h" #include "shareableobject.h" namespace regina { /** * \weakgroup file * @{ */ /** * Stores information about a Regina data file, including file type and * version. * * Routine identify() can be used to determine this information for a * given file. * * As of Regina 4.94, the old-style binary files are no longer supported. * These have not been in use for over a decade. The only file type * that this class now recognises is TYPE_XML (compressed or uncompressed * XML data files). */ class REGINA_API NFileInfo : public ShareableObject { public: static const int TYPE_XML; /**< Represents a new-style XML data file. */ private: std::string pathname; /**< The pathname of the data file being described. */ int type; /**< The type of data file; this will be one of the file type constants defined in this class. */ std::string typeDescription; /**< A human-readable description of the type of data file. */ std::string engine; /**< The version of the calculation engine that wrote this file. */ bool compressed; /**< \c true if this file is stored in compressed format, \c false otherwise. Currently this option only applies to XML data files. */ bool invalid; /**< \c true if the file metadata could not be read, \c false otherwise. */ public: /** * Returns the pathname of the data file being described. * * \i18n The \ref i18n "character encoding" used in the pathname will * be whatever was originally passed to identify(). This might or * might not be UTF-8, since it needs to be understood by the * low-level C/C++ file I/O routines. * * @return the pathname. */ const std::string& getPathname() const; /** * Returns the type of data file. The type will be given as one * of the file type constants defined in this class. * * @return the type of data file. */ int getType() const; /** * Returns a human-readable description of the type of data file. * * @return a description of the type of data file. */ const std::string& getTypeDescription() const; /** * Returns the version of the calculation engine that wrote this file. * * @return the engine version for this file. */ const std::string& getEngine() const; /** * Returns whether this file is stored in compressed format. * Currently this option only applies to XML data files. * * @return \c true if this file is compressed or \c false otherwise. */ bool isCompressed() const; /** * Returns whether the file metadata could not be read. * * @return \c true if the metadata could not be read, \c false * otherwise. */ bool isInvalid() const; /** * Return information about the given Regina data file. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given path \e name, * and simply passes it through unchanged to low-level C/C++ file I/O * routines. If an NFileInfo structure is returned, its getPathname() * routine will use the same encoding that is passed here. * * @param idPathname the pathname of the data file to be examined. * @return a newly created NFileInfo structure containing * information about the given file, or 0 if the file type could not * be identified. */ static NFileInfo* identify(const std::string& idPathname); void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Create a new uninitialised structure. */ NFileInfo(); }; /*@}*/ // Inline functions for NFileInfo inline NFileInfo::NFileInfo() { } inline const std::string& NFileInfo::getPathname() const { return pathname; } inline int NFileInfo::getType() const { return type; } inline const std::string& NFileInfo::getTypeDescription() const { return typeDescription; } inline const std::string& NFileInfo::getEngine() const { return engine; } inline bool NFileInfo::isCompressed() const { return compressed; } inline bool NFileInfo::isInvalid() const { return invalid; } } // namespace regina #endif regina-4.95/engine/file/nglobaldirs.cpp000644 000765 000024 00000006011 12235500316 017772 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "regina-config.h" #include "file/nglobaldirs.h" namespace regina { std::string NGlobalDirs::home_(REGINA_DATADIR); std::string NGlobalDirs::pythonModule_(REGINA_PYLIBDIR); std::string NGlobalDirs::home() { return home_; } std::string NGlobalDirs::pythonModule() { return pythonModule_; } std::string NGlobalDirs::pythonLibs() { return home_ + "/pylib"; } std::string NGlobalDirs::examples() { return home_ + "/examples"; } std::string NGlobalDirs::engineDocs() { return home_ + "/engine-docs"; } std::string NGlobalDirs::data() { return home_ + "/data"; } void NGlobalDirs::setDirs(const std::string& homeDir, const std::string& pythonModuleDir) { home_ = homeDir; pythonModule_ = pythonModuleDir; } } // namespace regina regina-4.95/engine/file/nglobaldirs.h000644 000765 000024 00000020745 12235500316 017451 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file file/nglobaldirs.h * \brief Gives information about system installation directories. */ #ifndef __NGLOBALDIRS_H #ifndef __DOXYGEN #define __NGLOBALDIRS_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup file * @{ */ /** * Provides global routines that return directories in which various * components of Regina are installed on the system. * * By default, these routines are only useful with a fixed filesystem * installation of Regina (e.g., a typical Linux install). Specifically, * they return the relevant directories as they were configured by \e cmake * at build time. * * If Regina may have been moved around on the filesystem (e.g., if you are * running an app bundle on MacOS), you \e must call setDirs() when your * application starts. Otherwise the directories that NGlobalDirs returns * might be incorrect, and might not even exist. * * At present this class does not support running Regina directly out of * the source tree. This might be supported in future versions of Regina. */ class REGINA_API NGlobalDirs { public: /** * Returns Regina's primary home directory on the system. This * directory should contains subdirectories \e icons/, * \e examples/ and so on. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's primary home directory. */ static std::string home(); /** * Returns the directory in which Regina's python module is installed, * or the empty string if the module is installed in python's * standard site-packages directory. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's python module directory. */ static std::string pythonModule(); /** * Returns the directory in which optional "helper" Python libraries * are installed. These libraries are not a formal part of Regina, * but can be made to load automatically as extra user libraries * through Regina's python settings. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's optional Python library directory. */ static std::string pythonLibs(); /** * Returns the directory in which example data files and census * data files are installed. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's example and census data directory. */ static std::string examples(); /** * Returns the directory in which API documentation for Regina's * calculation engine is installed. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's calculation engine documentation directory. */ static std::string engineDocs(); /** * Returns the directory containing internal data files for Regina's * calculation engine. * * \warning If Regina is not installed in the exact location * configured at compile time (e.g., if you are running a MacOSX * app bundle), you \e must call setDirs() before calling this routine. * \warning If you are running out of the source tree, this * routine will almost certainly return an incorrect (and possibly * non-existent) directory. * * @return Regina's calculation engine data directory. */ static std::string data(); /** * Tells Regina where data files are installed. This is * necessary if Regina is not installed in the location that was * configured by \e cmake at build time (e.g., if you are * running a MacOSX app bundle). * * @param homeDir Regina's primary home directory; this will be * returned by homeDir(). * @param pythonModuleDir the directory containing Regina's * python module, or the empty string if the module has been * installed in python's standard site-packages directory; * this will be returned by pythonModule(). */ static void setDirs(const std::string& homeDir, const std::string& pythonModuleDir); private: static std::string home_; /**< Regina's primary home directory. */ static std::string pythonModule_; /**< The directory containing Regina's python module. */ }; /*@}*/ } // namespace regina #endif regina-4.95/engine/file/nxmlcallback.cpp000644 000765 000024 00000012450 12234011536 020131 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "file/nxmlcallback.h" namespace regina { const int NXMLCallback::WAITING = 1; const int NXMLCallback::WORKING = 2; const int NXMLCallback::DONE = 3; const int NXMLCallback::ABORTED = 4; NXMLCallback::~NXMLCallback() { if (! readers.empty()) abort(); } void NXMLCallback::start_document(regina::xml::XMLParser* parser) { topReader.usingParser(parser); } void NXMLCallback::end_document() { if (state == WAITING) { errStream << "XML Fatal Error: File contains no tags." << std::endl; abort(); } else if (state == WORKING || ! readers.empty()) { errStream << "XML Fatal Error: Unfinished file." << std::endl; abort(); } } void NXMLCallback::start_element(const std::string& n, const regina::xml::XMLPropertyDict& p) { if (state == DONE) { errStream << "XML Fatal Error: File contains multiple top-level tags." << std::endl; abort(); } else if (state == WAITING) { currentReader()->startElement(n, p, 0); currChars = ""; charsAreInitial = true; state = WORKING; } else if (state == WORKING) { NXMLElementReader* current = currentReader(); if (charsAreInitial) current->initialChars(currChars); NXMLElementReader* child = current->startSubElement(n, p); readers.push(child); child->startElement(n, p, current); currChars = ""; charsAreInitial = true; } } void NXMLCallback::end_element(const std::string& n) { if (state == WORKING) { NXMLElementReader* current = currentReader(); if (charsAreInitial) { charsAreInitial = false; current->initialChars(currChars); } current->endElement(); if (readers.empty()) { // In this case, current is the top-level reader. state = DONE; } else { // In this case, current is at the top of the stack. readers.pop(); currentReader()->endSubElement(n, current); delete current; } } } void NXMLCallback::characters(const std::string& s) { if (state == WORKING) if (charsAreInitial) currChars += s; } void NXMLCallback::warning(const std::string& s) { errStream << "XML Warning: " << s << std::endl; } void NXMLCallback::error(const std::string& s) { errStream << "XML Error: " << s << std::endl; abort(); } void NXMLCallback::fatal_error(const std::string& s) { errStream << "XML Fatal Error: " << s << std::endl; abort(); } void NXMLCallback::abort() { if (state == ABORTED) return; state = ABORTED; // Make sure we don't delete a child reader until we've called // abortElement() on its parent. NXMLElementReader* child = 0; while (! readers.empty()) { readers.top()->abort(child); if (child) delete child; child = readers.top(); readers.pop(); } topReader.abort(child); if (child) delete child; } } // namespace regina regina-4.95/engine/file/nxmlcallback.h000644 000765 000024 00000015604 12234011536 017602 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file file/nxmlcallback.h * \brief Deals with parsing XML program data at the file level. */ #ifndef __NXMLCALLBACK_H #ifndef __DOXYGEN #define __NXMLCALLBACK_H #endif #include #include #include "regina-core.h" #include "file/nxmlelementreader.h" namespace regina { /** * \weakgroup file * @{ */ /** * Provides the callbacks for an XMLParser required to parse an entire * file using a series of NXMLElementReader objects. * See the NXMLElementReader class notes for details of precisely how * processing will take place. * * \ifacespython Not present. */ class REGINA_API NXMLCallback : public regina::xml::XMLParserCallback { public: static const int WAITING; /**< Signifies that the top-level XML element has not yet been seen. */ static const int WORKING; /**< Signifies that XML elements are currently being processed. */ static const int DONE; /**< Signifies that processing of all XML elements has finished. */ static const int ABORTED; /**< Signifies that XML processing was aborted. */ private: NXMLElementReader& topReader; /**< The top-level element reader. */ std::stack readers; /**< A stack of all currently active element readers. */ std::ostream& errStream; /**< The output stream to use for warning or error messages. */ std::string currChars; /**< The initial characters that have currently been received for the current deepest-level XML element. */ bool charsAreInitial; /**< \c true if and only if we have not yet finished receiving initial characters for the current deepest-level XML element. */ int state; /**< The current state of this callback object; this will be one of the state constants defined in this class. */ public: /** * Creates a new callback object. * * @param newTopReader the element reader to use for the * top-level XML element. This is the only element reader that * will not be destroyed once parsing has finished. * @param newErrStream the output stream to which any warning or * error messages should be sent. */ NXMLCallback(NXMLElementReader& newTopReader, std::ostream& newErrStream); /** * Destroys this callback object. Any element reader (aside from * the top-level reader) that has not yet been destroyed will * have abort() called upon it and will be destroyed at this point. */ virtual ~NXMLCallback(); /** * Returns the state that this callback object is currently in. * The returned value will be one of the state constants defined * in this class. * * @return the current state of this callback object. */ int getState() const; /** * Aborts processing of the XML file completely. The XMLParser * may continue sending information but it will be completely * ignored by this NXMLCallback object from this point onwards. * * All currently active readers will have * NXMLElementReader::abort() called upon them and all except for * the top-level reader will be destroyed. */ void abort(); virtual void start_document(regina::xml::XMLParser* parser); virtual void end_document(); virtual void start_element(const std::string& n, const regina::xml::XMLPropertyDict& p); virtual void end_element(const std::string& n); virtual void characters(const std::string& s); virtual void warning(const std::string& s); virtual void error(const std::string& s); virtual void fatal_error(const std::string& s); private: /** * Returns the element reader processing the deepest-level * XML element that is currently being parsed. * * @return the current deepest element reader. */ NXMLElementReader* currentReader(); }; /*@}*/ // Inline functions for NXMLCallback inline NXMLCallback::NXMLCallback(NXMLElementReader& newTopReader, std::ostream& newErrStream) : topReader(newTopReader), errStream(newErrStream), charsAreInitial(true), state(WAITING) { } inline NXMLElementReader* NXMLCallback::currentReader() { return (readers.empty() ? &topReader : readers.top()); } inline int NXMLCallback::getState() const { return state; } } // namespace regina #endif regina-4.95/engine/file/nxmlelementreader.h000644 000765 000024 00000025067 12234011536 020666 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file file/nxmlelementreader.h * \brief Deals with parsing XML program data at the tag level. */ #ifndef __NXMLELEMENTREADER_H #ifndef __DOXYGEN #define __NXMLELEMENTREADER_H #endif #include "regina-core.h" #include "utilities/xmlutils.h" namespace regina { /** * \weakgroup file * @{ */ /** * Used to read the contents of a single XML element. Specifically, this * class concerns itself with reading and storing the contents between a * single opening tag and the corresponding closing tag. It is not concerned * with reading subelements of the element in question, although the contents * of subelements will be made available. * * Generally a subclass of NXMLElementReader will be used to receive and * store information that you care about. However, if you simply wish to * ignore the contents of a particular XML element (and all of its * subelements), you can use class NXMLElementReader itself for the * element(s) you wish to ignore. * * When the parser runs through a particular XML element, the routines of * the corresponding NXMLElementReader will be called as follows. First * startElement() and initialChars() will be called. Then for each * subelement encountered the following processing will take place: * startSubElement() will be called to create a new child reader, the entire * cycle of parsing routines will be called upon this child reader and * then endSubElement() will be called upon the parent reader, \e after * which the child reader will be destroyed. After all subelements have * been processed, endElement() will be called. * * If at any point parsing is aborted, routine abort() will be called * upon all active readers and all active readers will be destroyed. * * To parse an entire XML file using a variety of element readers (all of * which may be of different subclasses of NXMLElementReader), create a * new regina::xml::XMLParser with an NXMLCallback as its corresponding * callback object. * * When parsing begins on an entire XML file, an additional call is made: * usingParser() will be called on the top-level element reader, so that * it can gain direct access to the parser if required. * * \ifacespython Not present. */ class REGINA_API NXMLElementReader { public: /** * Creates a new element reader. */ NXMLElementReader(); /** * Destroys this element reader. * * The default implementation does nothing. */ virtual ~NXMLElementReader(); /** * Signifies that parsing of this XML element is beginning. * * The default implementation does nothing. * * @param tagName the name of the opening tag for this element. * @param tagProps the properties associated with the opening tag. * @param parentReader the reader currently parsing the parent XML * element, or 0 if this is the top-level element. If this * paraneter is non-zero, it is guaranteed that startSubElement() * has already been called upon the parent reader. */ virtual void startElement(const std::string& tagName, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader* parentReader); /** * Signifies that the initial text belonging to this XML element * has been read. The initial text is everything between the * opening tag and the first subelement or closing tag. * * The default implementation does nothing. * * @param chars the initial text for this element. */ virtual void initialChars(const std::string& chars); /** * Signifies that a subelement of this XML element is about to be * parsed. * * The default implementation returns a new NXMLElementReader * which can be used to ignore the subelement completely. * * @param subTagName the name of the subelement opening tag. * @param subTagProps the properties associated with the * subelement opening tag. * @return a newly created element reader that will be used to * parse the subelement. This class should \e not take care of * the new reader's destruction; that will be done by the parser. */ virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); /** * Signifies that parsing has finished for a subelement of this * XML element. * * The default implementation does nothing. * * @param subTagName the name of the subelement closing tag. * @param subReader the child reader that was used to parse the * subelement (this is the reader that was returned by the * corresponding startSubElement() call). It is guaranteed that * endElement() has already been called upon this child reader * and that the child reader has not yet been destroyed. */ virtual void endSubElement(const std::string& subTagName, NXMLElementReader* subReader); /** * Signifies that parsing of this XML element is finished. * * It is guaranteed that endSubElement() has not yet been called * upon the parent reader (if one exists). * * The default implementation does nothing. */ virtual void endElement(); /** * Called for the top-level element in an XML file when parsing * begins. This allows direct access to the parser if needed * (for instance, to change the character encoding). * * The default implementation does nothing. * * @param parser the current XML parser. */ virtual void usingParser(regina::xml::XMLParser* parser); /** * Signifies that XML parsing has been aborted. * This element reader will be destroyed shortly after this * routine is called. * * The default implementation does nothing. * * @param subReader the corresponding child reader if a * subelement is currently being parsed, or 0 otherwise. If this * parameter is non-zero, it is guaranteed that abort() has * already been called upon the child reader and that the child * reader has not yet been destroyed. */ virtual void abort(NXMLElementReader* subReader); }; /** * A reader for an XML element that contains only characters. * Any XML subelements will be ignored (as will any characters occurring * after any subelements). * * \ifacespython Not present. */ class REGINA_API NXMLCharsReader : public NXMLElementReader { private: std::string readChars; /**< The characters stored in this XML element. */ public: /** * Creates a new XML element reader. */ NXMLCharsReader(); /** * Returns the characters stored in the XML element that has * been read. * * @return the characters stored in the XML element. */ const std::string& getChars(); virtual void initialChars(const std::string& chars); }; /*@}*/ // Inline functions for NXMLElementReader inline NXMLElementReader::NXMLElementReader() { } inline NXMLElementReader::~NXMLElementReader() { } inline void NXMLElementReader::startElement(const std::string&, const regina::xml::XMLPropertyDict&, NXMLElementReader*) { } inline void NXMLElementReader::initialChars(const std::string&) { } inline NXMLElementReader* NXMLElementReader::startSubElement( const std::string&, const regina::xml::XMLPropertyDict&) { return new NXMLElementReader(); } inline void NXMLElementReader::endSubElement(const std::string&, NXMLElementReader*) { } inline void NXMLElementReader::endElement() { } inline void NXMLElementReader::usingParser(regina::xml::XMLParser*) { } inline void NXMLElementReader::abort(NXMLElementReader*) { } // Inline functions for NXMLCharsReader inline NXMLCharsReader::NXMLCharsReader() { } inline const std::string& NXMLCharsReader::getChars() { return readChars; } inline void NXMLCharsReader::initialChars(const std::string& chars) { readChars = chars; } } // namespace regina #endif regina-4.95/engine/file/nxmlfile.cpp000644 000765 000024 00000024541 12236524106 017324 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "engine.h" #include "file/nfileinfo.h" #include "file/nxmlcallback.h" #include "file/nxmlfile.h" #include "packet/ncontainer.h" #include "packet/nxmlpacketreader.h" #include "packet/nxmltreeresolver.h" #include "utilities/stringutils.h" #include "utilities/zstream.h" namespace regina { namespace { /** * Reads the outermost \ XML element. */ class ReginaDataReader : public regina::NXMLPacketReader { private: NContainer container; /**< Sits above the entire packet tree read from file. */ bool isReginaData; /**< Are we actually reading a \ element? */ std::string version; /**< The version of Regina that created this file, or the empty string if this is not known. */ public: /** * Create a new top-level reader. */ ReginaDataReader(NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), isReginaData(false) { } virtual NPacket* getPacket() { if (isReginaData) return &container; else return 0; } const std::string& getVersion() const { return version; } virtual void startElement(const std::string& n, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { if (n == "reginadata") { isReginaData = true; regina::xml::XMLPropertyDict::const_iterator it = props.find("engine"); if (it != props.end()) version = stripWhitespace(it->second); } } virtual void abort(NXMLElementReader*) { // Delete all children of the top-level container. while (NPacket* child = container.getFirstTreeChild()) { child->makeOrphan(); delete child; } } }; /** * The number of characters by which we expect to have finished the * opening tag in an old pre-utf8 XML data stream. */ const int regDataOpenBy = 200; /** * The size of the XML chunks that are sent through to libxml2 for parsing. * * Since we plan to perform surgery on pre-utf8 data streams, the chunk * size must be large enough to catch the regina engine version in the * first bite. In particular, we require regChunkSize > regDataOpenBy. */ const int regChunkSize = 1024; } bool writeXMLFile(const char* fileName, NPacket* subtree, bool compressed) { if (compressed) { CompressionStream out(fileName); if (! out) return false; subtree->writeXMLFile(out); } else { std::ofstream out(fileName); if (! out) return false; subtree->writeXMLFile(out); } return true; } NPacket* readXMLFile(const char* fileName) { DecompressionStream in(fileName); if (! in) return 0; NXMLTreeResolver resolver; ReginaDataReader reader(resolver); regina::NXMLCallback callback(reader, std::cerr); // Instead of using the ready-made regina::xml::XMLParser::parse_stream(), // we split the stream into parseable chunks manually. This allows us to // perform surgery on pre-utf8 streams and tell the parser that old files // actually store their data in latin1 (which the old file format // neglected to specify in the XML prologue). { regina::xml::XMLParser parser(callback); char* buf = new char[regChunkSize]; int chunkRead; bool seenFirstChunk = false; while (true) { // Read in the next chunk. for (chunkRead = 0; chunkRead < regChunkSize; chunkRead++) { buf[chunkRead] = static_cast(in.get()); if (in.eof()) break; } if (chunkRead == 0) break; // Parse the chunk that has just been read. // Interesting things (i.e., surgery) can only happen in the // first chunk. if (seenFirstChunk) { parser.parse_chunk(std::string(buf, chunkRead)); continue; } seenFirstChunk = true; // This is the first chunk in the data stream. See if we // need to perform surgery upon it. // // We will be fairly rigid in looking for substrings, since // we are only trying to match old pre-utf8 data files. If // future versions of the calculation engine change the // opening "signature" then this does not matter, since they // will not need surgery anyway. // // If anything looks out of place, just abort the surgery // and send everything straight through to the XML parser, // in the hope that either the file is new enough to not // need surgery (i.e., it uses utf8 already) or it's old but // only uses plain ASCII. char tmp = buf[regDataOpenBy]; buf[regDataOpenBy] = 0; char* start = ::strstr(buf, ""); if (! start) { // Can't find the end of the opening sequence! *finish = tmp; parser.parse_chunk(std::string(buf, chunkRead)); continue; } // Good to go. Parse the chunk with a new encoding squeezed in // just before the "?>". *finish = tmp; parser.parse_chunk(std::string(buf, start - buf)); parser.parse_chunk(" encoding=\"ISO-8859-1\""); parser.parse_chunk(std::string(start, chunkRead - (start - buf))); } parser.finish(); delete[] buf; } // See if we read anything. // If so, break it away from the top-level container and return it. NPacket* p = reader.getPacket(); if (p) { p = p->getFirstTreeChild(); if (p) p->makeOrphan(); // Resolve any dangling packet references. resolver.resolve(); return p; } else return 0; } NPacket* readFileMagic(const std::string& fileName) { NFileInfo* info = NFileInfo::identify(fileName); if (! info) return 0; NPacket* ans; if (info->getType() == NFileInfo::TYPE_XML) ans = readXMLFile(fileName.c_str()); else ans = 0; delete info; return ans; } } // namespace regina regina-4.95/engine/file/nxmlfile.h000644 000765 000024 00000012324 12234011536 016761 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file file/nxmlfile.h * \brief Deals with storing program data (including packet trees) * in XML data files. */ #ifndef __NXMLFILE_H #ifndef __DOXYGEN #define __NXMLFILE_H #endif #include #include "regina-core.h" namespace regina { class NPacket; /** * \addtogroup file File I/O * File reading and writing. * @{ */ /** * Writes the subtree with the given packet as matriarch to disk as a * complete XML file. The XML file may be optionally compressed. * * This is the preferred way of writing a packet tree to file. * * \pre The given packet does not depend on its parent. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and simply * passes it through unchanged to low-level C/C++ file I/O routines. * * @param fileName the pathname of the file to write to. * @param subtree the matriarch of the packet tree that should be written. * @param compressed \c true if the XML file should be compressed or * \c false if it should be stored as plain text. * @return \c true if and only if the packet subtree was successfully * written. */ REGINA_API bool writeXMLFile(const char* fileName, NPacket* subtree, bool compressed = true); /** * Reads the packet tree stored in the given XML file. It does not * matter whether the XML file is compressed. * * If the matriarch of the packet tree could not be read, this routine * will return 0. If a lower-level packet could not be read, it (and * its descendants) will simply be ignored. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and simply * passes it through unchanged to low-level C/C++ file I/O routines. * * @param fileName the pathname of the file to read from. * @return the packet tree read from file, or 0 if problems were * encountered or the file could not be opened. */ REGINA_API NPacket* readXMLFile(const char* fileName); /** * Reads a packet tree from a file whose format is unknown. * * As of Regina 4.94, old-style binary files are no longer supported. * This means that the file must be in XML format (optionally compressed). * In other words, this routine now behaves identically to readXMLFile(). * * If the matriarch of the packet tree could not be read, this routine * will return 0. If a lower-level packet could not be read, it (and * its descendants) will simply be ignored. * * The given file might be opened and closed multiple times during this routine. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and simply * passes it through unchanged to low-level C/C++ file I/O routines. * * @param fileName the pathname of the file to read from. * @return the packet tree read from file, or 0 if problems were * encountered or the file could not be opened. */ REGINA_API NPacket* readFileMagic(const std::string& fileName); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/casson.h000644 000765 000024 00000002576 12234011536 017153 0ustar00babstaff000000 000000 /** * This is the file casson.h. It was initially written by Damien Heard * as part of the program Orb (http://www.ms.unimelb.edu.au/~snap/orb.html). * * This header was introduced into Regina for the Orb / Casson import * and export routines, which were contributed by Ryan Budney. It is * for internal use by these routines only; any other code should call * the public routines from orb.h. * * Many thanks to Damien Heard for giving permission for his code to * be distributed under the terms of the GNU General Public License. */ #ifndef CASSON_H #define CASSON_H #include "regina-core.h" #define LN(ch) (ch=='u') ? 0 : ((ch=='v') ? 1 : ((ch=='w') ? 2 : 3)) const int vertex_at_faces[4][4] = {{9,2,3,1}, {3,9,0,2}, {1,3,9,0}, {2,0,1,9}}; typedef struct CassonFormat CassonFormat; typedef struct EdgeInfo EdgeInfo; typedef struct TetEdgeInfo TetEdgeInfo; struct REGINA_LOCAL CassonFormat { int num_tet; EdgeInfo *head; }; struct REGINA_LOCAL EdgeInfo { int index, singular_index; double singular_order; TetEdgeInfo *head; EdgeInfo *prev, *next; }; struct REGINA_LOCAL TetEdgeInfo { int tet_index,f1,f2; TetEdgeInfo *prev, *next; }; #endif regina-4.95/engine/foreign/CMakeLists.txt000644 000765 000024 00000001017 12234011536 020241 0ustar00babstaff000000 000000 # foreign # Files to compile SET ( FILES csvsurfacelist dehydration isosig orb pdf recogniser snappea ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} foreign/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES csvsurfacelist.h dehydration.h isosig.h orb.h pdf.h recogniser.h snappea.h DESTINATION ${INCLUDEDIR}/foreign COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/foreign/csvsurfacelist.cpp000644 000765 000024 00000022667 12234011536 021263 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "foreign/csvsurfacelist.h" #include "maths/nmatrixint.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" namespace regina { namespace { /** * Write the given string to the given output stream, properly * quoted and escaped. The entire string will be placed in double * quotes, and any double quotes that appear inside the string will * be replaced by a pair of double quotes. */ void writeCSVQuotedString(std::ostream& out, const char* str) { out << '"'; while (*str) { if (*str == '"') out << "\"\""; else out << *str; ++str; } out << '"'; } /** * Writes a piece of the CSV header corresponding to the given set * of optional fields. */ void writePropHeader(std::ostream& out, int fields) { if (fields & surfaceExportName) out << "name,"; if (fields & surfaceExportEuler) out << "euler,"; if (fields & surfaceExportOrient) out << "orientable,"; if (fields & surfaceExportSides) out << "sides,"; if (fields & surfaceExportBdry) out << "boundary,"; if (fields & surfaceExportLink) out << "link,"; if (fields & surfaceExportType) out << "type,"; } /** * Writes a piece of the CSV data for the given normal surface * corresponding to the given set of optional fields. */ void writePropData(std::ostream& out, const NNormalSurface* s, int fields) { if (fields & surfaceExportName) { if (s->getName().length() > 0) writeCSVQuotedString(out, s->getName().c_str()); out << ','; } if (fields & surfaceExportEuler) { if (s->isCompact()) out << s->getEulerCharacteristic(); out << ','; } if (fields & surfaceExportOrient) { if (s->isCompact()) { out << (s->isOrientable() ? "TRUE" : "FALSE"); } out << ','; } if (fields & surfaceExportSides) { if (s->isCompact()) { out << (s->isTwoSided() ? '2' : '1'); } out << ','; } if (fields & surfaceExportBdry) { if (! s->isCompact()) { #ifndef EXCLUDE_SNAPPEA regina::NMatrixInt* slopes = s->boundarySlopes(); if (slopes) { out << "\"spun:"; for (unsigned i = 0; i < slopes->rows(); ++i) out << " (" << slopes->entry(i, 1) << ", " << - slopes->entry(i, 0) << ')'; out << '\"'; } else out << "spun"; #else out << "spun"; #endif } else if (s->hasRealBoundary()) out << "real"; else out << "none"; out << ','; } if (fields & surfaceExportLink) { // Mirror the information that gets shown in the Link column // in the GUI. NTriangulation* t = s->getTriangulation(); const NVertex* v = s->isVertexLink(); if (v) out << "\"Vertex " << t->vertexIndex(v) << "\""; else { std::pair e = s->isThinEdgeLink(); if (e.second) out << "\"Thin edges " << t->edgeIndex(e.first) << ", " << t->edgeIndex(e.second) << "\""; else if (e.first) out << "\"Thin edge " << t->edgeIndex(e.first) << "\""; } out << ','; } if (fields & surfaceExportType) { // Mirror the information that gets shown in the Type column // in the GUI. if (s->isSplitting()) out << "\"Splitting\""; else { NLargeInteger tot = s->isCentral(); if (tot != 0) out << "\"Central (" << tot << ")\""; } out << ','; } } } bool writeCSVStandard(const char* filename, NNormalSurfaceList& surfaces, int additionalFields) { std::ofstream out(filename); if (! out) return false; unsigned long n = surfaces.getTriangulation()->getNumberOfTetrahedra(); unsigned long i, j; // Write the CSV header. writePropHeader(out, additionalFields); for (i = 0; i < n; ++i) { out << 'T' << i << ":0,"; out << 'T' << i << ":1,"; out << 'T' << i << ":2,"; out << 'T' << i << ":3,"; out << 'Q' << i << ":01/23,"; out << 'Q' << i << ":02/13,"; out << 'Q' << i << ":03/12"; if (! surfaces.allowsAlmostNormal()) { if (i < n - 1) out << ','; continue; } out << ','; out << 'K' << i << ":01/23,"; out << 'K' << i << ":02/13,"; out << 'K' << i << ":03/12"; if (i < n - 1) out << ','; } out << std::endl; // Write the data for individual surfaces. unsigned long tot = surfaces.getNumberOfSurfaces(); const NNormalSurface* s; for (i = 0; i < tot; ++i) { s = surfaces.getSurface(i); writePropData(out, s, additionalFields); for (j = 0; j < n; ++j) { out << s->getTriangleCoord(j, 0) << ','; out << s->getTriangleCoord(j, 1) << ','; out << s->getTriangleCoord(j, 2) << ','; out << s->getTriangleCoord(j, 3) << ','; out << s->getQuadCoord(j, 0) << ','; out << s->getQuadCoord(j, 1) << ','; out << s->getQuadCoord(j, 2); if (! surfaces.allowsAlmostNormal()) { if (j < n - 1) out << ','; continue; } out << ','; out << s->getOctCoord(j, 0) << ','; out << s->getOctCoord(j, 1) << ','; out << s->getOctCoord(j, 2); if (j < n - 1) out << ','; } out << std::endl; } // All done! return true; } bool writeCSVEdgeWeight(const char* filename, NNormalSurfaceList& surfaces, int additionalFields) { std::ofstream out(filename); if (! out) return false; unsigned long n = surfaces.getTriangulation()->getNumberOfEdges(); unsigned long i, j; // Write the CSV header. writePropHeader(out, additionalFields); for (i = 0; i < n; ++i) { out << 'E' << i; if (i < n - 1) out << ','; } out << std::endl; // Write the data for individual surfaces. unsigned long tot = surfaces.getNumberOfSurfaces(); const NNormalSurface* s; for (i = 0; i < tot; ++i) { s = surfaces.getSurface(i); writePropData(out, s, additionalFields); for (j = 0; j < n; ++j) { out << s->getEdgeWeight(j); if (j < n - 1) out << ','; } out << std::endl; } // All done! return true; } } // namespace regina regina-4.95/engine/foreign/csvsurfacelist.h000644 000765 000024 00000023365 12234011536 020724 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/csvsurfacelist.h * \brief Exports normal surface lists to plain text CSV files. */ #ifndef __CSVSURFACELIST_H #ifndef __DOXYGEN #define __CSVSURFACELIST_H #endif #include #include "regina-core.h" namespace regina { class NNormalSurfaceList; /** * \weakgroup foreign * @{ */ /** * Indicates a set of additional fields that can be exported as part * of a normal surface list. This type can be used as a bitmask: to * describe a set of fields, simply combine several individual fields * using bitwise \e or. * * The list of available fields may grow with future releases of * Regina. */ enum SurfaceExportFields { surfaceExportName = 0x0001, /**< Represents the user-assigned surface name. */ surfaceExportEuler = 0x0002, /**< Represents the calculated Euler characteristic of a surface. This will be an integer, and will be left empty if the Euler characteristic cannot be computed. */ surfaceExportOrient = 0x0004, /**< Represents the calculated property of whether a surface is orientable. This will be \c TRUE or \c FALSE, or will be left empty if the orientability cannot be computed. */ surfaceExportSides = 0x0008, /**< Represents the calculated property of whether a surface is one-sided or two-sided. This will be the integer 1 or 2, or will be left empty if the "sidedness" cannot be computed. */ surfaceExportBdry = 0x0010, /**< Represents the calculated property of whether a surface is bounded. In most cases, this will be one of the strings "closed", "real bdry" or "infinite" (where "infinite" indicates a surface with infinitely many discs). For spun-normal surfaces in certain ideal triangulations, this string will be followed by the boundary slopes of the surface at the cusps: these written as a list of pairs (\a p, \a q), one for each cusp, indicating that the boundary curves of the surface run \a p times around the meridian and \a q times around the longitude. See NNormalSurface::boundarySlopes() for further information on interpreting these values. */ surfaceExportLink = 0x0020, /**< Represents whether a surface is a single vertex link or a thin edge link. See NNormalSurface::isVertexLink() and NNormalSurface::isThinEdgeLink() for details. This will be written as a human-readable string. */ surfaceExportType = 0x0040, /**< Represents any additional high-level properties of a surface, such as whether it is a splitting surface or a central surface. This will be written as a human-readable string. This field is somewhat arbitrary, and the precise properties it describes are subject to change in future releases of Regina. */ surfaceExportNone = 0, /**< Indicates that no additional fields should be exported. */ surfaceExportAllButName = 0x007e, /**< Indicates that all available fields should be exported, except for the user-assigned surface name. Since the list of available fields may grow with future releases, the numerical value of this constant may change as a result. */ surfaceExportAll = 0x007f /**< Indicates that all available fields should be exported, including the user-assigned surface name. Since the list of available fields may grow with future releases, the numerical value of this constant may change as a result. */ }; /** * Exports the given list of normal surfaces as a plain text CSV * (comma-separated value) file. CSV files are human-readable and * human-editable, and are suitable for importing into spreadsheets and * databases. * * The surfaces will be exported in standard coordinates (tri-quad * coordinates for normal surfaces, or tri-quad-oct coordinates for * almost normal surfaces). Each coordinate will become a separate * field in the CSV file. * * As well as the normal surface coordinates, additional properties of the * normal surfaces (such as Euler characteristic, orientability, and so on) * can be included as extra fields in the export. Users can select * precisely which properties to include by passing a bitmask formed * from regina::SurfaceExportFields constants. * * The CSV format used here begins with a header row, and uses commas as * field separators. Text fields with arbitrary contents are placed inside * double quotes, and the double quote character itself is represented by a * pair of double quotes. Thus the string * my "normal" surface's name would be stored as * "my ""normal"" surface's name". * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O * routines. Any user strings such as surface names will be written * in UTF-8. * * @param filename the name of the CSV file to export to. * @param surfaces the list of normal surfaces to export. * @param additionalFields a bitwise combination of regina::SurfaceExportFields * constants indicating which additional properties of surfaces should * be included in the export. * @return \c true if the export was successful, or \c false otherwise. */ REGINA_API bool writeCSVStandard(const char* filename, NNormalSurfaceList& surfaces, int additionalFields = surfaceExportAll); /** * Exports the given list of normal surfaces as a plain text CSV * (comma-separated value) file. CSV files are human-readable and * human-editable, and are suitable for importing into spreadsheets and * databases. * * The surfaces will be exported in edge weight coordinates. Thus * there will be one coordinate for each edge of the underlying * triangulation; each such coordinate will become a separate field in * the CSV file. * * As well as the normal surface coordinates, additional properties of the * normal surfaces (such as Euler characteristic, orientability, and so on) * can be included as extra fields in the export. Users can select * precisely which properties to include by passing a bitmask formed * from regina::SurfaceExportFields constants. * * The CSV format used here begins with a header row, and uses commas as * field separators. Text fields with arbitrary contents are placed inside * double quotes, and the double quote character itself is represented by a * pair of double quotes. Thus the string * my "normal" surface's name would be stored as * "my ""normal"" surface's name". * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O * routines. Any user strings such as surface names will be written * in UTF-8. * * @param filename the name of the CSV file to export to. * @param surfaces the list of normal surfaces to export. * @param additionalFields a bitwise combination of regina::SurfaceExportFields * constants indicating which additional properties of surfaces should * be included in the export. * @return \c true if the export was successful, or \c false otherwise. */ REGINA_API bool writeCSVEdgeWeight(const char* filename, NNormalSurfaceList& surfaces, int additionalFields = surfaceExportAll); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/dehydration.cpp000644 000765 000024 00000010766 12236524106 020536 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "foreign/dehydration.h" #include "packet/ncontainer.h" #include "packet/ntext.h" #include "triangulation/ntriangulation.h" namespace regina { NContainer* readDehydrationList(const char *filename, unsigned colDehydrations, int colLabels, unsigned long ignoreLines) { // Open the file. std::ifstream in(filename); if (! in) return 0; // Ignore the specified number of lines. std::string line; unsigned long i; for (i = 0; i < ignoreLines; i++) { std::getline(in, line); if (in.eof()) return new NContainer(); } // Read in and process the remaining lines. NContainer* ans = new NContainer(); std::string errStrings; int col; std::string token; std::string dehydration; std::string label; NTriangulation* tri; while(! in.eof()) { // Read in the next line. line.clear(); std::getline(in, line); if (line.empty()) continue; // Find the appropriate tokens. std::istringstream tokens(line); dehydration.clear(); label.clear(); for (col = 0; col <= static_cast(colDehydrations) || col <= colLabels; col++) { tokens >> token; if (token.empty()) break; if (col == static_cast(colDehydrations)) dehydration = token; if (col == colLabels) label = token; } if (! dehydration.empty()) { // Process this dehydration string. tri = new NTriangulation(); if (tri->insertRehydration(dehydration)) { tri->setPacketLabel(label.empty() ? dehydration : label); ans->insertChildLast(tri); } else { errStrings = errStrings + '\n' + dehydration; delete tri; } } } // Finish off. if (! errStrings.empty()) { NText* errPkt = new NText(std::string( "The following dehydration string(s) could not be rehydrated:\n") + errStrings); errPkt->setPacketLabel("Errors"); ans->insertChildLast(errPkt); } return ans; } } // namespace regina regina-4.95/engine/foreign/dehydration.h000644 000765 000024 00000011362 12234011536 020170 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/dehydration.h * \brief Allows reading lists of dehydrated triangulations. */ #ifndef __DEHYDRATION_H #ifndef __DOXYGEN #define __DEHYDRATION_H #endif #include "regina-core.h" namespace regina { class NContainer; /** * \weakgroup foreign * @{ */ /** * Reads a list of dehydrated triangulations from the given text file. * The file should contain one dehydration string per line. These * strings will be rehydrated as described in * NTriangulation::insertRehydration(). * * A newly allocated container will be returned; the imported * triangulations will be inserted as children of this container. * The container will not be assigned a label. The individual * triangulations will be assigned labels according to the parameter * \a colLabels. * * If any dehydrations strings are invalid, these will be recorded in an * additional text packet that will be the last child of the returned * container. * * If an I/O error occurred while trying to read the given file, 0 will be * returned. * * In its simplest form, the text file can simply contain one * dehydration string per line and nothing else. However, more complex * formats are allowed. In particular, by passing appropriate values * for the arguments \a colDehydrations and \a colLabels, the dehydration * strings and triangulation packet labels can be taken from arbitrary * columns of the text file. Columns are considered to be separated by * whitespace and are numbered beginning at 0. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * It assumes however that the \e contents of the file are in UTF-8. * * @param filename the name of the text file from which to read. * @param colDehydrations the column of the text file containing the * dehydration strings. * @param colLabels the column of the text file containing the * triangulation packet labels. If this is negative then the dehydration * strings themselves will be used as packet labels. * @param ignoreLines the number of lines at the beginning of the text * file that should be ignored completely. * @return a new container as described above, or 0 if an I/O error occurred * whilst reading the given file. */ REGINA_API NContainer* readDehydrationList(const char *filename, unsigned colDehydrations = 0, int colLabels = -1, unsigned long ignoreLines = 0); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/isosig.cpp000644 000765 000024 00000011132 12236713375 017515 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "foreign/isosig.h" #include "packet/ncontainer.h" #include "packet/ntext.h" #include "triangulation/ntriangulation.h" namespace regina { NContainer* readIsoSigList(const char *filename, unsigned dimension, unsigned colSigs, int colLabels, unsigned long ignoreLines) { // Open the file. std::ifstream in(filename); if (! in) return 0; // Ignore the specified number of lines. std::string line; unsigned long i; for (i = 0; i < ignoreLines; i++) { std::getline(in, line); if (in.eof()) return new NContainer(); } // Read in and process the remaining lines. NContainer* ans = new NContainer(); std::string errStrings; int col; std::string token; std::string isoSig; std::string label; NTriangulation* tri3; while(! in.eof()) { // Read in the next line. line.clear(); std::getline(in, line); if (line.empty()) continue; // Find the appropriate tokens. std::istringstream tokens(line); isoSig.clear(); label.clear(); for (col = 0; col <= static_cast(colSigs) || col <= colLabels; col++) { tokens >> token; if (token.empty()) break; if (col == static_cast(colSigs)) isoSig = token; if (col == colLabels) label = token; } if (! isoSig.empty()) { // Process this isomorphism signature. if (dimension == 3) { if ((tri3 = NTriangulation::fromIsoSig(isoSig))) { tri3->setPacketLabel(label.empty() ? isoSig : label); ans->insertChildLast(tri3); } else errStrings = errStrings + '\n' + isoSig; } else errStrings = errStrings + '\n' + isoSig; } } // Finish off. if (! errStrings.empty()) { std::ostringstream msg; msg << "The following isomorphism string(s) could not be interpreted " "as " << dimension << "-manifold triangulations:\n" << errStrings; NText* errPkt = new NText(msg.str()); errPkt->setPacketLabel("Errors"); ans->insertChildLast(errPkt); } return ans; } } // namespace regina regina-4.95/engine/foreign/isosig.h000644 000765 000024 00000011747 12236713375 017176 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/isosig.h * \brief Allows reading lists of isomorphism signatures. */ #ifndef __ISOSIG_H #ifndef __DOXYGEN #define __ISOSIG_H #endif #include "regina-core.h" namespace regina { class NContainer; /** * \weakgroup foreign * @{ */ /** * Reads a list of isomorphism signatures from the given text file. * The file should contain one isomorphism signature per line. * These isomorphism signatures will be converted into triangulations using * NTriangulation::fromIsoSig(). * * A newly allocated container will be returned; the imported * triangulations will be inserted as children of this container. * The container will not be assigned a label. The individual * triangulations will be assigned labels according to the parameter * \a colLabels. * * If any isomorphism signatures are invalid, these will be recorded in an * additional text packet that will be the last child of the returned * container. * * If an I/O error occurred while trying to read the given file, 0 will be * returned. * * In its simplest form, the text file can simply contain one * isomorphism signature per line and nothing else. However, more complex * formats are allowed. In particular, by passing appropriate values * for the arguments \a colSigs and \a colLabels, the isomorphism signatures * and triangulation packet labels can be taken from arbitrary * columns of the text file. Columns are considered to be separated by * whitespace and are numbered beginning at 0. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * It assumes however that the \e contents of the file are in UTF-8. * * @param filename the name of the text file from which to read. * @param dimension must be set to 3, indicating that isomorphism * signatures should be expanded into 3-manifold triangulations. * This argument is a placeholder for future expansion, and currently no * value other than 3 is allowed. * @param colSigs the column of the text file containing the * isomorphism signatures. * @param colLabels the column of the text file containing the * triangulation packet labels. If this is negative then the * isomorphism signatures themselves will be used as packet labels. * @param ignoreLines the number of lines at the beginning of the text * file that should be ignored completely. * @return a new container as described above, or 0 if an I/O error occurred * whilst reading the given file. */ REGINA_API NContainer* readIsoSigList(const char *filename, unsigned dimension = 3, unsigned colSigs = 0, int colLabels = -1, unsigned long ignoreLines = 0); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/orb.cpp000644 000765 000024 00000030477 12234011536 017003 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /** * Thanks to Ryan Budney and Damien Heard for providing the bulk of the * code in this file. * * Ryan Budney contributed the Orb / Casson import filters for Regina. * From his initial comments: * * This file contains the engine routines to read Casson/Orb format * triangulations and import them into Regina. The main routines are * adapted from Damian Heard's Orb. * -Ryan Budney (April 3rd, 2006) * * For information on which routines are adapted from Orb and how they * have been modified, see the comments above each routine. For information * on Orb itself, see http://www.ms.unimelb.edu.au/~snap/orb.html . */ #include #include #include #include #include #include #include #include "foreign/casson.h" #include "foreign/orb.h" #include "triangulation/ntriangulation.h" #include "utilities/stringutils.h" namespace regina { // Anonymous namespace for the private routines used only by this file. namespace { /** * Modified from Orb's cassonToTriangulation() routine. * * The routine was changed to be compatible with Regina's NTriangulation * data structure. */ NTriangulation *cassonToNTriangulation( CassonFormat *cf ) { int i; NTriangulation *triang = new NTriangulation(); // since CassonFormat does not allow naming of triangulations, // triang is given a name in the readOrb() function. // I try to mimic NTriangulation::readSnapPea and // Orb::cassonToTriangulation as much as possible. // If the triangulation is empty, leave now before we start allocating // empty arrays. if (cf->num_tet == 0) return triang; NTetrahedron **tet = new NTetrahedron*[cf->num_tet]; // tet corresponds to tet_array in Orb for (i=0; inum_tet; i++) tet[i]=triang->newTetrahedron(); // now tet is a pointer to an array of NTetrahedrons, // so for each tet[i] we need to run // for (j=0; j<4; j++) // tet[i]->joinTo(j,tet[g[j]],NPerm4(p[j][0],p[j][1],p[j][2],p[j][3],p[j][4])) // where g[j] is the tetrahedron adjacent to face j of tet[i] // p[j][k] is the permutation specifying how the faces are glued together. EdgeInfo *ei; TetEdgeInfo *tei1, *tei2; int t1, t2, a1, a2, a3, a4, b1, b2, b3, b4; ei = cf->head; // this routine goes through the edges of cf, picking off the adjacent // tetrahedra and assembled the information into tet. this code is // adapted from Orb::cassonToTriangulation in Orb's organizer.cpp while (ei!=NULL) // if we have a non-trivial edge, proceed { tei1 = ei->head; while (tei1!=NULL) // now we spin about the tetrahedra adj to ei. { if (tei1->next==NULL) tei2 = ei->head; else tei2 = tei1->next; t1 = tei1->tet_index; a1 = tei1->f1; a2 = tei1->f2; a3 = vertex_at_faces[a1][a2]; a4 = vertex_at_faces[a2][a1]; t2 = tei2->tet_index; b1 = tei2->f1; b2 = tei2->f2; b3 = vertex_at_faces[b1][b2]; b4 = vertex_at_faces[b2][b1]; tet[t1]->joinTo( tei1->f1 , tet[t2], // 1st entry is the face of tet[t1] NPerm4(a1,b2,a2,b1,a3,b3,a4,b4) ); // being attached to tet[t2] tet[t2]->joinTo( tei2->f2 , tet[t1], NPerm4(b1,a2,b2,a1,b3,a3,b4,a4) ); tei1 = tei1->next; } ei = ei->next; } delete[] tet; return triang; } /** * Modified from Orb's readCassonFormat() routine. * * This routine was modified to remove the dependence on Qt strings and * I/O streams, and work only with standard C++ strings and I/O streams * instead. * * On entering this routine we assume the lines containing "% orb" and * the manifold name have already been read. */ CassonFormat *readCassonFormat( std::istream &ts ) { CassonFormat *cf; std::string line, section; EdgeInfo *nei, *ei; TetEdgeInfo *ntei, *tei; bool vertices_known = false; cf = new CassonFormat; cf->head = NULL; cf->num_tet = 0; // Skip any initial non-empty lines (looking whether there is // "vertices_known") and then some empty lines. // After that there should be the real information. // The code from Orb used QString::skipWhiteSpace(); we do it // manually. do { getline(ts, line); stripWhitespace(line); if (line == "vertices_known") vertices_known=true; } while ((!ts.eof()) && (!line.empty())); do { getline(ts,line); stripWhitespace(line); } while ((! ts.eof()) && line.empty()); // Process lines one at a time until we hit an empty line or EOF. while ((! ts.eof()) && (! line.empty()) && (line != "% diagram")) { // The code from Orb used QString's record separation // routines. We don't have that in std::string, so // we'll do it all with istringstreams instead. std::istringstream tokens(line); nei = new EdgeInfo; if (cf->head==NULL) cf->head = nei; else ei->next = nei; nei->next = NULL; nei->head = NULL; ei = nei; tokens >> ei->index; ei->index--; // We never use these two values; just suck them in and // forget them. tokens >> ei->singular_index >> ei->singular_order; // if vertices are listed, discard if (vertices_known) { tokens >> section; tokens>>section; } tokens >> section; while (!section.empty()) { ntei = new TetEdgeInfo; if (ei->head==NULL) ei->head = ntei; else tei->next = ntei; ntei->next = NULL; tei = ntei; tei->f1 = LN(section[section.length()-2]); tei->f2 = LN(section[section.length()-1]); section.resize(section.length()-2); tei->tet_index = atoi(section.c_str()) - 1; if (tei->tet_index + 1 > cf->num_tet) cf->num_tet = tei->tet_index + 1; section.clear(); tokens >> section; } getline(ts, line); } return cf; } /** * A direct copy of Orb's verifyCassonFormat() routine. */ bool verifyCassonFormat( CassonFormat *cf ) { int i,j,k; bool check[4][4]; EdgeInfo *ei; TetEdgeInfo *tei; for(i=0;inum_tet;i++) { for(j=0;j<4;j++) for(k=0;k<4;k++) if (j==k) check[j][k] = true; else check[j][k] = false; ei = cf->head; if (ei == NULL) return false; while(ei!=NULL) { tei = ei->head; if (tei == NULL) return false; while(tei!=NULL) { if (tei->tet_index == i ) { if (check[tei->f1][tei->f2]) return true; check[tei->f1][tei->f2] = true; check[tei->f2][tei->f1] = true; } tei = tei->next; } ei = ei->next; } for(j=0;j<4;j++) for(k=0;k<4;k++) if (check[j][k]==false) return false; } return true; } /** * A direct copy of Orb's freeCassonFormat() routine. */ void freeCassonFormat( CassonFormat *cf ) { EdgeInfo *e1, *e2; TetEdgeInfo *t1, *t2; e1 = cf->head; while (e1!=NULL) { e2 = e1->next; t1 = e1->head; while (t1!=NULL) { t2 = t1->next; delete t1; t1 = t2; } delete e1; e1 = e2; } delete cf; } /** * Modified from Orb's readTriangulation() routine. * * The routine was changed to be compatible with Regina's NTriangulation * data structure, and to use standard C++ string and I/O streams * instead of Qt strings and I/O streams. */ NTriangulation *readTriangulation( std::istream &ts) { std::string line, file_id; getline(ts, line); if (line != "% orb") { std::cerr << "Orb / Casson file is not in the correct format." << std::endl; return 0; } getline(ts, file_id); CassonFormat* cf = readCassonFormat( ts ); if (! verifyCassonFormat( cf )) { std::cerr << "Error verifying Orb / Casson file." << std::endl; freeCassonFormat( cf ); return 0; } NTriangulation* manifold = cassonToNTriangulation( cf ); freeCassonFormat( cf ); manifold->setPacketLabel(file_id); return manifold; } } // End anonymous namespace NTriangulation *readOrb(const char *filename) { std::ifstream file(filename); if (! file) { std::cerr << "Error opening Orb / Casson file." << std::endl; return 0; } return readTriangulation(file); } } // namespace regina regina-4.95/engine/foreign/orb.h000644 000765 000024 00000007153 12234011536 016443 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/orb.h * \brief Allows reading Orb / Casson triangulation files. */ #ifndef __ORB_H #ifndef __DOXYGEN #define __ORB_H #endif #include #include "regina-core.h" namespace regina { class NTriangulation; /** * \weakgroup foreign * @{ */ /** * Reads a triangulation from the given Orb / Casson file. A newly * allocated triangulation will be returned; it is the user's * responsibility to deallocate this when it is finished with. * * The packet label of the new triangulation will be the manifold name * read from the second line of the Orb / Casson file. The first line * of the Orb / Casson file must simply be ``% orb''. * * If the file could not be read or if the data was not in the correct * format, 0 will be returned. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * It assumes however that the \e contents of the file are in UTF-8. * * @param filename the name of the Orb / Casson file from which to read. * @return a new triangulation containing the data read from the Orb / Casson * file, or 0 on error. * * @author Ryan Budney, also with code from Damien Heard */ REGINA_API NTriangulation* readOrb(const char *filename); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/pdf.cpp000644 000765 000024 00000007260 12234011536 016764 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "foreign/pdf.h" #include "packet/npdf.h" namespace regina { NPDF* readPDF(const char* filename) { // Use FILE* so we can call fstat(). // Open the file. FILE* in = fopen(filename, "rb"); if (! in) return 0; // Get the file size. struct stat s; if (fstat(fileno(in), &s)) { fclose(in); return 0; } size_t size = s.st_size; if (size == 0) { fclose(in); return new NPDF(); } // Read the file contents. char* data = new char[size]; if (fread(data, 1, size, in) != size) { fclose(in); delete[] data; return 0; } // Is there more to the file that we weren't expecting? char c; if (fread(&c, 1, 1, in) > 0) { fclose(in); delete[] data; return 0; } // All good! fclose(in); return new NPDF(data, size, NPDF::OWN_NEW); } bool writePDF(const char* filename, const NPDF& pdf) { // Use FILE* for symmetry with readPDF(). // Open the file. FILE* out = fopen(filename, "wb"); if (!out) return false; // Is there anything to write? const char* data = pdf.data(); if (data) { size_t size = pdf.size(); if (fwrite(data, 1, size, out) != size) { fclose(out); return false; } } // All done. fclose(out); return true; } } // namespace regina regina-4.95/engine/foreign/pdf.h000644 000765 000024 00000010201 12234011536 016416 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/pdf.h * \brief Allows reading and writing PDF documents. */ #ifndef __PDF_H #ifndef __DOXYGEN #define __PDF_H #endif #include "regina-core.h" namespace regina { class NPDF; /** * \weakgroup foreign * @{ */ /** * Reads a PDF document from the given file. A newly allocated PDF packet * will be returned; it is the user's responsibility to deallocate this * when it is finished with. * * This routine does not check whether the given file \e looks like a * PDF document; it simply loads the file contents blindly. * * The packet label of the new PDF packet will be left empty. * * If the file could not be read, 0 will be returned. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * * @param filename the filename of the PDF document to read. * @return a new PDF packet containing the PDF document, or 0 on error. */ REGINA_API NPDF* readPDF(const char *filename); /** * Writes the given PDF document to the given file. * * This routine does not check whether the contents of the given packet * \e look like a PDF document; it simply writes them blindly to the * given file. * * If the given PDF packet is empty (i.e., does not contain a real block * of data) then the resulting file will be created but left empty. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * * @param filename the filename of the PDF document to write. * @param pdf the PDF packet to write to the given file. * @return \c true if the export was successful, or \c false otherwise. */ REGINA_API bool writePDF(const char* filename, const NPDF& pdf); /*@}*/ } // namespace regina #endif regina-4.95/engine/foreign/recogniser.cpp000644 000765 000024 00000010231 12234011536 020343 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "foreign/recogniser.h" #include "triangulation/ntriangulation.h" namespace regina { namespace { // Anonymous routine to write to the given output stream. // // PRE: All preconditions for writeRecogniser() have already been // tested, and are known to be met. bool writeRecogniser(std::ostream& out, NTriangulation& tri) { // Write the header. out << "triangulation" << std::endl; // Write face gluings. NTriangle* f; NTetrahedron* tet; NPerm4 vert; for (unsigned i = 0; i < tri.getNumberOfTriangles(); ++i) { f = tri.getTriangle(i); tet = f->getEmbedding(0).getTetrahedron(); vert = f->getEmbedding(0).getVertices(); out << 't' << (tri.tetrahedronIndex(tet) + 1) << '(' << (vert[0] + 1) << ',' << (vert[1] + 1) << ',' << (vert[2] + 1) << ") - "; tet = f->getEmbedding(1).getTetrahedron(); vert = f->getEmbedding(1).getVertices(); out << 't' << (tri.tetrahedronIndex(tet) + 1) << '(' << (vert[0] + 1) << ',' << (vert[1] + 1) << ',' << (vert[2] + 1) << ')'; if (i != tri.getNumberOfTriangles() - 1) out << ','; out << std::endl; } // Write the footer. out << "end" << std::endl; return true; } } bool writeRecogniser(const char* filename, NTriangulation& tri) { // Sanity checks. if (! tri.isValid()) return false; if (tri.hasBoundaryTriangles()) return false; // Write to file or stdout as appropriate. if (filename && *filename) { std::ofstream out(filename); if (! out) return 0; return writeRecogniser(out, tri); } else { return writeRecogniser(std::cout, tri); } } } // namespace regina regina-4.95/engine/foreign/recogniser.h000644 000765 000024 00000007341 12234011536 020020 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/recogniser.h * \brief Allows exports to Matveev's 3-manifold recogniser. */ #ifndef __RECOGNISER_H #ifndef __DOXYGEN #define __RECOGNISER_H #endif #include #include "regina-core.h" namespace regina { class NTriangulation; /** * \weakgroup foreign * @{ */ /** * Writes the given triangulation to the given file in Matveev's 3-manifold * recogniser format. * * \pre The given triangulation is not invalid, and does not contain any * boundary triangles. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * The \e contents of the file will be written using UTF-8. * * @param filename the name of the Recogniser file to which to write. * This may be the null pointer (or the empty string), in which case the * data will be written to standard output instead. * @param tri the triangulation to write to the Recogniser file. * @return \c true if the export was successful, or \c false otherwise. */ REGINA_API bool writeRecogniser(const char* filename, NTriangulation& tri); /** * A synonym for writeRecogniser(). See writeRecogniser() for details. */ REGINA_API bool writeRecognizer(const char* filename, NTriangulation& tri); /*@}*/ // Inline functions: inline bool writeRecognizer(const char* filename, NTriangulation& tri) { return writeRecogniser(filename, tri); } } // namespace regina #endif regina-4.95/engine/foreign/snappea.cpp000644 000765 000024 00000016326 12234011536 017645 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include #include "foreign/snappea.h" #include "triangulation/ntriangulation.h" #include "utilities/stringutils.h" namespace regina { NTriangulation* readSnapPea(const char* filename) { std::ifstream in(filename); if (!in) return 0; else return readSnapPea(in); } NTriangulation* readSnapPea(std::istream& in) { // Check that this is a SnapPea triangulation. char name[1001]; unsigned len; in.getline(name, 1000); if (in.fail() || in.eof()) return 0; // Allow junk on the same line following the triangulation marker. if (strncmp(name, "% Triangulation", 15) && strncmp(name, "% triangulation", 15)) return 0; // Read in the manifold name. in.getline(name, 1000); if (in.fail() || in.eof()) return 0; if ((len = strlen(name)) > 0 && name[len - 1] == '\r') name[len - 1] = 0; // Read in junk. std::string tempStr; double tempDbl; in >> tempStr; // Solution type in >> tempDbl; // Volume in >> tempStr; // Orientability in >> tempStr; // Chern-Simmon if (tempStr[3] == 'k') in >> tempDbl; // Chern-Simmon is known unsigned i,j,k; // Read in cusp details and ignore them. unsigned numOrientCusps, numNonOrientCusps; in >> numOrientCusps >> numNonOrientCusps; for (i=0; i> tempStr; // Cusp type in >> tempDbl >> tempDbl; // Filling information } // Create the new tetrahedra. NTriangulation* triang = new NTriangulation(); triang->setPacketLabel(name); unsigned numTet; in >> numTet; NTetrahedron **tet = new NTetrahedron*[numTet]; for (i=0; inewTetrahedron(); int g[4]; int p[4][4]; for (i=0; i> g[j]; // Read in gluing permutations. for (j=0; j<4; j++) { in >> tempStr; for (k=0; k<4; k++) switch( tempStr[k] ) { case '0': p[j][k] = 0; break; case '1': p[j][k] = 1; break; case '2': p[j][k] = 2; break; case '3': p[j][k] = 3; break; default: delete triang; delete[] tet; return 0; } } // Perform the gluings. for (j=0; j<4; j++) tet[i]->joinTo(j, tet[g[j]], NPerm4(p[j][0], p[j][1], p[j][2], p[j][3])); // Read in junk. for (j=0; j<4; j++) in >> tempStr; for (j=0; j<64; j++) in >> tempStr; for (j=0; j<2; j++) in >> tempStr; } // All done! delete[] tet; return triang; } bool writeSnapPea(const char* filename, const NTriangulation& tri) { std::ofstream out(filename); if (!out) return false; else { writeSnapPea(out, tri); return true; } } void writeSnapPea(std::ostream& out, const NTriangulation& tri) { // Write header information. out << "% Triangulation\n"; if (tri.getPacketLabel().empty()) out << "Regina_Triangulation\n"; else out << stringToToken(tri.getPacketLabel()) << '\n'; // Write general details. out << "not_attempted 0.0\n"; out << "unknown_orientability\n"; out << "CS_unknown\n"; // Write cusps. out << "0 0\n"; // Write tetrahedra. out << tri.getNumberOfTetrahedra() << '\n'; int i, j; for (NTriangulation::TetrahedronIterator it = tri.getTetrahedra().begin(); it != tri.getTetrahedra().end(); it++) { // Although our precondition states that there are no boundary // triangles, we test for this anyway. If somebody makes a mistake and // calls this routine with a bounded triangulation, we don't want // to wind up calling tetrahedronIndex(0) and crashing. for (i = 0; i < 4; i++) if ((*it)->adjacentTetrahedron(i)) out << " " << tri.tetrahedronIndex( (*it)->adjacentTetrahedron(i)) << ' '; else out << " -1 "; out << '\n'; for (i = 0; i < 4; i++) out << ' ' << (*it)->adjacentGluing(i).str(); out << '\n'; // Incident cusps. for (i = 0; i < 4; i++) out << " -1 "; out << '\n'; // Meridians and longitudes. for (i = 0; i < 4; i++) { for (j = 0; j < 16; j++) out << " 0"; out << '\n'; } // Tetrahedron shape. out << "0.0 0.0\n"; } } } // namespace regina regina-4.95/engine/foreign/snappea.h000644 000765 000024 00000016231 12235500316 017305 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file foreign/snappea.h * \brief Allows reading and writing SnapPea files. */ #ifndef __SNAPPEA_H #ifndef __DOXYGEN #define __SNAPPEA_H #endif #include #include "regina-core.h" namespace regina { class NTriangulation; /** * \addtogroup foreign Foreign File Formats * Reading and writing foreign file formats. * @{ */ /** * Reads a triangulation from the given SnapPea file. * This routine reads from the filesystem; see readSnapPea(std::istream&) * for a variant of this routine that can read from an arbitrary input stream. * * A newly allocated triangulation will be returned; it is the user's * responsibility to deallocate this when it is finished with. * * The packet label of the new triangulation will be the manifold name * read from the second line of the SnapPea file. The first line of the * SnapPea file must simply be ``% Triangulation. * * If the file could not be read or if the data was not in the correct * format, 0 will be returned. * * \pre The first two lines of the SnapPea file each contain at most * 1000 characters. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * It assumes however that the \e contents of the file are in UTF-8. * * @param filename the name of the SnapPea file from which to read. * @return a new triangulation containing the data read from the SnapPea * file, or 0 on error. */ REGINA_API NTriangulation* readSnapPea(const char *filename); /** * Reads a triangulation from an input stream that contains the contents * of a SnapPea file. This is essentially the same as * readSnapPea(const char*), except that it can work with any input stream. * * A newly allocated triangulation will be returned; it is the user's * responsibility to deallocate this when it is finished with. * * The packet label of the new triangulation will be the manifold name * read from the second line of the SnapPea file. The first line of the * SnapPea file must simply be ``% Triangulation. * * If the input stream could not be read or if the data was not in the correct * format, 0 will be returned. * * \pre The first two lines of the SnapPea file each contain at most * 1000 characters. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * It assumes however that the \e contents of the file are in UTF-8. * * \ifacespython Not present, although the filesystem variant * readSnapPea(const char*) is available. * * @param in the input stream from which to read. * @return a new triangulation containing the data read from the SnapPea * data, or 0 on error. */ REGINA_API NTriangulation* readSnapPea(std::istream& in); /** * Writes the given triangulation to the given file in SnapPea format. * This routine writes to the filesystem; see * writesnapPea(std::ostream&, NTriangulation&) for a variant of this * routine that can write to an arbitrary output stream. * All information aside from tetrahedron gluings will be flagged as * unknown for SnapPea to recalculate. The manifold name written in the * file will be derived from the packet label. * * \pre The given triangulation is not invalid, and does not contain any * boundary triangles. * * \i18n This routine makes no assumptions about the * \ref i18n "character encoding" used in the given file \e name, and * simply passes it through unchanged to low-level C/C++ file I/O routines. * The \e contents of the file will be written using UTF-8. * * @param filename the name of the SnapPea file to which to write. * @param tri the triangulation to write to the SnapPea file. * @return \c true if the export was successful, or \c false if there * was a problem writing to the file. */ REGINA_API bool writeSnapPea(const char* filename, const NTriangulation& tri); /** * Writes the given triangulation to the given output stream using SnapPea's * file format. This is essentially the same as * writeSnapPea(const char*, NTriangulation&), except that it can work with * any output stream. * * All information aside from tetrahedron gluings will be flagged as * unknown for SnapPea to recalculate. The manifold name written in the * file will be derived from the packet label. * * \pre The given triangulation is not invalid, and does not contain any * boundary triangles. * * \i18n The contents of the SnapPea file will be written using UTF-8. * * \ifacespython Not present, although the filesystem variant * writeSnapPea(const char*, NTriangulation&) is available. * * @param out the output stream to which to write. * @param tri the triangulation to write to the SnapPea file. */ REGINA_API void writeSnapPea(std::ostream& out, const NTriangulation& tri); /*@}*/ } // namespace regina #endif regina-4.95/engine/generic/CMakeLists.txt000644 000765 000024 00000001053 12236247215 020233 0ustar00babstaff000000 000000 # generic # Files to compile SET ( FILES ngenerictriangulation ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} generic/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES dimtraits.h isosig-impl.h nfacetspec.h ngenericisomorphism-impl.h ngenericisomorphism.h ngenerictriangulation.h DESTINATION ${INCLUDEDIR}/generic COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/generic/dimtraits.h000644 000765 000024 00000010430 12236713375 017650 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file generic/dimtraits.h * \brief A template class that provides information on working in * different dimensions. */ #ifndef __DIMTRAITS_H #ifndef __DOXYGEN #define __DIMTRAITS_H #endif #include "regina-core.h" namespace regina { /** * \addtogroup generic Generic code * Template code to work with triangulations of arbitrary dimension. * @{ */ /** * A template class that provides typedefs and other information about * working in each of the supported dimensions. * * Note that this file does not bring in all of the headers for the * individual types. * * \ifacespython Not present. */ template struct DimTraits { typedef void Triangulation; /**< The main data type for a \a dim-manifold triangulation. */ typedef void Simplex; /**< The data type for a top-dimensional simplex in a \a dim-manifold triangulation. */ typedef void Isomorphism; /**< The data type for an isomorphism between two \a dim-manifold triangulations. */ typedef void FacetPairing; /**< The data type that represents a pairing of facets of top-dimensional simplices in a \a dim-manifold triangulation. */ typedef void Perm; /**< The permutation type used to describe gluings between top-dimensional simplices in a \a dim-manifold triangulation. */ }; #ifndef __DOXYGEN class Dim2Triangulation; class Dim2Triangle; class Dim2Isomorphism; class Dim2EdgePairing; class NPerm3; template <> struct DimTraits<2> { typedef Dim2Triangulation Triangulation; typedef Dim2Triangle Simplex; typedef Dim2Isomorphism Isomorphism; typedef Dim2EdgePairing FacetPairing; typedef NPerm3 Perm; }; class NTriangulation; class NTetrahedron; class NIsomorphism; class NFacePairing; class NPerm4; template <> struct DimTraits<3> { typedef NTriangulation Triangulation; typedef NTetrahedron Simplex; typedef NIsomorphism Isomorphism; typedef NFacePairing FacetPairing; typedef NPerm4 Perm; }; #endif } // namespace regina #endif regina-4.95/engine/generic/isosig-impl.h000644 000765 000024 00000050405 12236524106 020102 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "generic/ngenerictriangulation.h" #include "packet/npacket.h" /** * The numbers of base64 characters required to store an index into * DimTraits::Perm::Sn. * * This is 1 if dim <= 3 (since 4! <= 64), and 2 if dim = 4 (since 5! > 64). */ #define CHARS_PER_PERM(dim) ((dim) <= 3 ? 1 : 2) namespace regina { namespace { /** * Determine the integer value represented by the given character in * a signature string. */ inline unsigned SVAL(char c) { if (c >= 'a' && c <= 'z') return (c - 'a'); if (c >= 'A' && c <= 'Z') return (c - 'A' + 26); if (c >= '0' && c <= '9') return (c - '0' + 52); if (c == '+') return 62; return 63; } /** * Determine the character that represents the given integer value * in a signature string. */ inline char SCHAR(unsigned c) { if (c < 26) return (char(c) + 'a'); if (c < 52) return (char(c - 26) + 'A'); if (c < 62) return (char(c - 52) + '0'); if (c == 62) return '+'; return '-'; } /** * Is the given character a valid character in a signature string? */ inline bool SVALID(char c) { return ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '+' || c == '-'); } /** * Does the given string contain at least nChars characters? */ inline bool SHASCHARS(const char* s, unsigned nChars) { for ( ; nChars > 0; --nChars) if (! *s) return false; return true; } /** * Append an encoding of the given integer to the given string. * The integer is broken into nChars distinct 6-bit blocks, and the * lowest-significance blocks are written first. */ void SAPPEND(std::string& s, unsigned val, unsigned nChars) { for ( ; nChars > 0; --nChars) { s += SCHAR(val & 0x3F); val >>= 6; } } /** * Read the integer at the beginning of the given string. * Assumes the string has length >= nChars. */ unsigned SREAD(const char* s, unsigned nChars) { unsigned ans = 0; for (unsigned i = 0; i < nChars; ++i) ans += (SVAL(s[i]) << (6 * i)); return ans; } /** * Append up to three trits (0, 1 or 2) to the given string. * These are packed into a single character, with the first trit * representing the lowest-significance bits and so on. */ void SAPPENDTRITS(std::string& s, const char* trits, unsigned nTrits) { char ans = 0; if (nTrits >= 1) ans |= trits[0]; if (nTrits >= 2) ans |= (trits[1] << 2); if (nTrits >= 3) ans |= (trits[2] << 4); s += SCHAR(ans); } /** * Reads three trits (0, 1 or 2) from the given character. */ void SREADTRITS(char c, char* result) { unsigned val = SVAL(c); result[0] = val & 3; result[1] = (val >> 2) & 3; result[2] = (val >> 4) & 3; } } template std::string NGenericTriangulation::isoSig( const typename DimTraits::Triangulation& tri, unsigned simp, const typename DimTraits::Perm& vertices, typename DimTraits::Isomorphism* relabelling) { // These typedefs are already present in the class declaration, // but gcc 4.4 seems to break unless we include them here also. typedef typename DimTraits::Perm Perm; typedef typename DimTraits::Simplex Simplex; // Only process the component that simp belongs to. // --------------------------------------------------------------------- // Data for reconstructing a triangulation from an isomorphism signature // --------------------------------------------------------------------- // The number of simplices. unsigned nSimp = tri.getNumberOfSimplices(); // What happens to each new facet that we encounter? // Options are: // 0 -> boundary // 1 -> joined to a simplex not yet seen [gluing perm = identity] // 2 -> joined to a simplex already seen // These actions are stored in lexicographical order by (simplex, facet), // but only once for each facet (so we "skip" gluings that we've // already seen from the other direction). char* facetAction = new char[tri.template getNumberOfFaces()]; // What are the destination simplices and gluing permutations for // each facet under case #2 above? // For gluing permutations, we store the index of the permutation in // Perm::orderedSn. unsigned* joinDest = new unsigned[tri.template getNumberOfFaces()]; unsigned* joinGluing = new unsigned[tri.template getNumberOfFaces()]; // --------------------------------------------------------------------- // Data for finding the unique canonical isomorphism from this // connected component that maps (simplex, vertices) -> (0, 0..dim) // --------------------------------------------------------------------- // The image for each simplex and its vertices: int* image = new int[nSimp]; Perm* vertexMap = new Perm[nSimp]; // The preimage for each simplex: int* preImage = new int[nSimp]; // --------------------------------------------------------------------- // Looping variables // --------------------------------------------------------------------- unsigned facetPos, joinPos, nextUnusedSimp; unsigned simpImg, facetImg; unsigned simpSrc, facetSrc, dest; const Simplex* s; // --------------------------------------------------------------------- // The code! // --------------------------------------------------------------------- std::fill(image, image + nSimp, -1); std::fill(preImage, preImage + nSimp, -1); image[simp] = 0; vertexMap[simp] = vertices.inverse(); preImage[0] = simp; facetPos = 0; joinPos = 0; nextUnusedSimp = 1; // To obtain a canonical isomorphism, we must run through the simplices // and their facets in image order, not preimage order. // // This main loop is guaranteed to exit when (and only when) we have // exhausted a single connected component of the triangulation. for (simpImg = 0; simpImg < nSimp && preImage[simpImg] >= 0; ++simpImg) { simpSrc = preImage[simpImg]; s = tri.getSimplex(simpSrc); for (facetImg = 0; facetImg <= dim; ++facetImg) { facetSrc = vertexMap[simpSrc].preImageOf(facetImg); // INVARIANTS (held while we stay within a single component): // - nextUnusedSimp > simpImg // - image[simpSrc], preImage[image[simpSrc]] and vertexMap[simpSrc] // are already filled in. // Work out what happens to our source facet. if (! s->adjacentSimplex(facetSrc)) { // A boundary facet. facetAction[facetPos++] = 0; continue; } // We have a real gluing. Is it a gluing we've already seen // from the other side? dest = tri.simplexIndex(s->adjacentSimplex(facetSrc)); if (image[dest] >= 0) if (image[dest] < image[simpSrc] || (dest == simpSrc && vertexMap[simpSrc][s->adjacentFacet(facetSrc)] < vertexMap[simpSrc][facetSrc])) { // Yes. Just skip this gluing entirely. continue; } // Is it a completely new simplex? if (image[dest] < 0) { // Yes. The new simplex takes the next available // index, and the canonical gluing becomes the identity. image[dest] = nextUnusedSimp++; preImage[image[dest]] = dest; vertexMap[dest] = vertexMap[simpSrc] * s->adjacentGluing(facetSrc).inverse(); facetAction[facetPos++] = 1; continue; } // It's a simplex we've seen before. Record the gluing. joinDest[joinPos] = image[dest]; joinGluing[joinPos] = (vertexMap[dest] * s->adjacentGluing(facetSrc) * vertexMap[simpSrc].inverse()). orderedSnIndex(); ++joinPos; facetAction[facetPos++] = 2; } } // We have all we need. Pack it all together into a string. // We need to encode: // - the number of simplices in this component; // - facetAction[i], 0 <= i < facetPos; // - joinDest[i], 0 <= i < joinPos; // - joinGluing[i], 0 <= i < joinPos. std::string ans; // Keep it simple for small triangulations (1 character per integer). // For large triangulations, start with a special marker followed by // the number of chars per integer. unsigned nCompSimp = simpImg; unsigned nChars; if (nCompSimp < 63) nChars = 1; else { nChars = 0; unsigned tmp = nCompSimp; while (tmp > 0) { tmp >>= 6; ++nChars; } ans = SCHAR(63); ans += SCHAR(nChars); } // Off we go. unsigned i; SAPPEND(ans, nCompSimp, nChars); for (i = 0; i < facetPos; i += 3) SAPPENDTRITS(ans, facetAction + i, (facetPos >= i + 3 ? 3 : facetPos - i)); for (i = 0; i < joinPos; ++i) SAPPEND(ans, joinDest[i], nChars); for (i = 0; i < joinPos; ++i) SAPPEND(ans, joinGluing[i], CHARS_PER_PERM(dim)); // Record the canonical isomorphism if required. if (relabelling) for (i = 0; i < nCompSimp; ++i) { relabelling->simpImage(i) = image[i]; relabelling->facetPerm(i) = vertexMap[i]; } // Done! delete[] image; delete[] vertexMap; delete[] preImage; delete[] facetAction; delete[] joinDest; delete[] joinGluing; return ans; } template std::string NGenericTriangulation::isoSig( const typename DimTraits::Triangulation& tri, typename DimTraits::Isomorphism** relabelling) { // These typedefs are already present in the class declaration, // but gcc 4.7.3 seems to break unless we include them here also. typedef typename DimTraits::Perm Perm; typedef typename DimTraits::Triangulation Triangulation; typedef typename DimTraits::Isomorphism Isomorphism; // Make sure the user is not trying to do something illegal. if (relabelling && tri.getNumberOfComponents() != 1) { *relabelling = 0; // Return 0 to the user... relabelling = 0; // ... and forget they ever asked for an isomorphism. } Isomorphism* currRelabelling = 0; if (relabelling) { *relabelling = new Isomorphism(tri.getNumberOfSimplices()); currRelabelling = new Isomorphism(tri.getNumberOfSimplices()); } if (tri.getSimplices().empty()) { char c[2]; c[0] = SCHAR(0); c[1] = 0; return c; } // The triangulation is non-empty. Get a signature string for each // connected component. unsigned i; typename Triangulation::ComponentIterator it; unsigned cSimp; unsigned simp, perm; std::string curr; std::string* comp = new std::string[tri.getNumberOfComponents()]; for (it = tri.getComponents().begin(), i = 0; it != tri.getComponents().end(); ++it, ++i) { cSimp = (*it)->getNumberOfSimplices(); for (simp = 0; simp < (*it)->getNumberOfSimplices(); ++simp) for (perm = 0; perm < Perm::nPerms; ++perm) { curr = isoSig(tri, (*it)->getSimplex(simp)->markedIndex(), Perm::orderedSn[perm], currRelabelling); if ((simp == 0 && perm == 0) || (curr < comp[i])) { comp[i].swap(curr); if (relabelling) std::swap(*relabelling, currRelabelling); } } } // Pack the components together. std::sort(comp, comp + tri.getNumberOfComponents()); std::string ans; for (i = 0; i < tri.getNumberOfComponents(); ++i) ans += comp[i]; delete[] comp; delete currRelabelling; return ans; } template typename DimTraits::Triangulation* NGenericTriangulation::fromIsoSig(const std::string& sig) { // These typedefs are already present in the class declaration, // but gcc 4.4 seems to break unless we include them here also. typedef typename DimTraits::Perm Perm; typedef typename DimTraits::Simplex Simplex; typedef typename DimTraits::Triangulation Triangulation; std::auto_ptr ans(new Triangulation()); NPacket::ChangeEventSpan span(ans.get()); const char* c = sig.c_str(); // Initial check for invalid characters. const char* d; for (d = c; *d; ++d) if (! SVALID(*d)) return 0; unsigned i, j; unsigned nSimp, nChars; while (*c) { // Read one component at a time. nSimp = SVAL(*c++); if (nSimp < 63) nChars = 1; else { if (! *c) return 0; nChars = SVAL(*c++); if (! SHASCHARS(c, nChars)) return 0; nSimp = SREAD(c, nChars); c += nChars; } if (nSimp == 0) { // Empty component. continue; } // Non-empty component; keep going. char* facetAction = new char[(dim+1) * nSimp + 2]; unsigned nFacets = 0; unsigned facetPos = 0; unsigned nJoins = 0; for ( ; nFacets < (dim+1) * nSimp; facetPos += 3) { if (! *c) { delete[] facetAction; return 0; } SREADTRITS(*c++, facetAction + facetPos); for (i = 0; i < 3; ++i) { // If we're already finished, make sure the leftover trits // are zero. if (nFacets == (dim+1) * nSimp) { if (facetAction[facetPos + i] != 0) { delete[] facetAction; return 0; } continue; } if (facetAction[facetPos + i] == 0) ++nFacets; else if (facetAction[facetPos + i] == 1) nFacets += 2; else if (facetAction[facetPos + i] == 2) { nFacets += 2; ++nJoins; } else { delete[] facetAction; return 0; } if (nFacets > (dim+1) * nSimp) { delete[] facetAction; return 0; } } } unsigned* joinDest = new unsigned[nJoins + 1]; for (i = 0; i < nJoins; ++i) { if (! SHASCHARS(c, nChars)) { delete[] facetAction; delete[] joinDest; return 0; } joinDest[i] = SREAD(c, nChars); c += nChars; } unsigned* joinGluing = new unsigned[nJoins + 1]; for (i = 0; i < nJoins; ++i) { if (! SHASCHARS(c, 1)) { delete[] facetAction; delete[] joinDest; delete[] joinGluing; return 0; } joinGluing[i] = SREAD(c, CHARS_PER_PERM(dim)); c += CHARS_PER_PERM(dim); if (joinGluing[i] >= Perm::nPerms) { delete[] facetAction; delete[] joinDest; delete[] joinGluing; return 0; } } // End of component! Simplex** simp = new Simplex*[nSimp]; for (i = 0; i < nSimp; ++i) simp[i] = ans->newSimplex(); facetPos = 0; unsigned nextUnused = 1; unsigned joinPos = 0; for (i = 0; i < nSimp; ++i) for (j = 0; j <= dim; ++j) { // Already glued from the other side: if (simp[i]->adjacentSimplex(j)) continue; if (facetAction[facetPos] == 0) { // Boundary facet. } else if (facetAction[facetPos] == 1) { // Join to new simplex. simp[i]->joinTo(j, simp[nextUnused++], Perm()); } else { // Join to existing simplex. if (joinDest[joinPos] >= nextUnused || simp[joinDest[joinPos]]->adjacentSimplex( Perm::orderedSn[joinGluing[joinPos]][j])) { delete[] facetAction; delete[] joinDest; delete[] joinGluing; for (int k = 0; k < nSimp; ++k) delete simp[k]; delete[] simp; return 0; } simp[i]->joinTo(j, simp[joinDest[joinPos]], Perm::orderedSn[joinGluing[joinPos]]); ++joinPos; } ++facetPos; } delete[] facetAction; delete[] joinDest; delete[] joinGluing; delete[] simp; } return ans.release(); } template size_t NGenericTriangulation::isoSigComponentSize(const std::string& sig) { const char* c = sig.c_str(); // Examine the first character. // Note that SVALID also ensures that *c is non-null (i.e., it // detects premature end of string). if (! SVALID(*c)) return 0; size_t nSimp = SVAL(*c); if (nSimp < 63) return nSimp; // The number of simplices is so large that it requires several // characters to store. ++c; if (! *c) return 0; size_t nChars = SVAL(*c++); for (const char* d = c; d < c + nChars; ++d) if (! SVALID(*d)) return 0; return SREAD(c, nChars); } } // namespace regina regina-4.95/engine/generic/nfacetspec.h000644 000765 000024 00000033643 12236713375 017776 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file generic/nfacetspec.h * \brief Allows lightweight representation of individual facets of * simplices. */ #ifndef __NFACETSPEC_H #ifndef __DOXYGEN #define __NFACETSPEC_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup generic * @{ */ /** * A lightweight class used to refer to a particular facet of a * particular simplex in a triangulation. Only the simplex index * and the facet number are stored. * * The template parameter gives the dimension of the triangulation * (so for dimension three, this class describes a face of a tetrahedron, * and for dimension four it describes a facet of a pentachoron). * * Facilities are provided for iterating through simplex facets. * With this in mind, it is also possible to represent the overall * boundary, a past-the-end value and a before-the-start value. * * When iterating through the simplex facets, the facets will be * ordered first by simplex index and then by facet number. The * overall boundary appears after all other simplex facets. * * If there are \a n simplices, the simplices will be numbered from 0 * to n-1 inclusive. The boundary will be represented as * simplex \a n, facet 0. The past-the-end value will be represented * as simplex \a n, facet 1, and the before-the-start value will be * represented as simplex -1, facet \a dim. * * \ifacespython The generic template NFacetSpec is not available to * Python users, although the special 3-dimensional case NTetFace is. * All Python notes in this class refer to the special case * NTetFace only. */ template struct NFacetSpec { int simp; /**< The simplex referred to. Simplex numbering begins * at 0. */ int facet; /**< The facet of the simplex referred to. The facet number * is between 0 and \a dim inclusive. */ /** * Creates a new specifier with no initialisation. This * specifier must be initialised before it is used. */ NFacetSpec(); /** * Creates a new specifier referring to the given facet of the given * simplex. * * @param newSimp the given simplex; see the class notes for * allowable values of this parameter. * @param newFacet the given facet; this should be between 0 and * \a dim inclusive. */ NFacetSpec(int newSimp, int newFacet); /** * Creates a new specifier referring to the same simplex facet as * the given specifier. * * @param cloneMe the specifier to clone. */ NFacetSpec(const NFacetSpec& cloneMe); /** * Determines if this specifier represents the overall boundary. * * @param nSimplices the number of simplices under consideration. * Note that the boundary is represented in this specifier as * simplex \a nSimplices, facet 0. * @return \c true if and only if this specifier represents the * overall boundary. */ bool isBoundary(unsigned nSimplices) const; /** * Determines if this specifier represents a before-the-start value. * * @return \c true if and only if this specifier is before-the-start. */ bool isBeforeStart() const; /** * Determines if this specifier represents a past-the-end value. * You can optionally declare the overall boundary to be * past-the-end as well as the already predefined past-the-end value. * * @param nSimplices the number of simplices under consideration. * Note that past-the-end is represented in this specifier as * simplex \a nSimplices, facet 1. * @param boundaryAlso \c true if the overall boundary should be * considered past-the-end in addition to the predefined past-the-end * value. * @return \c true if and only if this specifier is past-the-end. */ bool isPastEnd(unsigned nSimplices, bool boundaryAlso) const; /** * Sets this specifier to the first facet of the first simplex. */ void setFirst(); /** * Sets this specifier to the overall boundary. * * @param nSimplices the number of simplices under consideration. * Note that the boundary is represented in this specifier as * simplex \a nSimplices, facet 0. */ void setBoundary(unsigned nSimplices); /** * Sets this specifier to before-the-start. */ void setBeforeStart(); /** * Sets this specifier to past-the-end. * * @param nSimplices the number of simplices under consideration. * Note that past-the-end is represented in this specifier as * simplex \a nSimplices, facet 1. */ void setPastEnd(unsigned nSimplices); /** * Sets this specifier to the value of the given specifier. * * @param other the given specifier. * @return a reference to this specifier. */ NFacetSpec& operator = (const NFacetSpec& other); /** * Increments this specifier. It will be changed to point to the * next simplex facet. * * Facets are ordered first by simplex index and then by facet * number. The overall boundary appears after all other facets. * * \pre This specifier is not past-the-end. * * \ifacespython Not present, although the preincrement operator is * present in python as the member function inc(). * * @return A copy of this specifier after it has been incremented. */ NFacetSpec operator ++ (); /** * Increments this specifier. It will be changed to point to the * next simplex facet. * * Facets are ordered first by simplex index and then by facet * number. The overall boundary appears after all other facets. * * \pre This specifier is not past-the-end. * * \ifacespython This routine is named inc() since python does not * support the increment operator. * * @return A copy of this specifier before it was incremented. */ NFacetSpec operator ++ (int); /** * Decrements this specifier. It will be changed to point to the * previous simplex facet. * * Facets are ordered first by simplex index and then by facet * number. The overall boundary appears after all other facets. * * \pre This specifier is not before-the-start. * * \ifacespython Not present, although the predecrement operator is * present in python as the member function dec(). * * @return A copy of this specifier after it has been decremented. */ NFacetSpec operator -- (); /** * Decrements this specifier. It will be changed to point to the * previous simplex facet. * * Facets are ordered first by simplex index and then by facet * number. The overall boundary appears after all other facets. * * \pre This specifier is not before-the-start. * * \ifacespython This routine is named dec() since python does not * support the decrement operator. * * @return A copy of this specifier before it was decremented. */ NFacetSpec operator -- (int); /** * Determines if this and the given specifier are identical. * * @param other the specifier to compare with this. * @return \c true if and only if this and the given specifier are * equal. */ bool operator == (const NFacetSpec& other) const; /** * Determines if this is less than the given specifier. * * @param other the specifier to compare with this. * @return \c true if and only if this is less than the given * specifier. */ bool operator < (const NFacetSpec& other) const; /** * Determines if this is less than or equal to the given specifier. * * @param other the specifier to compare with this. * @return \c true if and only if this is less than or equal to * the given specifier. */ bool operator <= (const NFacetSpec& other) const; }; /** * A lightweight class used to refer to a particular edge of a * particular triangle in a 2-manifold triangulation. This is a * convenience typedef for the template instance NFacetSpec<2>. * * \ifacespython The specific class Dim2TriangleEdge is available through * Python, even though the generic template NFacetSpec is not. */ typedef NFacetSpec<2> Dim2TriangleEdge; /** * A lightweight class used to refer to a particular face of a * particular tetrahedron in a 3-manifold triangulation. This is a * convenience typedef for the template instance NFacetSpec<3>. * * \ifacespython The specific class NTetFace is available through Python, * even though the generic template NFacetSpec is not. Both the old field * names (\a tet and \a face) and the new field names (\a simp and \a facet) * are provided, though the old names are deprecated and will be removed * in a future version of Regina. * * \deprecated For the 3-dimensional class NTetFace, the old field names * \a tet and \a face are deprecated. Please use the new (generic) names * \a simp and \a facet instead. The old names are no longer supported * in C++, but will continue to be supported in Python until Regina 5.0. */ typedef NFacetSpec<3> NTetFace; /*@}*/ // Inline functions for NFacetSpec template inline NFacetSpec::NFacetSpec() { } template inline NFacetSpec::NFacetSpec(int newSimp, int newFacet) : simp(newSimp), facet(newFacet) { } template inline NFacetSpec::NFacetSpec(const NFacetSpec& cloneMe) : simp(cloneMe.simp), facet(cloneMe.facet) { } template inline bool NFacetSpec::isBoundary(unsigned nSimplices) const { return (simp == static_cast(nSimplices) && facet == 0); } template inline bool NFacetSpec::isBeforeStart() const { return (simp < 0); } template inline bool NFacetSpec::isPastEnd(unsigned nSimplices, bool boundaryAlso) const { return (simp == static_cast(nSimplices) && (boundaryAlso || facet > 0)); } template inline void NFacetSpec::setFirst() { simp = facet = 0; } template inline void NFacetSpec::setBoundary(unsigned nSimplices) { simp = nSimplices; facet = 0; } template inline void NFacetSpec::setBeforeStart() { simp = -1; facet = dim; } template inline void NFacetSpec::setPastEnd(unsigned nSimplices) { simp = nSimplices; facet = 1; } template inline NFacetSpec& NFacetSpec::operator = ( const NFacetSpec& other) { simp = other.simp; facet = other.facet; return *this; } template inline NFacetSpec NFacetSpec::operator ++ () { if (++facet > dim) { facet = 0; ++simp; } return *this; } template inline NFacetSpec NFacetSpec::operator ++ (int) { NFacetSpec ans(*this); if (++facet > dim) { facet = 0; ++simp; } return ans; } template inline NFacetSpec NFacetSpec::operator -- () { if (--facet < 0) { facet = dim; --simp; } return *this; } template inline NFacetSpec NFacetSpec::operator -- (int) { NFacetSpec ans(*this); if (--facet < 0) { facet = dim; --simp; } return ans; } template inline bool NFacetSpec::operator == (const NFacetSpec& other) const { return (simp == other.simp && facet == other.facet); } template inline bool NFacetSpec::operator < (const NFacetSpec& other) const { return (simp < other.simp || (simp == other.simp && facet < other.facet)); } template inline bool NFacetSpec::operator <= (const NFacetSpec& other) const { return (simp < other.simp || (simp == other.simp && facet <= other.facet)); } } // namespace regina #endif regina-4.95/engine/generic/ngenericisomorphism-impl.h000644 000765 000024 00000016453 12234011536 022672 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /* Template definitions for ngenericisomorphism.h. */ #include #include #include "packet/npacket.h" #include "generic/ngenericisomorphism.h" namespace regina { template void NGenericIsomorphism::writeTextShort(std::ostream& out) const { out << "Isomorphism between " << dim << "-manifold triangulations"; } template void NGenericIsomorphism::writeTextLong(std::ostream& out) const { for (unsigned i = 0; i < nSimplices_; ++i) out << i << " -> " << simpImage_[i] << " (" << facetPerm_[i] << ")\n"; } template bool NGenericIsomorphism::isIdentity() const { for (unsigned p = 0; p < nSimplices_; ++p) { if (simpImage_[p] != static_cast(p)) return false; if (! facetPerm_[p].isIdentity()) return false; } return true; } template NGenericIsomorphism::NGenericIsomorphism( const NGenericIsomorphism& cloneMe) : ShareableObject(), nSimplices_(cloneMe.nSimplices_), simpImage_(cloneMe.nSimplices_ > 0 ? new int[cloneMe.nSimplices_] : 0), facetPerm_(cloneMe.nSimplices_ > 0 ? new Perm[cloneMe.nSimplices_] : 0) { std::copy(cloneMe.simpImage_, cloneMe.simpImage_ + nSimplices_, simpImage_); std::copy(cloneMe.facetPerm_, cloneMe.facetPerm_ + nSimplices_, facetPerm_); } template typename NGenericIsomorphism::Isomorphism* NGenericIsomorphism:: random(unsigned nSimplices) { Isomorphism* ans = new Isomorphism(nSimplices); // Randomly choose the destination simplices. unsigned i; for (i = 0; i < nSimplices; i++) ans->simpImage_[i] = i; std::random_shuffle(ans->simpImage_, ans->simpImage_ + nSimplices); // Randomly choose the individual permutations. for (i = 0; i < nSimplices; i++) ans->facetPerm_[i] = Perm::Sn[rand() % Perm::nPerms]; return ans; } template typename NGenericIsomorphism::Triangulation* NGenericIsomorphism::apply( const typename NGenericIsomorphism::Triangulation* original) const { if (original->getNumberOfSimplices() != nSimplices_) return 0; if (nSimplices_ == 0) return new Triangulation(); Triangulation* ans = new Triangulation(); Simplex** tet = new Simplex*[nSimplices_]; unsigned long t; int f; NPacket::ChangeEventSpan span(ans); for (t = 0; t < nSimplices_; t++) tet[t] = ans->newSimplex(); for (t = 0; t < nSimplices_; t++) tet[simpImage_[t]]->setDescription( original->getSimplex(t)->getDescription()); const Simplex *myTet, *adjTet; unsigned long adjTetIndex; Perm gluingPerm; for (t = 0; t < nSimplices_; t++) { myTet = original->getSimplex(t); for (f = 0; f <= dim; f++) if ((adjTet = myTet->adjacentSimplex(f))) { // We have an adjacent simplex. adjTetIndex = original->simplexIndex(adjTet); gluingPerm = myTet->adjacentGluing(f); // Make the gluing from one side only. if (adjTetIndex > t || (adjTetIndex == t && gluingPerm[f] > f)) tet[simpImage_[t]]->joinTo(facetPerm_[t][f], tet[simpImage_[adjTetIndex]], facetPerm_[adjTetIndex] * gluingPerm * facetPerm_[t].inverse()); } } delete[] tet; return ans; } template void NGenericIsomorphism::applyInPlace( typename NGenericIsomorphism::Triangulation* tri) const { if (tri->getNumberOfSimplices() != nSimplices_) return; if (nSimplices_ == 0) return; Triangulation staging; Simplex** tet = new Simplex*[nSimplices_]; unsigned long t; int f; NPacket::ChangeEventSpan span1(&staging); for (t = 0; t < nSimplices_; t++) tet[t] = staging.newSimplex(); for (t = 0; t < nSimplices_; t++) tet[simpImage_[t]]->setDescription( tri->getSimplex(t)->getDescription()); const Simplex *myTet, *adjTet; unsigned long adjTetIndex; Perm gluingPerm; for (t = 0; t < nSimplices_; t++) { myTet = tri->getSimplex(t); for (f = 0; f <= dim; f++) if ((adjTet = myTet->adjacentSimplex(f))) { // We have an adjacent simplex. adjTetIndex = tri->simplexIndex(adjTet); gluingPerm = myTet->adjacentGluing(f); // Make the gluing from one side only. if (adjTetIndex > t || (adjTetIndex == t && gluingPerm[f] > f)) tet[simpImage_[t]]->joinTo(facetPerm_[t][f], tet[simpImage_[adjTetIndex]], facetPerm_[adjTetIndex] * gluingPerm * facetPerm_[t].inverse()); } } delete[] tet; NPacket::ChangeEventSpan span2(tri); tri->removeAllSimplices(); tri->swapContents(staging); } } // namespace regina regina-4.95/engine/generic/ngenericisomorphism.h000644 000765 000024 00000042303 12234011536 021724 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file generic/ngenericisomorphism.h * \brief Deals with combinatorial isomorphisms of \a n-manifold * triangulations. */ #ifndef __NGENERALISOMORPHISM_H #ifndef __DOXYGEN #define __NGENERALISOMORPHISM_H #endif #include "regina-core.h" #include "shareableobject.h" #include "generic/dimtraits.h" #include "generic/nfacetspec.h" namespace regina { /** * \weakgroup generic * @{ */ /** * A dimension-agnostic base class that represents a combinatorial * isomorphism from one \a dim-manifold triangulation into another. * * Each dimension that Regina works with (2, 3 and 4) offers its own * subclass with richer functionality; users typically do not need to * work with this template base class directly. * * In essence, a combinatorial isomorphism from triangulation T to * triangulation U is a one-to-one map from the simplices of T to the * simplices of U that allows relabelling of both the simplices and * their facets (or equivalently, their vertices), and that preserves * gluings across adjacent simplices. * * More precisely: An isomorphism consists of (i) a one-to-one map f * from the simplices of T to the simplices of U, and (ii) for each * simplex S of T, a permutation f_S of the facets (0,...,\a dim) of S, * for which the following condition holds: * * - If facet k of simplex S and facet k' of simplex S' * are identified in T, then facet f_S(k) of f(S) and facet f_S'(k') * of f(S') are identified in U. Moreover, their gluing is consistent * with the facet/vertex permutations; that is, there is a commutative * square involving the gluing maps in T and U and the permutations * f_S and f_S'. * * Isomorphisms can be boundary complete or * boundary incomplete. A boundary complete isomorphism * satisfies the additional condition: * * - If facet x is a boundary facet of T then facet f(x) is a boundary * facet of U. * * A boundary complete isomorphism thus indicates that a copy of * triangulation T is present as an entire component (or components) of U, * whereas a boundary incomplete isomorphism represents an embedding of a * copy of triangulation T as a subcomplex of some possibly larger component * (or components) of U. * * Note that in all cases triangulation U may contain more simplices * than triangulation T. * * \pre The dimension argument \a dim is either 2, 3 or 4. * * \ifacespython Not present, though the dimension-specific subclasses * (such as NIsomorphism and Dim4Isomorphism) are available for Python users. * * \testpart */ template class REGINA_API NGenericIsomorphism : public ShareableObject { public: typedef typename DimTraits::Isomorphism Isomorphism; /**< The isomorphism class used by triangulations of this specific dimension. Typically this is a subclass of NGenericIsomorphism. */ typedef typename DimTraits::Perm Perm; /**< The permutation class used to glue together facets of simplices when building triangulations in this dimension. */ typedef typename DimTraits::Simplex Simplex; /**< The class that represents a top-level simplex of a triangulation in this dimension. */ typedef typename DimTraits::Triangulation Triangulation; /**< The triangulation class specific to this dimension. */ protected: unsigned nSimplices_; /**< The number of simplices in the source triangulation. */ int* simpImage_; /**< The simplex of the destination triangulation that each simplex of the source triangulation maps to. */ Perm* facetPerm_; /**< The permutation applied to the facets of each source simplex. */ public: /** * Creates a new isomorphism with no initialisation. * * @param nSimplices the number of simplices in the source * triangulation associated with this isomorphism; this may be zero. */ NGenericIsomorphism(unsigned nSimplices); /** * Creates a new isomorphism identical to the given isomorphism. * * @param cloneMe the isomorphism upon which to base the new * isomorphism. */ NGenericIsomorphism(const NGenericIsomorphism& cloneMe); /** * Destroys this isomorphism. */ ~NGenericIsomorphism(); /** * Returns the number of simplices in the source triangulation * associated with this isomorphism. Note that this is always * less than or equal to the number of simplices in the * destination triangulation. * * @return the number of simplices in the source triangulation. */ unsigned getSourceSimplices() const; /** * Determines the image of the given source simplex under * this isomorphism. * * \ifacespython This is not available for Python users, even in * the dimension-specific subclasses. However, the read-only * version of this routine is. * * @param sourceSimp the index of the source simplex; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return a reference to the index of the destination simplex * that the source simplex maps to. */ int& simpImage(unsigned sourceSimp); /** * Determines the image of the given source simplex under * this isomorphism. * * @param sourceSimp the index of the source simplex; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return the index of the destination simplex * that the source simplex maps to. */ int simpImage(unsigned sourceSimp) const; /** * Returns a read-write reference to the permutation that is * applied to the (\a dim + 1) facets of the given source simplex * under this isomorphism. * Facet \a i of source simplex \a sourceSimp will be mapped to * facet facetPerm(sourceSimp)[i] of simplex * simpImage(sourceSimp). * * \ifacespython This is not available for Python users, even in * the dimension-specific subclasses. However, the read-only * version of this routine is. * * @param sourceSimp the index of the source simplex containing * the original (\a dim + 1) facets; this must be between 0 and * getSourceSimplices()-1 inclusive. * @return a read-write reference to the permutation applied to the * facets of the source simplex. */ Perm& facetPerm(unsigned sourceSimp); /** * Determines the permutation that is applied to the (\a dim + 1) * facets of the given source simplex under this isomorphism. * Facet \a i of source simplex \a sourceSimp will be mapped to * face facetPerm(sourceSimp)[i] of simplex * simpImage(sourceSimp). * * @param sourceSimp the index of the source simplex containing * the original (\a dim + 1) facets; this must be between 0 and * getSourceSimplices()-1 inclusive. * @return the permutation applied to the facets of the * source simplex. */ Perm facetPerm(unsigned sourceSimp) const; /** * Determines the image of the given source simplex facet * under this isomorphism. Note that a value only is returned; this * routine cannot be used to alter the isomorphism. * * @param source the given source simplex facet; this must * be one of the (\a dim + 1) facets of one of the getSourceSimplices() * simplices in the source triangulation. * @return the image of the source simplex facet under this * isomorphism. */ NFacetSpec operator [] (const NFacetSpec& source) const; /** * Determines whether or not this is an identity isomorphism. * * In an identity isomorphism, each simplex image is itself, * and within each simplex the facet/vertex permutation is * the identity permutation. * * @return \c true if this is an identity isomorphism, or * \c false otherwise. */ bool isIdentity() const; /** * This NGenericIsomorphism object represents a combinatorial * identification from a triangulation T to a triangulation U. * This routine produces the triangulation U, i.e. the range. The * input parameter (original) represents the domain, T. * * The given triangulation (call this T) is not modified in any way. * A new triangulation (call this U) is returned, so that this * isomorphism represents a one-to-one, onto and boundary complete * isomorphism from T to U. That is, T and U are combinatorially * identical triangulations, and this isomorphism describes the * corresponding mapping between simplex and simplex facets. * * The resulting triangulation U is newly created, and must be * destroyed by the caller of this routine. * * There are several preconditions to this routine. This * routine does a small amount of sanity checking (and returns 0 * if an error is detected), but it certainly does not check the * entire set of preconditions. It is up to the caller of this * routine to verify that all of the following preconditions are * met. * * \pre The number of simplices in the given triangulation is * precisely the number returned by getSourceSimplices() for * this isomorphism. * \pre This is a valid isomorphism (i.e., it has been properly * initialised, so that all simplex images are non-negative * and distinct, and all facet permutations are real permutations * of (0,...,\a dim). * \pre Each simplex image for this isomorphism lies * between 0 and getSourceSimplices()-1 inclusive * (i.e., this isomorphism does not represent a mapping from a * smaller triangulation into a larger triangulation). * * @param original the triangulation to which this isomorphism * should be applied. * @return the resulting new triangulation, or 0 if a problem * was encountered (i.e., an unmet precondition was noticed). */ Triangulation* apply(const Triangulation* original) const; /** * Applies this isomorphism to the given triangulation, * modifying the given triangulation directly. * * This is similar to apply(), except that instead of creating a * new triangulation, the simplices and vertices of the given * triangulation are modified directly. * * See apply() for further details on how this operation is performed. * * As with apply(), there are several preconditions to this routine. * This routine does a small amount of sanity checking (and returns * without changes if an error is detected), but it certainly does * not check the entire set of preconditions. It is up to the * caller of this routine to verify that all of the following * preconditions are met. * * \pre The number of simplices in the given triangulation is * precisely the number returned by getSourceSimplices() for * this isomorphism. * \pre This is a valid isomorphism (i.e., it has been properly * initialised, so that all simplex images are non-negative * and distinct, and all facet permutations are real permutations * of (0,...,\a dim). * \pre Each simplex image for this isomorphism lies * between 0 and getSourceSimplices()-1 inclusive * (i.e., this isomorphism does not represent a mapping from a * smaller triangulation into a larger triangulation). * * @param tri the triangulation to which this isomorphism * should be applied. */ void applyInPlace(Triangulation* tri) const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Returns a random isomorphism for the given number of * simplices. This isomorphism will reorder simplices * 0 to nSimplices-1 in a random fashion, and for * each simplex a random permutation of its (\a dim + 1) vertices * will be selected. * * The isomorphism will be newly constructed, and must be * destroyed by the caller of this routine. The new isomorphism * will be of the appropriate dimension-specific subclass * (e.g., NIsomorphism for \a dim=3, or Dim2Isomorphism for * \a dim=2). * * Note that both the STL random number generator and the * standard C function rand() are used in this routine. All * possible isomorphisms for the given number of simplices are * equally likely. * * @param nSimplices the number of simplices that the new * isomorphism should operate upon. * @return the newly constructed random isomorphism. */ static Isomorphism* random(unsigned nSimplices); }; /*@}*/ // Inline functions for NGenericIsomorphism template inline NGenericIsomorphism::NGenericIsomorphism(unsigned nSimplices) : nSimplices_(nSimplices), simpImage_(nSimplices > 0 ? new int[nSimplices] : 0), facetPerm_(nSimplices > 0 ? new Perm[nSimplices] : 0) { } template inline NGenericIsomorphism::~NGenericIsomorphism() { // Always safe to delete null. delete[] simpImage_; delete[] facetPerm_; } template inline unsigned NGenericIsomorphism::getSourceSimplices() const { return nSimplices_; } template inline int& NGenericIsomorphism::simpImage(unsigned sourceSimp) { return simpImage_[sourceSimp]; } template inline int NGenericIsomorphism::simpImage(unsigned sourceSimp) const { return simpImage_[sourceSimp]; } template inline typename NGenericIsomorphism::Perm& NGenericIsomorphism::facetPerm( unsigned sourceSimp) { return facetPerm_[sourceSimp]; } template inline typename NGenericIsomorphism::Perm NGenericIsomorphism::facetPerm( unsigned sourceSimp) const { return facetPerm_[sourceSimp]; } template inline NFacetSpec NGenericIsomorphism::operator [] ( const NFacetSpec& source) const { return NFacetSpec(simpImage_[source.simp], facetPerm_[source.simp][source.facet]); } } // namespace regina #endif regina-4.95/engine/generic/ngenerictriangulation.cpp000644 000765 000024 00000005173 12236713375 022606 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "generic/ngenerictriangulation.h" // Template implementations: #include "generic/isosig-impl.h" // Dimension-specific headers required for template instantiations: #include "dim2/dim2triangulation.h" #include "triangulation/ntriangulation.h" // Instantiate the templates! template class regina::NGenericTriangulation<2>; template class regina::NGenericTriangulation<3>; regina-4.95/engine/generic/ngenerictriangulation.h000644 000765 000024 00000026317 12236524106 022246 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file generic/ngenerictriangulation.h * \brief Generic code for working with triangulations of arbitrary dimension. */ #ifndef __NGENERICTRIANGULATION_H #ifndef __DOXYGEN #define __NGENERICTRIANGULATION_H #endif #include "regina-core.h" #include "generic/dimtraits.h" #include namespace regina { /** * \weakgroup generic * @{ */ /** * A generic helper class for working with triangulations of arbitrary * dimension. * * This class is designed to implement member functions of the various * triangulation classes in a unified, dimension-agnostic manner. * * End users should not use this class directly. Instead they should * call the corresponding member functions from the corresponding * triangulation classes (NTriangulation and so on). * * \apinotfinal * * \ifacespython Not present. * * \pre The template argument \a dim must be one of the dimensions that * Regina supports. */ template class REGINA_API NGenericTriangulation : public DimTraits { public: using typename DimTraits::Isomorphism; using typename DimTraits::Perm; using typename DimTraits::Simplex; using typename DimTraits::Triangulation; protected: /** * Constructs the isomorphism signature for the given triangulation. * * An isomorphism signature is a compact text representation of * a triangulation. Unlike dehydrations for 3-manifold triangulations, * an isomorphism signature uniquely determines a triangulation up * to combinatorial isomorphism (assuming the dimension is known in * advance). * That is, two triangulations of dimension \a dim are combinatorially * isomorphic if and only if their isomorphism signatures are the same. * * The isomorphism signature is constructed entirely of * printable characters, and has length proportional to * n log n, where \a n is the number of simplices. * * Isomorphism signatures are more general than dehydrations: * they can be used with any triangulation (including closed, * bounded and/or disconnected triangulations, as well * as triangulations with large numbers of triangles). * * The time required to construct the isomorphism signature of a * triangulation is O(n^2 log^2 n). * * The routine fromIsoSig() can be used to recover a * triangulation from an isomorphism signature. The triangulation * recovered might not be identical to the original, but it will be * combinatorially isomorphic. * * If \a relabelling is non-null (i.e., it points to some * Isomorphism pointer \a p), then it will be modified to point * to a new isomorphism that describes the precise relationship * between this triangulation and the reconstruction from fromIsoSig(). * Specifically, the triangulation that is reconstructed from * fromIsoSig() will be combinatorially identical to * relabelling.apply(this). * * \pre If \a relabelling is non-null, then this triangulation * must be non-empty and connected. The facility to return a * relabelling for disconnected triangulations may be added to * Regina in a later release. * * \warning Do not mix isomorphism signatures between dimensions! * It is possible that the same string could corresponding to both a * \a p-dimensional triangulation and a \a q-dimensional triangulation * for different \a p and \a q. * * @param tri the triangulation whose isomorphism signature will be * computed. * @param relabelling if non-null, this will be modified to point to a * new isomorphism describing the relationship between this * triangulation and that reconstructed from fromIsoSig(), as * described above. * @return the isomorphism signature of the given triangulation. */ static std::string isoSig( const typename DimTraits::Triangulation& tri, typename DimTraits::Isomorphism** relabelling = 0); /** * Recovers a full triangulation from an isomorphism signature. * * See isoSig() for more information on isomorphism signatures. * It will be assumed that the signature describes a triangulation of * dimension \a dim. * * The triangulation that is returned will be newly created. * * Calling isoSig() followed by fromIsoSig() is not guaranteed to * produce an identical triangulation to the original, but it * \e is guaranteed to produce a combinatorially isomorphic * triangulation. * * \warning Do not mix isomorphism signatures between dimensions! * It is possible that the same string could corresponding to both a * \a p-dimensional triangulation and a \a q-dimensional triangulation * for different \a p and \a q. * * @param sig the isomorphism signature of the * triangulation to construct. Note that, unlike dehydration * strings for 3-manifold triangulations, case is important for * isomorphism signatures. * @return a newly allocated triangulation if the reconstruction was * successful, or null if the given string was not a valid * isomorphism signature. */ static typename DimTraits::Triangulation* fromIsoSig( const std::string& sig); /** * Deduces the number of top-dimensional simplices in a * connected triangulation from its isomorphism signature. * * See isoSig() for more information on isomorphism signatures. * It will be assumed that the signature describes a triangulation of * dimension \a dim. * * If the signature describes a connected triangulation, this * routine will simply return the size of that triangulation * (e.g., the number of tetrahedra in the case \a dim = 3). * You can also pass an isomorphism signature that describes a * disconnected triangulation; however, this routine will only * return the number of simplices in the first connected component. * If you need the total number of simplices in a disconnected * triangulation, you will need to reconstruct the full triangulation * by calling fromIsoSig() instead. * * This routine is very fast, since it only examines the first * few characters of the isomorphism signature (in which the size * of the first component is encoded). However, it is therefore * possible to pass an invalid isomorphism signature and still * receive a positive result. If you need to \e test whether a * signature is valid or not, you must call fromIsoSig() * instead, which will examine the entire signature in full. * * \warning Do not mix isomorphism signatures between dimensions! * It is possible that the same string could corresponding to both a * \a p-dimensional triangulation and a \a q-dimensional triangulation * for different \a p and \a q. * * @param sig an isomorphism signature of a \a dim-dimensional * triangulation. Note that, unlike dehydration strings for * 3-manifold triangulations, case is important for isomorphism * signatures. * @return the number of top-dimensional simplices in the first * connected component, or 0 if this could not be determined * because the given string was not a valid isomorphism signature. */ static size_t isoSigComponentSize(const std::string& sig); private: /** * Internal to isoSig(). * * Constructs a candidate isomorphism signature for a single * component of this triangulation. This candidate signature * assumes that the given simplex with the given labelling * of its vertices becomes simplex zero with vertices 0..dim * under the "canonical isomorphism". * * @param simp the index of some simplex in this triangulation. * @param vertices some ordering of the vertices of the * given tetrahedron. * @param if this is non-null, it will be filled with the canonical * isomorphism; in this case it must already have been constructed * for the correct number of simplices. * @return the candidate isomorphism signature. */ static std::string isoSig( const typename DimTraits::Triangulation& tri, unsigned simp, const typename DimTraits::Perm& vertices, typename DimTraits::Isomorphism* relabelling); }; /*@}*/ } // namespace regina #endif regina-4.95/engine/manifold/CMakeLists.txt000644 000765 000024 00000001340 12234011536 020400 0ustar00babstaff000000 000000 # manifold # Files to compile SET ( FILES ngraphloop ngraphpair ngraphtriple nhandlebody nlensspace nmanifold nsfs nsfsaltset nsimplesurfacebundle nsnappeacensusmfd ntorusbundle order ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} manifold/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES ngraphloop.h ngraphpair.h ngraphtriple.h nhandlebody.h nlensspace.h nmanifold.h notation.h nsfs.h nsfsaltset.h nsimplesurfacebundle.h nsnappeacensusmfd.h ntorusbundle.h DESTINATION ${INCLUDEDIR}/manifold COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/manifold/ngraphloop.cpp000644 000765 000024 00000022342 12234011536 020522 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/ngraphloop.h" #include "manifold/nsfs.h" #include "maths/nmatrixint.h" #include // For labs(). namespace regina { NGraphLoop::~NGraphLoop() { delete sfs_; } bool NGraphLoop::operator < (const NGraphLoop& compare) const { if (*sfs_ < *compare.sfs_) return true; if (*compare.sfs_ < *sfs_) return false; return simpler(matchingReln_, compare.matchingReln_); } NAbelianGroup* NGraphLoop::getHomologyH1() const { // Just for safety (this should always be true anyway): if (sfs_->punctures(false) != 2 || sfs_->punctures(true) != 0) return 0; // Construct a matrix. // Generators: fibre, base curves, two base boundaries, exceptional // fibre boundaries, obstruction boundary, // reflector boundaries, reflector half-fibres, plus one // for the loop created by the joining of boundaries. // Relations: base curve relation, exception fibre relations, // obstruction relation, reflector relations, // fibre constraint, joining boundaries. unsigned long genus = sfs_->baseGenus(); unsigned long fibres = sfs_->fibreCount(); unsigned long ref = sfs_->reflectors(); // If we have an orientable base space, we get two curves per genus. // The easiest thing to do is just to double the genus now. if (sfs_->baseOrientable()) genus *= 2; NMatrixInt m(fibres + ref + 5, genus + fibres + 2 * ref + 5); unsigned long i, f; // The relation for the base orbifold: for (i = 1 + genus; i < 1 + genus + 2 + fibres + 1 + ref; i++) m.entry(0, i) = 1; if (! sfs_->baseOrientable()) for (i = 1; i < 1 + genus; i++) m.entry(0, i) = 2; // A relation for each exceptional fibre: NSFSFibre fibre; for (f = 0; f < fibres; f++) { fibre = sfs_->fibre(f); m.entry(f + 1, 1 + genus + 2 + f) = fibre.alpha; m.entry(f + 1, 0) = fibre.beta; } // A relation for the obstruction constant: m.entry(1 + fibres, 1 + genus + 2 + fibres) = 1; m.entry(1 + fibres, 0) = sfs_->obstruction(); // A relation for each reflector boundary: for (i = 0; i < ref; i++) { m.entry(2 + fibres + i, 0) = -1; m.entry(2 + fibres + i, 1 + genus + 2 + fibres + 1 + ref + i) = 2; } // A relation constraining the fibre. This relationship only // appears in some cases; otherwise we will just have a (harmless) // zero row in the matrix. if (sfs_->reflectors(true)) m.entry(2 + fibres + ref, 0) = 1; else if (sfs_->fibreReversing()) m.entry(2 + fibres + ref, 0) = 2; // Two relations for the joining of boundaries: m.entry(3 + fibres + ref, 0) = -1; m.entry(3 + fibres + ref, 0) += matchingReln_[0][0]; m.entry(3 + fibres + ref, 2 + genus) = matchingReln_[0][1]; m.entry(4 + fibres + ref, 1 + genus) = -1; m.entry(4 + fibres + ref, 0) = matchingReln_[1][0]; m.entry(4 + fibres + ref, 2 + genus) = matchingReln_[1][1]; NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(m); return ans; } std::ostream& NGraphLoop::writeName(std::ostream& out) const { sfs_->writeName(out); return out << " / [ " << matchingReln_[0][0] << ',' << matchingReln_[0][1] << " | " << matchingReln_[1][0] << ',' << matchingReln_[1][1] << " ]"; } std::ostream& NGraphLoop::writeTeXName(std::ostream& out) const { sfs_->writeTeXName(out); return out << "_{\\homtwo{" << matchingReln_[0][0] << "}{" << matchingReln_[0][1] << "}{" << matchingReln_[1][0] << "}{" << matchingReln_[1][1] << "}}"; } void NGraphLoop::reduce() { /** * Things to observe: * * 1. Inverting the matching matrix is harmless (it corresponds to * rotating the space a half-turn to switch the two boundary tori). * * 2. If we add a (1,1) twist to the SFS we can compensate by either: * - setting row 2 -> row 2 + row 1, or * - setting col 1 -> col 1 - col 2. */ sfs_->reduce(false); // Bring the SFS obstruction constant back to zero. long b = sfs_->obstruction(); if (b != 0) { sfs_->insertFibre(1, -b); matchingReln_[0][0] += b * matchingReln_[0][1]; matchingReln_[1][0] += b * matchingReln_[1][1]; } reduce(matchingReln_); // See if we can do any better by reflecting the entire space // and adding (1,1) twists to bring the obstruction constant back // up to zero again. // TODO: For non-orientable manifolds, reflect()/reduce() may yield // better results. NMatrix2 compMatch = NMatrix2(1, 0, sfs_->fibreCount(), 1) * NMatrix2(1, 0, 0, -1) * matchingReln_ * NMatrix2(1, 0, 0, -1); reduce(compMatch); if (simpler(compMatch, matchingReln_)) { // Do it. matchingReln_ = compMatch; sfs_->complementAllFibres(); } } void NGraphLoop::reduce(NMatrix2& reln) { // Reduce both the original and the inverse, and see who comes out // on top. reduceBasis(reln); NMatrix2 inv = reln.inverse(); reduceBasis(inv); if (simpler(inv, reln)) reln = inv; } void NGraphLoop::reduceBasis(NMatrix2& reln) { // Use (1,1) / (1,-1) pairs to make the top-left element of the // matrix as close to zero as possible. if (reln[0][1] != 0 && reln[0][0] != 0) { long nOps = (labs(reln[0][0]) + ((labs(reln[0][1]) - 1) / 2)) / labs(reln[0][1]); if ((reln[0][0] > 0 && reln[0][1] > 0) || (reln[0][0] < 0 && reln[0][1] < 0)) { // Same signs. for (long i = 0; i < nOps; i++) { reln[0][0] -= reln[0][1]; reln[1][0] -= reln[1][1]; reln[1][0] -= reln[0][0]; reln[1][1] -= reln[0][1]; } } else { // Opposite signs. for (long i = 0; i < nOps; i++) { reln[0][0] += reln[0][1]; reln[1][0] += reln[1][1]; reln[1][0] += reln[0][0]; reln[1][1] += reln[0][1]; } } // If abs(0,0) is half abs(0,1) then we might do better with yet // another operation. Check with simpler() in this case. if (labs(reln[0][0]) * 2 == labs(reln[0][1])) { NMatrix2 alt = reln; if ((alt[0][0] > 0 && alt[0][1] > 0) || (alt[0][0] < 0 && alt[0][1] < 0)) { // Same signs. alt[0][0] -= alt[0][1]; alt[1][0] -= alt[1][1]; alt[1][0] -= alt[0][0]; alt[1][1] -= alt[0][1]; } else { // Opposite signs. alt[0][0] += alt[0][1]; alt[1][0] += alt[1][1]; alt[1][0] += alt[0][0]; alt[1][1] += alt[0][1]; } if (simpler(alt, reln)) reln = alt; } } else { // TODO: We can still do something here. } } } // namespace regina regina-4.95/engine/manifold/ngraphloop.h000644 000765 000024 00000023532 12234011536 020171 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/ngraphloop.h * \brief Deals with graph manifolds formed from self-identified Seifert * fibred spaces. */ #ifndef __NGRAPHLOOP_H #ifndef __DOXYGEN #define __NGRAPHLOOP_H #endif #include "regina-core.h" #include "manifold/nmanifold.h" #include "maths/nmatrix2.h" namespace regina { class NSFSpace; /** * \weakgroup manifold * @{ */ /** * Represents a closed graph manifold formed by joining a * single bounded Seifert fibred space to itself along a torus. * * The Seifert fibred space must have two boundary components, each a * torus corresponding to a puncture in the base orbifold (with no * fibre-reversing twist as one travels around the boundary). * * The way in which the two torus boundaries are joined together is specified * by a 2-by-2 matrix \a M. This matrix relates the locations of the fibres * and base orbifold on the two boundary tori. * * More specifically, suppose that \a f0 and \a o0 are generators of the * first boundary torus, where \a f0 represents a directed fibre in the * Seifert fibred space and \a o0 represents the oriented boundary of * the base orbifold. Likewise, let \a f1 and \a o1 be generators of the * second boundary torus representing a directed fibre and the oriented * boundary of the base orbifold. Then the tori are joined together so * that the curves \a f0, \a o0, \a f1 and \a o1 become related as follows: * *
 *     [f1]       [f0]
 *     [  ] = M * [  ]
 *     [o1]       [o0]
 * 
* * See the page on \ref sfsnotation for details on some of the * terminology used above. * * The optional NManifold routine getHomologyH1() is implemented, but * the optional routine construct() is not. * * \todo \opt Speed up homology calculations involving orientable base * spaces by adding rank afterwards, instead of adding generators for * genus into the presentation matrix. */ class REGINA_API NGraphLoop : public NManifold { private: NSFSpace* sfs_; /**< The bounded Seifert fibred space that is joined to itself. */ NMatrix2 matchingReln_; /**< The matrix describing how the two boundary tori are joined; see the class notes for details. */ public: /** * Creates a new graph manifold as a self-identified Seifert fibred * space. The bounded Seifert fibred space and the four elements of * the 2-by-2 matching matrix are all passed separately. The elements * of the matching matrix combine to give the full matrix \a M as * follows: * *
         *           [ mat00  mat01 ]
         *     M  =  [              ]
         *           [ mat10  mat11 ]
         * 
* * Note that the new object will take ownership of the given * Seifert fibred space, and when this object is destroyed the * Seifert fibred space will be destroyed also. * * \pre The given Seifert fibred space has precisely two torus * boundaries, corresponding to two untwisted punctures in the * base orbifold. * \pre The given matching matrix has determinant +1 or -1. * * @param sfs the bounded Seifert fibred space. * @param mat00 the (0,0) element of the matching matrix. * @param mat01 the (0,1) element of the matching matrix. * @param mat10 the (1,0) element of the matching matrix. * @param mat11 the (1,1) element of the matching matrix. */ NGraphLoop(NSFSpace* sfs, long mat00, long mat01, long mat10, long mat11); /** * Creates a new graph manifold as a self-identified Seifert fibred * space. The bounded Seifert fibred space and the entire 2-by-2 * matching matrix are each passed separately. * * Note that the new object will take ownership of the given * Seifert fibred space, and when this object is destroyed the * Seifert fibred space will be destroyed also. * * \pre The given Seifert fibred space has precisely two torus * boundaries, corresponding to two punctures in the base orbifold. * \pre The given matching matrix has determinant +1 or -1. * * @param sfs the bounded Seifert fibred space. * @param matchingReln the 2-by-2 matching matrix. */ NGraphLoop(NSFSpace* sfs, const NMatrix2& matchingReln); /** * Destroys this structure along with the bounded Seifert * fibred space and the matching matrix. */ ~NGraphLoop(); /** * Returns a reference to the bounded Seifert fibred space that * is joined to itself. * * @return a reference to the bounded Seifert fibred space. */ const NSFSpace& sfs() const; /** * Returns a reference to the 2-by-2 matrix describing how the * two boundary tori of the Seifert fibred space are joined together. * See the class notes for details on precisely how this matrix is * represented. * * @return a reference to the matching matrix. */ const NMatrix2& matchingReln() const; /** * Determines in a fairly ad-hoc fashion whether this * representation of this space is "smaller" than the given * representation of the given space. * * The ordering imposed on graph manifolds is purely aesthetic * on the part of the author, and is subject to change in future * versions of Regina. It also depends upon the particular * representation, so that different representations of the same * space may be ordered differently. * * All that this routine really offers is a well-defined way of * ordering graph manifold representations. * * @param compare the representation with which this will be compared. * @return \c true if and only if this is "smaller" than the * given graph manifold representation. */ bool operator < (const NGraphLoop& compare) const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Uses (1,1) twists, inversion and/or reflection to make the * presentation of this space more aesthetically pleasing. */ void reduce(); /** * Uses (1,1) twists and/or inversion to make the given matching * matrix more aesthetically pleasing. * * This routine is for internal use by reduce(). * * @param reln the matching matrix to simplify. */ static void reduce(NMatrix2& reln); /** * Uses (1,1) twists to make the given matching matrix more * aesthetically pleasing. * * This routine is for internal use by reduce(). * * @param reln the matching matrix to simplify. */ static void reduceBasis(NMatrix2& reln); }; /*@}*/ // Inline functions for NGraphLoop inline NGraphLoop::NGraphLoop(NSFSpace* sfs, long mat00, long mat01, long mat10, long mat11) : sfs_(sfs), matchingReln_(mat00, mat01, mat10, mat11) { reduce(); } inline NGraphLoop::NGraphLoop(NSFSpace* sfs, const NMatrix2& matchingReln) : sfs_(sfs), matchingReln_(matchingReln) { reduce(); } inline const NSFSpace& NGraphLoop::sfs() const { return *sfs_; } inline const NMatrix2& NGraphLoop::matchingReln() const { return matchingReln_; } } // namespace regina #endif regina-4.95/engine/manifold/ngraphpair.cpp000644 000765 000024 00000027721 12234011536 020512 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/ngraphpair.h" #include "manifold/nsfs.h" #include "manifold/nsfsaltset.h" #include "maths/nmatrixint.h" namespace regina { NGraphPair::~NGraphPair() { delete sfs_[0]; delete sfs_[1]; } bool NGraphPair::operator < (const NGraphPair& compare) const { if (*sfs_[0] < *compare.sfs_[0]) return true; if (*compare.sfs_[0] < *sfs_[0]) return false; if (*sfs_[1] < *compare.sfs_[1]) return true; if (*compare.sfs_[1] < *sfs_[1]) return false; return simpler(matchingReln_, compare.matchingReln_); } NAbelianGroup* NGraphPair::getHomologyH1() const { // Just for safety (this should always be true anyway): if (sfs_[0]->punctures(false) != 1 || sfs_[0]->punctures(true) != 0) return 0; if (sfs_[1]->punctures(false) != 1 || sfs_[1]->punctures(true) != 0) return 0; // Construct a matrix. // Generators: fibre 0, base curves 0, base boundary 0, // exceptional fibre boundaries 0, obstruction 0, // reflector boundaries 0, reflector half-fibres 0, // fibre 1, base curves 1, base boundary 1, // exceptional fibre boundaries 1, obstruction 1, // reflector boundaries 0, reflector half-fibres 1. // Relations: base curve relation 0, exceptional fibre relations 0, // obstruction relation 0, reflector relations 0, // fibre constraint 0, // base curve relation 1, exceptional fibre relations 1, // obstruction relation 1, reflector relations 1, // fibre constraint 1, // joining of boundaries. unsigned long genus0 = sfs_[0]->baseGenus(); unsigned long fibres0 = sfs_[0]->fibreCount(); unsigned long ref0 = sfs_[0]->reflectors(); unsigned long all0 = 3 + genus0 + fibres0 + 2 * ref0; unsigned long genus1 = sfs_[1]->baseGenus(); unsigned long fibres1 = sfs_[1]->fibreCount(); unsigned long ref1 = sfs_[1]->reflectors(); // If we have an orientable base space, we get two curves per genus. // The easiest thing to do is just to double each genus now. if (sfs_[0]->baseOrientable()) genus0 *= 2; if (sfs_[1]->baseOrientable()) genus1 *= 2; NMatrixInt m(fibres0 + fibres1 + ref0 + ref1 + 8, genus0 + fibres0 + 2 * ref0 + genus1 + fibres1 + 2 * ref1 + 6); unsigned long i, f; // The relation for each base orbifold: for (i = 1 + genus0; i < 1 + genus0 + 1 + fibres0 + 1 + ref0; i++) m.entry(0, i) = 1; if (! sfs_[0]->baseOrientable()) for (i = 1; i < 1 + genus0; i++) m.entry(0, i) = 2; for (i = 1 + genus1; i < 1 + genus1 + 1 + fibres1 + 1 + ref1; i++) m.entry(1, all0 + i) = 1; if (! sfs_[1]->baseOrientable()) for (i = 1; i < 1 + genus1; i++) m.entry(1, all0 + i) = 2; // A relation for each exceptional fibre and obstruction constant: NSFSFibre fibre; for (f = 0; f < fibres0; f++) { fibre = sfs_[0]->fibre(f); m.entry(2 + f, 1 + genus0 + 1 + f) = fibre.alpha; m.entry(2 + f, 0) = fibre.beta; } m.entry(2 + fibres0, 1 + genus0 + 1 + fibres0) = 1; m.entry(2 + fibres0, 0) = sfs_[0]->obstruction(); for (f = 0; f < fibres1; f++) { fibre = sfs_[1]->fibre(f); m.entry(3 + fibres0 + f, all0 + 1 + genus1 + 1 + f) = fibre.alpha; m.entry(3 + fibres0 + f, all0) = fibre.beta; } m.entry(3 + fibres0 + fibres1, all0 + 1 + genus1 + 1 + fibres1) = 1; m.entry(3 + fibres0 + fibres1, all0) = sfs_[1]->obstruction(); // A relation for each reflector boundary: for (i = 0; i < ref0; i++) { m.entry(4 + fibres0 + fibres1 + i, 0) = -1; m.entry(4 + fibres0 + fibres1 + i, 1 + genus0 + 1 + fibres0 + 1 + ref0 + i) = 2; } for (i = 0; i < ref1; i++) { m.entry(4 + fibres0 + fibres1 + ref0 + i, all0) = -1; m.entry(4 + fibres0 + fibres1 + ref0 + i, all0 + 1 + genus1 + 1 + fibres1 + 1 + ref1 + i) = 2; } // A relation contraining each fibre type. This relationship only // appears in some cases; otherwise we will just have a (harmless) // zero row in the matrix. if (sfs_[0]->reflectors(true)) m.entry(4 + fibres0 + fibres1 + ref0 + ref1, 0) = 1; else if (sfs_[0]->fibreReversing()) m.entry(4 + fibres0 + fibres1 + ref0 + ref1, 0) = 2; if (sfs_[1]->reflectors(true)) m.entry(5 + fibres0 + fibres1 + ref0 + ref1, all0) = 1; else if (sfs_[1]->fibreReversing()) m.entry(5 + fibres0 + fibres1 + ref0 + ref1, all0) = 2; // Finally, two relations for the joining of boundaries: m.entry(6 + fibres0 + fibres1 + ref0 + ref1, all0) = -1; m.entry(6 + fibres0 + fibres1 + ref0 + ref1, 0) = matchingReln_[0][0]; m.entry(6 + fibres0 + fibres1 + ref0 + ref1, 1 + genus0) = matchingReln_[0][1]; m.entry(7 + fibres0 + fibres1 + ref0 + ref1, all0 + 1 + genus1) = -1; m.entry(7 + fibres0 + fibres1 + ref0 + ref1, 0) = matchingReln_[1][0]; m.entry(7 + fibres0 + fibres1 + ref0 + ref1, 1 + genus0) = matchingReln_[1][1]; NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(m); return ans; } std::ostream& NGraphPair::writeName(std::ostream& out) const { sfs_[0]->writeName(out); out << " U/m "; sfs_[1]->writeName(out); return out << ", m = [ " << matchingReln_[0][0] << ',' << matchingReln_[0][1] << " | " << matchingReln_[1][0] << ',' << matchingReln_[1][1] << " ]"; } std::ostream& NGraphPair::writeTeXName(std::ostream& out) const { sfs_[0]->writeTeXName(out); out << " \\bigcup_{\\homtwo{" << matchingReln_[0][0] << "}{" << matchingReln_[0][1] << "}{" << matchingReln_[1][0] << "}{" << matchingReln_[1][1] << "}} "; return sfs_[1]->writeTeXName(out); } void NGraphPair::reduce() { /** * Things to observe: * * 1. If we add a (1,1) twist to sfs_[0] we can compensate by setting * col 1 -> col 1 - col 2. * * 2. If we add a (1,1) twist to sfs_[1] we can compensate by setting * row 2 -> row 2 + row 1. * * 3. We can negate the entire matrix without problems (this * corresponds to rotating one space by 180 degrees). * * 4. If we negate all fibres in sfs_[0] we can compensate by * negating col 1, though note that this negates the determinant * of the matrix. * * 5. If we negate all fibres in sfs_[1] we can compensate by * negating row 1, though again note that this negates the * determinant of the matrix. * * 6. If we wish to swap the two spaces, we invert M. */ // Simplify each space and build a list of possible reflections and // other representations that we wish to experiment with using. NSFSAltSet alt0(sfs_[0]); NSFSAltSet alt1(sfs_[1]); delete sfs_[0]; delete sfs_[1]; // Decide which of these possible representations gives the nicest // matching relation. NSFSpace* use0 = 0; NSFSpace* use1 = 0; NMatrix2 useReln; NMatrix2 tryReln; unsigned i, j; for (i = 0; i < alt0.size(); i++) for (j = 0; j < alt1.size(); j++) { // Insist on the leftmost space being at least as simple as // the rightmost. // See if the (i,j) combination is better than what we've // seen so far. tryReln = alt1.conversion(j) * matchingReln_ * alt0.conversion(i).inverse(); reduceSign(tryReln); // Try without space swapping. if (! (*alt1[j] < *alt0[i])) { if ((! use0) || simpler(tryReln, useReln)) { use0 = alt0[i]; use1 = alt1[j]; useReln = tryReln; } else if (! simpler(useReln, tryReln)) { // The matrix is the same as our best. Compare spaces. if (*alt0[i] < *use0 || (*alt0[i] == *use0 && *alt1[j] < *use1)) { use0 = alt0[i]; use1 = alt1[j]; useReln = tryReln; } } } // Now try with space swapping. if (! (*alt0[i] < *alt1[j])) { tryReln = tryReln.inverse(); reduceSign(tryReln); if ((! use0) || simpler(tryReln, useReln)) { use0 = alt1[j]; use1 = alt0[i]; useReln = tryReln; } else if (! simpler(useReln, tryReln)) { // The matrix is the same as our best. Compare spaces. if (*alt1[j] < *use0 || (*alt1[j] == *use0 && *alt0[i] < *use1)) { use0 = alt1[j]; use1 = alt0[i]; useReln = tryReln; } } } } // This should never happen, but just in case... let's not crash. if (! (use0 && use1)) { use0 = alt0[0]; use1 = alt1[0]; useReln = alt1.conversion(0) * matchingReln_ * alt0.conversion(0).inverse(); reduceSign(useReln); } // Use what we found. sfs_[0] = use0; sfs_[1] = use1; matchingReln_ = useReln; // And what we don't use, delete. alt0.deleteAll(use0, use1); alt1.deleteAll(use0, use1); // TODO: Exploit the (1,2) = (1,0) and (1,1) = (1,0) relations in // the relevant non-orientable cases. } void NGraphPair::reduceSign(NMatrix2& reln) { // All we can do is negate the entire matrix (180 degree rotation // along the join). if (simpler(- reln, reln)) reln.negate(); } } // namespace regina regina-4.95/engine/manifold/ngraphpair.h000644 000765 000024 00000023674 12234011536 020162 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/ngraphpair.h * \brief Deals with graph manifolds formed from pairs of Seifert fibred * spaces. */ #ifndef __NGRAPHPAIR_H #ifndef __DOXYGEN #define __NGRAPHPAIR_H #endif #include "regina-core.h" #include "manifold/nmanifold.h" #include "maths/nmatrix2.h" namespace regina { class NSFSpace; /** * \weakgroup manifold * @{ */ /** * Represents a closed graph manifold formed by joining * two bounded Seifert fibred spaces along a common torus. * * Each Seifert fibred space must have just one boundary component, * corresponding to a puncture in the base orbifold (with no * fibre-reversing twist as one travels around this boundary). * * The way in which the two spaces are joined is specified by a 2-by-2 * matrix \a M. This matrix expresses the locations of the fibres and * base orbifold of the second Seifert fibred space in terms of the first. * * More specifically, suppose that \a f0 and \a o0 are generators of the * common torus, where \a f0 represents a directed fibre in the first * Seifert fibred space and \a o0 represents the oriented boundary of * the corresponding base orbifold. Likewise, let \a f1 and \a o1 be * generators of the common torus representing a directed fibre and * the base orbifold of the second Seifert fibred space. Then the curves * \a f0, \a o0, \a f1 and \a o1 are related as follows: * *
 *     [f1]       [f0]
 *     [  ] = M * [  ]
 *     [o1]       [o0]
 * 
* * See the page on \ref sfsnotation for details on some of the * terminology used above. * * The optional NManifold routine getHomologyH1() is implemented, but * the optional routine construct() is not. * * \testpart * * \todo \opt Speed up homology calculations involving orientable base * spaces by adding rank afterwards, instead of adding generators for * genus into the presentation matrix. */ class REGINA_API NGraphPair : public NManifold { private: NSFSpace* sfs_[2]; /**< The two bounded Seifert fibred spaces that are joined together. */ NMatrix2 matchingReln_; /**< The matrix describing how the two spaces are joined; see the class notes for details. */ public: /** * Creates a new graph manifold as a pair of joined Seifert fibred * spaces. The two bounded Seifert fibred spaces and the four * elements of the 2-by-2 matching matrix are all passed separately. * The elements of the matching matrix combine to give the full * matrix \a M as follows: * *
         *           [ mat00  mat01 ]
         *     M  =  [              ]
         *           [ mat10  mat11 ]
         * 
* * Note that the new object will take ownership of the two given * Seifert fibred spaces, and when this object is destroyed the * Seifert fibred spaces will be destroyed also. * * \pre Each Seifert fibred space has a single torus boundary, * corresponding to a single untwisted puncture in the base orbifold. * \pre The given matching matrix has determinant +1 or -1. * * @param sfs0 the first Seifert fibred space. * @param sfs1 the second Seifert fibred space. * @param mat00 the (0,0) element of the matching matrix. * @param mat01 the (0,1) element of the matching matrix. * @param mat10 the (1,0) element of the matching matrix. * @param mat11 the (1,1) element of the matching matrix. */ NGraphPair(NSFSpace* sfs0, NSFSpace* sfs1, long mat00, long mat01, long mat10, long mat11); /** * Creates a new graph manifold as a pair of joined Seifert fibred * spaces. The two bounded Seifert fibred spaces and the entire * 2-by-2 matching matrix are each passed separately. * * Note that the new object will take ownership of the two given * Seifert fibred spaces, and when this object is destroyed the * Seifert fibred spaces will be destroyed also. * * \pre Each Seifert fibred space has a single torus boundary, * corresponding to a single untwisted puncture in the base orbifold. * \pre The given matching matrix has determinant +1 or -1. * * @param sfs0 the first Seifert fibred space. * @param sfs1 the second Seifert fibred space. * @param matchingReln the 2-by-2 matching matrix. */ NGraphPair(NSFSpace* sfs0, NSFSpace* sfs1, const NMatrix2& matchingReln); /** * Destroys this structure along with the component Seifert * fibred spaces and the matching matrix. */ ~NGraphPair(); /** * Returns a reference to one of the two bounded Seifert fibred * spaces that are joined together. * * @param which 0 if the first Seifert fibred space is to be * returned, or 1 if the second space is to be returned. * @return a reference to the requested Seifert fibred space. */ const NSFSpace& sfs(unsigned which) const; /** * Returns a reference to the 2-by-2 matrix describing how the * two Seifert fibred spaces are joined together. See the class * notes for details on precisely how this matrix is represented. * * @return a reference to the matching matrix. */ const NMatrix2& matchingReln() const; /** * Determines in a fairly ad-hoc fashion whether this * representation of this space is "smaller" than the given * representation of the given space. * * The ordering imposed on graph manifolds is purely aesthetic * on the part of the author, and is subject to change in future * versions of Regina. It also depends upon the particular * representation, so that different representations of the same * space may be ordered differently. * * All that this routine really offers is a well-defined way of * ordering graph manifold representations. * * @param compare the representation with which this will be compared. * @return \c true if and only if this is "smaller" than the * given graph manifold representation. */ bool operator < (const NGraphPair& compare) const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Uses (1,1) twists, reflections and other techniques to make * the presentation of this space more aesthetically pleasing. */ void reduce(); /** * Uses 180 degree rotation to make the given matching matrix * more aesthetically pleasing. * * This routine is for internal use by reduce(). * * @param reln the matching matrix to simplify. */ static void reduceSign(NMatrix2& reln); }; /*@}*/ // Inline functions for NGraphPair inline NGraphPair::NGraphPair(NSFSpace* sfs0, NSFSpace* sfs1, long mat00, long mat01, long mat10, long mat11) : matchingReln_(mat00, mat01, mat10, mat11) { sfs_[0] = sfs0; sfs_[1] = sfs1; reduce(); } inline NGraphPair::NGraphPair(NSFSpace* sfs0, NSFSpace* sfs1, const NMatrix2& matchingReln) : matchingReln_(matchingReln) { sfs_[0] = sfs0; sfs_[1] = sfs1; reduce(); } inline const NSFSpace& NGraphPair::sfs(unsigned which) const { return *sfs_[which]; } inline const NMatrix2& NGraphPair::matchingReln() const { return matchingReln_; } } // namespace regina #endif regina-4.95/engine/manifold/ngraphtriple.cpp000644 000765 000024 00000040470 12234011536 021052 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/ngraphtriple.h" #include "manifold/nsfs.h" #include "manifold/nsfsaltset.h" #include "maths/nmatrixint.h" namespace regina { NGraphTriple::~NGraphTriple() { delete end_[0]; delete end_[1]; delete centre_; } bool NGraphTriple::operator < (const NGraphTriple& compare) const { if (*centre_ < *compare.centre_) return true; if (*compare.centre_ < *centre_) return false; if (*end_[0] < *compare.end_[0]) return true; if (*compare.end_[0] < *end_[0]) return false; if (*end_[1] < *compare.end_[1]) return true; if (*compare.end_[1] < *end_[1]) return false; if (simpler(matchingReln_[0], compare.matchingReln_[0])) return true; if (simpler(compare.matchingReln_[0], matchingReln_[0])) return false; return simpler(matchingReln_[1], compare.matchingReln_[1]); } NAbelianGroup* NGraphTriple::getHomologyH1() const { // Just for safety (this should always be true anyway): if (end_[0]->punctures(false) != 1 || end_[0]->punctures(true) != 0) return 0; if (end_[1]->punctures(false) != 1 || end_[1]->punctures(true) != 0) return 0; if (centre_->punctures(false) != 2 || centre_->punctures(true) != 0) return 0; // Construct a matrix. // Generators: // - Spaces are ordered centre, end 0, end 1. // - For each space, generators are: // - fibre // - base curves // - base boundary // - exceptional fibre boundaries // - obstruction // - reflector boundaries // - reflector half-fibres // Relations: // - For each space: // - base curve relation // - exceptional fibre relations // - obstruction relation // - reflector relations // - fibre constraint // - Plus two boundary joinings. NSFSpace* sfs[3]; unsigned long genus[3], punc[3], fibres[3], ref[3], gens[3]; unsigned long start[3]; sfs[0] = centre_; sfs[1] = end_[0]; sfs[2] = end_[1]; punc[0] = 2; punc[1] = 1; punc[2] = 1; int s; for (s = 0; s < 3; s++) { genus[s] = sfs[s]->baseGenus(); fibres[s] = sfs[s]->fibreCount(); ref[s] = sfs[s]->reflectors(); // If we have an orientable base space, we get two curves per genus. // The easiest thing seems to be to just double the genus now. if (sfs[s]->baseOrientable()) genus[s] *= 2; gens[s] = 1 + genus[s] + punc[s] + fibres[s] + 1 + ref[s] + ref[s]; } start[0] = 0; start[1] = gens[0]; start[2] = gens[0] + gens[1]; NMatrixInt m(fibres[0] + fibres[1] + fibres[2] + ref[0] + ref[1] + ref[2] + 13, gens[0] + gens[1] + gens[2]); unsigned long i, f; NSFSFibre fibre; unsigned long reln = 0; // Relations internal to each space: for (s = 0; s < 3; s++) { // The relation for the base orbifold: for (i = 1 + genus[s]; i < 1 + genus[s] + punc[s] + fibres[s] + 1 + ref[s]; i++) m.entry(reln, start[s] + i) = 1; if (! sfs[s]->baseOrientable()) for (i = 1; i < 1 + genus[s]; i++) m.entry(reln, start[s] + i) = 2; reln++; // A relation for each exception fibre: for (f = 0; f < fibres[s]; f++) { fibre = sfs[s]->fibre(f); m.entry(reln, start[s] + 1 + genus[s] + punc[s] + f) = fibre.alpha; m.entry(reln, start[s]) = fibre.beta; reln++; } // The obstruction constant: m.entry(reln, start[s] + 1 + genus[s] + punc[s] + fibres[s]) = 1; m.entry(reln, start[s]) = sfs[s]->obstruction(); reln++; // A relation for each reflector boundary: for (i = 0; i < ref[s]; i++) { m.entry(reln, start[s]) = -1; m.entry(reln, start[s] + 1 + genus[s] + punc[s] + fibres[s] + 1 + ref[s] + i) = 2; reln++; } // A relation constraining the fibre. This relation only // appears in some cases; otherwise we will just have a // (harmless) zero row in the matrix. if (sfs[s]->reflectors(true)) m.entry(reln, start[s]) = 1; else if (sfs[s]->fibreReversing()) m.entry(reln, start[s]) = 2; reln++; } // Joining of boundaries: m.entry(reln, start[1]) = -1; m.entry(reln, 0) = matchingReln_[0][0][0]; m.entry(reln, 1 + genus[0]) = matchingReln_[0][0][1]; reln++; m.entry(reln, start[1] + 1 + genus[1]) = -1; m.entry(reln, 0) = matchingReln_[0][1][0]; m.entry(reln, 1 + genus[0]) = matchingReln_[0][1][1]; reln++; m.entry(reln, start[2]) = -1; m.entry(reln, 0) = matchingReln_[1][0][0]; m.entry(reln, 1 + genus[0] + 1) = matchingReln_[1][0][1]; reln++; m.entry(reln, start[2] + 1 + genus[2]) = -1; m.entry(reln, 0) = matchingReln_[1][1][0]; m.entry(reln, 1 + genus[0] + 1) = matchingReln_[1][1][1]; reln++; // Phew. NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(m); return ans; } std::ostream& NGraphTriple::writeName(std::ostream& out) const { end_[0]->writeName(out); out << " U/m "; centre_->writeName(out); out << " U/n "; end_[1]->writeName(out); NMatrix2 m0 = matchingReln_[0].inverse(); out << ", m = [ " << m0[0][0] << ',' << m0[0][1] << " | " << m0[1][0] << ',' << m0[1][1] << " ]"; out << ", n = [ " << matchingReln_[1][0][0] << ',' << matchingReln_[1][0][1] << " | " << matchingReln_[1][1][0] << ',' << matchingReln_[1][1][1] << " ]"; return out; } std::ostream& NGraphTriple::writeTeXName(std::ostream& out) const { end_[0]->writeTeXName(out); NMatrix2 m0 = matchingReln_[0].inverse(); out << " \\bigcup_{\\homtwo{" << m0[0][0] << "}{" << m0[0][1] << "}{" << m0[1][0] << "}{" << m0[1][1] << "}} "; centre_->writeTeXName(out); out << " \\bigcup_{\\homtwo{" << matchingReln_[1][0][0] << "}{" << matchingReln_[1][0][1] << "}{" << matchingReln_[1][1][0] << "}{" << matchingReln_[1][1][1] << "}} "; end_[1]->writeTeXName(out); return out; } void NGraphTriple::reduce() { /** * Things to observe: * * 1. If we add a (1,1) twist to centre_ we can compensate by setting * col 1 -> col 1 - col 2 in one of the matching relations. * * 2. If we add a (1,1) twist to end_[i] we can compensate by setting * row 2 -> row 2 + row 1 in matching relation i. * * 3. We can negate an entire matrix without problems (this * corresponds to rotating some spaces by 180 degrees). * * 4. If we negate all fibres in centre_ we can compensate by * negating col 1 of both matching relations, though note * that this negates the determinant of each matrix. * * 5. If we negate all fibres in end_[i] we can compensate by * negating row 1 of matching relation i, though again note that * this negates the determinant of the matrix. * * 6. If we wish to swap the order of spaces, we swap both matrices. */ // Simplify each space and build a list of possible reflections and // other representations that we wish to experiment with using. NSFSAltSet alt0(end_[0]); NSFSAltSet alt1(end_[1]); NSFSAltSet altCentre(centre_); delete end_[0]; delete end_[1]; delete centre_; // Decide which of these possible representations gives the nicest // matching relations. NSFSpace* use0 = 0; NSFSpace* use1 = 0; NSFSpace* useCentre = 0; NMatrix2 useReln[2]; NMatrix2 tryReln[2], tmpReln; unsigned i0, i1, c; for (i0 = 0; i0 < alt0.size(); i0++) for (i1 = 0; i1 < alt1.size(); i1++) for (c = 0; c < altCentre.size(); c++) { // See if (i0, i1, c) gives us a combination better than // anything we've seen so far. tryReln[0] = alt0.conversion(i0) * matchingReln_[0] * altCentre.conversion(c).inverse(); if (altCentre.reflected(c)) tryReln[1] = alt1.conversion(i1) * matchingReln_[1] * NMatrix2(1, 0, 0, -1); else tryReln[1] = alt1.conversion(i1) * matchingReln_[1]; reduceBasis(tryReln[0], tryReln[1]); // Insist on the first end space being at least as // simple as the second. // First try without end space swapping. if (! (*alt1[i1] < *alt0[i0])) { if ((! use0) || simpler(tryReln[0], tryReln[1], useReln[0], useReln[1])) { use0 = alt0[i0]; use1 = alt1[i1]; useCentre = altCentre[c]; useReln[0] = tryReln[0]; useReln[1] = tryReln[1]; } else if (! simpler(useReln[0], useReln[1], tryReln[0], tryReln[1])) { // The matrices are the same as our best. // Compare spaces. if (*altCentre[c] < *useCentre || (*altCentre[c] == *useCentre && *alt0[i0] < *use0) || (*altCentre[c] == *useCentre && *alt0[i0] == *use0 && *alt1[i1] < *use1)) { use0 = alt0[i0]; use1 = alt1[i1]; useCentre = altCentre[c]; useReln[0] = tryReln[0]; useReln[1] = tryReln[1]; } } } // Now try with end space swapping. if (! (*alt0[i0] < *alt1[i1])) { reduceBasis(tryReln[1], tryReln[0]); if ((! use0) || simpler(tryReln[1], tryReln[0], useReln[0], useReln[1])) { use0 = alt1[i1]; use1 = alt0[i0]; useCentre = altCentre[c]; useReln[0] = tryReln[1]; useReln[1] = tryReln[0]; } else if (! simpler(useReln[0], useReln[1], tryReln[1], tryReln[0])) { // The matrices are the same as our best. // Compare spaces. if (*altCentre[c] < *useCentre || (*altCentre[c] == *useCentre && *alt1[i1] < *use0) || (*altCentre[c] == *useCentre && *alt1[i1] == *use0 && *alt0[i0] < *use1)) { use0 = alt1[i1]; use1 = alt0[i0]; useCentre = altCentre[c]; useReln[0] = tryReln[1]; useReln[1] = tryReln[0]; } } } } // This should never happen, but just in case... let's not crash. if (! (use0 && use1 && useCentre)) { use0 = alt0[0]; use1 = alt1[0]; useCentre = altCentre[0]; useReln[0] = alt0.conversion(0) * matchingReln_[0] * altCentre.conversion(0).inverse(); useReln[1] = alt1.conversion(0) * matchingReln_[1] * altCentre.conversion(0).inverse(); reduceBasis(useReln[0], useReln[1]); } // Use what we found. end_[0] = use0; end_[1] = use1; centre_ = useCentre; matchingReln_[0] = useReln[0]; matchingReln_[1] = useReln[1]; // And what we don't use, delete. alt0.deleteAll(use0, use1); alt1.deleteAll(use0, use1); altCentre.deleteAll(useCentre); // TODO: More reductions! } void NGraphTriple::reduceBasis(NMatrix2& reln0, NMatrix2& reln1) { /** * The operation we allow here is to add a (1,1) / (1,-1) pair of * twists to centre_, which means: * * col 1 -> col 1 + col 2 in one of the matching relations; * col 1 -> col 1 - col 2 in the other. */ // Start by making the first entry in each column 2 positive (for // consistency). if (reln0[0][1] < 0 || (reln0[0][1] == 0 && reln0[1][1] < 0)) reln0.negate(); if (reln1[0][1] < 0 || (reln1[0][1] == 0 && reln1[1][1] < 0)) reln1.negate(); // Go for the local minimum. // TODO: We can certainly do better than this (both in terms of being // faster [use division] and simpler matrices coming out the end). NMatrix2 alt0, alt1; while (true) { alt0 = reln0 * NMatrix2(1, 0, 1, 1); alt1 = reln1 * NMatrix2(1, 0, -1, 1); if (simpler(alt0, alt1, reln0, reln1)) { reln0 = alt0; reln1 = alt1; continue; } alt0 = reln0 * NMatrix2(1, 0, -1, 1); alt1 = reln1 * NMatrix2(1, 0, 1, 1); if (simpler(alt0, alt1, reln0, reln1)) { reln0 = alt0; reln1 = alt1; continue; } // We're at a local minimum. Call it enough for now. break; } // Final tidying up. reduceSign(reln0); reduceSign(reln1); } void NGraphTriple::reduceSign(NMatrix2& reln) { // Make the first non-zero entry positive. int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { if (reln[i][j] > 0) return; if (reln[i][j] < 0) { // Negate everything (180 degree rotation along the join) // and return. for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) reln[i][j] = - reln[i][j]; return; } } // The matrix is entirely zero (which, incidentally, should never // happen). Do nothing. } } // namespace regina regina-4.95/engine/manifold/ngraphtriple.h000644 000765 000024 00000032773 12234011536 020526 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/ngraphtriple.h * \brief Deals with graph manifolds formed from sequences of three Seifert * fibred spaces. */ #ifndef __NGRAPHTRIPLE_H #ifndef __DOXYGEN #define __NGRAPHTRIPLE_H #endif #include "regina-core.h" #include "manifold/nmanifold.h" #include "maths/nmatrix2.h" namespace regina { class NSFSpace; /** * \weakgroup manifold * @{ */ /** * Represents a closed graph manifold formed by joining * three bounded Seifert fibred spaces along their torus boundaries. * * There must be one Seifert fibred space at either end, each with a * single torus boundary (corresponding to a single puncture in the * base orbifold, with no fibre-reversing twist around this puncture). * Each of these end spaces is joined to the space in the centre, which * has two disjoint torus boundaries (corresponding to two punctures * in the base orbifold, again with no fibre-reversing twists around * these punctures). * * This configuration is illustrated in the diagram below. The large * boxes represent the bounded Seifert fibred spaces, and the small * tunnels show how their boundaries are joined. * *
 *     /---------------\   /-----------------\   /---------------\
 *     |               |   |                 |   |               |
 *     |  End space 0   ---   Central space   ---   End space 1  |
 *     |                ---                   ---                |
 *     |               |   |                 |   |               |
 *     \---------------/   \-----------------/   \---------------/
 * 
* * The way in which each pair of spaces is joined is specified by a * 2-by-2 matrix. This matrix expresses the locations of the fibres and * base orbifold of the corresponding end space in terms of the central * space. * Note that these are not the same matrices that appear in the * manifold name in the census data files! See the warning below. * * More specifically, consider the matrix \a M that describes the * joining of the central space and the first end space (marked above as * end space 0). Suppose that \a f and \a o are generators of the common * boundary torus, where \a f represents a directed fibre in the central * space and \a o represents the oriented boundary of the corresponding * base orbifold. Likewise, let \a f0 and \a o0 be generators of the * common boundary torus representing a directed fibre and the base * orbifold of the first end space. Then the curves \a f, \a o, * \a f0 and \a o0 are related as follows: * *
 *     [f0]       [f ]
 *     [  ] = M * [  ]
 *     [o0]       [o ]
 * 
* * Likewise, let matrix \a M' describe the joining of the central * space and the second end space (marked in the diagram above as end * space 1). Let \a f' and \a o' be curves on the common boundary torus * representing the fibres and the base orbifold of the central space, * and let \a f1 and \a o1 be curves on this same torus representing the * fibres and the base orbifold of the second end space. Then the curves * \a f', \a o', \a f1 and \a o1 are related as follows: * *
 *     [f1]        [f']
 *     [  ] = M' * [  ]
 *     [o1]        [o']
 * 
* * See the page on \ref sfsnotation for details on some of the * terminology used above. * * The optional NManifold routine getHomologyH1() is implemented, but * the optional routine construct() is not. * * \warning The 2-by-2 matrices used in this class are \e not the same * matrices that appear in the manifold name returned by getName() * and getTeXName() and seen in the census data files. The matrices * used in this class work from the inside out, describing the boundary torus * on each end space in terms of a boundary torus on the central space. * The matrices used in the manifold name work from left to right in the * diagram above, describing a boundary torus on the central space or * rightmost end space in terms of a boundary torus on the leftmost end * space or central space respectively. The upshot of all this is that * the first matrix becomes inverted (and the second matrix * remains unchanged). It is likely that future versions of Regina will * replace this class with a more general class that (amongst other * things) removes this inconsistency. * * \testpart * * \todo \opt Speed up homology calculations involving orientable base * spaces by adding rank afterwards, instead of adding generators for * genus into the presentation matrix. */ class REGINA_API NGraphTriple : public NManifold { private: NSFSpace* end_[2]; /**< The two end spaces, i.e., the Seifert fibred spaces with just one boundary torus. */ NSFSpace* centre_; /**< The central space, i.e., the Seifert fibred space with two boundary tori that meets both end spaces. */ NMatrix2 matchingReln_[2]; /**< The matrices describing how the various spaces are joined. In particular, matrix \a matchingReln_[i] describes how the central space is joined to end space \a i. See the class notes for further details. */ public: /** * Creates a new graph manifold from three bounded * Seifert fibred spaces, as described in the class notes. * The three Seifert fibred spaces and both 2-by-2 matching * matrices are passed separately. * * Note that the new object will take ownership of the three given * Seifert fibred spaces, and when this object is destroyed the * Seifert fibred spaces will be destroyed also. * * \pre Spaces \a end0 and \a end1 each have a single torus * boundary, corresponding to a single untwisted puncture in the * base orbifold. * \pre Space \a centre has two disjoint torus boundaries, * corresponding to two untwisted punctures in the base orbifold. * \pre Each of the given matrices has determinant +1 or -1. * * @param end0 the first end space, as described in the class notes. * @param centre the central space, as described in the class notes. * @param end1 the second end space, as described in the class notes. * @param matchingReln0 the 2-by-2 matching matrix that * specifies how spaces \a end0 and \a centre are joined. * @param matchingReln1 the 2-by-2 matching matrix that * specifies how spaces \a end1 and \a centre are joined. */ NGraphTriple(NSFSpace* end0, NSFSpace* centre, NSFSpace* end1, const NMatrix2& matchingReln0, const NMatrix2& matchingReln1); /** * Destroys this structure along with the component Seifert * fibred spaces and matching matrices. */ ~NGraphTriple(); /** * Returns a reference to one of the two end spaces. * These are the Seifert fibred spaces with just one boundary * component, to be joined to the central space. See the class * notes for further discussion. * * @param which 0 if the first end space is to be returned, or * 1 if the second end space is to be returned. * @return a reference to the requested Seifert fibred space. */ const NSFSpace& end(unsigned which) const; /** * Returns a reference to the central space. * This is the Seifert fibred space with two boundary components, * to which the two end spaces are joined. See the class notes * for further discussion. * * @return a reference to the requested Seifert fibred space. */ const NSFSpace& centre() const; /** * Returns a reference to the 2-by-2 matrix describing how the * two requested bounded Seifert fibred spaces are joined together. * See the class notes for details on precisely how these matrices * are represented. * * The argument \a which indicates which particular join should be * examined. A value of 0 denotes the join between the central * space and the first end space (corresponding to matrix \a M * in the class notes), whereas a value of 1 denotes the join * between the central space and the second end space * (corresponding to matrix \a M' in the class notes). * * @param which indicates which particular join should be * examined; this should be 0 or 1 as described above. * @return a reference to the requested matching matrix. */ const NMatrix2& matchingReln(unsigned which) const; /** * Determines in a fairly ad-hoc fashion whether this * representation of this space is "smaller" than the given * representation of the given space. * * The ordering imposed on graph manifolds is purely aesthetic * on the part of the author, and is subject to change in future * versions of Regina. It also depends upon the particular * representation, so that different representations of the same * space may be ordered differently. * * All that this routine really offers is a well-defined way of * ordering graph manifold representations. * * @param compare the representation with which this will be compared. * @return \c true if and only if this is "smaller" than the * given graph manifold representation. */ bool operator < (const NGraphTriple& compare) const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Uses (1,1) twists and other techniques to make the presentation * of this manifold more aesthetically pleasing. */ void reduce(); /** * Uses 180 degree rotation and/or (1,1) twists to make the * given pair of matching matrices more aesthetically pleasing. * * This routine is for internal use by reduce(). * * @param reln0 the first matching matrix in the pair to simplify. * @param reln1 the second matching matrix in the pair to simplify. */ static void reduceBasis(NMatrix2& reln0, NMatrix2& reln1); /** * Uses 180 degree rotation to make the given matching matrix * more aesthetically pleasing. * * This routine is for internal use by reduce(). * * @param reln the matching matrix to simplify. */ static void reduceSign(NMatrix2& reln); }; /*@}*/ // Inline functions for NGraphTriple inline NGraphTriple::NGraphTriple(NSFSpace* end0, NSFSpace* centre, NSFSpace* end1, const NMatrix2& matchingReln0, const NMatrix2& matchingReln1) { end_[0] = end0; centre_ = centre; end_[1] = end1; matchingReln_[0] = matchingReln0; matchingReln_[1] = matchingReln1; reduce(); } inline const NSFSpace& NGraphTriple::end(unsigned which) const { return *end_[which]; } inline const NSFSpace& NGraphTriple::centre() const { return *centre_; } inline const NMatrix2& NGraphTriple::matchingReln(unsigned which) const { return matchingReln_[which]; } } // namespace regina #endif regina-4.95/engine/manifold/nhandlebody.cpp000644 000765 000024 00000006574 12234011536 020651 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" namespace regina { NAbelianGroup* NHandlebody::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (nHandles) ans->addRank(nHandles); return ans; } std::ostream& NHandlebody::writeName(std::ostream& out) const { if (nHandles == 0) out << "B3"; else if (nHandles == 1) { if (orientable) out << "B2 x S1"; else out << "B2 x~ S1"; } else { if (orientable) out << "Handle-Or(" << nHandles << ')'; else out << "Handle-Nor(" << nHandles << ')'; } return out; } std::ostream& NHandlebody::writeTeXName(std::ostream& out) const { if (nHandles == 0) out << "B^3"; else if (nHandles == 1) { if (orientable) out << "B^2 \\times S^1"; else out << "B^2 \\twisted S^1"; } else { if (orientable) out << "\\mathit{Handle-Or}(" << nHandles << ')'; else out << "\\mathit{Handle-Nor}(" << nHandles << ')'; } return out; } } // namespace regina regina-4.95/engine/manifold/nhandlebody.h000644 000765 000024 00000012510 12234011536 020301 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nhandlebody.h * \brief Deals with arbitrary handlebodies. */ #ifndef __NHANDLEBODY_H #ifndef __DOXYGEN #define __NHANDLEBODY_H #endif #include "regina-core.h" #include "nmanifold.h" namespace regina { /** * \weakgroup manifold * @{ */ /** * Represents an arbitrary handlebody. * * All optional NManifold routines except for NManifold::construct() are * implemented for this class. */ class REGINA_API NHandlebody : public NManifold { private: unsigned long nHandles; /**< The number of handles. */ bool orientable; /**< Is the handlebody orientable? */ public: /** * Creates a new handlebody with the given parameters. * * @param newHandles the number of handles of the handlebody. * @param newOrientable \c true if the handlebody is to be orientable * or \c false if the handlebody is to be non-orientable. This * must be \c true if the handlebody has no handles. */ NHandlebody(unsigned long newHandles, bool newOrientable); /** * Creates a clone of the given handlebody. * * @param cloneMe the handlebody to clone. */ NHandlebody(const NHandlebody& cloneMe); /** * Destroys this handlebody. */ virtual ~NHandlebody(); /** * Returns the number of handles of this handlebody. * * @return the number of handles. */ unsigned long getHandles() const; /** * Returns whether this handlebody is orientable. * * @return \c true if this handlebody is orientable, or \c false * if this handlebody is non-orientable. */ bool isOrientable() const; /** * Determines whether this and the given handlebody represent * the same 3-manifold. * * @param compare the handlebody with which this will be compared. * @return \c true if and only if this and the given handlebody * are homeomorphic. */ bool operator == (const NHandlebody& compare) const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; }; /*@}*/ // Inline functions for NHandlebody inline NHandlebody::NHandlebody(unsigned long newHandles, bool newOrientable) : nHandles(newHandles), orientable(newOrientable) { } inline NHandlebody::NHandlebody(const NHandlebody& cloneMe) : NManifold(), nHandles(cloneMe.nHandles), orientable(cloneMe.orientable) { } inline NHandlebody::~NHandlebody() { } inline unsigned long NHandlebody::getHandles() const { return nHandles; } inline bool NHandlebody::isOrientable() const { return orientable; } inline bool NHandlebody::operator == (const NHandlebody& compare) const { if (orientable && ! compare.orientable) return false; if (compare.orientable && ! orientable) return false; return (nHandles == compare.nHandles); } } // namespace regina #endif regina-4.95/engine/manifold/nlensspace.cpp000644 000765 000024 00000007331 12234011536 020505 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nlensspace.h" #include "maths/numbertheory.h" #include "triangulation/ntriangulation.h" namespace regina { void NLensSpace::reduce() { if (p == 0) { q = 1; return; } else if (p == 1) { q = 0; return; } // p > 1 and gcd(p,q) = 1. // Reduce q to +/-q. q = q % p; if (2 * q > p) q = p - q; unsigned long inv = modularInverse(p, q); if (2 * inv > p) inv = p - inv; if (inv < q) q = inv; } NTriangulation* NLensSpace::construct() const { NTriangulation* ans = new NTriangulation(); ans->insertLayeredLensSpace(p, q); return ans; } NAbelianGroup* NLensSpace::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (p == 0) ans->addRank(); else if (p > 1) ans->addTorsionElement(p); return ans; } std::ostream& NLensSpace::writeName(std::ostream& out) const { if (p == 0) out << "S2 x S1"; else if (p == 1) out << "S3"; else if (p == 2 && q == 1) out << "RP3"; else out << "L(" << p << ',' << q << ')'; return out; } std::ostream& NLensSpace::writeTeXName(std::ostream& out) const { if (p == 0) out << "S^2 \\times S^1"; else if (p == 1) out << "S^3"; else if (p == 2 && q == 1) out << "\\mathbb{R}P^3"; else out << "L(" << p << ',' << q << ')'; return out; } } // namespace regina regina-4.95/engine/manifold/nlensspace.h000644 000765 000024 00000014413 12234011536 020151 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nlensspace.h * \brief Deals with general lens spaces. */ #ifndef __NLENSSPACE_H #ifndef __DOXYGEN #define __NLENSSPACE_H #endif #include "regina-core.h" #include "nmanifold.h" namespace regina { /** * \weakgroup manifold * @{ */ /** * Represents a general lens space. * * The lens space L(\a p,\a q) is the 3-manifold you get by * \a p/q Dehn surgery on the unknot. For instance, L(1,0) and L(1,1) * are the 3-sphere, L(0,1) is the product S^1 x S^2, and L(\a p,1) is the * circle bundle over S^2 with Euler class \a p. In L(\a p,\a q) if you * take a generator \a g of H_1 and evaluate the torsion linking form on it, * then <\a g,\a g> = [+/- \a r^2 \a q/\a p] in Q/Z where \a r is an * integer. * * All optional NManifold routines are implemented for this class. * * \testpart */ class REGINA_API NLensSpace : public NManifold { private: unsigned long p; /**< The first parameter of the lens space. */ unsigned long q; /**< The second parameter of the lens space. This will always be stored in reduced form. */ public: /** * Creates a new lens space with the given parameters. * See the class notes for details. * * \pre The two given parameters are coprime (have a gcd of 1). * * @param newP the first parameter \a p of the lens space L(p,q). * @param newQ the second parameter \a q of the lens space L(p,q). * Note that there are no range restrictions whatsoever on this * parameter. */ NLensSpace(unsigned long newP, unsigned long newQ); /** * Creates a clone of the given lens space. * * @param cloneMe the lens space to clone. */ NLensSpace(const NLensSpace& cloneMe); /** * Destroys this lens space. */ virtual ~NLensSpace(); /** * Returns the first parameter \a p of this lens space L(p,q). * See the class notes for details. * * @return the first parameter. */ unsigned long getP() const; /** * Returns the second parameter \a q of this lens space L(p,q). * See the class notes for details. * * The value of \a q returned will be the smallest \a q between * 0 and p-1 inclusive that produces the same 3-manifold * as this lens space. This means it might not be the value of \a q * that was used to initialise this lens space. */ unsigned long getQ() const; /** * Determines whether this and the given lens space are * homeomorphic 3-manifolds. Note that this may be true * even if they were initialised with different parameters. * * @param compare the lens space with which this will be compared. * @return \c true if and only if this and the given lens space * are homeomorphic. */ bool operator == (const NLensSpace& compare) const; NTriangulation* construct() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Reduces the second parameter \a q to the smallest non-negative * value that gives the same (i.e., a homeomorphic) 3-manifold. */ void reduce(); }; /*@}*/ // Inline functions for NLensSpace inline NLensSpace::NLensSpace(unsigned long newP, unsigned long newQ) : p(newP), q(newQ) { reduce(); } inline NLensSpace::NLensSpace(const NLensSpace& cloneMe) : NManifold(), p(cloneMe.p), q(cloneMe.q) { } inline NLensSpace::~NLensSpace() { } inline unsigned long NLensSpace::getP() const { return p; } inline unsigned long NLensSpace::getQ() const { return q; } inline bool NLensSpace::operator == (const NLensSpace& compare) const { return (p == compare.p && q == compare.q); } } // namespace regina #endif regina-4.95/engine/manifold/nmanifold.cpp000644 000765 000024 00000005300 12234011536 020313 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "manifold/nmanifold.h" namespace regina { std::string NManifold::getName() const { std::ostringstream ans; writeName(ans); return ans.str(); } std::string NManifold::getTeXName() const { std::ostringstream ans; writeTeXName(ans); return ans.str(); } std::string NManifold::getStructure() const { std::ostringstream ans; writeStructure(ans); return ans.str(); } } // namespace regina regina-4.95/engine/manifold/nmanifold.h000644 000765 000024 00000023704 12234011536 017770 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nmanifold.h * \brief Deals with the underlying 3-manifolds of triangulations. */ #ifndef __NMANIFOLD_H #ifndef __DOXYGEN #define __NMANIFOLD_H #endif #include "regina-core.h" #include "shareableobject.h" namespace regina { class NAbelianGroup; class NTriangulation; /** * \addtogroup manifold Standard 3-Manifolds * Standard 3-manifolds whose structures are well-understood. * @{ */ /** * Represents a particular 3-manifold. The triangulation of this * 3-manifold that may be in use is not of interest. * * Subclasses corresponding to the different types of 3-manifold must * override at least the virtual functions writeName() and writeTeXName(). * They do not need to override writeTextShort() or writeTextLong() * since these routines are properly implemented in the base class * NManifold. * * \testpart */ class REGINA_API NManifold : public ShareableObject { public: /** * A destructor that does nothing. */ virtual ~NManifold(); /** * Returns the common name of this 3-manifold as a * human-readable string. * * @return the common name of this 3-manifold. */ std::string getName() const; /** * Returns the common name of this 3-manifold in TeX format. * No leading or trailing dollar signs will be included. * * \warning The behaviour of this routine has changed as of * Regina 4.3; in earlier versions, leading and trailing dollar * signs were provided. * * @return the common name of this 3-manifold in TeX format. */ std::string getTeXName() const; /** * Returns details of the structure of this 3-manifold that * might not be evident from its common name. For instance, for * an orbit space S^3/G this routine might return the full * Seifert structure. * * This routine may return the empty string if no additional * details are deemed necessary. * * @return a string describing additional structural details. */ std::string getStructure() const; /** * Returns a triangulation of this 3-manifold, if such a * construction has been implemented. If no construction routine * has yet been implemented for this 3-manifold (for instance, * if this 3-manifold is a Seifert fibred space with * sufficiently many exceptional fibres) then this routine will * return 0. * * The details of which 3-manifolds have construction routines * can be found in the notes for the corresponding subclasses of * NManifold. The default implemention of this routine returns 0. * * @return a triangulation of this 3-manifold, or 0 if the * appropriate construction routine has not yet been implemented. */ virtual NTriangulation* construct() const; /** * Returns the first homology group of this 3-manifold, if such * a routine has been implemented. If the calculation of * homology has not yet been implemented for this 3-manifold * then this routine will return 0. * * The details of which 3-manifolds have homology calculation routines * can be found in the notes for the corresponding subclasses of * NManifold. The default implemention of this routine returns 0. * * The homology group will be newly allocated and must be destroyed * by the caller of this routine. * * @return the first homology group of this 3-manifold, or 0 if * the appropriate calculation routine has not yet been implemented. */ virtual NAbelianGroup* getHomologyH1() const; /** * Determines in a fairly ad-hoc fashion whether this representation * of this 3-manifold is "smaller" than the given representation * of the given 3-manifold. * * The ordering imposed on 3-manifolds is purely aesthetic on * the part of the author, and is subject to change in future * versions of Regina. * * The ordering also depends on the particular representation of * the 3-manifold that is used. As an example, different * representations of the same Seifert fibred space might well * be ordered differently. * * All that this routine really offers is a well-defined way of * ordering 3-manifold representations. * * \warning Currently this routine is only implemented in full for * closed 3-manifolds. For most classes of bounded 3-manifolds, * this routine simply compares the strings returned by getName(). * * @param compare the 3-manifold representation with which this * will be compared. * @return \c true if and only if this is "smaller" than the * given 3-manifold representation. */ bool operator < (const NManifold& compare) const; /** * Writes the common name of this 3-manifold as a * human-readable string to the given output stream. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeName(std::ostream& out) const = 0; /** * Writes the common name of this 3-manifold in TeX format to * the given output stream. No leading or trailing dollar signs * will be included. * * \warning The behaviour of this routine has changed as of * Regina 4.3; in earlier versions, leading and trailing dollar * signs were provided. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeTeXName(std::ostream& out) const = 0; /** * Writes details of the structure of this 3-manifold that * might not be evident from its common name to the given output * stream. For instance, for an orbit space S^3/G this routine * might write the full Seifert structure. * * This routine may write nothing if no additional * details are deemed necessary. The default implementation of * this routine behaves in this way. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeStructure(std::ostream& out) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; }; /*@}*/ // Inline functions for NManifold inline NManifold::~NManifold() { } inline NTriangulation* NManifold::construct() const { return 0; } inline NAbelianGroup* NManifold::getHomologyH1() const { return 0; } inline std::ostream& NManifold::writeStructure(std::ostream& out) const { return out; } inline void NManifold::writeTextShort(std::ostream& out) const { writeName(out); } inline void NManifold::writeTextLong(std::ostream& out) const { writeName(out); std::string details = getStructure(); if (! details.empty()) out << " ( " << details << " )"; } } // namespace regina #endif regina-4.95/engine/manifold/notation.h000644 000765 000024 00000011127 12234011536 017650 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/notation.h * \brief Explains notation used for describing various types of 3-manifold. */ /*! \page sfsnotation Notation for Seifert fibred spaces * * Consider a Seifert fibred space over some base orbifold, containing * one or more exceptional fibres and optionally one or more boundary * components. Here we describe the notation used for the exceptional * fibres, as well as their relationship to curves on the boundary * components. * * Consider the illustration below. Here the base orbifold is * an annulus, with boundary components at the top and bottom of the * diagram. The two circles in the middle correspond to two * exceptional fibres. * * \image html sfsnotation.png * * The boundary curves \a o1 and \a o2, as well as the curves \a e1 and * \a e2 bounding the exceptional fibres, can be given consistent * orientations as illustrated by the arrows. The fibres can also be * consistently oriented; let them be parallel copies of some curve \a f. * * Suppose that we describe the space as having exceptional fibres * with parameters (\a p1, \a q1) and (\a p2, \a q2). This corresponds * to removing all fibres inside curves \a e1 and \a e2, and filling * the resulting boundaries with solid tori whose meridinal curves are * (\a p1 * \a e1 + \a q1 * \a f) and (\a p2 * \a e2 + \a q2 * \a f). * * An obstruction constant of \a b is treated as an additional * exceptional fibre with parameters (1, \a b) according to the * description above. * * Where necessary, we use the oriented curves \a o1 and \a o2 as * curves on the boundaries "representing the base orbifold", * and parallel copies of \a f as curves on the boundaries * "representing the fibres". This becomes particularly * important when joining different boundary components together. * * It is worth noting the following observation. Suppose we have a * Seifert fibred space with boundary, and let one of its * boundary components have oriented curves (\a f, \a o) representing the * fibres and base orbifold respectively. Then exactly the same space can * be written with an additional (1,1) fibre (i.e., the obstruction * constant \a b is incremented by one), with the effect that the * curves on that boundary representing the fibres and base orbifold * become (\a f, \a o + \a f) instead. */ regina-4.95/engine/manifold/nsfs.cpp000644 000765 000024 00000127776 12234011536 017343 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "algebra/nabeliangroup.h" #include "manifold/nlensspace.h" #include "manifold/nsfs.h" #include "maths/nmatrixint.h" #include "maths/numbertheory.h" #include "subcomplex/nsatannulus.h" #include "triangulation/ntriangulation.h" #include "utilities/boostutils.h" namespace regina { namespace { /** * Some small exceptional fibres that we will use for comparisons. */ NSFSFibre two(2, 1); NSFSFibre three(3, 1); NSFSFibre threeB(3, 2); NSFSFibre four(4, 1); } typedef std::list::iterator FibreIterator; typedef std::list::const_iterator FibreIteratorConst; std::ostream& operator << (std::ostream& out, const NSFSFibre& f) { return (out << '(' << f.alpha << ',' << f.beta << ')'); } void NSFSpace::operator = (const NSFSpace& cloneMe) { class_ = cloneMe.class_; genus_ = cloneMe.genus_; punctures_ = cloneMe.punctures_; puncturesTwisted_ = cloneMe.puncturesTwisted_; reflectors_ = cloneMe.reflectors_; reflectorsTwisted_ = cloneMe.reflectorsTwisted_; fibres_ = cloneMe.fibres_; nFibres_ = cloneMe.nFibres_; b_ = cloneMe.b_; } NSFSFibre NSFSpace::fibre(unsigned long which) const { FibreIteratorConst pos = fibres_.begin(); advance(pos, which); return *pos; } void NSFSpace::addHandle(bool fibreReversing) { // First fix the class. // The transitions between classes have been worked out on paper // case by case (in particular, following how the generators of the // handle relate to the new crosscap generators in the non-orientable // case). // Recall also that in the orientable case we can convert +/- to -/-, // and in the non-orientable case we can convert +/+/+/- to +/-/-/- // (where + and - correspond to fibre-preserving and fibre-reversing // generators respectively). See Orlik [1972], p89 for details. if (fibreReversing) { // Fibre-reversing. switch (class_) { case o1: class_ = o2; break; case n1: class_ = (genus_ % 2 == 0 ? n4 : n3); break; case n2: class_ = n4; break; case bo1: class_ = bo2; break; case bn1: case bn2: class_ = bn3; break; default: // No change. break; } } else { // Fibre-preserving. // Never changes the class. } // Finally increment the genus (either orientable or non-orientable). if (baseOrientable()) genus_++; else genus_ += 2; } void NSFSpace::addCrosscap(bool fibreReversing) { // We're making the base orbifold non-orientable. // Convert orientable genus to non-orientable genus if required. if (baseOrientable()) genus_ *= 2; // Now fix the class. // The transitions between classes have been worked out on paper // case by case (in particular, following how the generators of the // original handles relate to the reformulated crosscap generators in // the orientable case). // Recall also that in the orientable case we can convert +/- to -/-, // and in the non-orientable case we can convert +/+/+/- to +/-/-/- // (where + and - correspond to fibre-preserving and fibre-reversing // generators respectively). See Orlik [1972], p89 for details. if (fibreReversing) { // Fibre-reversing. switch(class_) { case o1: class_ = n2; break; case o2: class_ = n4; break; case n1: class_ = (genus_ % 2 == 0 ? n4 : n3); break; case bo1: class_ = bn2; break; case bo2: case bn1: class_ = bn3; break; default: // No change. break; } } else { // Fibre-preserving. switch(class_) { case o1: class_ = n1; break; case o2: case n2: case n4: class_ = n3; break; case n3: class_ = n4; break; case bo1: class_ = bn1; break; case bo2: case bn2: class_ = bn3; break; default: // No change. break; } } // Finally increment the genus. // We always have non-orientable genus here. genus_++; } void NSFSpace::addPuncture(bool twisted, unsigned long nPunctures) { if (twisted) { puncturesTwisted_ += nPunctures; if (baseOrientable()) class_ = bo2; else class_ = bn3; } else { punctures_ += nPunctures; switch(class_) { case o1: class_ = bo1; break; case o2: class_ = bo2; break; case n1: class_ = bn1; break; case n2: class_ = bn2; break; case n3: case n4: class_ = bn3; break; default: // No change. break; } } } void NSFSpace::addReflector(bool twisted, unsigned long nReflectors) { if (twisted) { reflectorsTwisted_ += nReflectors; if (baseOrientable()) class_ = bo2; else class_ = bn3; } else { reflectors_ += nReflectors; switch(class_) { case o1: class_ = bo1; break; case o2: class_ = bo2; break; case n1: class_ = bn1; break; case n2: class_ = bn2; break; case n3: case n4: class_ = bn3; break; default: // No change. break; } } } void NSFSpace::insertFibre(long alpha, long beta) { // We are assuming that the parameters of this fibre are coprime and // that alpha is strictly positive. // Sanity check. if (alpha == 0) { // TODO: We should probably throw an exception here or something. std::cerr << "ERROR: Inserting illegal fibre (0," << beta << ")." << std::endl; return; } // Is it a regular fibre? if (alpha == 1) { b_ += beta; return; } // Put the fibre in standard form. b_ += (beta / alpha); beta = beta % alpha; if (beta < 0) { beta += alpha; b_--; } // Now we have 0 <= beta < alpha and alpha >= 2. nFibres_++; NSFSFibre f(alpha, beta); fibres_.insert(lower_bound(fibres_.begin(), fibres_.end(), f), f); // We're done! } void NSFSpace::reduce(bool mayReflect) { FibreIterator it, it2; // If the SFS is non-orientable, we can get rid of b completely and // convert most (if not all) exceptional fibres to beta <= alpha / 2. if (reflectors_ || reflectorsTwisted_) { // (1,1) == (1,0). b_ = 0; } else if (fibreNegating() && b_) { // (p,q) == (p,-q), and so (1,2) == (1,0). b_ = b_ % 2; if (b_ && nFibres_) { // We have b == +/-1. // Merge this into the first exceptional fibre instead. // Instead of modifying the fibre directly, delete and reinsert // so that sorted order is maintained. NSFSFibre f(fibres_.front().alpha, fibres_.front().alpha - fibres_.front().beta); fibres_.pop_front(); // Rather than doing a binary search, just hunt from the // front (since we haven't changed alpha, so the fibre will // generally stay near the front). it = fibres_.begin(); while (it != fibres_.end() && (*it) < f) it++; fibres_.insert(it, f); b_ = 0; } } // Completely finish off the case with no exceptional fibres. if (! nFibres_) { // Not much more we can do. // Just reflect if it helps. if (mayReflect && b_ < 0) b_ = -b_; return; } // FACT: There is at least one fibre. // Normalise them as best we can. if (fibreNegating()) { // (p,q) == (p,-q) == (1,1) (p,p-q) == (1,-1) (p,p-q). // We can therefore reduce fibres with large beta in pairs. // Except for the following cases, where we can simply reduce // all of them. if (reflectors_ || reflectorsTwisted_ || fibres_.front().alpha == 2) { // (1,1) == (1,0) if we have reflectors, and // (1,1) (2,1) == (1,2) (2,-1) == (2,1) if we have some alpha = 2. // So we can reduce _all_ fibres with large beta. it = fibres_.begin(); while (it != fibres_.end()) if (it->beta * 2 > it->alpha) it = negateFibreDown(it); else it++; } else { // We have to do them in pairs. // A place to store the first of a pair while we look for // the second: it2 = fibres_.end(); it = fibres_.begin(); while (it != fibres_.end()) { if (it->beta * 2 > it->alpha) { // This one's worth reducing. if (it2 == fibres_.end()) { // First in a pair. // Remember it and move on. it2 = it++; } else { // Second in a pair. // Process them both (first then last, so we // don't mess up the sequence of iterators in // the loop). negateFibreDown(it2); it = negateFibreDown(it); it2 = fibres_.end(); } } else it++; } // Was there anything left over? If so, pair it with the // final fibre (which will get larger, not smaller). if (it2 != fibres_.end()) { // It can be shown that, if we are already looking at // the final fibre, this code will do the right thing // (specifically, switch this with another fibre if this // will improve things, and switch this with itself if // it won't). negateFibreDown(it2); // No need to resort the final fibre, since it gets // larger anyway. fibres_.back().beta = fibres_.back().alpha - fibres_.back().beta; } } } else if (reflectors_ || reflectorsTwisted_) { // Individual fibres cannot be negated, but we have reflector // boundaries. // We still have the option of simultaneously replacing all (p,q) // with (p,-q) == (1,-1) (p,p-q) == (p,p-q) if it's worth it. if (mayReflect) { unsigned long nLarge = 0; unsigned long nSmall = 0; // Don't count (2,1) fibres, they don't get changed anyway. for (it = fibres_.begin(); it != fibres_.end() && it->alpha == 2; it++) ; // Remember where we really started. it2 = it; for ( ; it != fibres_.end(); it++) { if (it->beta * 2 > it->alpha) nLarge++; else nSmall++; } // So. Was it worth it? if (nLarge > nSmall) complementAllFibres(); else if (nLarge == nSmall && it2 != fibres_.end()) { // We need to look in a little more detail. FibreIterator next; bool shouldReflect = false; // Restore our starting position, and let it2 become a // temporary variable again. it = it2; while (it != fibres_.end()) { // INV: it points to the next block with the same // value of alpha. it2 = it; for (it2++; it2 != fibres_.end() && it2->alpha == it->alpha; it2++) ; // Now it2 points to the first element of the // following block. next = it2; it2--; // Now it2 points to the last element of this block. // If the block were negated, it would also be // reversed; see what would happen. while (it != next) { if (it2->alpha - it2->beta < it->beta) { shouldReflect = true; next = fibres_.end(); break; } else if (it2->alpha - it2->beta > it->beta) { shouldReflect = false; next = fibres_.end(); break; } // Still tied. it++; it2--; } // Move on to the next block. it = next; } if (shouldReflect) complementAllFibres(); } } } else { // Individual fibres cannot be negated, no reflector boundaries. // The best we can do is just reflect everything if b is far enough // negative. if (mayReflect) { if (b_ < (-b_ - static_cast(nFibres_))) { b_ = -b_ - static_cast(nFibres_); complementAllFibres(); } else if (b_ == (-b_ - static_cast(nFibres_))) { // Reflecting won't change b, but it will complement all // fibres. See whether this is worthwhile. FibreIterator next; bool shouldReflect = false; it = fibres_.begin(); while (it != fibres_.end()) { // INV: it points to the next block with the same // value of alpha. it2 = it; for (it2++; it2 != fibres_.end() && it2->alpha == it->alpha; it2++) ; // Now it2 points to the first element of the // following block. next = it2; it2--; // Now it2 points to the last element of this block. // If the block were negated, it would also be // reversed; see what would happen. while (it != next) { if (it2->alpha - it2->beta < it->beta) { shouldReflect = true; next = fibres_.end(); break; } else if (it2->alpha - it2->beta > it->beta) { shouldReflect = false; next = fibres_.end(); break; } // Still tied. it++; it2--; } // Move on to the next block. it = next; } if (shouldReflect) complementAllFibres(); } } } } std::list::iterator NSFSpace::negateFibreDown( std::list::iterator it) { // The replacement fibre. NSFSFibre f(it->alpha, it->alpha - it->beta); // The return value. This is also a strict upper bound for the // location of the replacement fibre. std::list::iterator next = it; next++; // Delete the old iterator. fibres_.erase(it); // Insert the new. Treat front insertion specially, so we don't // find ourselves doing an it-- past the beginning. if (fibres_.empty() || f < fibres_.front()) { fibres_.push_front(f); return next; } // It's not a front insertion. Find the insertion place. // Note that this loop is guaranteed at least one iteration. for (it = next; it == fibres_.end() || f < *it; it--) ; // We have the first instance of *it <= f. // This means the insertion should take place immediately after it. it++; fibres_.insert(it, f); return next; } void NSFSpace::complementAllFibres() { FibreIterator it, it2, next; for (it = fibres_.begin(); it != fibres_.end(); it++) it->beta = it->alpha - it->beta; // Ensure that the array remains in sorted order. // Each portion of the array with fixed index must be reversed. NSFSFibre tmpFibre; it = fibres_.begin(); while (it != fibres_.end()) { // INV: it points to the next block to be reversed. it2 = it; for (it2++; it2 != fibres_.end() && (*it2).alpha == (*it).alpha; it2++) ; // Now it2 points to the first element of the following block. next = it2; it2--; // Now it2 points to the last element of this block. // Reverse this block by swapping elements at each end and // working towards the centre. while (it != it2) { tmpFibre = (*it); (*it) = (*it2); (*it2) = tmpFibre; it++; if (it == it2) break; it2--; } // Move on to the next block. it = next; } } NLensSpace* NSFSpace::isLensSpace() const { if (punctures_ || puncturesTwisted_ || reflectors_ || reflectorsTwisted_) { // Not a chance. return 0; } if (genus_ == 0 && class_ == o1) { // Base orbifold is the sphere. if (fibres_.empty()) return new NLensSpace(b_ >= 0 ? b_ : -b_, 1); else if (nFibres_ == 1) { long q = fibres_.front().alpha; long p = fibres_.front().beta + (b_ * q); // We have SFS [S2 : (q,p)]. return new NLensSpace(p >= 0 ? p : -p, q >= 0 ? q : -q); } else if (nFibres_ == 2) { // Precisely two fibres. long q = fibres_.back().alpha; long p = fibres_.back().beta + (b_ * q); long x = fibres_.front().alpha; long y = fibres_.front().beta; // INV: We have SFS [S2 : (x,y) (q,p)] with 0 <= y < x. while (y > 0) { x = x - y; q = q + p; if (y >= x) { p += (q * (y / x)); y = y % x; } } // We should now have (x,y) == (1,0). return new NLensSpace(p >= 0 ? p : -p, q >= 0 ? q : -q); } // Not a lens space. return 0; } else if (genus_ == 1 && class_ == n2) { // Base orbifold is the projective plane. if (nFibres_ == 1) { // We have precisely one exceptional fibre. long a = fibres_.front().alpha; long n = b_ * a + fibres_.front().beta; if (n == 1 || n == -1) return new NLensSpace(4 * a, 2 * a - 1); } // Not a lens space. return 0; } return 0; } bool NSFSpace::operator == (const NSFSpace& compare) const { if (class_ != compare.class_) return false; if (genus_ != compare.genus_) return false; if (punctures_ != compare.punctures_) return false; if (puncturesTwisted_ != compare.puncturesTwisted_) return false; if (reflectors_ != compare.reflectors_) return false; if (reflectorsTwisted_ != compare.reflectorsTwisted_) return false; if (nFibres_ != compare.nFibres_) return false; if (! (fibres_ == compare.fibres_)) return false; if (b_ != compare.b_) return false; // Exactly the same! return true; } bool NSFSpace::operator < (const NSFSpace& compare) const { // Double the genus if it's orientable, so that we can line up tori // with Klein bottles, etc. unsigned long adjGenus1 = (baseOrientable() ? genus_ * 2 : genus_); unsigned long adjGenus2 = (compare.baseOrientable() ? compare.genus_ * 2 : compare.genus_); // Too many punctures is worse than anything. if (punctures_ + puncturesTwisted_ < compare.punctures_ + compare.puncturesTwisted_) return true; if (punctures_ + puncturesTwisted_ > compare.punctures_ + compare.puncturesTwisted_) return false; // After this, order by a combination of genus and reflectors to // group closed spaces with approximately the same complexity. if (adjGenus1 + reflectors_ + reflectorsTwisted_ < adjGenus2 + compare.reflectors_ + compare.reflectorsTwisted_) return true; if (adjGenus1 + reflectors_ + reflectorsTwisted_ > adjGenus2 + compare.reflectors_ + compare.reflectorsTwisted_) return false; // Within this genus + reflectors combination, reflectors are worse. if (reflectors_ + reflectorsTwisted_ < compare.reflectors_ + compare.reflectorsTwisted_) return true; if (reflectors_ + reflectorsTwisted_ > compare.reflectors_ + compare.reflectorsTwisted_) return false; // If we reach this point, we must have adjGenus1 == adjGenus2. // Down to more mundane comparisons. // Comparing class will catch orientability also (placing orientable // before non-orientable). if (class_ < compare.class_) return true; if (class_ > compare.class_) return false; if (reflectorsTwisted_ < compare.reflectorsTwisted_) return true; if (reflectorsTwisted_ > compare.reflectorsTwisted_) return false; if (puncturesTwisted_ < compare.puncturesTwisted_) return true; if (puncturesTwisted_ > compare.puncturesTwisted_) return false; if (nFibres_ < compare.nFibres_) return true; if (nFibres_ > compare.nFibres_) return false; if (fibres_ < compare.fibres_) return true; if (compare.fibres_ < fibres_) return false; if (b_ < compare.b_) return true; if (b_ > compare.b_) return false; // Exactly the same! return false; } NTriangulation* NSFSpace::construct() const { // Things that we don't deal with just yet. if (punctures_ || puncturesTwisted_ || reflectors_ || reflectorsTwisted_) return 0; // We already know how to construct lens spaces. NLensSpace* lens = isLensSpace(); if (lens) { NTriangulation* t = lens->construct(); delete lens; return t; } // Currently we work over the 2-sphere only. if (genus_ != 0 || class_ != o1) return 0; // Since we've already dealt with lens spaces, we must have at least // three exceptional fibres. Build a blocked structure. NTriangulation* ans = new NTriangulation(); NTetrahedron *a, *b, *c; // Begin with the first triangular solid torus. a = ans->newTetrahedron(); b = ans->newTetrahedron(); c = ans->newTetrahedron(); a->joinTo(1, b, NPerm4()); b->joinTo(2, c, NPerm4()); c->joinTo(3, a, NPerm4(1, 2, 3, 0)); std::list::const_iterator fit = fibres_.begin(); NSatAnnulus(a, NPerm4(1, 0, 2, 3), b, NPerm4(1, 2, 0, 3)). attachLST(ans, fit->alpha, fit->beta); fit++; NSatAnnulus(b, NPerm4(2, 1, 3, 0), c, NPerm4(2, 3, 1, 0)). attachLST(ans, fit->alpha, fit->beta); fit++; // Run through the rest of the fibres, one at a time. Each extra // fibre (aside from the third) will require another triangular // solid torus. NTetrahedron* prevA = a; NTetrahedron* prevC = c; NSFSFibre nextFibre = *fit++; while (fit != fibres_.end()) { a = ans->newTetrahedron(); b = ans->newTetrahedron(); c = ans->newTetrahedron(); a->joinTo(3, prevA, NPerm4(2, 3)); b->joinTo(3, prevC, NPerm4(0, 2, 3, 1)); a->joinTo(1, b, NPerm4()); b->joinTo(2, c, NPerm4()); c->joinTo(3, a, NPerm4(1, 2, 3, 0)); NSatAnnulus(b, NPerm4(2, 1, 3, 0), c, NPerm4(2, 3, 1, 0)). attachLST(ans, nextFibre.alpha, nextFibre.beta); prevA = a; prevC = c; nextFibre = *fit++; } // We have one remaining fibre. Fill in the final annulus of the // last triangular solid torus. NSatAnnulus(a, NPerm4(1, 0, 3, 2), c, NPerm4(2, 3, 0, 1)).attachLST(ans, nextFibre.alpha, -(nextFibre.beta + b_ * nextFibre.alpha)); return ans; } NAbelianGroup* NSFSpace::getHomologyH1() const { if (punctures_ || puncturesTwisted_) { // Not just now. return 0; } // Construct the presentation of the fundamental group and // abelianise. The presentation without reflectors is given on // p91 of Orlik [1972]. Each reflector gives additional generators // y and z, for which y acts as a boundary component and z^2 = fibre. NAbelianGroup* ans = new NAbelianGroup(); unsigned long nRef = reflectors_ + reflectorsTwisted_; bool twisted = fibreReversing(); if (baseOrientable()) { // Orientable base surface. // Generators: a_1, b_1, ..., a_g, b_g, q_1, q_2, ..., q_r, h, // y_1, z_1, ..., y_t, z_t (for reflectors) // Relations: // q_j^alpha_j h^beta_j = 1 // z_j^2 = h // q_1 ... q_r y_1 ... y_t = h^b // h^2 = 1 (if twisted), or h = 1 (if twisted reflectors) // // We ignore a_i and b_i, and just add extra rank 2g at the end. // Generators in the matrix are q_1, ..., q_r, h, z_1, ..., z_t, // y_1, ..., y_t. NMatrixInt pres(nFibres_ + nRef + (twisted ? 2 : 1), nFibres_ + 1 + 2 * nRef); unsigned long which = 0; for (FibreIteratorConst it = fibres_.begin(); it != fibres_.end(); it++) { pres.entry(nFibres_ + nRef, which) = 1; pres.entry(which, nFibres_) = it->beta; pres.entry(which, which) = it->alpha; which++; } unsigned long ref; for (ref = 0; ref < nRef; ref++) { pres.entry(nFibres_ + ref, nFibres_) = -1; pres.entry(nFibres_ + ref, nFibres_ + 1 + ref) = 2; pres.entry(nFibres_ + nRef, nFibres_ + 1 + nRef + ref) = 1; } pres.entry(nFibres_ + nRef, nFibres_) = -b_; if (reflectorsTwisted_) pres.entry(nFibres_ + nRef + 1, nFibres_) = 1; else if (twisted) pres.entry(nFibres_ + nRef + 1, nFibres_) = 2; ans->addGroup(pres); ans->addRank(2 * genus_); } else { // Non-orientable base surface. // Generators: v_1, v_2, ..., v_g, q_1, q_2, ..., q_r, h, // y_1, z_1, ..., y_t, z_t (for reflectors) // Relations: // q_j^alpha_j h^beta_j = 1 // z_j^2 = h // q_1 ... q_r v_1^2 ... v_g^2 y_1 ... y_t = h^b // h^2 = 1 (if twisted), or h = 1 (if twisted reflectors) // // Generators in the matrix are q_1, ..., q_r, v_1, ..., v_g, h, // z_1, ..., z_t, y_1, ..., y_t. NMatrixInt pres(nFibres_ + nRef + (twisted ? 2 : 1), nFibres_ + genus_ + 1 + 2 * nRef); unsigned long which = 0; for (FibreIteratorConst it = fibres_.begin(); it != fibres_.end(); it++) { pres.entry(nFibres_ + nRef, which) = 1; pres.entry(which, nFibres_ + genus_) = it->beta; pres.entry(which, which) = it->alpha; which++; } unsigned long ref; for (ref = 0; ref < nRef; ref++) { pres.entry(nFibres_ + ref, nFibres_ + genus_) = -1; pres.entry(nFibres_ + ref, nFibres_ + genus_ + 1 + ref) = 2; pres.entry(nFibres_ + nRef, nFibres_ + genus_ + 1 + nRef + ref) = 1; } for (which = 0; which < genus_; which++) pres.entry(nFibres_ + nRef, nFibres_ + which) = 2; pres.entry(nFibres_ + nRef, nFibres_ + genus_) = -b_; if (reflectorsTwisted_) pres.entry(nFibres_ + nRef + 1, nFibres_ + genus_) = 1; else if (twisted) pres.entry(nFibres_ + nRef + 1, nFibres_ + genus_) = 2; ans->addGroup(pres); } return ans; } void NSFSpace::writeBaseExtraCount(std::ostream& out, unsigned long count, const char* object, bool tex) { out << " + " << count << (tex ? "\\ \\mbox{" : " ") << object; if (count != 1) out << 's'; if (tex) out << '}'; } std::ostream& NSFSpace::writeCommonBase(std::ostream& out, bool tex) const { bool named = false; // IMPORTANT: We do not allow spaces with > 2 reflector boundary // components to be named. Otherwise this messes up the reflector // boundary output. unsigned long totRef = reflectors_ + reflectorsTwisted_; unsigned long totBdries = totRef + punctures_ + puncturesTwisted_; if (baseOrientable()) { // Orientable base surface. if (genus_ == 0 && totBdries == 0) { out << (tex ? "S^2" : "S2"); named = true; } else if (genus_ == 0 && totBdries == 1) { if (totRef && tex) out << "\\overline{"; out << 'D'; if (totRef) out << (tex ? '}' : '_'); named = true; } else if (genus_ == 0 && totBdries == 2) { if (totRef == 1 && tex) out << "\\overline{"; else if (totRef == 2 && tex) out << "\\overline{\\overline{"; out << 'A'; if (totRef == 1) out << (tex ? '}' : '_'); else if (totRef == 2) out << (tex ? "}}" : "="); named = true; } else if (genus_ == 1 && totBdries == 0) { out << (tex ? "T^2" : "T"); named = true; } } else { // Non-orientable base surface. if (genus_ == 1 && totBdries == 0) { out << (tex ? "\\mathbb{R}P^2" : "RP2"); named = true; } else if (genus_ == 1 && totBdries == 1) { if (totRef && tex) out << "\\overline{"; out << 'M'; if (totRef) out << (tex ? '}' : '_'); named = true; } else if (genus_ == 2 && totBdries == 0) { out << (tex ? "K^2" : "KB"); named = true; } } if (! named) { if (baseOrientable()) out << (tex ? "\\mathrm{Or},\\ " : "Or, ") << "g=" << genus_; else out << (tex ? "\\mathrm{Non-or},\\ " : "Non-or, ") << "g=" << genus_; if (punctures_) writeBaseExtraCount(out, punctures_, "puncture", tex); if (puncturesTwisted_) writeBaseExtraCount(out, puncturesTwisted_, "twisted puncture", tex); if (reflectors_) writeBaseExtraCount(out, reflectors_, "reflector", tex); if (reflectorsTwisted_) writeBaseExtraCount(out, reflectorsTwisted_, "twisted reflector", tex); } if (class_ == o2 || class_ == bo2) out << (tex ? "/o_2" : "/o2"); else if (class_ == n2 || class_ == bn2) out << (tex ? "/n_2" : "/n2"); else if (class_ == n3 || class_ == bn3) out << (tex ? "/n_3" : "/n3"); else if (class_ == n4) out << (tex ? "/n_4" : "/n4"); return out; } std::ostream& NSFSpace::writeCommonStructure(std::ostream& out, bool tex) const { if (b_ == 0 && fibres_.empty()) { // We have a straightforward product (possibly twisted). writeCommonBase(out, tex); // The o1/o2/n1/n2/etc specification has already been written in // writeCommonBase(). Just do the pretty x S1 and get out. if (fibreReversing()) return out << (tex ? " \\twisted S^1" : " x~ S1"); else return out << (tex ? " \\times S^1" : " x S1"); } // We have at least one fibre, even if it's only (1,b). out << (tex ? "\\mathrm{SFS}\\left(" : "SFS ["); writeCommonBase(out, tex); out << ':'; if (fibres_.empty()) { // We have b non-zero. out << ' ' << NSFSFibre(1, b_); } else { out << ' '; copy(fibres_.begin(), --fibres_.end(), std::ostream_iterator(out, " ")); NSFSFibre final = fibres_.back(); final.beta += final.alpha * b_; out << final; } return out << (tex ? "\\right)" : "]"); } std::ostream& NSFSpace::writeCommonName(std::ostream& out, bool tex) const { // Things we don't deal with just yet. if (fibreNegating()) return writeStructure(out); if (reflectors_ || reflectorsTwisted_ || punctures_ || puncturesTwisted_) return writeStructure(out); // We're looking at an orientable SFS (with either orientable or // non-orientable base orbifold), where the base orbifold has no // punctures or reflector boundaries. // Take out the lens spaces first. NLensSpace* lens = isLensSpace(); if (lens) { if (tex) lens->writeTeXName(out); else lens->writeName(out); delete lens; return out; } // Pull off the number of fibres we're capable of dealing with. // At this moment this is four. if (nFibres_ > 4) return writeStructure(out); NSFSFibre fibre[4]; std::copy(fibres_.begin(), fibres_.end(), fibre); // Note that with three fibres our reduced form will always have // b >= -1. // TODO: The four non-orientable flat manifolds are on Orlik p140. // SFS over the 2-sphere: if (genus_ == 0 && class_ == o1) { if (nFibres_ == 4 && fibre[0] == two && fibre[1] == two && fibre[2] == two && fibre[3] == two && b_ == -2) { // [ S2 : (2,1), (2,1), (2,-1), (2,-1) ] // Orlik, p138, case M2. return out << (tex ? "K^2/n2 \\twisted S^1" : "KB/n2 x~ S1"); } else if (nFibres_ == 3 && fibre[0] == two && gcd(fibre[2].alpha, fibre[2].beta) == 1 && b_ >= -1) { // [ S2 : (2,1), (...), (...) ] if (fibre[1] == two) { // [ S2 : (2,1), (2,1), (a,b) ]. // Orlik, p112, case (ii). long a = fibre[2].alpha; long m = fibre[2].beta + a * (b_ + 1); // Note that a,m >= 0. if (gcd(m, 2 * a) == 1) { // S3/Q{4a} x Z{m}. if (tex) out << "S^3/Q_{" << (a * 4) << '}'; else out << "S3/Q" << (a * 4); if (m > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << m << '}'; else out << " x Z" << m; } return out; } else if (m % 2 == 0) { // S3/D{2^{k+2}a} x Z{2m''+1} where m=2^k(2m''+1). // It seems Orlik is missing a factor of two here? // He uses m=2^{k+1}(2m''+1). long odd = m; long twos = 1; while (! (odd & 1)) { odd >>= 1; twos <<= 1; } if (tex) out << "S^3/D_{" << ((twos << 2) * a) << '}'; else out << "S3/D" << ((twos << 2) * a); if (odd > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << odd << '}'; else out << " x Z" << odd; } return out; } } else if (fibre[1] == three || fibre[1] == threeB) { // [ S2 : (2,1), (3,1/2), (a,b) ] long a = fibre[2].alpha; if (a == 3) { // [ S2 : (2,1), (3,x), (3,y) ] // Orlik, p112, case (iii). long m = 6 * b_ + 3 + 2 * (fibre[1].beta + fibre[2].beta); // Note that m >= 1. if (m % 2 != 0 && m % 3 != 0) { out << (tex ? "S^3/P_{24}" : "S3/P24"); if (m > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << m << '}'; else out << " x Z" << m; } return out; } else if (m % 2 != 0) { long threes = 1; while (m % 3 == 0) { m = m / 3; threes *= 3; } // I believe Orlik is missing a factor of three. // He claims this should be (threes * 8). if (tex) out << "S^3/P'_{" << (threes * 24) << '}'; else out << "S3/P'" << (threes * 24); if (m > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << m << '}'; else out << " x Z" << m; } return out; } } else if (a == 4) { // [ S2 : (2,1), (3,x), (4,y) ] // Orlik, p112, case (iv). long m = 12 * b_ + 6 + 4 * fibre[1].beta + 3 * fibre[2].beta; // Note that m >= 1. out << (tex ? "S^3/P_{48}" : "S3/P48"); if (m > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << m << '}'; else out << " x Z" << m; } return out; } else if (a == 5) { // [ S2 : (2,1), (3,x), (5,y) ] // Orlik, p112, case (v). long m = 30 * b_ + 15 + 10 * fibre[1].beta + 6 * fibre[2].beta; // Note that m >= 1. out << (tex ? "S^3/P_{120}" : "S3/P120"); if (m > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << m << '}'; else out << " x Z" << m; } return out; } else if (a == 6 && fibre[1].beta == 1 && fibre[2].beta == 1 && b_ == -1) { // [ S2 : (2,1), (3,1), (6,-5) ]. // Orlik, p138, case M5. if (tex) return out << "T^2 \\times I / \\homtwo{1}{1}{-1}{0}"; else return out << "T x I / [ 1,1 | -1,0 ]"; } } else if (fibre[1] == four && fibre[2] == four && b_ == -1) { // [ S2 : (2,1), (4,1), (4,-3) ]. // Orlik, p138, case M4. if (tex) return out << "T^2 \\times I / \\homtwo{0}{-1}{1}{0}"; else return out << "T x I / [ 0,1 | -1,0 ]"; } } else if (nFibres_ == 3 && fibre[0] == three && fibre[1] == three && fibre[2] == three && b_ == -1) { // [ S2 : (3,1), (3,1), (3,-2) ] // Orlik, p138, case M3. if (tex) return out << "T^2 \\times I / \\homtwo{0}{-1}{1}{-1}"; else return out << "T x I / [ -1,1 | -1,0 ]"; } } // SFS over the real projective plane: if (genus_ == 1 && class_ == n2) { if (nFibres_ == 0) { // No exceptional fibres. if (b_ == 0) { // [ RP2 ] // Orlik, p113, remark. return out << (tex ? "\\mathbb{R}P^3 \\# \\mathbb{R}P^3" : "RP3 # RP3"); } else { // TODO: [ RP2 : (1,b) ] // Is this Orlik, p112, case (vi)? What is this? // ans << "S3/Q" << (4 * (b > 0 ? b : -b)); } } else if (nFibres_ == 1 && fibre[0].alpha > 1) { // Just one exceptional fibre. long a = fibre[0].alpha; long n = b_ * a + fibre[0].beta; if (n < 0) n = -n; if (n > 1) { // We have a prism manifold. // Orlik, p112, case (vi). if (a % 2 != 0) { return (tex ? out << "S^3/Q_{" << (4 * n) << "} \\times \\mathbb{Z}_{" << a << "}": out << "S3/Q" << (4 * n) << " x Z" << a); } else { long odd = a; long twos = 1; while (! (odd & 1)) { odd >>= 1; twos <<= 1; } if (tex) out << "S^3/D_{" << ((twos << 2) * n) << '}'; else out << "S3/D" << ((twos << 2) * n); if (odd > 1) { if (tex) out << " \\times \\mathbb{Z}_{" << odd << '}'; else out << " x Z" << odd; } return out; } } } } return writeStructure(out); } } // namespace regina regina-4.95/engine/manifold/nsfs.h000644 000765 000024 00000120746 12234011536 016776 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nsfs.h * \brief Deals with general Seifert fibred spaces. */ #ifndef __NSFS_H #ifndef __DOXYGEN #define __NSFS_H #endif #include #include "regina-core.h" #include "nmanifold.h" namespace regina { class NAbelianGroup; class NLensSpace; /** * \weakgroup manifold * @{ */ /** * Represents an exceptional (alpha, beta) fibre in a Seifert * fibred space. * * The first parameter \a alpha must be strictly positive, and * the two parameters \a alpha and \a beta must be coprime. * * Note that we allow regular fibres with \a alpha = 1, and we do not * impose range limits on \a beta (thus \a beta may be negative, or it * may be larger than \a alpha). This is to allow more flexibility in * routines such as NSFSpace::insertFibre(). * * \warning In Regina 4.2.1 and earlier, this class was named * NExceptionalFibre. The new NSFSFibre class was introduced with * Regina 4.3, and has changed its behaviour (in particular, the * natural ordering of fibres has changed). Code that was written * to work with the old NExceptionalFibre class should be looked at closely * before being adapted to the new NSFSFibre class (i.e., it may require * more than just substituting class names). * * \testpart */ struct REGINA_API NSFSFibre { long alpha; /**< The first parameter of this (alpha, beta) fibre. Note that this is the index of the exceptional fibre. This parameter must always be strictly positive. */ long beta; /**< The second parameter of this (alpha, beta) fibre. This parameter must have no common factors with \a alpha. */ /** * Creates a new uninitialised exceptional fibre. */ NSFSFibre(); /** * Creates a new exceptional fibre with the given parameters. * * @param newAlpha the first parameter (the index) of this * exceptional fibre; this must be strictly positive. * @param newBeta the second parameter of this exceptional fibre; * this must have no common factors with the first parameter \a newAlpha. */ NSFSFibre(long newAlpha, long newBeta); /** * Creates a new exceptional fibre that is a clone of the given * fibre. * * @param cloneMe the exceptional fibre to clone. */ NSFSFibre(const NSFSFibre& cloneMe); /** * Makes this exceptional fibre a clone of the given fibre. * * @param cloneMe the exceptional fibre to clone. */ void operator = (const NSFSFibre& cloneMe); /** * Determines if this and the given exceptional fibre are identical. * This requires both fibres to have the same values for \a alpha * and the same values for \a beta. * * @param compare the fibre with which this will be compared. * @return \c true if and only if this and the given fibre are * identical. */ bool operator == (const NSFSFibre& compare) const; /** * Determines if this exceptional fibre is smaller than the given * fibre. Fibres are sorted by \a alpha and then by \a beta. * * @param compare the fibre with which this will be compared. * @return \c true if and only if this is smaller than the given fibre. */ bool operator < (const NSFSFibre& compare) const; }; /** * Writes the given fibre in human-readable format to the given output * stream. The fibre will be written in the format * (alpha,beta) with no newline appended. * * @param out the output stream to which to write. * @param f the fibre to write. * @return the output stream \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NSFSFibre& f); /** * Represents a general Seifert fibred space, which may be orientable or * non-orientable. Punctures and reflector boundaries in the base orbifold * are supported. * * A Seifert fibred space whose base orbifold has no punctures or * reflector boundaries can be placed into one of the six classes * \c o1, \c o2, \c n1, \c n2, \c n3 and \c n4, as detailed on page 88 * of "Seifert Manifolds", Peter Orlik, Springer-Verlag, 1972. * These classes describe whether this base surface is orientable, as well * as how many of its generators give fibre-reversing paths in the 3-manifold. * * In the case where the base orbifold has punctures and/or reflector * boundaries, we use the five simplified classes \c bo1, \c bo2, \c bn1, * \c bn2 and \c bn3. These classes are not standard terminology (i.e., * they have been created explicitly for Regina), and generally they do not * provide enough information to uniquely identify the 3-manifold. They do * however identify whether or not the base orbifold is orientable, * and whether or not it contains any fibre-reversing paths. * * When describing punctures and reflector boundaries, a \e twisted * boundary is one that gives a fibre-reversing path, and an \e untwisted * boundary is one around which the direction of fibres is preserved. * * Exceptional fibres are sorted first by \a alpha (the index) and then * by \a beta. The obstruction constant \a b is stored separately, * though in output routines such as getName() and getStructure() it is * merged in with the exceptional fibres. Specifically, it is merged in * with the \a beta of the final exceptional fibre (replacing it with * beta + b.alpha), or if there are no exceptional fibres then * it is presented as a single (1,b) fibre. * * The NManifold routines getHomologyH1() and construct() are only * implemented in some cases. The getHomologyH1() routine is * implemented if and only if the base orbifold has no punctures. * The construct() routine is implemented only for lens spaces and * Seifert fibred spaces over the 2-sphere without punctures or reflector * boundaries. * * \warning In Regina 4.2.1 and earlier, this class was named NSFS. * The new NSFSpace class was introduced with Regina 4.3, and has changed * its behaviour in siginficant ways (becoming more general, as well as * keeping the obstruction parameter \a b separate). Code that was written * to work with the old NSFS class should be looked at closely before * being adapted to the new NSFSpace class (i.e., it may require more * than just substituting class names). * * \testpart * * \todo \featurelong Implement recognition of more common names. * \todo \featurelong Implement triangulation construction and homology * calculation for more Seifert fibred spaces. */ class REGINA_API NSFSpace : public NManifold { public: /** * Lists the six classes \c o1, \c o2, \c n1, \c n2, \c n3, \c n4 * for base orbifolds without boundaries, plus five classes * \c bo1, \c b02, \c bn1, \c bn2, \c bn3 for base orbifolds * with boundaries. */ enum classType { o1 = 101, /**< Indicates that the base orbifold is orientable with no punctures or reflector boundaries, and that none of its generators give fibre-reversing paths. */ o2 = 102, /**< Indicates that the base orbifold is orientable with no punctures or reflector boundaries, and that all of its generators give fibre-reversing paths. */ n1 = 201, /**< Indicates that the base orbifold is non-orientable with no punctures or reflector boundaries, and that none of its generators give fibre-reversing paths. */ n2 = 202, /**< Indicates that the base orbifold is non-orientable with no punctures or reflector boundaries, and that all of its generators give fibre-reversing paths. */ n3 = 203, /**< Indicates that the base orbifold is non-orientable with no punctures or reflector boundaries, that it has non-orientable genus at least two, and that precisely one of its generators gives a fibre-reversing path. */ n4 = 204, /**< Indicates that the base orbifold is non-orientable with no punctures or reflector boundaries, that it has non-orientable genus at least three, and that precisely two of its generators give fibre-reversing paths. */ bo1 = 301, /**< Indicates that the base orbifold contains punctures and/or reflector boundaries, that it is orientable, and that it contains no fibre-reversing paths. */ bo2 = 302, /**< Indicates that the base orbifold contains punctures and/or reflector boundaries, that it is orientable, and that it contains at least one fibre-reversing path. */ bn1 = 401, /**< Indicates that the base orbifold contains punctures and/or reflector boundaries, that it is non-orientable, and that it contains no fibre-reversing paths. */ bn2 = 402, /**< Indicates that the base orbifold contains punctures and/or reflector boundaries, that it is non-orientable, and that its fibre-reversing paths correspond precisely to its orientation-reversing paths. */ bn3 = 403 /**< Indicates that the base orbifold contains punctures and/or reflector boundaries, that it is non-orientable, that it contains at least one fibre-reversing path, and that its fibre-reversing paths do not correspond precisely to its orientation-reversing paths. */ }; private: classType class_; /**< Indicates which of the classes above this space belongs to. */ unsigned long genus_; /**< The genus of the base orbifold. For non-orientable base orbifolds this is the non-orientable genus. */ unsigned long punctures_; /**< The number of punctures in the base orbifold whose boundaries are fibre-preserving. This only counts ordinary boundary components, not reflector boundary components. */ unsigned long puncturesTwisted_; /**< The number of punctures in the base orbifold whose boundaries are fibre-reversing. This only counts ordinary boundary components, not reflector boundary components. */ unsigned long reflectors_; /**< The number of reflector boundary components in the base orbifold whose boundaries are fibre-preserving. These are in addition to the regular boundary components described by \a punctures_. */ unsigned long reflectorsTwisted_; /**< The number of reflector boundary components in the base orbifold whose boundaries are fibre-reversing. These are in addition to the regular boundary components described by \a puncturesTwisted_. */ std::list fibres_; /**< The exceptional fibres. This list will be sorted, and will only contain fibres for which \a alpha and \a beta are coprime and 0 <= beta < alpha > 1. */ unsigned long nFibres_; /**< The size of the \a fibres_ list, used to avoid calling the linear time fibres_.size(). */ long b_; /**< The obstruction parameter \a b, which corresponds to an additional (1,b) fibre. */ public: /** * Creates a new Seifert fibred space with base orbifold the * 2-sphere and no exceptional fibres. */ NSFSpace(); /** * Creates a new Seifert fibred space of the given class with the * given base orbifold and no exceptional fibres. * * \pre If there are no punctures or reflector boundary components, * then \a useClass is one of the six classes \c o1, \c o2, \c n1, * \c n2, \c n3 or \c n4. Likewise, if there are punctures and/or * reflector boundary components, then \a useClass is one of the * five classes \c bo1, \c bo2, \c bn1, \c bn2 or \c bn3. * \pre If there are any twisted punctures or reflector boundary * components, then \a useClass is either \c bo2 or \c bn3. * * @param useClass indicates whether the base orbifold is closed * and/or orientable, and gives information about fibre-reversing * paths in the 3-manifold. See the NSFSpace class notes and the * classType enumeration notes for details. * @param genus the genus of the base orbifold (the * number of tori or projective planes that it contains). * Note that for non-orientable base surfaces, this is the * non-orientable genus. * @param punctures the number of untwisted ordinary boundary * components of the base orbifold. Here "ordinary" means that * the puncture gives rise to a real 3-manifold boundary (i.e., * this is not a reflector boundary of the base orbifold). * @param puncturesTwisted the number of twisted ordinary boundary * components of the base orbifold. Here "ordinary" means that * the puncture gives rise to a real 3-manifold boundary (i.e., * this is not a reflector boundary of the base orbifold). * @param reflectors the number of untwisted reflector boundary * components of the base orbifold. These are in addition to * the ordinary boundary components described by \a punctures. * @param reflectorsTwisted the number of twisted reflector boundary * components of the base orbifold. These are in addition to * the ordinary boundary components described by \a puncturesTwisted. */ NSFSpace(classType useClass, unsigned long genus, unsigned long punctures = 0, unsigned long puncturesTwisted = 0, unsigned long reflectors = 0, unsigned long reflectorsTwisted = 0); /** * Creates a new Seifert fibred space that is a clone of * the given space. * * @param cloneMe the Seifert fibred space to clone. */ NSFSpace(const NSFSpace& cloneMe); /** * Destroys this Seifert fibred space. */ virtual ~NSFSpace(); /** * Modifies this Seifert fibred space to be a clone of the given * space. * * @param cloneMe the Seifert fibred space to clone. */ void operator = (const NSFSpace& cloneMe); /** * Returns which of the eleven predefined classes this space * belongs to. The specific class indicates whether the * base orbifold has punctures and/or reflector boundaries, * whether the base orbifold is orientable, and gives information * on fibre-reversing paths. * * The class can be (indirectly) modified by calling * addHandle(), addCrosscap(), addPuncture() or addReflector(). * * For more information on the eleven predefined classes, see the * NSFSpace class notes or the classType enumeration notes. * * @return the particular class to which this space belongs. */ classType baseClass() const; /** * Returns the genus of the base orbifold. All punctures and * reflector boundaries in the base orbifold are ignored (i.e., * they are treated as though they had been replaced with ordinary * filled discs). * * The genus is the number of tori or projective planes that the * base surface is formed from. In particular, if the base * surface is non-orientable then this is the non-orientable genus. * * @return the genus of the base orbifold. */ unsigned long baseGenus() const; /** * Returns whether or not the base surface is orientable. * Reflector boundary components of the base orbifold are not * considered here. * * The orientability of the base surface can be (indirectly) * modified by calling addCrosscap(). * * @return \c true if and only if the base surface is orientable. */ bool baseOrientable() const; /** * Returns whether or not this space contains any fibre-reversing * paths. * * @return \c true if and only if a fibre-reversing path exists. */ bool fibreReversing() const; /** * Returns whether or not we can negate an exceptional fibre by * passing it around the interior of the base orbifold. That is, * this routine determines whether a (\a p, \a q) exceptional * fibre can become a (\a p, -\a q) exceptional fibre simply by * sliding it around. * * This is possible if either * - the base orbifold has an orientation-reversing loop that * does not reverse fibres in the 3-manifold, or * - the base orbifold has an orientation-preserving loop that * does reverse fibres in the 3-manifold. * * Note that reflector boundary components, whilst making the * overall 3-manifold non-orientable, have no bearing on the * outcome of this routine. * * @return \c true if and only an exceptional fibre can be * reflected as described above. */ bool fibreNegating() const; /** * Returns the total number of punctures in the base orbifold. * In other words, this routine returns the total number of real * torus or Klein bottle boundary components in the overall * 3-manifold. * * Note that reflector boundaries on the base orbifold are \e not * counted here; only the ordinary boundary components that give rise * to real 3-manifold boundaries are included. * * Both untwisted and twisted punctures (giving rise to torus * and Klein bottle boundaries respectively in the 3-manifold) * are counted by this routine. * * @return the total number of punctures. */ unsigned long punctures() const; /** * Returns the number of punctures of the given type in the base * orbifold. In other words, this routine returns the number of * real boundary components of the given type in the overall * 3-manifold. * * This routine either counts only twisted punctures (which give * rise to Klein bottle boundaries), or only untwisted punctures * (which give rise to torus boundaries). * * Either way, reflector boundaries on the base orbifold are * \e not counted here; only ordinary boundary components that * give rise to real 3-manifold boundaries are considered. * * @param twisted \c true if only twisted punctures should be * counted (those that give fibre-reversing paths and Klein * bottle boundaries), or \c false if only untwisted punctures * should be counted (those that are fibre-preserving and give * torus boundaries). * @return the number of punctures of the given type. */ unsigned long punctures(bool twisted) const; /** * Returns the total number of reflector boundary components of the * base orbifold. This includes both twisted and untwisted * reflector boundaries. * * @return the total number of reflector boundary components. */ unsigned long reflectors() const; /** * Returns the number of reflector boundary components of the * given type in the base orbifold. This either counts only twisted * reflector boundaries, or only untwisted reflector boundaries. * * @param twisted \c true if only twisted reflector boundaries * should be counted (those that give fibre-reversing paths), or * \c false if only untwisted reflector boundaries should be counted. * @return the number of reflector boundaries of the given type. */ unsigned long reflectors(bool twisted) const; /** * Returns the number of exceptional fibres in this Seifert fibred * space. * * Note that the obstruction parameter \a b is not included in * this count. That is, any (1,k) fibres are ignored. * * @return the number of exceptional fibres. */ unsigned long fibreCount() const; /** * Returns the requested exceptional fibre. Fibres are stored * in sorted order by \a alpha (the index) and then by \a beta. * See the NSFSpace class notes for details. * * \warning This routine takes linear time (specifically, * linear in the argument \a which). * * @param which determines which fibre to return; this must be between * 0 and getFibreCount()-1 inclusive. * @return the requested fibre. */ NSFSFibre fibre(unsigned long which) const; /** * Returns the obstruction constant \a b for this Seifert fibred * space. * * The obstruction constant corresponds to the insertion of an * additional (1,\a b) fibre. It can be modified by calling * insertFibre() with a value of \a alpha = 1. It will also be * modified whenever insertFibre() is called with \a beta out of * range (\a beta < 0 or \a beta >= \a alpha), since each exceptional * fibre must be stored in standard form (0 <= \a beta < \a alpha). * * @return the obstruction constant \a b. */ long obstruction() const; /** * Inserts a new handle into the base orbifold. * * This increases the orientable genus of the base orbifold by * one, or the non-orientable genus by two. It is equivalent to * removing a disc from the base orbifold and replacing it with * a punctured torus. * * Note that this operation may alter which of the classes * described by classType this space belongs to. * * The exceptional fibres and the obstruction constant \a b are * not modified by this routine. * * @param fibreReversing \c true if one or both generators of * the new handle should give fibre-reversing curves in the * overall 3-manifold, or \c false (the default) if both * generators should preserve the directions of the fibres. */ void addHandle(bool fibreReversing = false); /** * Inserts a new crosscap into the base orbifold. * * This makes the base orbifold non-orientable, and increases * its non-orientable genus by one. It is equivalent to * removing a disc from the base orbifold and replacing it with * a Mobius band. * * Note that this operation may alter which of the classes * described by classType this space belongs to. * * The exceptional fibres and the obstruction constant \a b are * not modified by this routine. * * @param fibreReversing \c true if the generator of the new * crosscap should give a fibre-reversing curve in the * overall 3-manifold, or \c false (the default) if it should * preserve the directions of the fibres. */ void addCrosscap(bool fibreReversing = false); /** * Inserts one or more new punctures into the base orbifold. * The punctures may be twisted or untwisted. * * Each puncture insertion is equivalent to removing a disc from * the base orbifold. In the untwisted case this results in a * new torus boundary for the 3-manifold, and in the twisted * case it results in a new Klein bottle boundary. * * The exceptional fibres and the obstruction constant \a b are * not modified by this routine. * * @param twisted \c true if the new punctures should be twisted * (i.e., their boundaries should be fibre-reversing), or \c false * if the new punctures should be untwisted. * @param nPunctures the number of new punctures to insert. */ void addPuncture(bool twisted = false, unsigned long nPunctures = 1); /** * Adds one or more new reflector boundary components to the base * orbifold. The new reflector boundaries may be twisted or * untwisted. * * Each addition of a reflector boundary component is equivalent to * removing a disc from the base orbifold and replacing it with an * annulus with one reflector boundary. * * In the untwisted case, it has the effect of removing a trivially * fibred solid torus from the overall 3-manifold and replacing it * with an appropriately fibred twisted I-bundle over the torus. * * The exceptional fibres and the obstruction constant \a b are * not modified by this routine. * * @param twisted \c true if the new reflector boundaries should be * twisted (i.e., the boundaries should be fibre-reversing), or * \c false if the new reflector boundaries should be untwisted. * @param nReflectors the number of new reflector boundaries to add. */ void addReflector(bool twisted = false, unsigned long nReflectors = 1); /** * Adds the given fibre to this Seifert fibred space. * * This may be an exceptional fibre (\a alpha > 1) or it may be * a regular fibre (\a alpha = 1). If it is a regular fibre, * the obstruction constant \a b will be adjusted according to * the value of \a beta. * * Note that there is no restriction on the range of the second * parameter \a beta. If it is out of the usual range * 0 <= \a beta < \a alpha, it will be pulled back into this * range and the excess will be pushed into the obstruction * constant \a b. * * @param fibre the fibre to insert. The first parameter of * this fibre (i.e., its index) must be strictly positive, and * the two parameters of this fibre must be coprime. */ void insertFibre(const NSFSFibre& fibre); /** * Adds the given fibre to this Seifert fibred space. * * This may be an exceptional fibre (\a alpha > 1) or it may be * a regular fibre (\a alpha = 1). If it is a regular fibre, * the obstruction constant \a b will be adjusted according to * the value of \a beta. * * Note that there is no restriction on the range of the second * parameter \a beta. If it is out of the usual range * 0 <= \a beta < \a alpha, it will be pulled back into this * range and the excess will be pushed into the obstruction * constant \a b. * * @param alpha the first parameter (i.e., the index) of the * fibre to insert; this must be strictly positive. * @param beta the second parameter of the fibre to insert; this * must have no common factors with the first parameter \a alpha. */ void insertFibre(long alpha, long beta); /** * Replaces this space with its mirror image. Specifically, all * exceptional fibres and the obstruction constant \a b will be * negated. Note that the obstruction constant will generally * undergo further change as the exceptional fibres are * standardised into the usual 0 <= \a beta < \a alpha form. * * This routine will not change the curves made by the fibres * and the base orbifold on any boundary components (i.e., * boundaries caused by punctures in the base orbifold), with * the exception that each base curve will be reflected. * * \warning The space is \e not reduced after reflecting. * It may be that the space can be further simplified * (especially in the case of non-orientable manifolds). */ void reflect(); /** * Replaces each exceptional fibre of the form (\a alpha, \a beta) * with a fibre of the form (\a alpha, \a alpha - \a beta). * The obstruction constant \a b is not touched. */ void complementAllFibres(); /** * Reduces the parameters of this Seifert fibred space to a * simpler form if possible, without changing the underlying * fibration. * * In some cases the parameters of the Seifert fibred space may * be simplified by taking a mirror image of the entire 3-manifold. * The argument \a mayReflect signifies whether this is allowed. * * This routine will not change the curves made by the fibres * and the base orbifold on any boundary components (i.e., * boundaries caused by punctures in the base orbifold). * * \warning If \a mayReflect is \c true then the entire 3-manifold * might be replaced with its mirror image, in which case any * subsequent modifications (such as inserting additional fibres * or altering the base orbifold) may give unexpected results. * * @param mayReflect \c true if we are allowed to take a mirror * image of the entire 3-manifold, or \c false if we are not. */ void reduce(bool mayReflect = true); /** * Determines if this Seifert fibred space is a Lens space. * * If this is a Lens space, the NLensSpace returned will be * newly created and it will be up to the caller * of this routine to destroy it. * * @return a structure containing the details of this Lens * space, or \c null if this is not a Lens space. */ NLensSpace* isLensSpace() const; /** * Determines whether this and the given structure contain * precisely the same representations of precisely the same * Seifert fibred spaces. * * Note that this routine examines the particular representation of * the Seifert fibred space. Different Seifert parameters that give * the same 3-manifold will be regarded as not equal by this routine. * * @param compare the representation with which this will be compared. * @return \c true if and only if this and the given Seifert * fibred space representations are identical. */ bool operator == (const NSFSpace& compare) const; /** * Determines in a fairly ad-hoc fashion whether this representation * of this space is "smaller" than the given representation of the * given space. * * The ordering imposed on Seifert fibred space representations * is purely aesthetic on the part of the author, and is subject to * change in future versions of Regina. It also depends upon the * particular representation, so that different representations * of the same space may be ordered differently. * * All that this routine really offers is a well-defined way of * ordering Seifert fibred space representations. * * @param compare the representation with which this will be compared. * @return \c true if and only if this is "smaller" than the given * Seifert fibred space representation. */ bool operator < (const NSFSpace& compare) const; NTriangulation* construct() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; std::ostream& writeStructure(std::ostream& out) const; private: /** * Replaces the fibre (\a alpha, \a beta) at the given iterator * with the fibre (\a alpha, \alpha - \a beta) instead. The fibre * is also moved backwards through the list in order to maintain * sorted order. * * The given iterator will be invalidated (since the * corresponding list element will be erased). * * \pre The fibre at the given iterator satisfies * \a beta * 2 > \a alpha. * * @return the iterator that was immediately after the given * iterator before this routine was called. Note that the given * iterator will be invalidated. */ std::list::iterator negateFibreDown( std::list::iterator it); /** * Internal to writeCommonBase(). * * Writes a particular countable feature of the base orbifold to * the given output stream in either TeX or plain format. */ static void writeBaseExtraCount(std::ostream& out, unsigned long count, const char* object, bool tex); /** * Writes the base orbifold to the given output stream in either * TeX or plain format. * * @param out the output stream to which to write. * @param tex \c true if we are writing in TeX format, or * \c false if we are writing in plain text format. * @return a reference to \a out. */ std::ostream& writeCommonBase(std::ostream& out, bool tex) const; /** * Provides the implementation of writeStructure() in both TeX * and plain formats. This is so that both writeName() and * writeTeXName() can call upon it if required. * * @param out the output stream to which to write. * @param tex \c true if we are writing in TeX format, or * \c false if we are writing in plain text format. * @return a reference to \a out. */ std::ostream& writeCommonStructure(std::ostream& out, bool tex) const; /** * Provides the implementation of both routines writeName() and * writeTeXName(). These routines are implemented together in * writeCommonName() since they share a common internal structure. * * @param out the output stream to which to write. * @param tex \c true if we are handling writeTeXName(), or * \c false if we are handling writeName(). * @return a reference to \a out. */ std::ostream& writeCommonName(std::ostream& out, bool tex) const; }; /*@}*/ // Inline functions for NSFSFibre inline NSFSFibre::NSFSFibre() { } inline NSFSFibre::NSFSFibre(long newAlpha, long newBeta) : alpha(newAlpha), beta(newBeta) { } inline NSFSFibre::NSFSFibre(const NSFSFibre& cloneMe) : alpha(cloneMe.alpha), beta(cloneMe.beta) { } inline void NSFSFibre::operator = (const NSFSFibre& cloneMe) { alpha = cloneMe.alpha; beta = cloneMe.beta; } inline bool NSFSFibre::operator == (const NSFSFibre& compare) const { return (alpha == compare.alpha && beta == compare.beta); } inline bool NSFSFibre::operator < (const NSFSFibre& compare) const { return (alpha < compare.alpha || (alpha == compare.alpha && beta < compare.beta)); } // Inline functions for NSFSpace inline NSFSpace::NSFSpace() : class_(o1), genus_(0), punctures_(0), puncturesTwisted_(0), reflectors_(0), reflectorsTwisted_(0), nFibres_(0), b_(0) { } inline NSFSpace::NSFSpace(NSFSpace::classType useClass, unsigned long genus, unsigned long punctures, unsigned long puncturesTwisted, unsigned long reflectors, unsigned long reflectorsTwisted) : class_(useClass), genus_(genus), punctures_(punctures), puncturesTwisted_(puncturesTwisted), reflectors_(reflectors), reflectorsTwisted_(reflectorsTwisted), nFibres_(0), b_(0) { } inline NSFSpace::NSFSpace(const NSFSpace& cloneMe) : NManifold(), class_(cloneMe.class_), genus_(cloneMe.genus_), punctures_(cloneMe.punctures_), puncturesTwisted_(cloneMe.puncturesTwisted_), reflectors_(cloneMe.reflectors_), reflectorsTwisted_(cloneMe.reflectorsTwisted_), fibres_(cloneMe.fibres_), nFibres_(cloneMe.nFibres_), b_(cloneMe.b_) { } inline NSFSpace::~NSFSpace() { } inline NSFSpace::classType NSFSpace::baseClass() const { return class_; } inline unsigned long NSFSpace::baseGenus() const { return genus_; } inline bool NSFSpace::baseOrientable() const { return (class_ == o1 || class_ == o2 || class_ == bo1 || class_ == bo2); } inline bool NSFSpace::fibreReversing() const { return ! (class_ == o1 || class_ == n1 || class_ == bo1 || class_ == bn1); } inline bool NSFSpace::fibreNegating() const { return ! (class_ == o1 || class_ == n2 || class_ == bo1 || class_ == bn2); } inline unsigned long NSFSpace::punctures() const { return punctures_ + puncturesTwisted_; } inline unsigned long NSFSpace::punctures(bool twisted) const { return (twisted ? puncturesTwisted_ : punctures_); } inline unsigned long NSFSpace::reflectors() const { return reflectors_ + reflectorsTwisted_; } inline unsigned long NSFSpace::reflectors(bool twisted) const { return (twisted ? reflectorsTwisted_ : reflectors_); } inline unsigned long NSFSpace::fibreCount() const { return nFibres_; } inline long NSFSpace::obstruction() const { return b_; } inline void NSFSpace::insertFibre(const NSFSFibre& fibre) { insertFibre(fibre.alpha, fibre.beta); } inline void NSFSpace::reflect() { complementAllFibres(); b_ = -b_ - static_cast(nFibres_); } inline std::ostream& NSFSpace::writeName(std::ostream& out) const { return writeCommonName(out, false); } inline std::ostream& NSFSpace::writeTeXName(std::ostream& out) const { return writeCommonName(out, true); } inline std::ostream& NSFSpace::writeStructure(std::ostream& out) const { return writeCommonStructure(out, false); } } // namespace regina #endif regina-4.95/engine/manifold/nsfsaltset.cpp000644 000765 000024 00000014076 12234011536 020544 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/nsfs.h" #include "manifold/nsfsaltset.h" namespace regina { NSFSAltSet::NSFSAltSet(const NSFSpace* sfs) { /** * Note that whenever we add a (1,1) twist, we compensate by setting * row 2 -> row 2 + row 1 in our conversion matrix. */ // Start with the original, reduced to give obstruction constant zero. data_[0] = new NSFSpace(*sfs); data_[0]->reduce(false); long b = data_[0]->obstruction(); if (b) data_[0]->insertFibre(1, -b); conversion_[0] = NMatrix2(1, 0, -b, 1); reflected_[0] = false; /** * If the space is M/n2, we can replace it with D:(2,1)(2,-1) * with fibre and orbifold curves switched. To preserve the * determinant of the matching matrix we will actually use a * [0,1,-1,0] switch instead of a [0,1,1,0] switch. * * In fact we will use D:(2,1)(2,1) instead, which means: * * M_basis = [ 0 1 ] [ 1 0 ] D_basis = [ -1 1 ] D_basis; * [ -1 0 ] [ -1 1 ] [ -1 0 ] * * D_basis = [ 1 0 ] [ 0 -1 ] M_basis = [ 0 -1 ] M_basis. * [ 1 1 ] [ 1 0 ] [ 1 -1 ] */ if (data_[0]->baseClass() == NSFSpace::bn2 && data_[0]->baseGenus() == 1 && (! data_[0]->baseOrientable()) && data_[0]->punctures(false) == 1 && data_[0]->punctures(true) == 0 && data_[0]->reflectors() == 0 && data_[0]->fibreCount() == 0 && data_[0]->obstruction() == 0) { delete data_[0]; data_[0] = new NSFSpace(NSFSpace::bo1, 0 /* genus */, 1 /* punctures */, 0 /* twisted */, 0 /* reflectors */, 0 /* twisted */); data_[0]->insertFibre(2, 1); data_[0]->insertFibre(2, 1); conversion_[0] = NMatrix2(0, -1, 1, -1) * conversion_[0]; } // Using data_[0] as a foundation, try now for a reflection. data_[1] = new NSFSpace(*data_[0]); data_[1]->reflect(); data_[1]->reduce(false); b = data_[1]->obstruction(); data_[1]->insertFibre(1, -b); conversion_[1] = NMatrix2(1, 0, -b, -1) * conversion_[0]; reflected_[1] = true; size_ = 2; // In the vanilla case, this is all. However, we can occasionally // do a little more. // Can we negate all fibres without reflecting? // Note that (1,2) == (1,0) in this case, so this is only // interesting if we have an odd number of exceptional fibres. if (data_[0]->fibreNegating() && (data_[0]->fibreCount() % 2 != 0)) { // Do it by adding a single (1,1). The subsequent reduce() will // negate fibres to bring the obstruction constant back down to // zero, giving the desired effect. data_[2] = new NSFSpace(*data_[0]); data_[2]->insertFibre(1, 1); data_[2]->reduce(false); b = data_[2]->obstruction(); data_[2]->insertFibre(1, -b); conversion_[2] = NMatrix2(1, 0, -b + 1, 1) * conversion_[0]; reflected_[2] = false; // And do it again with an added reflection. data_[3] = new NSFSpace(*data_[0]); data_[3]->insertFibre(1, 1); data_[3]->reflect(); data_[3]->reduce(false); b = data_[3]->obstruction(); data_[3]->insertFibre(1, -b); conversion_[3] = NMatrix2(1, 0, -b - 1, -1) * conversion_[0]; reflected_[3] = true; size_ = 4; } } void NSFSAltSet::deleteAll() { for (unsigned i = 0; i < size_; i++) delete data_[i]; } void NSFSAltSet::deleteAll(NSFSpace* exception) { for (unsigned i = 0; i < size_; i++) if (data_[i] != exception) delete data_[i]; } void NSFSAltSet::deleteAll(NSFSpace* exception1, NSFSpace* exception2) { for (unsigned i = 0; i < size_; i++) if (data_[i] != exception1 && data_[i] != exception2) delete data_[i]; } } // namespace regina regina-4.95/engine/manifold/nsfsaltset.h000644 000765 000024 00000025746 12234011536 020217 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nsfsaltset.h * \brief Assists with providing different representations of the same * Seifert fibred space. */ #ifndef __NSFSALTSET_H #ifndef __DOXYGEN #define __NSFSALTSET_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" namespace regina { class NSFSpace; /** * \weakgroup manifold * @{ */ /** * Provides a variety of alternative representations of a single bounded * Seifert fibred space. These alternatives are made possible by altering * the curves made by the fibre and base orbifold on a boundary torus. * * This class is designed to help in finding simple representations of * graph manifolds (or, indeed, any non-geometric manifolds containing * Seifert fibred blocks). * * Each alternative comes with its own representation of the original Seifert * fibred space, along with instructions for converting fibre/base * curves on the boundary tori between the original and alternative spaces. * * The alternative representations will generally be as simple as * possible (and indeed simpler than the original where possible). * In particular, each alternative space is guaranteed to have obstruction * constant zero. The base orbifold may be changed entirely (for instance, * an orientable Seifert fibred space over the Mobius band with no exceptional * fibres will be converted to a Seifert fibred space over the disc with * two exceptional fibres). * * The conversions between boundary curves are described by a conversion * matrix \a M as follows. Consider the first boundary torus. Let \a f_old * and \a o_old be directed curves on this boundary representing the fibre * and base orbifold of the original space, and let \a f_alt and \a o_alt * be directed curves on this same boundary representing the fibre and * base orbifold of the new alternative space. Then * *
 *     [f_alt]         [f_old]
 *     [     ]  =  M * [     ].
 *     [o_alt]         [o_old]
 * 
* * Note that this \e only applies to the first boundary torus! If the * Seifert fibred space has more than one boundary, then for the * remaining boundaries the unoriented fibre and base curves remain the * same. More specifically, the directed fibre remains identical, and * the directed curve representing the base orbifold is reversed if and * only if a reflection was used in creating the alternative space, as * returned by reflected(). * * See the page on \ref sfsnotation for details on some of the * terminology used above. * * \warning When an object of this class is destroyed, the alternative * spaces it holds are \e not destroyed with it. One of the deleteAll() * routines must be explicitly called to clean up properly. * * \ifacespython Not present. */ class REGINA_API NSFSAltSet { private: unsigned size_; /**< The number of alternative spaces in this set. */ NSFSpace* data_[4]; /**< The list of alternative representations of this Seifert fibred space. */ NMatrix2 conversion_[4]; /**< A list of conversion matrices for each alternative representation, as described in the class notes above. */ bool reflected_[4]; /**< Indicates for each alternative whether a reflection was used in its creation. */ public: /** * Creates a new set of alternatives for the given Seifert * fibred space. Note that in general, none of the alternatives * will have a representation identical to the given space * (generally these alternative representations will be simpler * if possible). * * \pre The given Seifert fibred space has at least one torus * boundary. * * @param sfs the original Seifert fibred space for which we are * creating a set of alternative representations. */ NSFSAltSet(const NSFSpace* sfs); /** * Destroys all of the alternative representations in this set. * * This routine is for situations where none of the alternatives * here are appropriate for keeping and using elsewhere. */ void deleteAll(); /** * Destroys all of the alternative representations in this set, * except for the given exception. * * If the given exception is null or is not one of the * alternatives in this set, every alternative will be destroyed. * * This routine is for situations where one of the alternatives * has been kept for later use, and the rest are to be discarded. * * @param exception the one alternative that should not be destroyed. */ void deleteAll(NSFSpace* exception); /** * Destroys all of the alternative representations in this set, * except for the two given exceptions. * * If either exception is null or is not one of the alternatives * in this set, it will be ignored (and this routine will behave * like the one-exception or no-exceptions variant). Likewise, * if both exceptions are the same then this routine will behave * like the one-exception variant. * * This routine is for situations where one of the alternatives * has been kept for later use, but due to other operations * that may have taken place (such as space swapping) it is only * known that the alternative we kept is one of two possibilities. * * @param exception1 the first alternative that should not be * destroyed. * @param exception2 the second alternative that should not be * destroyed. */ void deleteAll(NSFSpace* exception1, NSFSpace* exception2); /** * Returns the number of alternative spaces in this set. */ unsigned size() const; /** * Returns the requested alternative space. * * @param which indicates which of the alternatives should be * returned; this must be between 0 and size()-1 inclusive. * @return the requested alternative space. */ NSFSpace* operator [] (unsigned which) const; /** * Returns the conversion matrix for the requested alternative * space. This matrix describes the fibre and base curves of * the alternative space on the first boundary torus in terms of * the fibre and base curves of the original space (which was * passed to the NSFSAltSet constructor). See the class notes * above for details. * * Note that this conversion matrix applies \e only to the first * boundary torus! If there is more than one boundary, the * remaining boundary conversions are simpler and depend only * on whether a reflection has been used or not. See reflected() * or the class notes for details. * * @param which indicates which of the alternatives we should * return the conversion matrix for; this must be between 0 and * size()-1 inclusive. * @return the conversion matrix for the requested alternative * space. */ const NMatrix2& conversion(unsigned which) const; /** * Returns whether or not a reflection was used when creating * the requested alternative space. This determines the * conversion between boundary curves for all boundary tori * after the first. * * More specifically, if no reflection was used then the directed * fibre and base curves are identical for the original and * alternative spaces. If a reflection was used, then the * directed fibres are identical but the directed base curves * are reversed. * * The conversion between curves on the first boundary torus is * generally more complex, and is returned as a matrix by the * conversion() routine. * * @param which indicates which of the alternatives is being * queried; this must be between 0 and size()-1 inclusive. * @return \c true if a reflection was used in creating the * requested alternative space, or \c false if no reflection was * used. */ bool reflected(unsigned which) const; }; /*@}*/ // Inline functions for NSFSAltSet inline unsigned NSFSAltSet::size() const { return size_; } inline NSFSpace* NSFSAltSet::operator [] (unsigned which) const { return data_[which]; } inline const NMatrix2& NSFSAltSet::conversion(unsigned which) const { return conversion_[which]; } inline bool NSFSAltSet::reflected(unsigned which) const { return reflected_[which]; } } // namespace regina #endif regina-4.95/engine/manifold/nsimplesurfacebundle.cpp000644 000765 000024 00000010534 12234011536 022563 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nsimplesurfacebundle.h" #include "triangulation/ntriangulation.h" namespace regina { const int NSimpleSurfaceBundle::S2xS1 = 1; const int NSimpleSurfaceBundle::S2xS1_TWISTED = 2; const int NSimpleSurfaceBundle::RP2xS1 = 3; NTriangulation* NSimpleSurfaceBundle::construct() const { NTriangulation* ans = new NTriangulation(); if (type == S2xS1) { ans->insertLayeredLensSpace(0, 1); } else if (type == S2xS1_TWISTED) { // Taken from section 3.5.1 of Ben Burton's PhD thesis. NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(1, s, NPerm4()); r->joinTo(3, s, NPerm4()); r->joinTo(2, s, NPerm4(3, 2, 0, 1)); s->joinTo(2, r, NPerm4(3, 2, 0, 1)); } else if (type == RP2xS1) { // Taken from section 3.5.1 of Ben Burton's PhD thesis. NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); NTetrahedron* t = ans->newTetrahedron(); s->joinTo(0, r, NPerm4(0, 1, 2, 3)); s->joinTo(3, r, NPerm4(3, 0, 1, 2)); s->joinTo(1, t, NPerm4(3, 0, 1, 2)); s->joinTo(2, t, NPerm4(0, 1, 2, 3)); r->joinTo(1, t, NPerm4(2, 3, 0, 1)); r->joinTo(3, t, NPerm4(2, 3, 0, 1)); } return ans; } NAbelianGroup* NSimpleSurfaceBundle::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(); if (type == RP2xS1) ans->addTorsionElement(2); return ans; } std::ostream& NSimpleSurfaceBundle::writeName(std::ostream& out) const { if (type == S2xS1) out << "S2 x S1"; else if (type == S2xS1_TWISTED) out << "S2 x~ S1"; else if (type == RP2xS1) out << "RP2 x S1"; return out; } std::ostream& NSimpleSurfaceBundle::writeTeXName(std::ostream& out) const { if (type == S2xS1) out << "S^2 \\times S^1"; else if (type == S2xS1_TWISTED) out << "S^2 \\twisted S^1"; else if (type == RP2xS1) out << "\\mathbb{R}P^2 \\times S^1"; return out; } } // namespace regina regina-4.95/engine/manifold/nsimplesurfacebundle.h000644 000765 000024 00000012552 12234011536 022232 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nsimplesurfacebundle.h * \brief Deals with simple closed surface bundles. */ #ifndef __NSIMPLESURFACEBUNDLE_H #ifndef __DOXYGEN #define __NSIMPLESURFACEBUNDLE_H #endif #include "regina-core.h" #include "nmanifold.h" namespace regina { /** * \weakgroup manifold * @{ */ /** * Represents a particularly simple closed surface bundle over the circle. * Only 2-sphere bundles, twisted 2-sphere bundles and projective plane * bundles are considered. * * All optional NManifold routines are implemented for this class. * * \testpart */ class REGINA_API NSimpleSurfaceBundle : public NManifold { public: /** * Represents the orientable 2-sphere bundle over the circle. */ static const int S2xS1; /** * Represents the non-orientable twisted 2-sphere bundle over the * circle. */ static const int S2xS1_TWISTED; /** * Represents the projective plane bundle over the circle. */ static const int RP2xS1; private: int type; /**< The specific surface bundle being represented. This must be one of the 3-manifold constants defined in this class. */ public: /** * Creates a new surface bundle of the given type. * * @param newType the specific type of surface bundle to * represent. This must be one of the 3-manifold constants * defined in this class. */ NSimpleSurfaceBundle(int newType); /** * Creates a clone of the given surface bundle. * * @param cloneMe the surface bundle to clone. */ NSimpleSurfaceBundle(const NSimpleSurfaceBundle& cloneMe); /** * Returns the specific type of surface bundle being represented. * * @return the type of surface bundle. This will be one of the * 3-manifold constants defined in this class. */ int getType() const; /** * Determines whether this and the given surface bundle represent * the same 3-manifold. * * @param compare the surface bundle with which this will be compared. * @return \c true if and only if this and the given surface bundle * are homeomorphic. */ bool operator == (const NSimpleSurfaceBundle& compare) const; virtual NTriangulation* construct() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; }; /*@}*/ // Inline functions for NSimpleSurfaceBundle inline NSimpleSurfaceBundle::NSimpleSurfaceBundle( int newType) : type(newType) { } inline NSimpleSurfaceBundle::NSimpleSurfaceBundle( const NSimpleSurfaceBundle& cloneMe) : NManifold(), type(cloneMe.type) { } inline int NSimpleSurfaceBundle::getType() const { return type; } inline bool NSimpleSurfaceBundle::operator == (const NSimpleSurfaceBundle& compare) const { return (type == compare.type); } } // namespace regina #endif regina-4.95/engine/manifold/nsnappeacensusmfd.cpp000644 000765 000024 00000021715 12234011536 022071 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "file/nglobaldirs.h" #include "manifold/nsnappeacensusmfd.h" #include "subcomplex/nsnappeacensustri.h" #include "triangulation/nexampletriangulation.h" #include "triangulation/ntriangulation.h" #include #include namespace regina { namespace { int homDecode(char c) { if (c >= 'a' && c <= 'z') return c - 'a'; if (c >= 'A' && c <= 'Z') return c - 'A' + 26; return -1; } } const char NSnapPeaCensusManifold::SEC_5 = 'm'; const char NSnapPeaCensusManifold::SEC_6_OR = 's'; const char NSnapPeaCensusManifold::SEC_6_NOR = 'x'; const char NSnapPeaCensusManifold::SEC_7_OR = 'v'; const char NSnapPeaCensusManifold::SEC_7_NOR = 'y'; NTriangulation* NSnapPeaCensusManifold::construct() const { NTriangulation* ans = 0; // Hard-code a few special cases so that the numbering of tetrahedra // and vertices is compatible with earlier versions of Regina. if (section == SEC_5) { if (index == 0) { ans = NExampleTriangulation::gieseking(); ans->setPacketLabel(""); } else if (index == 1) { ans = new NTriangulation(); NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(0, s, NPerm4(0, 1, 3, 2)); r->joinTo(1, s, NPerm4(2, 3, 1, 0)); r->joinTo(2, s, NPerm4(3, 2, 1, 0)); r->joinTo(3, s, NPerm4(1, 0, 3, 2)); } else if (index == 2) { ans = new NTriangulation(); NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(0, s, NPerm4(0, 1, 3, 2)); r->joinTo(1, s, NPerm4(3, 1, 2, 0)); r->joinTo(2, s, NPerm4(2, 1, 3, 0)); r->joinTo(3, s, NPerm4(3, 1, 0, 2)); } else if (index == 3) { ans = new NTriangulation(); NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(0, s, NPerm4(0, 1, 3, 2)); r->joinTo(1, s, NPerm4(2, 1, 0, 3)); r->joinTo(2, s, NPerm4(0, 3, 2, 1)); r->joinTo(3, s, NPerm4(1, 0, 2, 3)); } else if (index == 4) { ans = NExampleTriangulation::figureEightKnotComplement(); ans->setPacketLabel(""); } else if (index == 129) { ans = NExampleTriangulation::whiteheadLinkComplement(); ans->setPacketLabel(""); } } if (ans) return ans; // Fetch the relevant data from the census dehydration files. std::string file = NGlobalDirs::data(); switch (section) { case SEC_5: file += "/snappea-census-sec5.dat"; break; case SEC_6_OR: file += "/snappea-census-sec6o.dat"; break; case SEC_6_NOR: file += "/snappea-census-sec6n.dat"; break; case SEC_7_OR: file += "/snappea-census-sec7o.dat"; break; case SEC_7_NOR: file += "/snappea-census-sec7n.dat"; break; default: return 0; } FILE* dat = fopen(file.c_str(), "r"); if (! dat) { std::cerr << "Cannot open data file: " << file << std::endl; return 0; } char tri[30], hom[30]; /* Long enough to deal with the snappea census files for <= 7 tetrahedra. */ for (unsigned i = 0; i <= index; ++i) { if (fscanf(dat, "%s%s", tri, hom) != 2) { if (feof(dat)) std::cerr << "Read beyond end of data file: " << file << std::endl; else std::cerr << "Error reading data file: " << file << std::endl; return 0; } } fclose(dat); ans = NTriangulation::rehydrate(tri); return ans; } NAbelianGroup* NSnapPeaCensusManifold::getHomologyH1() const { // Fetch the relevant data from the census dehydration files. std::string file = NGlobalDirs::data(); switch (section) { case SEC_5: file += "/snappea-census-sec5.dat"; break; case SEC_6_OR: file += "/snappea-census-sec6o.dat"; break; case SEC_6_NOR: file += "/snappea-census-sec6n.dat"; break; case SEC_7_OR: file += "/snappea-census-sec7o.dat"; break; case SEC_7_NOR: file += "/snappea-census-sec7n.dat"; break; default: return 0; } FILE* dat = fopen(file.c_str(), "r"); if (! dat) { std::cerr << "Cannot open data file: " << file << std::endl; return 0; } char tri[30], hom[30]; /* Long enough to deal with the snappea census files for <= 7 tetrahedra. */ for (unsigned i = 0; i <= index; ++i) { if (fscanf(dat, "%s%s", tri, hom) != 2) { if (feof(dat)) std::cerr << "Read beyond end of data file: " << file << std::endl; else std::cerr << "Error reading data file: " << file << std::endl; return 0; } } fclose(dat); NAbelianGroup* ans = new NAbelianGroup(); char* c; int val; // First character of the homology string represents rank. val = homDecode(hom[0]); // Empty string is picked up and dealt with here. if (val < 0) { delete ans; return 0; } ans->addRank(val); // The remaining characters represent torsion. std::multiset torsion; for (c = hom + 1; *c; ++c) { val = homDecode(*c); if (val < 0) { delete ans; return 0; } torsion.insert(val); } ans->addTorsionElements(torsion); return ans; } std::ostream& NSnapPeaCensusManifold::writeName(std::ostream& out) const { // Some manifolds will get special names, and will have their usual // SnapPea names written in writeStructure() instead. if (section == SEC_5) { if (index == 0) return out << "Gieseking manifold"; if (index == 4) return out << "Figure eight knot complement"; if (index == 129) return out << "Whitehead link complement"; } // No special names, just the usual SnapPea notation. return NSnapPeaCensusTri(section, index).writeName(out); } std::ostream& NSnapPeaCensusManifold::writeTeXName(std::ostream& out) const { return NSnapPeaCensusTri(section, index).writeTeXName(out); } std::ostream& NSnapPeaCensusManifold::writeStructure(std::ostream& out) const { // If we didn't give the usual SnapPea name in writeName(), give it here. if (section == SEC_5) { if (index == 0 || index == 4 || index == 129) return NSnapPeaCensusTri(section, index).writeName(out); } return out; } } // namespace regina regina-4.95/engine/manifold/nsnappeacensusmfd.h000644 000765 000024 00000020530 12234011536 021530 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/nsnappeacensusmfd.h * \brief Deals with 3-manifolds from the SnapPea census. */ #ifndef __NSNAPPEACENSUSMFD_H #ifndef __DOXYGEN #define __NSNAPPEACENSUSMFD_H #endif #include "regina-core.h" #include "nmanifold.h" namespace regina { /** * \weakgroup manifold * @{ */ /** * Represents a 3-manifold from the SnapPea cusped census. * * The SnapPea cusped census is the census of cusped hyperbolic 3-manifolds * formed from up to seven tetrahedra. This census was tabulated by * Callahan, Hildebrand and Weeks, and is shipped with SnapPea 3.0d3 * (and also with Regina). * * The census is split into five different sections according to number * of tetrahedra and orientability. Each of these sections corresponds * to one of the section constants defined in this class. * * For further details regarding the SnapPea census, see "A census of cusped * hyperbolic 3-manifolds", Patrick J. Callahan, Martin V. Hildebrand and * Jeffrey R. Weeks, Math. Comp. 68 (1999), no. 225, pp. 321--332. * * Note that this class is closely tied to NSnapPeaCensusTri. * In particular, the section constants defined in NSnapPeaCensusTri and * NSnapPeaCensusManifold are identical, and so may be freely mixed. * Furthermore, the section and index parameters of an NSnapPeaCensusTri * are identical to those of its corresponding NSnapPeaCensusManifold. * * All of the optional NManifold routines are implemented for this class. * * \testpart */ class REGINA_API NSnapPeaCensusManifold : public NManifold { public: static const char SEC_5; /**< Represents the collection of manifolds formed from five or fewer tetrahedra (both orientable and non-orientable). There are 415 manifolds in this section. */ static const char SEC_6_OR; /**< Represents the collection of orientable manifolds formed from six tetrahedra. There are 962 manifolds in this section. */ static const char SEC_6_NOR; /**< Represents the collection of non-orientable manifolds formed from six tetrahedra. There are 259 manifolds in this section. */ static const char SEC_7_OR; /**< Represents the collection of orientable manifolds formed from seven tetrahedra. There are 3552 manifolds in this section. */ static const char SEC_7_NOR; /**< Represents the collection of non-orientable manifolds formed from seven tetrahedra. There are 887 manifolds in this section. */ private: char section; /**< The section of the SnapPea census to which this manifold belongs. This must be one of the section constants defined in this class. */ unsigned long index; /**< The index within the given section of this specific manifold. Note that the first index in each section is zero. */ public: /** * Creates a new SnapPea census manifold with the given parameters. * * @param newSection the section of the SnapPea census to which * this manifold belongs. This must be one of the section * constants defined in this class. * @param newIndex specifies which particular manifold within the * given section is represented. The indices for each section * begin counting at zero, and so this index * must be between 0 and k-1, where k is the total * number of manifolds in the given section. */ NSnapPeaCensusManifold(char newSection, unsigned long newIndex); /** * Creates a clone of the given SnapPea census manifold. * * @param cloneMe the census manifold to clone. */ NSnapPeaCensusManifold(const NSnapPeaCensusManifold& cloneMe); /** * Destroys this structure. */ virtual ~NSnapPeaCensusManifold(); /** * Returns the section of the SnapPea census to which this * manifold belongs. This will be one of the section constants * defined in this class. * * @return the section of the SnapPea census. */ char getSection() const; /** * Returns the index of this manifold within its particular * section of the SnapPea census. Note that indices for each * section begin counting at zero. * * @return the index of this manifold within its section. */ unsigned long getIndex() const; /** * Determines whether this and the given structure represent * the same 3-manifold from the SnapPea census. * * @param compare the structure with which this will be compared. * @return \c true if and only if this and the given structure * represent the same SnapPea census manifold. */ bool operator == (const NSnapPeaCensusManifold& compare) const; NTriangulation* construct() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; std::ostream& writeStructure(std::ostream& out) const; }; /*@}*/ // Inline functions for NSnapPeaCensusManifold inline NSnapPeaCensusManifold::NSnapPeaCensusManifold(char newSection, unsigned long newIndex) : section(newSection), index(newIndex) { } inline NSnapPeaCensusManifold::NSnapPeaCensusManifold( const NSnapPeaCensusManifold& cloneMe) : NManifold(), section(cloneMe.section), index(cloneMe.index) { } inline NSnapPeaCensusManifold::~NSnapPeaCensusManifold() { } inline char NSnapPeaCensusManifold::getSection() const { return section; } inline unsigned long NSnapPeaCensusManifold::getIndex() const { return index; } inline bool NSnapPeaCensusManifold::operator == ( const NSnapPeaCensusManifold& compare) const { return (section == compare.section && index == compare.index); } } // namespace regina #endif regina-4.95/engine/manifold/ntorusbundle.cpp000644 000765 000024 00000031272 12234011536 021077 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "maths/nmatrixint.h" #include "manifold/ntorusbundle.h" namespace regina { NAbelianGroup* NTorusBundle::getHomologyH1() const { NMatrixInt relns(2, 2); relns.entry(0, 0) = monodromy[0][0] - 1; relns.entry(0, 1) = monodromy[0][1]; relns.entry(1, 0) = monodromy[1][0]; relns.entry(1, 1) = monodromy[1][1] - 1; NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(relns); ans->addRank(); return ans; } std::ostream& NTorusBundle::writeName(std::ostream& out) const { if (monodromy.isIdentity()) return out << "T x I"; else return out << "T x I / [ " << monodromy[0][0] << ',' << monodromy[0][1] << " | " << monodromy[1][0] << ',' << monodromy[1][1] << " ]"; } std::ostream& NTorusBundle::writeTeXName(std::ostream& out) const { if (monodromy.isIdentity()) return out << "T^2 \\times I"; else return out << "T^2 \\times I / \\homtwo{" << monodromy[0][0] << "}{" << monodromy[0][1] << "}{" << monodromy[1][0] << "}{" << monodromy[1][1] << "}"; } void NTorusBundle::reduce() { // Make the monodromy prettier. // In general we are allowed to: // // - Replace M with A M A^-1 // - Replace M with M^-1 // // Some specific tricks we can pull include: // // - Rotate the matrix 180 degrees (A = [ 0 1 | 1 0 ]) // - Negate the off-diagonal (A = [ 1 0 | 0 -1 ]) // // If det == +1, we can also: // // - Swap either diagonal individually (invert, then negate the // off-diagonal, then optionally rotate by 180 degrees) // // If det == -1, we can also: // // - Simultaneously swap and negate the main diagonal (invert) // The determinant should be +/-1 according to our preconditions, // but we'd better check that anyway. long det = monodromy.determinant(); if (det != 1 && det != -1) { // Something is very wrong. Don't touch it. std::cerr << "ERROR: NTorusBundle monodromy does not have " "determinant +/-1.\n"; return; } // Deal with the case where the main diagonal has strictly opposite // signs. long x; if (monodromy[0][0] < 0 && monodromy[1][1] > 0) { // Rotate 180 degrees to put the positive element up top. rotate(); } while (monodromy[0][0] > 0 && monodromy[1][1] < 0) { // Set x to the greatest absolute value of any main diagonal element. if (monodromy[0][0] >= - monodromy[1][1]) x = monodromy[0][0]; else x = - monodromy[1][1]; // If we catch any of the following four cases, the main diagonal // will either no longer have opposite signs, or it will have a // strictly smaller maximum absolute value. if (0 < monodromy[0][1] && monodromy[0][1] <= x) { addRCDown(); continue; } else if (0 < - monodromy[0][1] && - monodromy[0][1] <= x) { subtractRCDown(); continue; } else if (0 < monodromy[1][0] && monodromy[1][0] <= x) { subtractRCUp(); continue; } else if (0 < - monodromy[1][0] && - monodromy[1][0] <= x) { addRCUp(); continue; } // Since the determinant is +/-1 and neither element of the // main diagonal is zero, we cannot have both elements of the // off-diagonal with absolute value strictly greater than x. // The only remaining possibility is that some element of the // off-diagonal is zero (and therefore the main diagonal // contains +1 and -1). // The non-zero off-diagonal element (if any) can be reduced // modulo 2. This leaves us with the following possibilities: // [ 1 0 | 0 -1 ] , [ 1 1 | 0 -1 ], [ 1 0 | 1 -1 ]. // The final two possibilities are both equivalent to [ 0 1 | 1 0 ]. if ((monodromy[0][1] % 2) || (monodromy[1][0] % 2)) { monodromy[0][0] = monodromy[1][1] = 0; monodromy[0][1] = monodromy[1][0] = 1; } else { monodromy[0][1] = monodromy[1][0] = 0; // The main diagonal elements stay as they are (1, -1). } // In these cases we are completely finished. return; } // We are now guaranteed that the main diagonal does not have // strictly opposite signs. // Time to arrange the same for the off-diagonal. // If the off-diagonal has strictly opposite signs, the elements // must be +1 and -1, and the main diagonal must contain a zero. // Otherwise there is no way we can get determinant +/-1. if (monodromy[0][1] < 0 && monodromy[1][0] > 0) { // We have [ a -1 | 1 d ]. // Move the -1 to the bottom left corner by negating the off-diagonal. monodromy[0][1] = 1; monodromy[1][0] = -1; } if (monodromy[0][1] > 0 && monodromy[1][0] < 0) { // We have [ a 1 | -1 d ], where one of a or d is zero. // Rotate by 180 degrees to move the 0 to the bottom right // corner, negating the off-diagonal if necessary to preserve // the 1/-1 positions. if (monodromy[1][1]) { monodromy[0][0] = monodromy[1][1]; monodromy[1][1] = 0; } // Now we have [ a 1 | -1 0 ]. if (monodromy[0][0] > 1) { addRCDown(); // Everything becomes non-negative. } else if (monodromy[0][0] < -1) { subtractRCUp(); // Everything becomes non-positive. } else { // We have [ 1 1 | -1 0 ], [ 0 1 | -1 0 ] or [ -1 1 | -1 0 ]. // All of these are canonical. return; } } // Neither diagonal has strictly opposite signs. // Time to give all elements of the matrix the same sign (or zero). bool allNegative = false; if (det == 1) { // Either all non-negative or all non-positive, as determined by // the main diagonal. // If it's going to end up negative, just switch the signs for // now and remember this fact for later on. if (monodromy[0][0] < 0 || monodromy[1][1] < 0) { allNegative = true; monodromy[0][0] = - monodromy[0][0]; monodromy[1][1] = - monodromy[1][1]; } if (monodromy[0][1] < 0 || monodromy[1][0] < 0) { // We're always allowed to do this. monodromy[0][1] = - monodromy[0][1]; monodromy[1][0] = - monodromy[1][0]; } } else { // The determinant is -1. // The entire matrix can be made non-negative. if (monodromy[0][0] < 0 || monodromy[1][1] < 0) { // Invert (swap and negate the main diagonal). x = monodromy[0][0]; monodromy[0][0] = - monodromy[1][1]; monodromy[1][1] = -x; } if (monodromy[0][1] < 0 || monodromy[1][0] < 0) { // Negate the off-diagonal as usual. monodromy[0][1] = - monodromy[0][1]; monodromy[1][0] = - monodromy[1][0]; } } // We now have a matrix whose entries are all non-negative. // Run through a cycle of equivalent matrices, and choose the nicest. // I'm pretty sure I can prove that this is a cycle, but the proof // really should be written down. NMatrix2 start = monodromy; NMatrix2 best = monodromy; while (1) { // INV: monodromy has all non-negative entries. // INV: best contains the best seen matrix, including the current one. // It can be proven (via det = +/-1) that one row must dominate // another, unless we have [ 1 0 | 0 1 ] or [ 0 1 | 1 0 ]. if (monodromy.isIdentity()) { if (allNegative) monodromy.negate(); return; } if (monodromy[0][0] == 0 && monodromy[0][1] == 1 && monodromy[1][0] == 1 && monodromy[1][1] == 0) { if (allNegative) monodromy.negate(); return; } // We know at this point that one row dominates the other. if (monodromy[0][0] >= monodromy[1][0] && monodromy[0][1] >= monodromy[1][1]) subtractRCUp(); else subtractRCDown(); // Looking at a new matrix. if (monodromy == start) break; if (NTorusBundle::simplerNonNeg(monodromy, best)) best = monodromy; } // In the orientable case, run this all again for the rotated matrix. // This is not necessary in the non-orientable case since the // rotated matrix belongs to the same cycle as the original. if (det > 0) { rotate(); if (NTorusBundle::simplerNonNeg(monodromy, best)) best = monodromy; start = monodromy; while (1) { if (monodromy.isIdentity()) { if (allNegative) monodromy.negate(); return; } if (monodromy[0][0] == 0 && monodromy[0][1] == 1 && monodromy[1][0] == 1 && monodromy[1][1] == 0) { if (allNegative) monodromy.negate(); return; } // We know at this point that one row dominates the other. if (monodromy[0][0] >= monodromy[1][0] && monodromy[0][1] >= monodromy[1][1]) subtractRCUp(); else subtractRCDown(); // Looking at a new matrix. if (monodromy == start) break; if (NTorusBundle::simplerNonNeg(monodromy, best)) best = monodromy; } } monodromy = best; // Don't forget that negative case. if (allNegative) monodromy.negate(); } bool NTorusBundle::simplerNonNeg(const NMatrix2& m1, const NMatrix2& m2) { // Value symmetric matrices above all else. if (m1[0][1] == m1[1][0] && m2[0][1] != m2[1][0]) return true; if (m1[0][1] != m1[1][0] && m2[0][1] == m2[1][0]) return false; // Go for the smallest possible bottom-right element, then so on // working our way up. if (m1[1][1] < m2[1][1]) return true; if (m1[1][1] > m2[1][1]) return false; if (m1[1][0] < m2[1][0]) return true; if (m1[1][0] > m2[1][0]) return false; if (m1[0][1] < m2[0][1]) return true; if (m1[0][1] > m2[0][1]) return false; if (m1[0][0] < m2[0][0]) return true; return false; } } // namespace regina regina-4.95/engine/manifold/ntorusbundle.h000644 000765 000024 00000024314 12234011536 020543 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file manifold/ntorusbundle.h * \brief Deals with torus bundles over the circle. */ #ifndef __NTORUSBUNDLE_H #ifndef __DOXYGEN #define __NTORUSBUNDLE_H #endif #include "regina-core.h" #include "manifold/nmanifold.h" #include "maths/nmatrix2.h" namespace regina { /** * \weakgroup manifold * @{ */ /** * Represents a torus bundle over the circle. This is expressed as the * product of the torus and the interval, with the two torus boundaries * identified according to some specified monodromy. * * The monodromy is described by a 2-by-2 matrix \a M as follows. * Let \a a and \a b be generating curves of the upper torus boundary, * and let \a p and \a q be the corresponding curves on the lower torus * boundary (so that \a a and \a p are parallel and \a b and \a q are * parallel). Then we identify the torus boundaries so that, in * additive terms: * *
 *     [a]       [p]
 *     [ ] = M * [ ]
 *     [b]       [q]
 * 
* * All optional NManifold routines except for construct() are implemented * for this class. * * \testpart * * \todo \feature Implement the == operator for finding conjugate and * inverse matrices. */ class REGINA_API NTorusBundle : public NManifold { private: NMatrix2 monodromy; /**< The monodromy describing how the two torus boundaries are identified. See the class notes for details. */ public: /** * Creates a new trivial torus bundle over the circle. * In other words, this routine creates a torus bundle with the * identity monodromy. */ NTorusBundle(); /** * Creates a new torus bundle over the circle using the given * monodromy. * * \pre The given matrix has determinant +1 or -1. * * @param newMonodromy describes precisely how the upper and lower * torus boundaries are identified. See the class notes for details. */ NTorusBundle(const NMatrix2& newMonodromy); /** * Creates a new torus bundle over the circle using the given * monodromy. The four elements of the monodromy matrix are * passed separately. They combine to give the full monodromy * matrix \a M as follows: * *
         *           [ mon00  mon01 ]
         *     M  =  [              ]
         *           [ mon10  mon11 ]
         * 
* * \pre The monodromy matrix formed from the given parameters * has determinant +1 or -1. * * @param mon00 the (0,0) element of the monodromy matrix. * @param mon01 the (0,1) element of the monodromy matrix. * @param mon10 the (1,0) element of the monodromy matrix. * @param mon11 the (1,1) element of the monodromy matrix. */ NTorusBundle(long mon00, long mon01, long mon10, long mon11); /** * Creates a clone of the given torus bundle. * * @param cloneMe the torus bundle to clone. */ NTorusBundle(const NTorusBundle& cloneMe); /** * Returns the monodromy describing how the upper and lower * torus boundaries are identified. See the class notes for * details. * * @return the monodromy for this torus bundle. */ const NMatrix2& getMonodromy() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Uses change of basis and/or inversion to reduces the monodromy * representation to something more aesthetically pleasing. */ void reduce(); /** * Rotate the monodromy matrix by 180 degrees (i.e., swap the * main diagonal and also swap the off-diagonal). * * This gives an alternate monodromy matrix for the same 3-manifold; * the transformation merely represents a change of basis. */ void rotate(); /** * Add the first row of the monodromy matrix to the second, * and then subtract the second column from the first. * * This gives an alternate monodromy matrix for the same 3-manifold; * the transformation merely represents a change of basis. */ void addRCDown(); /** * Subtract the first row of the monodromy matrix from the second, * and then add the second column to the first. * * This gives an alternate monodromy matrix for the same 3-manifold; * the transformation merely represents a change of basis. */ void subtractRCDown(); /** * Add the second row of the monodromy matrix to the first, * and then subtract the first column from the second. * * This gives an alternate monodromy matrix for the same 3-manifold; * the transformation merely represents a change of basis. */ void addRCUp(); /** * Subtract the second row of the monodromy matrix from the first, * and then add the first column to the second. * * This gives an alternate monodromy matrix for the same 3-manifold; * the transformation merely represents a change of basis. */ void subtractRCUp(); /** * Determines whether the first given monodromy matrix is more * aesthetically pleasing than the second. The way in which * this judgement is made is purely aesthetic on the part of the * author, and is subject to change in future versions of Regina. * * Note that this routine is not equivalent to the global * simpler(const NMatrix2&, const NMatrix2&). This routine is * tweaked specifically for use with torus bundle monodromies. * * \pre Both matrices consist entirely of non-negative elements. * * @param m1 the first monodromy matrix to examine. * @param m2 the second monodromy matrix to examine. * @return \c true if \a m1 is deemed to be more pleasing than \a m2, * or \c false if either the matrices are equal or \a m2 is more * pleasing than \a m1. */ static bool simplerNonNeg(const NMatrix2& m1, const NMatrix2& m2); }; /*@}*/ // Inline functions for NTorusBundle inline NTorusBundle::NTorusBundle() : monodromy(1, 0, 0, 1) { } inline NTorusBundle::NTorusBundle(const NMatrix2& newMonodromy) : monodromy(newMonodromy) { reduce(); } inline NTorusBundle::NTorusBundle(long mon00, long mon01, long mon10, long mon11) : monodromy(mon00, mon01, mon10, mon11) { reduce(); } inline NTorusBundle::NTorusBundle(const NTorusBundle& cloneMe) : NManifold(), monodromy(cloneMe.monodromy) { } inline const NMatrix2& NTorusBundle::getMonodromy() const { return monodromy; } inline void NTorusBundle::rotate() { long x = monodromy[0][0]; monodromy[0][0] = monodromy[1][1]; monodromy[1][1] = x; x = monodromy[0][1]; monodromy[0][1] = monodromy[1][0]; monodromy[1][0] = x; } inline void NTorusBundle::addRCDown() { monodromy[1][0] += monodromy[0][0]; monodromy[1][1] += monodromy[0][1]; monodromy[0][0] -= monodromy[0][1]; monodromy[1][0] -= monodromy[1][1]; } inline void NTorusBundle::subtractRCDown() { monodromy[1][0] -= monodromy[0][0]; monodromy[1][1] -= monodromy[0][1]; monodromy[0][0] += monodromy[0][1]; monodromy[1][0] += monodromy[1][1]; } inline void NTorusBundle::addRCUp() { monodromy[0][0] += monodromy[1][0]; monodromy[0][1] += monodromy[1][1]; monodromy[0][1] -= monodromy[0][0]; monodromy[1][1] -= monodromy[1][0]; } inline void NTorusBundle::subtractRCUp() { monodromy[0][0] -= monodromy[1][0]; monodromy[0][1] -= monodromy[1][1]; monodromy[0][1] += monodromy[0][0]; monodromy[1][1] += monodromy[1][0]; } } // namespace regina #endif regina-4.95/engine/manifold/order.cpp000644 000765 000024 00000011457 12234011536 017471 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include #include #include namespace regina { bool NManifold::operator < (const NManifold& compare) const { // Lens spaces go first. const NLensSpace* lens1 = dynamic_cast(this); const NLensSpace* lens2 = dynamic_cast(&compare); if (lens1 && ! lens2) return true; if (lens2 && ! lens1) return false; if (lens1 && lens2) { return (lens1->getP() < lens2->getP() || (lens1->getP() == lens2->getP() && lens1->getQ() < lens2->getQ())); } // Next go through Seifert fibred spaces. const NSFSpace* sfs1 = dynamic_cast(this); const NSFSpace* sfs2 = dynamic_cast(&compare); if (sfs1 && ! sfs2) return true; if (sfs2 && ! sfs1) return false; if (sfs1 && sfs2) return (*sfs1 < *sfs2); // Now for torus bundles. const NTorusBundle* bundle1 = dynamic_cast(this); const NTorusBundle* bundle2 = dynamic_cast(&compare); if (bundle1 && ! bundle2) return true; if (bundle2 && ! bundle1) return false; if (bundle1 && bundle2) { // TODO: Just sort by name here, since bundle parameters will // probably need to be made canonical anyway. return getName() < compare.getName(); } // Finally graph manifolds (SFS pairs, triples and loops). const NGraphPair* pair1 = dynamic_cast(this); const NGraphPair* pair2 = dynamic_cast(&compare); if (pair1 && ! pair2) return true; if (pair2 && ! pair1) return false; if (pair1 && pair2) return (*pair1 < *pair2); const NGraphTriple* triple1 = dynamic_cast(this); const NGraphTriple* triple2 = dynamic_cast(&compare); if (triple1 && ! triple2) return true; if (triple2 && ! triple1) return false; if (triple1 && triple2) return (*triple1 < *triple2); const NGraphLoop* loop1 = dynamic_cast(this); const NGraphLoop* loop2 = dynamic_cast(&compare); if (loop1 && ! loop2) return true; if (loop2 && ! loop1) return false; if (loop1 && loop2) return (*loop1 < *loop2); // No idea. Use the dictionary. return getName() < compare.getName(); } } // namespace regina regina-4.95/engine/manifold/sfsnotation.eps000644 000765 000024 00000013304 12234011536 020723 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: sfsnotation.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha7 %%CreationDate: Sat Feb 18 18:49:54 2006 %%BoundingBox: 0 0 172 128 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 128 moveto 0 0 lineto 172 0 lineto 172 128 lineto closepath clip newpath -169.3 241.9 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /DrawEllipse { /endangle exch def /startangle exch def /yrad exch def /xrad exch def /y exch def /x exch def /savematrix mtrx currentmatrix def x y tr xrad yrad sc 0 0 1 startangle endangle arc closepath savematrix setmatrix } def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 65 % Polyline 0 slj 0 slc 0.000 slw n 2700 2250 m 5400 2250 l 5400 3600 l 2700 3600 l cp gs col7 0.65 shd ef gr % here ends figure; % % here starts figure with depth 60 % Ellipse 0.000 slw n 4725 2925 318 318 0 360 DrawEllipse gs col7 0.85 shd ef gr % Ellipse n 3375 2925 318 318 0 360 DrawEllipse gs col7 0.85 shd ef gr % here ends figure; % % here starts figure with depth 55 % Arc 7.500 slw 0 slc n 3375.0 2925.0 318.2 -135.0000 45.0000 arcn gs col0 s gr % Arc n 4725.0 2925.0 318.2 -135.0000 45.0000 arcn gs col0 s gr % Polyline 0 slj n 3825 2250 m 5400 2250 l gs col0 s gr % Polyline n 4275 3600 m 2700 3600 l gs col0 s gr % here ends figure; % % here starts figure with depth 50 % Arc 7.500 slw 0 slc gs clippath 4668 2562 m 4466 2666 l 4507 2746 l 4709 2643 l 4709 2643 l 4529 2685 l 4668 2562 l cp eoclip n 4725.0 2925.0 318.2 45.0000 -135.0000 arcn gs col0 s gr gr % arrowhead 0 slj n 4668 2562 m 4529 2685 l 4709 2643 l 4668 2562 l cp gs col7 1.00 shd ef gr col0 s % Arc gs clippath 3318 2562 m 3116 2666 l 3157 2746 l 3359 2643 l 3359 2643 l 3179 2685 l 3318 2562 l cp eoclip n 3375.0 2925.0 318.2 45.0000 -135.0000 arcn gs col0 s gr gr % arrowhead n 3318 2562 m 3179 2685 l 3359 2643 l 3318 2562 l cp gs col7 1.00 shd ef gr col0 s % Polyline gs clippath 3838 2295 m 4065 2295 l 4065 2205 l 3838 2205 l 3838 2205 l 4018 2250 l 3838 2295 l cp eoclip n 2700 2250 m 4050 2250 l gs col0 s gr gr % arrowhead n 3838 2295 m 4018 2250 l 3838 2205 l 3838 2295 l cp gs col7 1.00 shd ef gr col0 s % Polyline gs clippath 4262 3555 m 4035 3555 l 4035 3645 l 4262 3645 l 4262 3645 l 4082 3600 l 4262 3555 l cp eoclip n 5400 3600 m 4050 3600 l gs col0 s gr gr % arrowhead n 4262 3555 m 4082 3600 l 4262 3645 l 4262 3555 l cp gs col7 1.00 shd ef gr col0 s % here ends figure; % % here starts figure with depth 40 % Polyline 0 slj 0 slc 7.500 slw n 5085 2610 m 5175 2700 l gs col0 s gr % Polyline n 5085 2700 m 5175 2610 l gs col0 s gr % Polyline [60] 0 sd gs clippath 5270 2152 m 5311 1928 l 5223 1911 l 5181 2135 l 5181 2135 l 5259 1967 l 5270 2152 l cp eoclip n 5130 2655 m 5265 1935 l gs col0 s gr gr [] 0 sd % arrowhead n 5270 2152 m 5259 1967 l 5181 2135 l 5270 2152 l cp gs col7 1.00 shd ef gr col0 s /Times-Italic ff 190.50 scf sf 3060 2655 m gs 1 -1 sc (e1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4365 2655 m gs 1 -1 sc (e2) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4050 3825 m gs 1 -1 sc (o2) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4050 2160 m gs 1 -1 sc (o1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 5130 2025 m gs 1 -1 sc (f) dup sw pop 2 div neg 0 rm col0 sh gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/manifold/sfsnotation.fig000644 000765 000024 00000002707 12234011536 020706 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 5 1 0 1 0 7 55 -1 -1 0.000 0 1 0 0 3375.000 2925.000 3150 2700 3150 3150 3600 3150 5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 4725.000 2925.000 4950 3150 4950 2700 4500 2700 1 0 1.00 90.00 180.00 5 1 0 1 0 7 55 -1 -1 0.000 0 1 0 0 4725.000 2925.000 4500 2700 4500 3150 4950 3150 5 1 0 1 0 7 50 -1 -1 0.000 0 1 1 0 3375.000 2925.000 3600 3150 3600 2700 3150 2700 1 0 1.00 90.00 180.00 1 3 0 0 0 7 60 -1 17 0.000 1 0.0000 4725 2925 318 318 4725 2925 4950 3150 1 3 0 0 0 7 60 -1 17 0.000 1 0.0000 3375 2925 318 318 3375 2925 3600 3150 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 1 0 1.00 90.00 180.00 2700 2250 4050 2250 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 1 0 1.00 90.00 180.00 5400 3600 4050 3600 2 1 0 1 0 7 55 -1 -1 0.000 0 0 -1 0 0 2 3825 2250 5400 2250 2 1 0 1 0 7 55 -1 -1 0.000 0 0 -1 0 0 2 4275 3600 2700 3600 2 2 0 0 0 7 65 -1 13 0.000 0 0 -1 0 0 5 2700 2250 5400 2250 5400 3600 2700 3600 2700 2250 2 1 0 1 0 7 40 -1 -1 0.000 0 0 -1 0 0 2 5085 2610 5175 2700 2 1 0 1 0 7 40 -1 -1 0.000 0 0 -1 0 0 2 5085 2700 5175 2610 2 1 1 1 0 7 40 -1 -1 4.000 0 0 -1 1 0 2 1 0 1.00 90.00 180.00 5130 2655 5265 1935 4 1 0 40 -1 3 12 0.0000 0 135 195 3060 2655 e1\001 4 1 0 40 -1 3 12 0.0000 0 135 195 4365 2655 e2\001 4 1 0 40 -1 3 12 0.0000 0 135 210 4050 3825 o2\001 4 1 0 40 -1 3 12 0.0000 0 135 210 4050 2160 o1\001 4 1 0 40 -1 3 12 0.0000 0 195 60 5130 2025 f\001 regina-4.95/engine/manifold/sfsnotation.png000644 000765 000024 00000005251 12234011536 020722 0ustar00babstaff000000 000000 ‰PNG  IHDRï² ÇL pHYsaa¨?§itIMEÖ2 [2ûÚ HIDATxÚílçÇ¿†lh5$s4b‡RJ­0,ÅÐÒff )Qm * $jÅ‚¦E A)… 2Òj‹Zmq–­ÛJƨBKªœ `ˆ+”^ ã‡0 a±xnh¬Ùí8Áع»÷½»à»ç/˾ó÷>÷üxŸ÷½óÙ$@W6¯Ákð¼¯Á«ž•™6Ç}ß”˜ãïf_{|²¤„Ä Õv¡YGñÜbq PG¼ŒCOõÊmjaLÌPl'~½ =¾«rÈÕp%|ËPþúð2÷Ý[[šøõ*ä»ïÞvWâóF¤¯{ó¨|ÊðÚýbJí¾ÑÂëw›ÜJœCŸ{h0ª-„=êcACCƒ_ê@åÐk{¯ ¸øTËùBË0=.™uE4Z…–Ñ4?b…Ëö˜¡Aþ¶Ì7e2À*Œë°phœ¿•öf¡Ý.Bº”Ur]Íó·Å}Ñ—@‹Ý¡lºØí£`>X»Ë¸ UçÁVÒ§)¯¯Õ–~Ÿ²îõùü~p„4­ÏÆÅl®t!ä†KQ!{3ÓrbÚóö¶õªÙG³ ´ˆh ”ªûÛw•>PM }=ti³EëuC-zLC“Øþ Y4ÜŒF¼±ýª*þ]ê`,—k4x\Þv„ߨ`V‹w`µA{f“°QqÀ…žû„ñ~bꟗª \[^Ôg%!YqÙ©—#pO7>æ .79–- ÙÊ Ÿ³žüì±i3¶ª»\F4Û7ÕêŸÃ¬›UÍU>Ž+Uáퟫ:+pEñ¾bƒ–æûÚà"W+ÞQd¯ÁûˆZ«¾xyV_¼œM_¼¬Îxƒ©úâMÕ™‹Œñ×à5xÙáWg¼MúâåRõÅ$ãå9ÿfËç zÖxT=ØÎê5;y%f âxSsÕ]b¹*wjÙwT™ â¹3f©*Ú šÍEq#Mñ—µE-þÝ?)ˆ› qüÃÿšü~Ú‡®?³­¬m6je“;XsˆUä:Ì "ZcSùwt¾c‰‚þõì-ppÁÙ¸†÷ «¹d?¨.öÚ÷óšÒ-ÇŒg®µÊÖZ óÁ©¤¯í/سYp,KyTä=å1Ÿ5ü¾–üqªÕ2â¹ÕÖŠÜTìÕª£ó°@õwÌIþƒ«S¦;hUïAE¾¾(:};αØ2ïèœä[ ®Mɘ9LBuò¹€œgƒDÞN÷€h+ˆx÷À½Ã°¿¸È9ðNÛ±=¶™9TÆÝ°bS‘-ªAêøÛ X '¬ «z÷gΜ§ªå4yrYW_.5žÍ:Þy‰øóðM/[>¿•·ããUw¶¦ÀVl*°E­¦îû÷‘°jÕŸUk>^k[GœoÙÀy¸ÿÔ—Ç:“0ìÈÒêÉAŽÝ   \=ø÷WlÈaæ|Wr]ÊýûvwÎ&ŽgOrl{[ *â$7Ø ð3'™i[­ªG—-ˆ37ºr³".nnÚCxDTÓý¿ª·J†9ç·Îz.:} ŠIoˈQ€{»×VŒï¤oÎÓ9±ªþ•1h\ͲUÎm};ßÁ©®^gšÓ÷soõÆéôǨq?Üä`NúÍZ_•­Î‹r1W_î±å’òŽ­;›ôÌìY S†ß$PþâbÚ¸wË¿6‚jß:G^® Mq%ðŽ}â¯Vçˆð–ù5ª´c§ïËQÕ·Š£pX<?µ]u?M`îsqªìÚˆ©ú@>ÇkxsÓDÎ÷ïîC܆;øcy÷ü@Üv%ö ½¶Ù"fr×7ç[EâOŽ\¦†{ðy±“’×OQœ⊿Dì1¦Ô¿Gkn|³»Bô¶¿hãèñÞÛS#þ(­õ¿£T«¶‹ß8eÇûôx/³J8ÎìgèÌœR¦ØÙË:hñ^:_"é@Ë~C÷Æé iªy:¼üj¤©õå ¼îíÒ¶OÉ?D‡—É·J<Ô’#<1îþÅRŒ*ÎðTx»K¤kÊbò f+$ï²¼ƒï—2ZâüsÄc‘Œ™GÞY¼×­Ò•³/’ò§IßÇz—§À{3KÆá>Cº’wÉ)c§ü. ¼çå,4;Iú‚œ³¼ð,9ïY¿çÈ»D†ûõ;)2öʾAÎÛ“%뀯“ñ†¬²vK!çåä)§“ñ¾%k·1AbÞòü;–l’tÁ)k·9ä¼—w],›Œ÷\”±‘ë´0Yå °²Ä¼”R‰yoËSî!»†&óW«Wmļ2•¯˜‰xefKÎ;^žð,²À4ËêGûFGä}L–rOïä[²TŸ"çtUVá˜KØOdñN&çµvËRžJÆ›%kÕ>0œ7ç¸,eÂqÎÖ#ë,?Aa>8CF³O’ޤÎ69U®ŸïÊW'’ò>+# ½úI|û¨tåcÄþµyeð&Óà5g|$Y˜#¿ùnAä$:ú ^¼ú¡Ôa¿n-y+üò§}÷x{¨ðš³¥ 7.žDÎk^.QµîY+^|ïCI§ÚûÅP°%ŸHê9Xo(ñš—ì–Íïl 3¹[ý3IÑ\ÖO‹ËïH(Y‹ÌtxŒ¯“ÍârH\´ò¢‡o÷P²×|¢O3{¼yÍÕ"“©o÷zËo´Šíí¶÷Óä…ùÍ¢€û¶.šD×¼¢\pߦl±«ÆbûzÛëÅ"BºmåÓ/¢ÙÞÜ%"¤½ëR_ûâï' þrÍêNóÛÿýþ7@×ø_ÍáBp_c÷±1%ú~3©'ª:·½úÜzÚ¸0ÿ8ðpUïú1›$¤”ûcÑ|ªlõðÎýj½2ÏÂ;õé %)Ã:÷‹ †?)þ°j[÷жá2×±N¡Gÿ-¨4\æŽ)—6ÚKò/€`Û¥åyQÅ0ÐßôºÊÿÙ‰ü¼¨9W_÷ò?‹¥Òî÷kw}y!ß™•2ˆÚ˜š™ž®ôù“ÿ¸¸hnø6è¾ïÕ€Y†ª^è¼Æ™®§×Òg<55¹êXç5ÿ˜ëéÀí 3Ÿ”§š›&ïyná_ßðfP —Š*Ñ:¢Z‘ªñ< ƒ×à5x ^ƒ×à5x ^ƒ×à5x ^ƒ×à5x ^ƒ×à5x ^ƒ7¡yyV™¤ÑÁìžxy§.xy6Ø=ѹÁ…ß¿C¬êYöjÃzïô¤Û©ß‰`½»u¶â¢™iI(×ȹŸwܸûUèþ?|Ž+§¸æLÿ/•a|!—Ãe†þ”QaÓþÿa™õÂ;ÈìïÕ/0.}ñý³Ákð>:¼-óMKCúáe|í½ö2Õe5«ÏŒ =.è†B™½:ˆç–ù¦Ì§û×–ª¯.¨m•öf¡Ý.‚ÐåR]\P·ÙÒ–í-íUŸWõx®ÝeÜ…@hU©‰Ï¡×Þ.…]‚p1ñýkaÀ,­t¡¬Ò“øþm¶ÃÑ,•€vÕý«·ùàÿ1ª]éþ}DzIEND®B`‚regina-4.95/engine/maths/approx.cpp000644 000765 000024 00000004545 12234011536 017212 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/approx.h" namespace regina { const double epsilon = 0.00000001; } // namespace regina regina-4.95/engine/maths/approx.h000644 000765 000024 00000012551 12234011536 016653 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/approx.h * \brief Provides facilities for working around rounding errors when * dealing with real numbers. */ #ifndef __APPROX_H #ifndef __DOXYGEN #define __APPROX_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * A very small positive real designed to accommodate for rounding error. * Any two numbers within \a epsilon of each other are considered to be * equal by the generic zero-testing and sign-testing routines defined in * this file (isZero(), isPositive(), isNonNegative() and so on). */ REGINA_API extern const double epsilon; /** * Determines whether the given real number is zero. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is approximately zero. */ template inline bool isZero(R x) { return (x > -epsilon && x < epsilon); } /** * Determines whether the given real number is non-zero. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is approximately non-zero. */ template inline bool isNonZero(R x) { return (x < -epsilon || x > epsilon); } /** * Determines whether the given real number is strictly positive. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is strictly positive. */ template inline bool isPositive(R x) { return (x > epsilon); } /** * Determines whether the given real number is strictly negative. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is strictly negative. */ template inline bool isNegative(R x) { return (x < -epsilon); } /** * Determines whether the given real number is non-negative. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is non-negative. */ template inline bool isNonNegative(R x) { return (x > -epsilon); } /** * Determines whether the given real number is non-positive. * Any number within \a regina::epsilon of zero is considered to be zero. * * \pre R must be of a floating point real type. * * \ifacespython Not present. * * @param x the number to examine. * @return \c true if and only if the given number is non-positive. */ template inline bool isNonPositive(R x) { return (x < epsilon); } /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/CMakeLists.txt000644 000765 000024 00000001306 12236713375 017741 0ustar00babstaff000000 000000 # maths # Files to compile SET ( FILES approx matrixops ninteger nmatrix2 nperm3 nperm4 nperm5 nprimes nrational nray numbertheory seedprimes ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} maths/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL( FILES approx.h matrixops.h ninteger.h nlargeinteger.h nmatrix.h nmatrix2.h nmatrixint.h nperm3.h nperm4.h nperm5.h nprimes.h nrational.h nray.h numbertheory.h nvector.h permconv.h DESTINATION ${INCLUDEDIR}/maths COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/maths/matrixops.cpp000644 000765 000024 00000145034 12234011536 017726 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/matrixops.h" #include "maths/numbertheory.h" namespace regina { void smithNormalForm(NMatrixInt& matrix) { unsigned long currStage = 0; unsigned long nonEmptyRows = matrix.rows(); unsigned long nonEmptyCols = matrix.columns(); bool flag; unsigned long i, j, k; NLargeInteger d, u, v, a, b; NLargeInteger tmp; while ((currStage < nonEmptyRows) && (currStage < nonEmptyCols)) { loopStart: // Have we got an empty row? flag = true; for (i=currStage; i i. // Find the first non-zero entry in row doneRows. for (c = doneRows; c < n; ++c) if (echelon.entry(doneRows, lead[c]) != NMatrixInt::zero) break; if (c == n) { // We have a zero row. Push it to the bottom. --rank; if (doneRows < rank) { echelon.swapRows(doneRows, rank); matrix.swapRows(doneRows, rank); } } else { // We have a non-zero row. // Save the column in which we found our non-zero entry. tmp = lead[doneRows]; lead[doneRows] = lead[c]; lead[c] = tmp; // Make all lower entries in column lead[doneRows] equal to zero. // Do this with only integer arithmetic. This could lead to // some very large matrix entries, though we're using NLargeInteger // so the worst that can happen is that things get slow. coeff1 = echelon.entry(doneRows, lead[doneRows]); for (r = doneRows + 1; r < rank; ++r) { coeff2 = echelon.entry(r, lead[doneRows]); if (coeff2 != NMatrixInt::zero) { echelon.multRow(r, coeff1); echelon.addRow(doneRows, r, -coeff2); // Factor out the gcd of this row. echelon.reduceRow(r); } } ++doneRows; } } // All done! delete[] lead; return rank; } unsigned rowBasisAndOrthComp(NMatrixInt& input, NMatrixInt& complement) { unsigned n = input.columns(); // Make a copy of the input matrix, and reduce it to row echelon form. NMatrixInt echelon(input); unsigned doneRows = 0; unsigned rank = echelon.rows(); unsigned* lead = new unsigned[n]; unsigned r, c, tmp; for (c = 0; c < n; ++c) lead[c] = c; NLargeInteger coeff1, coeff2; while (doneRows < rank) { // INV: For i < doneRows, echelon[i, lead[i]] is non-zero, and // every other entry echelon[j, lead[i]] is zero for j > i. // Find the first non-zero entry in row doneRows. for (c = doneRows; c < n; ++c) if (echelon.entry(doneRows, lead[c]) != NMatrixInt::zero) break; if (c == n) { // We have a zero row. Push it to the bottom. --rank; if (doneRows < rank) { echelon.swapRows(doneRows, rank); input.swapRows(doneRows, rank); } } else { // We have a non-zero row. // Save the column in which we found our non-zero entry. tmp = lead[doneRows]; lead[doneRows] = lead[c]; lead[c] = tmp; // Make all lower entries in column lead[doneRows] equal to zero. // Do this with only integer arithmetic. This could lead to // some very large matrix entries, though we're using NLargeInteger // so the worst that can happen is that things get slow. coeff1 = echelon.entry(doneRows, lead[doneRows]); for (r = doneRows + 1; r < rank; ++r) { coeff2 = echelon.entry(r, lead[doneRows]); if (coeff2 != NMatrixInt::zero) { echelon.multRow(r, coeff1); echelon.addRow(doneRows, r, -coeff2); // Factor out the gcd of this row. echelon.reduceRow(r); } } ++doneRows; } } // Now form the basis for the orthogonal complement. complement.initialise(NMatrixInt::zero); NLargeInteger lcmLead = 1; for (r = 0; r < n; ++r) { complement.entry(r, lead[r]) = lcmLead; complement.entry(r, lead[r]).negate(); for (c = 0; c < r && c < rank; ++c) { complement.entry(r, lead[c]) = echelon.entry(c, lead[r]) * lcmLead; complement.entry(r, lead[c]).divByExact(echelon.entry(c, lead[c])); } complement.reduceRow(r); if (r < rank) { coeff1 = echelon.entry(r, lead[r]); lcmLead = lcmLead.lcm(coeff1); for (tmp = 0; tmp < r; ++tmp) { coeff2 = echelon.entry(tmp, lead[r]); if (coeff2 != NMatrixInt::zero) { echelon.multRow(tmp, coeff1); echelon.addRow(r, tmp, -coeff2); // Factor out the gcd of this row. echelon.reduceRow(tmp); } // TODO: Is this actually necessary? lcmLead = lcmLead.lcm(echelon.entry(tmp, lead[tmp])); } } } // All done! delete[] lead; return rank; } void columnEchelonForm(NMatrixInt &M, NMatrixInt &R, NMatrixInt &Ri, const std::vector &rowList) { unsigned i,j; unsigned CR=0; unsigned CC=0; // these are the indices of the current WORKING rows and columns // respectively. // thus the entries of M above CR will not change, and to the left of // CC all that can happen is some reduction. std::vector rowNZlist; // in the current row, this is the // list of column coordinates // for the non-zero entries. NLargeInteger d,r; // given two NLargeIntegers a and b, we will represent // a/b by d and a % b by r in the algorithm. NLargeInteger u,v,gcd, a,b; // for column operations u,v,a,b represent // a 2x2 matrix. NLargeInteger tmp; // the algorithm will think of itself as working top to bottom. while ( (CR1) { // do column reduction on columns rowNZlist[0] and rowNZlist[1] // first we need to find the approp modification matrix. // This will be the matrix ( u -b ) where ua+vb = 1. We get // ( v a ) // a and b from entry(CR, r[0]) and entry(CR, r[1]) // by dividing by their GCD, found with // rowNZlist[0].gcdWithCoeffs(rowNZlist[1],u,v) gcd = M.entry(rowList[CR], rowNZlist[0]).gcdWithCoeffs( M.entry(rowList[CR], rowNZlist[1]), u,v); a = M.entry(rowList[CR], rowNZlist[0]).divExact(gcd); b = M.entry(rowList[CR], rowNZlist[1]).divExact(gcd); // so multiplication on the right by the above matrix // corresponds to replacing column r[0] by u r[0] + v r[1] // and column r[1] by -b r[0] + a r[1]. for (i=0;i preImageOfLattice(const NMatrixInt& hom, const std::vector& L) { // there are two main steps to this algorithm. // 1) find a basis for the domain which splits into a) vectors sent to the // complement of the primitive subspace generated by the range lattice // and b) a basis of vectors sent to the primitive subspace generated // by the range lattice. // 2) modify the basis (b) by column ops to get the preimage of the lattice. // step (1) is an application of the columnEchelonForm // step (2) starts with another application of columnEchelonForm, but then // it finishes with a variation on it... unsigned i,j; NMatrixInt basis(hom.columns(), hom.columns() ); basis.makeIdentity(); NMatrixInt basisi(hom.columns(), hom.columns() ); basisi.makeIdentity(); // and we proceed to modify it solely via column operations. // one for every column operation performed on homModL NMatrixInt homModL(hom); // set up two lists: the coordinates that correspond to free generators // of the range and coordinates corresponding to torsion generators. // these lists need to be built from L std::vector freeList; std::vector torList; for (i=0;i torCol(0); bool zeroCol; for (i=0; i tBasis( new NMatrixInt( basis.rows(), torCol.size() )); // this will be the // eventual retval. NMatrixInt dummy( torCol.size(), 0 ); // needed when we call // columnEchelonForm. choosing it to have 0 columns speeds up // the algorithm. for (i=0;ientry(i,j) = basis.entry(i, torCol[j]); columnEchelonForm( tHom, *tBasis, dummy, torList ); // so now we have a primitive collection of vectors being sent to the // primitive subspace generated by the torsion lattice in the target. // The idea is to run through the rows, for each non-zero row, through // a basis change we can ensure there is at most one non-zero entry. // multiply this column by the smallest factor so that it is in // the torsion lattice, repeat. etc. unsigned CR=0; // current row under consideration. The actual row index // will of course be torList[CR] since all other rows are already zero. std::vector rowNZlist; // in the current row, this is the list // of column coordinates for the non-zero entries. NLargeInteger d,r; // given two NLargeIntegers a and b, we will represent // a/b by d and a % b by r in the algorithm. NLargeInteger u,v,gcd, a,b; // for column operations u,v,a,b represent // a 2x2 matrix. NLargeInteger tmp; while (CRrows();i++) tBasis->entry( i, rowNZlist[0] ) *= d; // done. CR++; continue; } // case 3: rowNZlist.size()>1.row ops to reduce rowNZlist.size(). // then continue while (rowNZlist.size()>1) { // do column op on columns rowNZlist[0] and rowNZlist[1] // first we need to find the approp modification matrix. This will // be the matrix ( u -b ) where ua+vb = 1. We get a and b from // ( v a ) from entry(torList[CR], r[0]) and // entry(torlist[CR], r[1]) by dividing by their GCD, found with // rowNZlist[0].gcdWithCoeffs(rowNZlist[1],u,v) gcd = tHom.entry(torList[CR], rowNZlist[0]).gcdWithCoeffs( tHom.entry(torList[CR], rowNZlist[1]), u,v); a = tHom.entry(torList[CR], rowNZlist[0]).divExact(gcd); b = tHom.entry(torList[CR], rowNZlist[1]).divExact(gcd); // so multiplication on the right by the above matrix corresponds // to replacing column r[0] by u r[0] + v r[1] and column r[1] by // -b r[0] + a r[1]. for (i=0;irows();i++) { tmp = u * tBasis->entry( i, rowNZlist[0] ) + v * tBasis->entry(i, rowNZlist[1] ); tBasis->entry(i,rowNZlist[1]) = a * tBasis->entry( i, rowNZlist[1]) - b * tBasis->entry( i, rowNZlist[0]); tBasis->entry(i,rowNZlist[0]) = tmp; } // now rowNZlist[1] entry is zero, remove it from the list. rowNZlist.erase( rowNZlist.begin()+1 ); } } return tBasis; } // Lemma 1: [a b | c d] representing an element of End(Z_n x Z_{mn}) is in // Aut(Z_n x Z_{mn}) if and only if dA-bc is a unit of Z_{mn} for some lift // A \in Z_{mn} of a \in Z_n // // You can get an explicit formula for the inverse, basically it boils down // to a comparison with the Z_n^2 case, and the observation that // Z_{mn} --> Z_n is surjective on units. // The algorithm: // // Step 1: reduce all entries mod p_i where i is the row index. // Step 2: Consider the bottom row of A. Consider a group of columns for which // they all share the same p_i. standard Gaussian elimination works to // put zeros in all but one entry of this row. Potential problem here // the 1x1 case where the entry is a unit mod p_1 // Step 3: Now we are in the situation where in this row, any two non-zero // entries have distinct p_i's, where now i is the column index. // Let them be in columns i and n respectively. // Let l_1a_{ni} + l_2a_{nn} = gcd(a_{ni},a_{nn})=g, // consider matrix [ v_n l_1 | -v_i l_2 ] where // v_n = a_{nn}/g and v_i=a_{ni}/g. This is a valid // column operation by Lemma 1 and some congruence munching. // Apply, this reduces this bottom row to the point where it has only // one non-zero entry and it is a unit mod the relevant p_i, // so we can multiply by its inverse // Step 4: repeat inductively to square submatrix above and to the left of the // nn entry. This results in an upper diagonal matrix. // Reapply step 1 gives all 1's down diagonal. // Step 5: row ops to convert to identity. // *Step 6: keep track of all the corresponding matrices, put together to // assemble inverse. Notice it's all standard Gaussian elimination, // just done in a funny order and with some modular arithmatic // stuffed in there. std::auto_ptr torsionAutInverse(const NMatrixInt& input, const std::vector &invF) { // inductive step begins right away. Start at bottom row. NMatrixInt workMat( input ); NMatrixInt colOps( input.rows(), input.columns() ); colOps.makeIdentity(); unsigned long wRow = input.rows(); while (wRow > 0) { wRow--; // step 1 modular reduction on the current row. And find last non-zero // entry in this row up to wRow column NLargeInteger R; // divisionAlg needs a remainder so we give it one, // although we discard it. unsigned long pivCol=0; for (unsigned long i=0; i<=wRow; i++) { workMat.entry(wRow, i).divisionAlg(invF[wRow], R); workMat.entry(wRow, i) = R; if (R!=0) pivCol=i; } // now pivCol is the last non-zero entry in the 0..wRow square smatrix // Step 2: transpose pivCol and column wRow if (wRow != pivCol) for (unsigned long i=0; i 0) { wCol--; NLargeInteger g, l1, l2; g = workMat.entry( wRow, wCol ).gcdWithCoeffs( workMat.entry(wRow, pivCol), l1, l2 ); NLargeInteger u1, u2; u1 = workMat.entry(wRow, wCol).divExact(g); u2 = workMat.entry(wRow, pivCol).divExact(g); // u1 l1 + u2 l2 = 1 // [ u2 l1 | -u1 l2 ] is column op matrix for wCol and pivCol for (unsigned long i=0; i u2 wCol - u1 pivCol, pivCol -> l1 wCol + l2 pivCol NLargeInteger W(workMat.entry(i, wCol)), P(workMat.entry(i, pivCol)); workMat.entry(i, wCol) = u2*W - u1*P; workMat.entry(i, pivCol) = l1*W + l2*P; W = colOps.entry(i, wCol); P = colOps.entry(i, pivCol); colOps.entry(i, wCol) = u2*W - u1*P; colOps.entry(i, pivCol) = l1*W + l2*P; } } // now workMat.entry(wRow, pivCol) is a unit mod invF[pivCol], // so find its inverse NLargeInteger g, a1, a2; g= workMat.entry( wRow, pivCol ).gcdWithCoeffs( invF[pivCol], a1, a2 ); // a1 represents this multiplicative inverse so multiply column by it. for (unsigned long i=0; i identity. Use row i to kill i-th entry of row j. for (unsigned long i=1; ientry(i,j) += colOps.entry(i,k)*rowOps.entry(k,j); retval->entry(i,j) %= invF[i]; if (retval->entry(i,j) < 0) retval->entry(i,j) += invF[i]; } // done return std::auto_ptr(retval); } bool metricFindPivot(const unsigned long &currStage, const NMatrixInt &matrix, unsigned long &pr, unsigned long &pc, const std::vector &rowNorm, const std::vector &colNorm, const std::vector &rowGCD) { bool pivotFound = false; // find the smallest positive rowGCD NLargeInteger SProwGCD(NLargeInteger::zero); for (unsigned long i=currStage; i rowGCD[i].abs()) SProwGCD = rowGCD[i].abs(); } for (unsigned long i=currStage; i 1 we use the rowNorm comparison. else // if rows the same? use colNorm... { if (i == pr) { if ( colNorm[j] < colNorm[pc] ) { pr = i; pc =j; } } else { if ( rowNorm[i] < rowNorm[pr] ) { pr = i; pc = j; } } } } } } return pivotFound; } // switch rows i and j in matrix. Keep track of change-of-basis void metricSwitchRows(const unsigned long &currStage, const unsigned long &i, const unsigned long &j, NMatrixInt &matrix, NMatrixInt *colBasis, NMatrixInt *colBasisInv, std::vector &rowNorm, std::vector &rowGCD) { rowNorm[i].swap(rowNorm[j]); rowGCD[i].swap(rowGCD[j]); if (colBasis) colBasis->swapRows(i, j); if (colBasisInv) colBasisInv->swapColumns(i, j); for (unsigned long k=currStage; k &colNorm) { colNorm[i].swap(colNorm[j]); if (rowBasis) rowBasis->swapColumns(i, j); if (rowBasisInv) rowBasisInv->swapRows(i, j); for (unsigned long k=currStage; k &rowNorm, std::vector &colNorm) { NLargeInteger t1, t2; // smart rowMetric recomputation and transformation colNorm[i] = NLargeInteger::zero; colNorm[j] = NLargeInteger::zero; for (unsigned long k=currStage; kentry(k, i) + c*rowBasis->entry(k, j); t2 = b*rowBasis->entry(k, i) + d*rowBasis->entry(k, j); rowBasis->entry(k, i) = t1; rowBasis->entry(k, j) = t2; } if (rowBasisInv) for (unsigned long k=0; kentry(i, k) - b*rowBasisInv->entry(j, k); t2 = -c*rowBasisInv->entry(i, k) + a*rowBasisInv->entry(j, k); rowBasisInv->entry(i, k) = t1; rowBasisInv->entry(j, k) = t2; } } // row operation using 2x2-matrix [a b|c d] on rows i, j resp. void metricRowOp(const unsigned long &currStage, const unsigned long &i, const unsigned long &j, NMatrixInt &matrix, const NLargeInteger a, const NLargeInteger b, const NLargeInteger c, const NLargeInteger d, NMatrixInt *colBasis, NMatrixInt *colBasisInv, std::vector &rowNorm, std::vector &colNorm, std::vector &rowGCD) { NLargeInteger t1, t2; // smart norm recomputation and transformation rowNorm[i] = NLargeInteger::zero; rowNorm[j] = NLargeInteger::zero; rowGCD[i] = NLargeInteger::zero; rowGCD[j] = NLargeInteger::zero; for (unsigned long k=currStage; kentry(i, k) + b*colBasis->entry(j, k); t2 = c*colBasis->entry(i, k) + d*colBasis->entry(j, k); colBasis->entry(i, k) = t1; colBasis->entry(j, k) = t2; } if (colBasisInv) for (unsigned long k=0; kentry(k, i) - c*colBasisInv->entry(k, j); t2 = -b*colBasisInv->entry(k, i) + a*colBasisInv->entry(k, j); colBasisInv->entry(k, i) = t1; colBasisInv->entry(k, j) = t2; } } /** * This routine converts mxn matrix "matrix" into its Smith Normal Form. * It assumes rowSpaceBasis and rowSpaceBasisInv are pointers to NMatrixInts, * if alloceted, having dimension mxm, and colSpaceBasis and colSpaceBasisInv * has dimensions nxn. These matrices record the row and columns operations * used to convert between "matrix" and its Smith Normal Form. Specifically, * if orig_matrix is "matrix" before metricalSmithNormalForm is called, and * after_matrix is "matrix" after metricalSmithNormalForm is called, then * we have the relations: * * (*colSpaceBasis) * orig_matrix * (*rowSpaceBasis) == after_matrix * * (*colSpaceBasisInv) * after_matrix * (*rowSpaceBasisInv) == orig_matrix * * If any of rowSpaceBasis, colSpaceBasis or rowSpaceBasisInv or * colSpaceBasisInv are not allocated, this algotithm does not bother to compute * them (and is correspondingly faster. * * This routine uses a first-order technique to intelligently choose the * pivot when computing the Smith Normal Form, attempting to keep the matrix * sparse and its norm small throughout the reduction process. The technique * is loosely based on the papers: * * Havas, Holt, Rees. Recognizing badly Presented Z-modules. Linear Algebra * and its Applications. 192:137--163 (1993). * * Markowitz. The elimination form of the inverse and its application to linear * programming. Management Sci. 3:255--269 (1957). */ void metricalSmithNormalForm(NMatrixInt& matrix, NMatrixInt *rowSpaceBasis, NMatrixInt *rowSpaceBasisInv, NMatrixInt *colSpaceBasis, NMatrixInt *colSpaceBasisInv) { if (rowSpaceBasis) rowSpaceBasis->makeIdentity(); if (rowSpaceBasisInv) rowSpaceBasisInv->makeIdentity(); if (colSpaceBasis) colSpaceBasis->makeIdentity(); if (colSpaceBasisInv) colSpaceBasisInv->makeIdentity(); // set up metrics. std::vector rowNorm(matrix.rows(), NLargeInteger::zero); std::vector colNorm(matrix.columns(), NLargeInteger::zero); std::vector rowGCD(matrix.rows(), NLargeInteger::zero); for (unsigned long i=0; ientry( i, currStage ).negate(); if (rowSpaceBasisInv) for (i=0; ientry( currStage, i ).negate(); } // run through rows currStage+1 to bottom, check if divisible by // matrix.entry(cs,cs). if not, record row and gcd( matrix.entry(cs,cs), // rowGCD[this row], pick the row with the lowest of these gcds... unsigned long rowT=currStage; NLargeInteger bestGCD(matrix.entry(currStage, currStage).abs()); for (i=currStage+1; i currStage ) { metricRowOp(currStage, currStage, rowT, matrix, NLargeInteger::one, NLargeInteger::one, NLargeInteger::zero, NLargeInteger::one, colSpaceBasis, colSpaceBasisInv, rowNorm, colNorm, rowGCD); goto rowMuckerLoop; } // done currStage++; } // no pivot found -- matrix down and to the right of currStage is zero. // so we're done. } } // namespace regina regina-4.95/engine/maths/matrixops.h000644 000765 000024 00000037207 12234011536 017375 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __MATRIXOPS_H #ifndef __DOXYGEN #define __MATRIXOPS_H #endif /*! \file maths/matrixops.h * \brief Provides various complex matrix calculations. * \todo \featurelong Add a routine to find the rank of an integer * matrix; use this to show the rank of the matching equations. */ #include "regina-core.h" #include "maths/nmatrixint.h" #include namespace regina { /** * \weakgroup maths * @{ */ /** * Transforms the given integer matrix into Smith normal form. * Note that the given matrix need not be square and need not be of full * rank. * * Reading down the diagonal, the final Smith normal form will have a * series of non-negative, non-decreasing invariant factors followed by * zeroes. "Invariant factor" refers to the convention that the ith * term divides the (i+1)th term, and so they are unique. * * The algorithm used is due to Hafner and McCurley (1991). * It does not use modular arithmetic to control the intermediate * coefficient explosion. * * \testpart * * @param matrix the matrix to transform. */ REGINA_API void smithNormalForm(NMatrixInt& matrix); /** * A Smith normal form algorithm that also returns change of basis matrices. * * This is a modification of the one-argument smithNormalForm(NMatrixInt&). * As well as converting the given matrix \a matrix into Smith normal form, * it also returns the appropriate change-of-basis matrices corresponding * to all the row and column operations that were performed. * * The only input argument is \a matrix. The four remaining arguments * (the change of basis matrices) will be refilled, though they must be * constructed with the correct dimensions as seen in the preconditions * below. All five arguments are used to return information as follows. * * Let \a M be the initial value of \a matrix, and let \a S be the Smith * normal form of \a M. After this routine exits: * * - The argument \a matrix will contain the Smith normal form \a S; * - colSpaceBasis * M * rowSpaceBasis = S; * - colSpaceBasisInv * S * rowSpaceBasisInv = M; * - colSpaceBasis * colSpaceBasisInv and * rowSpaceBasis * rowSpaceBasisInv are both identity matrices. * * Thus, one obtains the Smith normal form the original matrix by multiplying * on the left by ColSpaceBasis and on the right by RowSpaceBasis. * * \pre The matrices \a rowSpaceBasis and \a rowSpaceBasisInv that are * passed are square, with side length matrix.columns(). * \pre The matrices \a colSpaceBasis and \a colSpaceBasisInv that are * passed are square, with side length matrix.rows(). * * \testpart * * @param matrix the original matrix to put into Smith Normal Form (this * need not be square). When the algorithm terminates, this matrix \e is * in its Smith Normal Form. * @param rowSpaceBasis used to return a change of basis matrix (see * above for details). * @param rowSpaceBasisInv used to return the inverse of \a rowSpaceBasis. * @param colSpaceBasis used to return a change of basis matrix (see * above for details). * @param colSpaceBasisInv used to return the inverse of \a colSpaceBasis. * * \author Ryan Budney */ REGINA_API void smithNormalForm(NMatrixInt& matrix, NMatrixInt& rowSpaceBasis, NMatrixInt& rowSpaceBasisInv, NMatrixInt& colSpaceBasis, NMatrixInt& colSpaceBasisInv); /** * An alternative Smith normal form algorithm that also returns change of * basis matrices. This routine may be preferable for extremely large * matrices. This is a variant of Hafner-McCurley and Havas-Holt-Rees's * description of pivoting methods. * * The only input argument is \a matrix. The four remaining arguments * (the change of basis matrices), if passed, will be refilled, though they * must be constructed with the correct dimensions as seen in the preconditions * below. All five arguments are used to return information as follows. * * Let \a M be the initial value of \a matrix, and let \a S be the Smith * normal form of \a M. After this routine exits: * * - The argument \a matrix will contain the Smith normal form \a S; * - colSpaceBasis * M * rowSpaceBasis = S; * - colSpaceBasisInv * S * rowSpaceBasisInv = M; * - colSpaceBasis * colSpaceBasisInv and * rowSpaceBasis * rowSpaceBasisInv are both identity matrices. * * Thus, one obtains the Smith normal form the original matrix by multiplying * on the left by ColSpaceBasis and on the right by RowSpaceBasis. * * \pre The matrices \a rowSpaceBasis and \a rowSpaceBasisInv, if passed, * must be square with side length matrix.columns(). * \pre The matrices \a colSpaceBasis and \a colSpaceBasisInv, if passed, * must be square, with side length matrix.rows(). * * \testpart * * @param matrix the original matrix to put into Smith Normal Form (this * need not be square). When the algorithm terminates, this matrix \e is * in its Smith Normal Form. * @param rowSpaceBasis used to return a change of basis matrix (see * above for details). This is optional; you may pass a null pointer instead. * @param rowSpaceBasisInv used to return the inverse of \a rowSpaceBasis. * This is optional; you may pass a null pointer instead. * @param colSpaceBasis used to return a change of basis matrix (see * above for details). This is optional; you may pass a null pointer instead. * @param colSpaceBasisInv used to return the inverse of \a colSpaceBasis. * This is optional; you may pass a null pointer instead. * * \author Ryan Budney */ REGINA_API void metricalSmithNormalForm(NMatrixInt& matrix, NMatrixInt *rowSpaceBasis=0, NMatrixInt *rowSpaceBasisInv=0, NMatrixInt *colSpaceBasis=0, NMatrixInt *colSpaceBasisInv=0); /** * Find a basis for the row space of the given matrix. * * This routine will rearrange the rows of the given matrix so that the * first \a rank rows form a basis for the row space (where \a rank is * the rank of the matrix). The rank itself will be returned. No other * changes will be made to the matrix aside from swapping rows. * * Although this routine takes an integer matrix (and only uses integer * operations), we consider the row space to be over the \e rationals. * That is, although we never divide, we act as though we could if we * wanted to. * * @param matrix the matrix to examine and rearrange. * @return the rank of the given matrix. */ REGINA_API unsigned rowBasis(NMatrixInt& matrix); /** * Finds a basis for the row space of the given matrix, as well as an * "incremental" basis for its orthogonal complement. * * This routine takes an (\a r by \a c) matrix \a input, as well as a * square (\a c by \a c) matrix \a complement, and does the following: * * - The rows of \a input are rearranged so that the first \a rank rows form * a basis for the row space (where \a rank is the rank of the matrix). * No other changes are made to this matrix aside from swapping rows. * * - The matrix \a complement is re-filled (any previous contents are * thrown away) so that, for any \a i between 0 and \a rank-1 inclusive, * the final (\a c - \a i) rows of \a complement form a basis for the * orthogonal complement of the first \a i rows of the rearranged \a input. * * - The rank of the matrix \a input is returned from this routine. * * This routine can help with larger procedures that need to build up a row * space and simultaneously cut down the complement one dimension at a time. * * Although this routine takes integer matrices (and only uses integer * operations), we consider all bases to be over the \e rationals. * That is, although we never divide, we act as though we could if we * wanted to. * * \pre The matrix \a complement is a square matrix, whose size is equal * to the number of columns in \a input. * * @param input the input matrix whose row space we will describe; this * matrix will be changed (though only by swapping rows). * @param complement the square matrix that will be re-filled with the * "incremental" basis for the orthogonal complement of \a input. * @return the rank of the given matrix \a input. */ REGINA_API unsigned rowBasisAndOrthComp(NMatrixInt& input, NMatrixInt& complement); /** * Transforms a given matrix into column echelon form with respect to a * collection of rows. * * Given the matrix \a M and the list \a rowList of rows from \a M, this * algorithm puts \a M in column echelon form with respect to the rows * in \a rowList. The only purpose of \a rowList is to clarify and/or * weaken precisely what is meant by "column echelon form"; all rows of * \a M are affected by the resulting column operations that take place. * * This routine also returns the corresponding change of coordinate * matrices \a R and \a Ri: * * - If \a R and \a Ri are passed as identity matrices, the returned * matrices will be such that original_M * R = final_M and * final_M * Ri = original_M (and of course final_M is * in column echelon form with respect to the given row list). * - If \a R and \a Ri are already non-trivial coordinate transformations, * they are modified appropriately by the algorithm. * * Our convention is that a matrix is in column echelon form if: * * -# each column is either zero or there is a first non-zero entry which * is positive (but see the note regarding \a rowList below); * -# moving from the leftmost column to the rightmost column, the rows * containing the first non-zero entries for these columns have strictly * increasing indices in \a rowList; * -# given a first non-zero column entry, in that row all the elements to * the left are smaller and non-negative (all elements to the right are * already zero by the previous condition); * -# all the zero columns are on the right hand side of the matrix. * * By a "zero column" here we simply mean "zero for every row in \a * rowList". Likewise, by "first non-zero entry" we mean "first row in * \a rowList with a non-zero entry". * * In a pinch, you can also use this routine to compute the inverse of an * invertible square matrix. * * \pre Both \a R and \a Ri are square matrices with side length M.columns(), * and these matrices are inverses of each other. * * \ifacespython The argument \a rowList should be supplied as a python list. * * @param M the matrix to reduce. * @param R used to return the row-reduction matrix, as described above. * @param Ri used to return the inverse of \a R. * @param rowList the rows to pay attention to. This list must contain * distinct integers, all between 0 and M.rows()-1 inclusive. The * integers may appear in any order (though changing the order will * change the resulting column echelon form). * * \author Ryan Budney */ REGINA_API void columnEchelonForm(NMatrixInt &M, NMatrixInt &R, NMatrixInt &Ri, const std::vector &rowList); /** * Given a homomorphism from Z^n to Z^k and a sublattice of Z^k, * compute the preimage of this sublattice under this homomorphism. * * The homomorphism from Z^n to Z^k is described by the given * \a k by \a n matrix \a hom. The sublattice is of the form * (p1 Z) * (p2 Z) * ... * (pk Z), where the non-negative integers * \a p1, ..., \a pk are passed in the given list \a sublattice. * * An equivalent problem is to consider \a hom to be a homomorphism * from Z^n to Z_p1 + ... + Z_pk; this routine then finds the kernel * of this homomorphism. * * The preimage of the sublattice (equivalently, the kernel described * above) is some rank \a n lattice in Z^n. This algorithm finds and * returns a basis for the lattice. * * \ifacespython The argument \a sublattice should be supplied as a python list. * * @param hom the matrix representing the homomorphism from Z^n to Z^k; * this must be a \a k by \a n matrix. * @param sublattice a list of length \a k describing the sublattice of Z^k; * the elements of this list must be the non-negative integers * \a p1, ..., \a pk as described above. * @return a new matrix whose columns are a basis for the preimage lattice. * This matrix will have precisely \a n rows. * * \author Ryan Budney */ REGINA_API std::auto_ptr preImageOfLattice(const NMatrixInt& hom, const std::vector& sublattice); /** * Given an automorphism of an abelian group, * this procedure computes the inverse automorphism. * * The abelian group is of the form Z_p1 + Z_p2 + ... + Z_pn. * The input is an n-by-n matrix \a A which represents a lift of the * automorphism to just some n-by-n matrix. Specifically, you have a little * commutative diagram with Z^n --A--> Z^n covering the automorphism * of Z_p1 + Z_p2 + ... + Z_pn, where the maps down are the direct * sum of the standard quotients Z --> Z_pi. So if you want this * procedure to give you meaningful output, \a A must be a lift of a genuine * automorphism of Z_p1 + ... + Z_pn. * * \pre The list p1, p2, ..., pn is a list of invariant factors, * which means that p1|p2, ..., p{n-1}|pn. * * \ifacespython The argument \a invF should be supplied as a python list. * * @param input the n-by-n matrix \a A, which must be a lift of a genuine * automorphism as described above. * @param invF the list p1, p2, ..., pn. * @return the inverse automorphism, also described as an n-by-n matrix * as per the discussion above. * * \author Ryan Budney */ REGINA_API std::auto_ptr torsionAutInverse(const NMatrixInt& input, const std::vector &invF); /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/ninteger.cpp000644 000765 000024 00000104365 12237427344 017530 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "maths/ninteger.h" #include "maths/numbertheory.h" #include "utilities/nthread.h" // We instantiate both variants of the NIntegerBase template at the bottom // of this file. /** * Old macros for testing signed integer overflow, given in order from * fastest to slowest (by experimentation). All are based on section * 2-21 from Hacker's Delight by Warren. * * These tests are all abandoned now because the 128-bit cast solution is * significantly faster than any of these. * * Note that a slicker test (such as checking whether answer / y == x) is * not possible, because compiler optimisations are too clever nowadays * and strip out the very tests we are trying to perform * (e.g., whether (x * y) / y == x). * * - Ben, 19/08/2013. * #define LONG_OVERFLOW(x, y) \ (((x) > 0 && ( \ ((y) > 0 && (y) > LONG_MAX / (x)) || \ (y) < 0 && (y) < LONG_MIN / (x))) || \ ((x) < 0 && ( \ ((y) > 0 && (x) < LONG_MIN / (y)) || \ ((y) < 0 && (x) < LONG_MAX / (y))))) #define LONG_OVERFLOW(x, y) \ (((x) > 0 && (y) > 0 && (y) > LONG_MAX / (x)) || \ ((x) > 0 && (y) < 0 && (y) < LONG_MIN / (x)) || \ ((x) < 0 && (y) > 0 && (x) < LONG_MIN / (y)) || \ ((x) < 0 && (y) < 0 && (x) < LONG_MAX / (y))) #define LONG_OVERFLOW(x, y) \ ((y) && labs(x) > (((~((x) ^ (y))) >> (sizeof(long)*8-1)) / labs(y))) */ namespace regina { namespace { /** * Global variables for the GMP random state data. */ NMutex randMutex; gmp_randstate_t randState; bool randInitialised(false); } // Initialize const static data: template const NIntegerBase NIntegerBase::zero; template const NIntegerBase NIntegerBase::one(1); template <> const NIntegerBase NIntegerBase::infinity(false, false); // The use of errno in this file should be threadsafe, since (as I // understand it) each thread gets its own errno. However, there may be // thread safety issues regarding locales when using strtol(), in // particular when another thread changes the locale mid-flight. // I could be wrong about this. template NIntegerBase::NIntegerBase( const char* value, int base, bool* valid) : large_(0) { char* endptr; errno = 0; small_ = strtol(value, &endptr, base); if (errno || *endptr) { // Something went wrong. Try again with large integers. // Note that in the case of overflow, we may have errno != 0 but // *endptr == 0. bool maybeTrailingWhitespace = (*endptr && ! errno); large_ = new mpz_t; if (valid) *valid = (mpz_init_set_str(large_, value, base) == 0); else mpz_init_set_str(large_, value, base); // If the error was just trailing whitespace, we might still fit // into a native long. if (maybeTrailingWhitespace) tryReduce(); } else { // All good. if (valid) *valid = true; } } template NIntegerBase::NIntegerBase( const std::string& value, int base, bool* valid) : large_(0) { char* endptr; errno = 0; small_ = strtol(value.c_str(), &endptr, base); if (errno || *endptr) { // Something went wrong. Try again with large integers. // Note that in the case of overflow, we may have errno != 0 but // *endptr == 0. bool maybeTrailingWhitespace = (*endptr && ! errno); large_ = new mpz_t; if (valid) *valid = (mpz_init_set_str(large_, value.c_str(), base) == 0); else mpz_init_set_str(large_, value.c_str(), base); // If the error was just trailing whitespace, we might still fit // into a native long. if (maybeTrailingWhitespace) tryReduce(); } else { // All good. if (valid) *valid = true; } } template std::string NIntegerBase::stringValue(int base) const { if (isInfinite()) return "inf"; else if (large_) { char* str = mpz_get_str(0, base, large_); std::string ans(str); free(str); return ans; } else { // Hmm. std::setbase() only takes 8, 10 or 16 as i understand it. // For now, be wasteful and always go through GMP. mpz_t tmp; mpz_init_set_si(tmp, small_); char* str = mpz_get_str(0, base, tmp); std::string ans(str); free(str); mpz_clear(tmp); return ans; } } template NIntegerBase& NIntegerBase::operator =( const char* value) { makeFinite(); char* endptr; errno = 0; small_ = strtol(value, &endptr, 10 /* base */); if (errno || *endptr) { // Something went wrong. Try again with large integers. // Note that in the case of overflow, we may have errno != 0 but // *endptr == 0. bool maybeTrailingWhitespace = (*endptr && ! errno); if (large_) mpz_set_str(large_, value, 10 /* base */); else { large_ = new mpz_t; mpz_init_set_str(large_, value, 10 /* base */); } // If the error was just trailing whitespace, we might still fit // into a native long. if (maybeTrailingWhitespace) tryReduce(); } else if (large_) { // All good, but we must clear out the old large integer. clearLarge(); } return *this; } template std::ostream& operator << (std::ostream& out, const NIntegerBase& i) { if (i.isInfinite()) out << "inf"; else if (i.large_) { char* str = mpz_get_str(0, 10, i.large_); out << str; free(str); } else out << i.small_; return out; } template NIntegerBase& NIntegerBase::operator +=(long other) { if (isInfinite()) return *this; if (! large_) { // Use native arithmetic if we can. if ( (small_ > 0 && other > (LONG_MAX - small_)) || (small_ < 0 && other < (LONG_MIN - small_))) { // Boom. It's an overflow. // Fall back to large integer arithmetic in the next block. forceLarge(); } else { // All good: we're done. small_ += other; return *this; } } // And now we're down to large integer arithmetic. // The following code should work even if other == LONG_MIN (in which case // -other == LONG_MIN also), since passing -other to mpz_sub_ui casts it // to an unsigned long (and gives it the correct positive value). if (other >= 0) mpz_add_ui(large_, large_, other); else mpz_sub_ui(large_, large_, -other); return *this; } template NIntegerBase& NIntegerBase::operator -=(long other) { if (isInfinite()) return *this; if (! large_) { // Use native arithmetic if we can. if ( (other > 0 && small_ < (LONG_MIN + other)) || (other < 0 && small_ > (LONG_MAX + other))) { // Boom. It's an overflow. // Fall back to large integer arithmetic in the next block. forceLarge(); } else { // All good: we're done. small_ -= other; return *this; } } // And now we're down to large integer arithmetic. // The following code should work even if other == LONG_MIN (in which case // -other == LONG_MIN also), since passing -other to mpz_add_ui casts it // to an unsigned long (and gives it the correct positive value). if (other >= 0) mpz_sub_ui(large_, large_, other); else mpz_add_ui(large_, large_, -other); return *this; } template NIntegerBase& NIntegerBase::operator *=( const NIntegerBase& other) { if (isInfinite()) return *this; else if (other.isInfinite()) { makeInfinite(); return *this; } if (large_) { if (other.large_) mpz_mul(large_, large_, other.large_); else mpz_mul_si(large_, large_, other.small_); } else if (other.large_) { large_ = new mpz_t; mpz_init(large_); mpz_mul_si(large_, other.large_, small_); } else { typedef typename IntOfSize<2 * sizeof(long)>::type Wide; Wide ans = static_cast(small_) * static_cast(other.small_); if (ans > LONG_MAX || ans < LONG_MIN) { // Overflow. large_ = new mpz_t; mpz_init_set_si(large_, small_); mpz_mul_si(large_, large_, other.small_); } else small_ = static_cast(ans); } return *this; } template NIntegerBase& NIntegerBase::operator *=(long other) { if (isInfinite()) return *this; if (large_) mpz_mul_si(large_, large_, other); else { typedef IntOfSize<2 * sizeof(long)>::type Wide; Wide ans = static_cast(small_) * static_cast(other); if (ans > LONG_MAX || ans < LONG_MIN) { // Overflow. large_ = new mpz_t; mpz_init_set_si(large_, small_); mpz_mul_si(large_, large_, other); } else small_ = static_cast(ans); } return *this; } template NIntegerBase& NIntegerBase::operator /=( const NIntegerBase& other) { if (isInfinite()) return *this; if (other.isInfinite()) return (*this = 0); if (supportInfinity && other.isZero()) { makeInfinite(); return *this; } if (other.large_) { if (large_) { mpz_tdiv_q(large_, large_, other.large_); return *this; } // This is a native C/C++ long. // One of four things must happen: // (i) |other| > |this|, in which case the result = 0; // (ii) this = LONG_MIN and OTHER = -1, in which case the result // is the large integer -LONG_MIN; // (iii) this = LONG_MIN and OTHER is the large integer -LONG_MIN, // in which case the result = -1; // (iv) other can be converted to a native long, and the result // is a native long also. // // Deal with the problematic LONG_MIN case first. if (small_ == LONG_MIN) { if (! mpz_cmp_ui(other.large_, LONG_MIN /* casting to unsigned makes this -LONG_MIN */)) { small_ = -1; return *this; } if (! mpz_cmp_si(other.large_, -1)) { // The result is -LONG_MIN, which requires large integers. // Reduce other while we're at it. const_cast&>(other).forceReduce(); large_ = new mpz_t; mpz_init_set_si(large_, LONG_MIN); mpz_neg(large_, large_); return *this; } if (mpz_cmp_ui(other.large_, LONG_MIN /* cast to ui makes this -LONG_MIN */) > 0 || mpz_cmp_si(other.large_, LONG_MIN) < 0) { small_ = 0; return *this; } // other is in [ LONG_MIN, -LONG_MIN ) \ {-1}. // Reduce it and use native arithmetic. const_cast&>(other).forceReduce(); small_ /= other.small_; return *this; } // From here we have this in ( LONG_MIN, -LONG_MIN ). if (small_ >= 0) { if (mpz_cmp_si(other.large_, small_) > 0 || mpz_cmp_si(other.large_, -small_) < 0) { small_ = 0; return *this; } } else { // We can negate, since small_ != LONG_MIN. if (mpz_cmp_si(other.large_, -small_) > 0 || mpz_cmp_si(other.large_, small_) < 0) { small_ = 0; return *this; } } // We can do this all in native longs from here. // Opportunistically reduce other, since we know we can. const_cast&>(other).forceReduce(); small_ /= other.small_; return *this; } else return (*this) /= other.small_; } template NIntegerBase& NIntegerBase::operator /=(long other) { if (isInfinite()) return *this; if (supportInfinity && other == 0) { makeInfinite(); return *this; } if (large_) { if (other >= 0) mpz_tdiv_q_ui(large_, large_, other); else { // The cast to (unsigned long) makes this correct even if // other = LONG_MIN. mpz_tdiv_q_ui(large_, large_, - other); mpz_neg(large_, large_); } } else if (small_ == LONG_MIN && other == -1) { // This is the special case where we must switch from native to // large integers. large_ = new mpz_t; mpz_init_set_si(large_, LONG_MIN); mpz_neg(large_, large_); } else { // We can do this entirely in native arithmetic. small_ /= other; } return *this; } template NIntegerBase& NIntegerBase::divByExact( const NIntegerBase& other) { if (other.large_) { if (large_) { mpz_divexact(large_, large_, other.large_); return *this; } // This is a native C/C++ long. // Because we are guaranteed other | this, it follows that // other must likewise fit within a native long, or else // (i) this == 0, or (ii) this == LONG_MIN and other == -LONG_MIN. // It also follows that the result must fit within a native long, // or else this == LONG_MIN and other == -1. if (small_ == 0) { // 0 / anything = 0 (we know from preconditions that other != 0). return *this; } else if (small_ == LONG_MIN) { if (! mpz_cmp_ui(other.large_, LONG_MIN /* casting to unsigned makes this -LONG_MIN */)) { // The result is -1, since we have LONG_MIN / -LONG_MIN. small_ = -1; return *this; } // At this point we know that other fits within a native long. // Opportunistically reduce its representation. const_cast&>(other).forceReduce(); if (other.small_ == -1) { // The result is -LONG_MIN, which requires large integers. large_ = new mpz_t; mpz_init_set_si(large_, LONG_MIN); mpz_neg(large_, large_); } else { // The result will fit within a native long also. small_ /= other.small_; } return *this; } // Here we know that other always fits within a native long, // and so does the result. // Opportunisticaly reduce the representation of other, since // we know we can. const_cast&>(other).forceReduce(); small_ /= other.small_; return *this; } else { // other is already a native int. // Use the native version of this routine instead. return divByExact(other.small_); } } template NIntegerBase& NIntegerBase::divByExact(long other) { if (large_) { if (other >= 0) mpz_divexact_ui(large_, large_, other); else { // The cast to (unsigned long) makes this correct even if // other = LONG_MIN. mpz_divexact_ui(large_, large_, - other); mpz_neg(large_, large_); } } else if (small_ == LONG_MIN && other == -1) { // This is the special case where we must switch from native to // large integers. large_ = new mpz_t; mpz_init_set_si(large_, LONG_MIN); mpz_neg(large_, large_); } else { // We can do this entirely in native arithmetic. small_ /= other; } return *this; } template NIntegerBase& NIntegerBase::operator %=( const NIntegerBase& other) { if (other.large_) { if (large_) { mpz_tdiv_r(large_, large_, other.large_); return *this; } // We fit into a native long. Either: // (i) |other| > |this|, in which case the result is just this; // (ii) |other| == |this|, in which case the result is 0; // (iii) |other| < |this|, in which case we can convert // everything to native C/C++ integer arithmetic. // Test other <=> |this|: int res = (small_ >= 0 ? mpz_cmp_si(other.large_, small_) : mpz_cmp_ui(other.large_, - small_) /* ui cast makes this work even if small_ = LONG_MIN */); if (res > 0) return *this; if (res == 0) { small_ = 0; return *this; } // Test other <=> -|this|: res = (small_ >= 0 ? mpz_cmp_si(other.large_, - small_) : mpz_cmp_si(other.large_, small_)); if (res < 0) return *this; if (res == 0) { small_ = 0; return *this; } // Everything can be made native integer arithmetic. // Opportunistically reduce other while we're at it. const_cast&>(other).forceReduce(); // Some compilers will crash on LONG_MIN % -1, sigh. if (other.small_ == -1) small_ = 0; else small_ %= other.small_; return *this; } else return (*this) %= other.small_; } template NIntegerBase& NIntegerBase::operator %=(long other) { // Since |result| < |other|, whatever happens we can fit the result // into a native C/C++ long. if (large_) { // We can safely cast other to an unsigned long, because the rounding // rules imply that (this % LONG_MIN) == (this % -LONG_MIN). mpz_tdiv_r_ui(large_, large_, other >= 0 ? other : -other); forceReduce(); } else { // All native arithmetic from here. // Some compilers will crash on LONG_MIN % -1, sigh. if (other == -1) small_ = 0; else small_ %= other; } return *this; } template void NIntegerBase::raiseToPower(unsigned long exp) { if (exp == 0) (*this) = one; else if (! isInfinite()) { if (large_) { // Outsource it all to MPI. mpz_pow_ui(large_, large_, exp); } else { // Implement fast modular exponentiation ourselves. NIntegerBase base(*this); *this = 1; while (exp) { // INV: desired result = (base ^ exp) * this. if (exp & 1) (*this) *= base; exp >>= 1; base *= base; } } } } template void NIntegerBase::gcdWith( const NIntegerBase& other) { if (large_) { if (other.large_) { mpz_gcd(large_, large_, other.large_); } else { mpz_t tmp; mpz_init_set_si(tmp, other.small_); mpz_gcd(large_, large_, tmp); mpz_clear(tmp); } mpz_abs(large_, large_); } else if (other.large_) { makeLarge(); mpz_gcd(large_, large_, other.large_); mpz_abs(large_, large_); } else { // Both integers are native. long a = small_; long b = other.small_; if ((a == LONG_MIN && (b == LONG_MIN || b == 0)) || (b == LONG_MIN && a == 0)) { // gcd(a,b) = LONG_MIN, which means we can't make it // non-negative without switching to large integers. large_ = new mpz_t; mpz_init_set_si(large_, LONG_MIN); mpz_neg(large_, large_); return; } if (a == LONG_MIN) { a >>= 1; // Won't affect the gcd, but allows us to negate. } else if (b == LONG_MIN) { b >>= 1; // Won't affect the gcd, but allows us to negate. } if (a < 0) a = -a; if (b < 0) b = -b; /** * Now everything is non-negative. * The following code is based on Stein's binary GCD algorithm. */ if (! a) { small_ = b; return; } if (! b) { small_ = a; return; } // Compute the largest common power of 2. int pow2; for (pow2 = 0; ! ((a | b) & 1); ++pow2) { a >>= 1; b >>= 1; } // Strip out all remaining powers of 2 from a and b. while (! (a & 1)) a >>= 1; while (! (b & 1)) b >>= 1; while (a != b) { // INV: a and b are both odd and non-zero. if (a < b) { b -= a; do b >>= 1; while (! (b & 1)); } else { a -= b; do a >>= 1; while (! (a & 1)); } } small_ = (a << pow2); } } template void NIntegerBase::lcmWith( const NIntegerBase& other) { if (isZero()) return; if (other.isZero()) { if (large_) clearLarge(); small_ = 0; return; } NIntegerBase gcd(*this); gcd.gcdWith(other); divByExact(gcd); (*this) *= other; } template NIntegerBase NIntegerBase::gcdWithCoeffs( const NIntegerBase& other, NIntegerBase& u, NIntegerBase& v) const { // TODO: Implement properly for native types. const_cast(*this).makeLarge(); const_cast(other).makeLarge(); u.makeLarge(); v.makeLarge(); // TODO: Fix for natives: // regina::gcdWithCoeffs(small_, other.small_, u.small_, v.small_); // TODO: Escalate to GMP if anyone is equal to MINLONG. // Otherwise smalls are fine, but check gmpWithCoeffs() for overflow. NIntegerBase ans; ans.makeLarge(); // Check for zero arguments. if (isZero()) { u = 0L; if (other.isZero()) { v = 0L; // ans is already zero. return ans; } v = 1; ans = other; if (ans < 0) { v.negate(); ans.negate(); } return ans; } if (other.isZero()) { v = 0L; u = 1; ans = *this; if (ans < 0) { u.negate(); ans.negate(); } return ans; } // Neither argument is zero. // Run the gcd algorithm. mpz_gcdext(ans.large_, u.large_, v.large_, large_, other.large_); // Ensure the gcd is positive. if (ans < 0) { ans.negate(); u.negate(); v.negate(); } // Get u and v in the correct range. NIntegerBase addToU(other); NIntegerBase addToV(*this); addToU.divByExact(ans); addToV.divByExact(ans); if (addToV < 0) addToV.negate(); else addToU.negate(); // We can add (addToU, addToV) to u and v. // We also know that addToV is positive. // Add enough copies to make v*sign(other) just non-positive. NIntegerBase copies(v); if (other > 0) { // v must be just non-positive. if (v > 0) { copies -= 1; copies /= addToV; copies.negate(); copies -= 1; } else { copies /= addToV; copies.negate(); } } else { // v must be just non-negative. if (v < 0) { copies += 1; copies /= addToV; copies.negate(); copies += 1; } else { copies /= addToV; copies.negate(); } } addToU *= copies; addToV *= copies; u += addToU; v += addToV; return ans; } template NIntegerBase NIntegerBase::divisionAlg( const NIntegerBase& divisor, NIntegerBase& remainder) const { if (divisor.isZero()) { remainder = *this; return zero; } // Preconditions state that nothing is infinite, and we've dealt with d=0. NIntegerBase quotient; // Throughout the following code: // - GMP mpz_fdiv_qr() could give a negative remainder, but that this // will only ever happen if the divisor is also negative. // - native integer division could leave a negative remainder // regardless of the sign of the divisor (I think the standard // indicates that the decision is based on the sign of *this?). if (large_) { // We will have to use GMP routines. quotient.makeLarge(); remainder.makeLarge(); if (divisor.large_) { // Just pass everything straight through to GMP. mpz_fdiv_qr(quotient.large_, remainder.large_, large_, divisor.large_); if (remainder < 0) { remainder -= divisor; ++quotient; } } else { // Put the divisor in GMP format for the GMP routines to use. mpz_t divisorGMP; mpz_init_set_si(divisorGMP, divisor.small_); mpz_fdiv_qr(quotient.large_, remainder.large_, large_, divisorGMP); mpz_clear(divisorGMP); // The remainder must fit into a long, since // 0 <= remainder < |divisor|. remainder.forceReduce(); if (remainder.small_ < 0) { remainder.small_ -= divisor.small_; ++quotient; } } } else { // This integer fits into a long. if (divisor.large_) { // Cases: // // 1) Divisor needs to be large (does not fit into long). // Subcases: // 1a) |divisor| > |this|. // --> quotient = -1/0/+1, remainder is large. // 1b) divisor = |LONG_MIN| and this = LONG_MIN. // --> quotient = -1, remainder = 0. // // 2) Otherwise, divisor actually fits into a long. // Fall through to the next code block. // // NOTE: Be careful not to take -small_ when small_ is negative! if (small_ >= 0 && (divisor > small_ || divisor < -small_)) { quotient = 0; remainder = small_; } else if (small_ < 0 && divisor < small_) { quotient = 1; remainder = small_; remainder -= divisor; } else if (small_ < 0 && -divisor < small_) { quotient = -1; remainder = small_; remainder += divisor; } else if (small_ == LONG_MIN && -divisor == small_) { quotient = -1; remainder = 0; } else { // Since we know we can reduce divisor to a native integer, // be kind: cast away the const and reduce it. const_cast(divisor).forceReduce(); // Fall through to the next block. } } if (! divisor.large_) { // Here we know divisor fits into a long. // Thus remainder also fits into a long, since // 0 <= |remainder| < |divisor|. // // Cases: // 1) quotient = |LONG_MIN|. // Only happens if this = LONG_MIN, divisor = -1. // 2) |quotient| < |LONG_MIN| --> quotient fits into a long also. if (small_ == LONG_MIN && divisor.small_ == -1) { quotient = LONG_MIN; quotient.negate(); remainder = 0; } else { quotient = small_ / divisor.small_; remainder = small_ - (quotient.small_ * divisor.small_); if (remainder.small_ < 0) { if (divisor.small_ > 0) { remainder.small_ += divisor.small_; --quotient; } else { remainder.small_ -= divisor.small_; ++quotient; } } } } } return quotient; } template int NIntegerBase::legendre( const NIntegerBase& p) const { // For now, just do this entirely through GMP. mpz_ptr gmp_this = large_; mpz_ptr gmp_p = p.large_; if (! large_) { gmp_this = new mpz_t; mpz_init_set_si(gmp_this, small_); } if (! p.large_) { gmp_p = new mpz_t; mpz_init_set_si(gmp_p, p.small_); } int ans = mpz_legendre(gmp_this, gmp_p); if (! large_) { mpz_clear(gmp_this); delete gmp_this; } if (! p.large_) { mpz_clear(gmp_p); delete gmp_p; } return ans; } template NIntegerBase NIntegerBase::randomBoundedByThis() const { NMutex::MutexLock ml(randMutex); if (! randInitialised) { gmp_randinit_default(randState); randInitialised = true; } NIntegerBase retval; retval.makeLarge(); if (large_) mpz_urandomm(retval.large_, randState, large_); else { // Go through GMP anyway, for the rand() routine, so that all // our random number generators use a consistent algorithm. mpz_t tmp; mpz_init_set_si(tmp, small_); mpz_urandomm(retval.large_, randState, tmp); mpz_clear(tmp); // Since this fits within a long, the result will also. retval.forceReduce(); } return retval; } template NIntegerBase NIntegerBase::randomBinary(unsigned long n) { NMutex::MutexLock ml(randMutex); if (! randInitialised) { gmp_randinit_default(randState); randInitialised = true; } NIntegerBase retval; retval.makeLarge(); mpz_urandomb(retval.large_, randState, n); // If n bits will fit within a signed long, reduce. if (n < sizeof(long) * 8) retval.forceReduce(); return retval; } template NIntegerBase NIntegerBase::randomCornerBinary(unsigned long n) { NMutex::MutexLock ml(randMutex); if (! randInitialised) { gmp_randinit_default(randState); randInitialised = true; } NIntegerBase retval; retval.makeLarge(); mpz_rrandomb(retval.large_, randState, n); // If n bits will fit within a signed long, reduce. if (n < sizeof(long) * 8) retval.forceReduce(); return retval; } // Instantiate the templates! template class NIntegerBase; template class NIntegerBase; template std::ostream& operator << (std::ostream&, const NIntegerBase&); template std::ostream& operator << (std::ostream&, const NIntegerBase&); } // namespace regina regina-4.95/engine/maths/ninteger.h000644 000765 000024 00000364524 12237427317 017202 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NINTEGER_H #ifndef __DOXYGEN #define __NINTEGER_H #endif /*! \file maths/ninteger.h * \brief Provides arbitrary-precision and fixed-precision integer types. */ #include #include #include // MPIR (and thus SAGE) needs this *before* gmp.h. #include #include #include #include "regina-core.h" #include "utilities/intutils.h" /** * \hideinitializer * * An internal copy of the GMP signed comparison optimisations. * This macro should not be used outside this class. * * By making our own copy of such optimisation macros we can use * C++-style casts instead of C-style casts and avoid noisy compiler * warnings. I'd love a better way of doing this. */ #ifdef __GNUC__ #define mpz_cmp_si_cpp(z, si) \ (__builtin_constant_p(si) && (si) == 0 ? mpz_sgn(z) : \ __builtin_constant_p(si) && (si) > 0 ? _mpz_cmp_ui(z, \ static_cast(si)) : \ _mpz_cmp_si(z, si)) #else #define mpz_cmp_si_cpp(z, si) _mpz_cmp_si(z, si) #endif namespace regina { /** * \weakgroup maths * @{ */ template class NNativeInteger; /** * Internal base classes for use with NIntegerBase, templated on whether we * should support infinity as an allowed value. * * See the NIntegerBase class notes for details. */ template struct InfinityBase; #ifndef __DOXYGEN /** * An internal base class inherited by NLargeInteger, which provides * support for infinity as an allowed value. */ template <> struct InfinityBase { bool infinite_; /**< Does this integer represent infinity? */ /** * Default constructor that sets this integer to be finite. */ inline InfinityBase() : infinite_(false) { } }; /** * An empty internal base class inherited by NInteger, which does not * support infinity as an allowed value. */ template <> struct InfinityBase { }; #endif // __DOXYGEN /** * Represents an arbitrary precision integer. * Calculations are always guaranteed to be exact, regardless of how * large the integers become. * * The current implementation uses fast native integer arithmetic wherever * possible, whilst always testing for potential overflow. If a potential * overflow is detected, this class switches to using the GNU multiple * precision arithmetic library (libgmp) instead. * * This class takes a single boolean argument \a supportInfinity. * If this is \c true, then this class will support infinity as an allowed * value. If this is \c false (the default), then infinity is not supported, * and any attempt to work with infinity will lead to undefined behaviour. * Supporting infinity is more flexible, but also comes with a slight * performance cost (very roughly estimated at around 10%-20%). * * For the purposes of comparison, infinity is * considered larger than any other integer but equal to itself. * * All routines in this class, including random number generation, are * thread-safe. * * The opportunistic use of native arithmetic where possible was inspired by * the (much more complex and powerful) lazy exact arithmetic in CGAL. * Thanks to Menelaos Karavelas for encouraging me to take another look at * these ideas. * * \ifacespython Both variants of this template are available through Python. * For \a supportInfinity = \c false (the default), simply use NIntegerBase. * For \a supportInfinity = \c true, use NLargeInteger. * * \testfull At present, the only routines not thoroughly tested in the * test suite are legendre() and the random number generation routines. */ template class REGINA_API NIntegerBase : private InfinityBase { public: static const NIntegerBase zero; /**< Globally available zero. */ static const NIntegerBase one; /**< Globally available one. */ static const NIntegerBase infinity; /**< Globally available infinity. This is only defined if \a supportInfinity is \c true. Any attempt to use it when \a supportInfinity is \c false should generate a linker error. */ private: long small_; /**< Contains the native representation of this integer, if we are still using native representations (i.e., if large_ is null). If we are using GMP large integer representations, or if this integer is infinity, then this native integer is ignored (and may be set to anything). */ mpz_ptr large_; /**< 0 if we are using native representations, or a pointer to the full GMP large integer if we are now using these instead. We require that, whenever this pointer is non-null, the corresponding GMP large integer is initialised. If this integer is infinity then large_ must be null. */ public: /** * Initialises this integer to zero. */ NIntegerBase(); /** * Initialises this integer to the given value. * * \ifacespython In Python, the only native-integer constructor * is NIntegerBase(long). * * @param value the new value of this integer. */ NIntegerBase(int value); /** * Initialises this integer to the given value. * * \ifacespython In Python, the only native-integer constructor * is NIntegerBase(long). * * @param value the new value of this integer. */ NIntegerBase(unsigned value); /** * Initialises this integer to the given value. * * \ifacespython In Python, this is the only native-integer * constructor available. * * @param value the new value of this integer. */ NIntegerBase(long value); /** * Initialises this integer to the given value. * * \ifacespython In Python, the only native-integer constructor * is NIntegerBase(long). * * @param value the new value of this integer. */ NIntegerBase(unsigned long value); /** * Initialises this integer to the given value. * * @param value the new value of this integer. */ NIntegerBase(const NIntegerBase& value); /** * Initialises this integer to the given value. * * This constructor is marked as explicit in the hope of * avoiding accidental (and unintentional) mixing of template * parameters. * * \pre The given integer is not infinite. * * @param value the new value of this integer. */ explicit NIntegerBase(const NIntegerBase& value); /** * Initialises this integer to the given value. * * This constructor is marked as explicit in the hope of * avoiding accidental (and unintentional) mixing of integer classes. * * \pre If \a bytes is larger than sizeof(long), then * \a bytes is a strict \e multiple of sizeof(long). For * instance, if longs are 8 bytes then you can use \a bytes=16 * but not \a bytes=12. This restriction is enforced through a * compile-time assertion, but may be lifted in future versions * of Regina. * * \ifacespython Not present. * * @param value the new value of this integer. */ template explicit NIntegerBase(const NNativeInteger& value); /** * Initialises this integer to the given value which is * represented as a string of digits in a given base. * * If not specified, the base defaults to 10. * If the given base is zero, the base will be automatically * determined. If the given string begins with \c 0x or \c 0X, * the base will be assumed to be 16. Otherwise, if the string * begins with \c 0, the base will be assumed to be 8. * Otherwise it will be taken as base 10. * * Whitespace may be present at the beginning or the end * of the given string, and will simply be ignored. * * Error detection is possible by passing a non-null boolean * pointer as the third parameter to this constructor. * * For finer details on how the string parsing works, see * strtol() from the standard C library (on which this method * is based). * * \pre The given base is zero, or is between 2 and 36 inclusive. * \pre The given string represents a finite integer * in the given base, with optional whitespace beforehand. * * \ifacespython The final parameter \a valid is not present. * * @param value the new value of this integer, represented as a string * of digits in base \a base. * @param base the base in which \a value is given. * @param valid if this pointer is not null, the boolean referenced * will be set to \c true if the entire given string was a valid * large integer representation and \c false otherwise. */ NIntegerBase(const char* value, int base = 10, bool* valid = 0); /** * Initialises this integer to the given value which is * represented as a string of digits in a given base. * * If not specified, the base defaults to 10. * If the given base is zero, the base will be automatically * determined. If the given string begins with \c 0x or \c 0X, * the base will be assumed to be 16. Otherwise, if the string * begins with \c 0, the base will be assumed to be 8. * Otherwise it will be taken as base 10. * * Whitespace may be present at the beginning or the end * of the given string, and will simply be ignored. * * Error detection is possible by passing a non-null boolean * pointer as the third parameter to this constructor. * * For finer details on how the string parsing works, see * strtol() from the standard C library (on which this method * is based). * * \pre The given base is zero, or is between 2 and 36 inclusive. * \pre The given string represents an integer * in the given base, with optional whitespace beforehand. * * \ifacespython The final parameter \a valid is not present. * * @param value the new value of this integer, represented as a string * of digits in base \a base. * @param base the base in which \a value is given. * @param valid if this pointer is not null, the boolean referenced * will be set to \c true if the entire given string was a valid * large integer representation and \c false otherwise. */ NIntegerBase(const std::string& value, int base = 10, bool* valid = 0); /** * Destroys this integer. */ ~NIntegerBase(); /** * Returns whether we are currently working with a native C/C++ * long, or whether we have switched to GMP large integer arithmetic * for this integer. * * If this integer is infinite, this routine will return \c false. * * @return \c true if and only if we are still using a native * C/C++ long. */ bool isNative() const; /** * Returns whether or not this integer is zero. * * This is micro-optimised to be faster than simply testing * whether (*this) == 0. * * @return \c true if and only if this integer is zero. */ bool isZero() const; /** * Returns the sign of this integer. * * In this routine, infinity is considered to have sign +1. * * @return +1, -1 or 0 according to whether this integer is * positive, negative or zero. */ int sign() const; /** * Returns whether this integer is infinity. * * @return \c true if and only if this integer is infinity. */ bool isInfinite() const; /** * Sets this integer to be infinity. * * If the template parameter \a supportInfinity is \c false, * this routine safely does nothing. */ inline void makeInfinite(); /** * Returns the value of this integer as a long. * * It is the programmer's reponsibility to ensure that this integer * is within the required range. If this integer is too large or * small to fit into a long, then the result will be undefined. * * Note that, assuming the value is within the required range, * this routine will give correct results regardless of whether the * underlying representation is a native or large integer. * * \pre This integer is not infinity. * * @return the value of this integer. */ long longValue() const; /** * Returns the value of this integer as a native integer of some * fixed byte length. * * It is the programmer's reponsibility to ensure that this integer * is within the required range. If this integer is too large or * small to fit into the return type, then the result will be undefined. * * Note that, assuming the value is within the required range, * this routine will give correct results regardless of whether the * underlying representation is a native or large integer. * * \pre If \a bytes is larger than sizeof(long), then * \a bytes is a strict \e multiple of sizeof(long). For * instance, if longs are 8 bytes then you can use this * routine with \a bytes=16 but not \a bytes=12. * This restriction is enforced through a compile-time assertion, * but may be lifted in future versions of Regina. * * \pre This integer is not infinity. * * \ifacespython Not present. * * @return the value of this integer. */ template typename IntOfSize::type nativeValue() const; /** * Returns the value of this integer as a string in the given * base. If not specified, the base defaults to 10. * * If this integer is infinity, the string returned will be * \c inf. * * \pre The given base is between 2 and 36 inclusive. * * @return the value of this integer as a newly allocated * string. */ std::string stringValue(int base = 10) const; /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(const NIntegerBase& value); /** * Sets this integer to the given value. * * \pre The given integer is not infinite. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator = (const NIntegerBase& value); /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(int value); /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(unsigned value); /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(long value); /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(unsigned long value); /** * Sets this integer to the given value which is * represented as a string of digits in base 10. * * Whitespace may be present at the beginning or end of the given * string and will simply be ignored. * * \pre The given string represents an integer * in base 10, with optional whitespace added. * * @param value the new value of this integer, represented as a string * of digits in base 10. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(const char* value); /** * Sets this integer to the given value which is * represented as a string of digits in base 10. * * Whitespace may be present at the beginning or end of the given * string and will simply be ignored. * * \pre The given string represents an integer * in base 10, with optional whitespace added. * * @param value the new value of this integer, represented as a string * of digits in base 10. * @return a reference to this integer with its new value. */ NIntegerBase& operator =(const std::string& value); /** * Swaps the values of this and the given integer. * * @param other the integer whose value will be swapped with * this. */ void swap(NIntegerBase& other); /** * Determines if this is equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * equal. */ bool operator ==(const NIntegerBase& rhs) const; /** * Determines if this is equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * equal. */ bool operator ==(const NIntegerBase& rhs) const; /** * Determines if this is equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * equal. */ bool operator ==(long rhs) const; /** * Determines if this is not equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * not equal. */ bool operator !=(const NIntegerBase& rhs) const; /** * Determines if this is not equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * not equal. */ bool operator !=(const NIntegerBase& rhs) const; /** * Determines if this is not equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * not equal. */ bool operator !=(long rhs) const; /** * Determines if this is less than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than the given * integer. */ bool operator <(const NIntegerBase& rhs) const; /** * Determines if this is less than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than the given * integer. */ bool operator <(long rhs) const; /** * Determines if this is greater than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than the given * integer. */ bool operator >(const NIntegerBase& rhs) const; /** * Determines if this is greater than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than the given * integer. */ bool operator >(long rhs) const; /** * Determines if this is less than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than or equal to * the given integer. */ bool operator <=(const NIntegerBase& rhs) const; /** * Determines if this is less than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than or equal to * the given integer. */ bool operator <=(long rhs) const; /** * Determines if this is greater than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than or equal * to the given integer. */ bool operator >=(const NIntegerBase& rhs) const; /** * Determines if this is greater than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than or equal * to the given integer. */ bool operator >=(long rhs) const; /** * The preincrement operator. * This operator increments this integer by one, and returns a * reference to the integer \e after the increment. * * \ifacespython Not available. * * @return a reference to this integer after the increment. */ NIntegerBase& operator ++(); /** * The postincrement operator. * This operator increments this integer by one, and returns a * copy of the integer \e before the increment. * * \ifacespython Not available. * * @return a copy of this integer before the * increment took place. */ NIntegerBase operator ++(int); /** * The predecrement operator. * This operator decrements this integer by one, and returns a * reference to the integer \e after the decrement. * * \ifacespython Not available. * * @return a reference to this integer after the decrement. */ NIntegerBase& operator --(); /** * The postdecrement operator. * This operator decrements this integer by one, and returns a * copy of the integer \e before the decrement. * * \ifacespython Not available. * * @return a copy of this integer before the * decrement took place. */ NIntegerBase operator --(int); /** * Adds this to the given integer and returns the result. * This integer is not changed. * * If either term of the sum is infinite, the result will be * infinity. * * @param other the integer to add to this integer. * @return the sum \a this plus \a other. */ NIntegerBase operator +(const NIntegerBase& other) const; /** * Adds this to the given integer and returns the result. * This integer is not changed. * * If either term of the sum is infinite, the result will be * infinity. * * @param other the integer to add to this integer. * @return the sum \a this plus \a other. */ NIntegerBase operator +(long other) const; /** * Subtracts the given integer from this and returns the result. * This integer is not changed. * * If either term of the difference is infinite, the result will be * infinity. * * @param other the integer to subtract from this integer. * @return the difference \a this minus \a other. */ NIntegerBase operator -(const NIntegerBase& other) const; /** * Subtracts the given integer from this and returns the result. * This integer is not changed. * * If either term of the difference is infinite, the result will be * infinity. * * @param other the integer to subtract from this integer. * @return the difference \a this minus \a other. */ NIntegerBase operator -(long other) const; /** * Multiplies this by the given integer and returns the * result. * This integer is not changed. * * If either factor of the product is infinite, the result will be * infinity. * * @param other the integer to multiply by this integer. * @return the product \a this times \a other. */ NIntegerBase operator *(const NIntegerBase& other) const; /** * Multiplies this by the given integer and returns the * result. * This integer is not changed. * * If either factor of the product is infinite, the result will be * infinity. * * @param other the integer to multiply by this integer. * @return the product \a this times \a other. */ NIntegerBase operator *(long other) const; /** * Divides this by the given integer and returns the result. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is not changed. * * If \a other is known to divide this integer exactly, * divExact() should be used instead. * * Infinity divided by anything will return infinity. * Anything finite divided by infinity will return zero. * Anything finite divided by zero will return infinity. * * For a division routine that always rounds down, see divisionAlg(). * * \pre If this class does not support infinity, then * \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NIntegerBase operator /(const NIntegerBase& other) const; /** * Divides this by the given integer and returns the result. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is not changed. * * If \a other is known to divide this integer exactly, * divExact() should be used instead. * * Infinity divided by anything will return infinity. * Anything finite divided by zero will return infinity. * * For a division routine that always rounds down, see divisionAlg(). * * \pre If this class does not support infinity, then * \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NIntegerBase operator /(long other) const; /** * Divides this by the given integer and returns the result. * This can only be used when the given integer divides into * this exactly, and for large integers can be much faster than * ordinary division. This integer is not changed. * * \pre The given integer divides exactly into * this integer, i.e. \a this divided by \a other is an integer. * \pre \a other is not zero. * \pre Neither this nor \a other is infinite. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NIntegerBase divExact(const NIntegerBase& other) const; /** * Divides this by the given integer and returns the result. * This can only be used when the given integer divides into * this exactly, and for large integers can be much faster than * ordinary division. This integer is not changed. * * \pre The given integer divides exactly into * this integer, i.e. \a this divided by \a other is an integer. * \pre \a other is not zero. * \pre This integer is not infinite. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NIntegerBase divExact(long other) const; /** * Determines the remainder when this integer is divided by the * given integer. If non-zero, the result will have the same sign * as this integer. * This integer is not changed. * * For a division routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * \pre Neither this nor \a other is infinite. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer to divide this by. * @return the remainder \a this modulo \a other. */ NIntegerBase operator %(const NIntegerBase& other) const; /** * Determines the remainder when this integer is divided by the * given integer. If non-zero, the result will have the same sign * as this integer. * This integer is not changed. * * For a division routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * \pre This integer is not infinite. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer to divide this by. * @return the remainder \a this modulo \a other. */ NIntegerBase operator %(long other) const; /** * Uses the division algorithm to obtain a quotient and * remainder when dividing by the given integer. * * Suppose this integer is \a n and we pass the divisor \a d. * The division algorithm describes the result of * dividing \a n by \a d; in particular, it expresses * n = qd + r, where \a q is the quotient and * \a r is the remainder. * * The division algorithm is precise about which values of \a q * and \a r are chosen; in particular it chooses the unique \a r * in the range 0 <= r < |d|. * * Note that this differs from other division routines in this * class, in that it always rounds to give a non-negative remainder. * Thus NIntegerBase(-7).divisionAlg(3) gives quotient -3 and * remainder 2, whereas (-7)/3 gives quotient -2 and (-7)\%3 gives * remainder -1. * * The two results are passed back to the caller as follows: * The quotient \a q is passed back as the return value of the * function, and the remainder \a r is stored in the reference * argument \a r. * * In the special case where the given divisor is 0 (not * allowed by the usual division algorithm), this routine selects * quotient 0 and remainder \a n. * * \pre Neither this nor the divisor are infinite. * * \ifacespython The argument \a remainder is missing; instead both * the quotient and remainder are passed back through the return * value of the function. Specifically, this function returns a * (\a quotient, \a remainder) pair. * * @param divisor the divisor \a d. * @param remainder used to store the remainder \a r when the * functon returns. The initial value of this argument is ignored. * @return the quotient \a q. * * @author Ryan Budney & B.B. */ NIntegerBase divisionAlg( const NIntegerBase& divisor, NIntegerBase& remainder) const; /** * Determines the negative of this integer. * This integer is not changed. * * Negative infinity will return infinity. * * @return the negative of this integer. */ NIntegerBase operator -() const; /** * Adds the given integer to this. * This integer is changed to reflect the result. * * If either term of the sum is infinite, the result will be * infinity. * * @param other the integer to add to this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator +=(const NIntegerBase& other); /** * Adds the given integer to this. * This integer is changed to reflect the result. * * If either term of the sum is infinite, the result will be * infinity. * * @param other the integer to add to this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator +=(long other); /** * Subtracts the given integer from this. * This integer is changed to reflect the result. * * If either term of the difference is infinite, the result will be * infinity. * * @param other the integer to subtract from this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator -=(const NIntegerBase& other); /** * Subtracts the given integer from this. * This integer is changed to reflect the result. * * If either term of the difference is infinite, the result will be * infinity. * * @param other the integer to subtract from this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator -=(long other); /** * Multiplies the given integer by this. * This integer is changed to reflect the result. * * If either factor of the product is infinite, the result will be * infinity. * * @param other the integer to multiply with this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator *=(const NIntegerBase& other); /** * Multiplies the given integer by this. * This integer is changed to reflect the result. * * If either factor of the product is infinite, the result will be * infinity. * * @param other the integer to multiply with this integer. * @return a reference to this integer with its new value. */ NIntegerBase& operator *=(long other); /** * Divides this by the given integer. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is changed to reflect the result. * * If \a other is known to divide this integer exactly, * divByExact() should be used instead. * * Infinity divided by anything will return infinity. * Anything finite divided by infinity will return zero. * Anything finite divided by zero will return infinity. * * For a division routine that always rounds down, see divisionAlg(). * * \pre If this class does not support infinity, then * \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NIntegerBase& operator /=(const NIntegerBase& other); /** * Divides this by the given integer. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is changed to reflect the result. * * If \a other is known to divide this integer exactly, * divByExact() should be used instead. * * Infinity divided by anything will return infinity. * Anything finite divided by zero will return infinity. * * For a division routine that always rounds down, see divisionAlg(). * * \pre If this class does not support infinity, then * \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NIntegerBase& operator /=(long other); /** * Divides this by the given integer. * This can only be used when the given integer divides into * this exactly, and for large integers this is much faster than * ordinary division. This integer is changed to reflect the result. * * \pre The given integer divides exactly into * this integer, i.e. \a this divided by \a other is an integer. * \pre \a other is not zero. * \pre Neither this nor \a other is infinite. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NIntegerBase& divByExact(const NIntegerBase& other); /** * Divides this by the given integer. * This can only be used when the given integer divides into * this exactly, and for large integers this is much faster than * ordinary division. This integer is changed to reflect the result. * * \pre The given integer divides exactly into * this integer, i.e. \a this divided by \a other is an integer. * \pre \a other is not zero. * \pre This integer is not infinite. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NIntegerBase& divByExact(long other); /** * Reduces this integer modulo the given integer. * If non-zero, the result will have the same sign as the original * value of this integer. * This integer is changed to reflect the result. * * For a mod routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * \pre Neither this nor \a other is infinite. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer modulo which this integer will be * reduced. * @return a reference to this integer with its new value. */ NIntegerBase& operator %=(const NIntegerBase& other); /** * Reduces this integer modulo the given integer. * If non-zero, the result will have the same sign as the original * value of this integer. * This integer is changed to reflect the result. * * For a mod routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * \pre This integer is not infinite. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer modulo which this integer will be * reduced. * @return a reference to this integer with its new value. */ NIntegerBase& operator %=(long other); /** * Negates this integer. * This integer is changed to reflect the result. * * Negating infinity will result in infinity. */ void negate(); /** * Raises this integer to the power of the given exponent. * This integer is changed to reflect the result. * * Note that 0 to the power of 0 will be 1, infinity to the * power of 0 will be 1, and infinity to the power of anything * else will be infinity. * * \pre The given exponent is non-negative. * * @param exp the power to which this integer will be raised. */ void raiseToPower(unsigned long exp); /** * Determines the absolute value of this integer. * This integer is not changed. * * @return the absolute value of this integer. */ NIntegerBase abs() const; /** * Sets this integer to be the greatest common divisor of this * and the given integer. * * The result is guaranteed to be non-negative. As a * special case, gcd(0,0) is considered to be zero. * * \pre Neither this integer nor \a other is infinite. * * @param other the integer whose greatest common divisor with * this will be found. */ void gcdWith(const NIntegerBase& other); /** * Determines the greatest common divisor of this and the given * integer. This integer is not changed. * * The result is guaranteed to be non-negative. As a * special case, gcd(0,0) is considered to be zero. * * \pre Neither this integer nor \a other is infinite. * * @param other the integer whose greatest common divisor with * this will be found. * @return the greatest common divisor of this and the given * integer. */ NIntegerBase gcd(const NIntegerBase& other) const; /** * Sets this integer to be the lowest common multiple of this * and the given integer. * * Note that the result might possibly be negative. * * \pre Neither this integer nor \a other is infinite. * * @param other the integer whose lowest common multiple with * this will be found. */ void lcmWith(const NIntegerBase& other); /** * Determines the lowest common multiple of this and the given * integer. This integer is not changed. * * Note that the result might possibly be negative. * * \pre Neither this integer nor \a other is infinite. * * @param other the integer whose lowest common multiple with * this will be found. * @return the lowest common multiple of this and the given * integer. */ NIntegerBase lcm(const NIntegerBase& other) const; /** * Determines the greatest common divisor of this and the given * integer and finds the smallest coefficients with which these * integers combine to give their gcd. * * Note that the given integers need not be non-negative. * However, the gcd returned is guaranteed to be non-negative. * * If \a d is the gcd of \a this and \a other, the values placed * into \a u and \a v will be those for which * u*this + v*other = d, * -abs(this)/d < v*sign(other) <= 0 and * 1 <= u*sign(this) <= abs(other)/d. * These equations are not satisfied when either of \a this or * \a other are zero, but in this case \a u and \a v are both * 0, 1 or -1, using as many zeros as possible. * * \pre Neither this integer nor \a other is infinite. * * @param other the integer whose greatest common divisor with * this will be found. * @param u a variable into which the final coefficient of * \a this will be placed. * @param v a variable into which the final coefficient of * \a other will be placed. * @return the greatest common divisor of \a this and \a other. */ NIntegerBase gcdWithCoeffs( const NIntegerBase& other, NIntegerBase& u, NIntegerBase& v) const; /** * Returns the Legendre symbol (\a a/\a p), where * \a a is this integer and \a p is an odd prime. * * The Legendre symbol is equal to 0 if this integer * is divisible by \a p, 1 if this integer is congruent * to a square mod \a p (but not divisible by \a p), * and -1 otherwise. * * \pre The given integer \a p is an odd positive prime. * \pre This integer is not infinite. * * @param p the given odd prime. * @return The Legendre symbol (0, 1 or -1) as described above. * * @author Ryan Budney */ int legendre(const NIntegerBase& p) const; /** * Generate a pseudo-random integer that is uniformly * distributed in the interval [0,*this). * * \pre This integer is strictly positive. * * \warning Even if this integer is small, this routine is still * slow - it always goes through the GMP large integer routines * so that the random number generation algorithm is consistent. * If you need a fast random number generator and this integer * is small, consider using the standard rand() function instead. * * @return a pseudo-random integer. */ NIntegerBase randomBoundedByThis() const; /** * Generate a pseudo-random integer that is uniformly * distributed in the interval [0,2^n). * * @param n the maximum number of bits in the pseudo-random * integer. * @return a pseudo-random integer. */ static NIntegerBase randomBinary(unsigned long n); /** * Generate a pseudo-random integer that is distributed in the * interval [0,2^n), with a tendency to have long strings of 0s * and 1s in its binary expansion. * * @param n the maximum number of bits in the pseudo-random integer. * @return a pseudo-random integer. */ static NIntegerBase randomCornerBinary( unsigned long n); /** * Set this to a copy of the given raw GMP integer. * * This routine allows NIntegerBase to interact directly with * libgmp and libgmpxx if necessary. * * \ifacespython Not available. * * @param fromData the raw GMP integer to clone. */ void setRaw(mpz_srcptr fromData); /** * Returns the raw GMP data that describes this integer. * * This routine allows NIntegerBase to interact directly with * libgmp and libgmpxx if necessary. * * \warning This routine will have the side-effect of converting * this integer to a (bulkier and slower) GMP representation, * regardless of whether it is small enough to fit within a native * integer. Excessive use of this routine could lead to a significant * performance loss. It is best to use this only when isNative() is * already known to return \c false. * * \pre This integer is not infinite. * * \ifacespython Not available. * * @return the raw GMP data. */ mpz_srcptr rawData() const; /** * Returns the raw GMP data that describes this integer. * * This routine allows NIntegerBase to interact directly with * libgmp and libgmpxx if necessary. * * \warning This routine will have the side-effect of converting * this integer to a (bulkier and slower) GMP representation, * regardless of whether it is small enough to fit within a native * integer. Excessive use of this routine could lead to a significant * performance loss. It is best to use this only when isNative() is * already known to return \c false. * * \pre This integer is not infinite. * * \ifacespython Not available. * * @return the raw GMP data. */ mpz_ptr rawData(); /** * Converts this integer to use a GMP large integer representation, * regardless of whether this is actually necessary. The contents * of this integer will be preserved. * * It does not matter which kind of representation this integer * is currently using. * * \pre This integer is not infinite. */ void makeLarge(); /** * Converts this integer to use a native C/C++ long representation, * if this is possible. However, if this integer is outside the range * of a C/C++ long, then it will remain as a GMP large integer instead * (i.e., nothing will change). Whatever happens, the contents of this * integer will be preserved. * * It does not matter which kind of representation this integer * is currently using. * * \pre This integer is not infinite. */ void tryReduce(); private: /** * Initialises this integer to infinity. * All parameters are ignored. * * This constructor is only defined if \a supportInfinity is \c true. * Any attempt to use it when \a supportInfinity is \c false * will generate a linker error. */ NIntegerBase(bool, bool); /** * Sets this integer to be finite. * Its new value will be determined by the current contents of * \a small_ which will not be touched. * * If the template parameter \a supportInfinity is \c false, * this routine safely does nothing. */ inline void makeFinite(); /** * Converts this integer from a native C/C++ long representation * into a GMP large integer representation. * * The contents of \a small will be copied into \a large. * * \pre \a large_ is null (i.e., we are indeed using a native * C/C++ long representation at present). * \pre This integer is not infinite. */ void forceLarge(); /** * Destroys the GMP large integer representation and reverts to * a native C/C++ long. * * The new value of this integer will be the current contents of * \a small_ (i.e., there is no attempt to "extract" a native long * from the contents of \a large_). * * \pre \a large_ is non-null (i.e., we are indeed using a large * integer reprentation at present). * \pre This integer is not infinite. */ void clearLarge(); /** * Converts this integer from a GMP large integer representation * into a native C/C++ long representation. * * The contents of \a large will be extracted and copied into \a small. * * \pre \a large_ is non-null, and the large integer that it * represents lies between LONG_MIN and LONG_MAX inclusive. * \pre This integer is not infinite. */ void forceReduce(); friend class NIntegerBase; // For conversions. template friend class NNativeInteger; // For conversions. template friend std::ostream& operator << (std::ostream& out, const NIntegerBase& large); }; /** * NLargeInteger is a typedef for NIntegerBase, which offers * arbitrary precision integers with support for infinity. * * \ifacespython This typedef is available in Python. */ typedef NIntegerBase NLargeInteger; /** * NInteger is a typedef for NIntegerBase, which offers * arbitrary precision integers without support for infinity. * * \ifacespython This typedef is available in Python. */ typedef NIntegerBase NInteger; /** * Writes the given integer to the given output stream. * * @param out the output stream to which to write. * @param i the integer to write. * @return a reference to \a out. */ template REGINA_API std::ostream& operator << (std::ostream& out, const NIntegerBase& i); /** * Adds the given native integer to the given large integer. * If the large integer is infinite, the result will also be infinity. * * \ifacespython Not available. * * @param lhs the native integer to add. * @param rhs the large integer to add. * @return the sum \a lhs plus \a rhs. */ template REGINA_API NIntegerBase operator + (long lhs, const NIntegerBase& rhs); /** * Multiplies the given native integer with the given large integer. * If the large integer is infinite, the result will also be infinity. * * \ifacespython Not available. * * @param lhs the native integer to multiply. * @param rhs the large integer to multiply. * @return the product \a lhs times \a rhs. */ template REGINA_API NIntegerBase operator * (long lhs, const NIntegerBase& rhs); /*@}*/ } // namespace regina #ifndef __DOXYGEN namespace libnormaliz { /** * Explicit integer cast functions, for compatibility with Normaliz. * * We define functions separately for each variant of \a supportInfinity * to avoid partial template specialisation. * * @param a an instance of some arbitrary integer type. * @return the given integer, cast as a native long. */ template long explicit_cast_to_long(const Integer& a); template <> inline long explicit_cast_to_long >( const regina::NIntegerBase& a) { return a.longValue(); } template <> inline long explicit_cast_to_long >( const regina::NIntegerBase& a) { return a.longValue(); } } //namespace libnormaliz #endif namespace regina { /** * A wrapper class for a native, fixed-precision integer type of the * given size. * * This class behaves just like native integer arithmetic, where the * underlying integer type is signed and stores the given number of bytes. * There is no overflow testing, and it is up to the user to ensure that * overflows do not occur. On the other hand, this class is almost as * fast as native integer arithmetic (i.e., there is very little overhead). * * The reason for using this class, instead of working directly in a native * integer type, is that this class offers an interface that is compatible with * NInteger. Only some of the NInteger member functions are offered here; * however, those that are offered behave just like their NInteger * counterparts (with the single exception that all arithmetic in * NNativeInteger is subject to overflow). Developers can therefore * switch between integer types easily with minimal changes to * their code, or support both NInteger and NNativeInteger types as * template arguments. * * \pre The system must support integers of the given size; in particular, * there must be an appropriate specialisation IntOfSize. * * \ifacespython Not present. */ template class REGINA_API NNativeInteger { public: typedef typename IntOfSize::type Native; /**< The native data type used to store this integer. */ private: Native data_; /**< The value of this integer. */ public: /** * Initialises this integer to zero. */ NNativeInteger(); /** * Initialises this integer to the given value. * * @param value the new value of this integer. */ NNativeInteger(Native value); /** * Initialises this integer to the given value. * * @param value the new value of this integer. */ NNativeInteger(const NNativeInteger& value); /** * Initialises this integer to the given value. * * This constructor is marked as explicit in the hope of * avoiding accidental (and unintentional) mixing of integer classes. * * It is the programmer's reponsibility to ensure that the given value * fits within the required range. If the given value is too large or * small to fit into this native type, then this new NNativeInteger * will have an undefined initial value. * * \pre If \a bytes is larger than sizeof(long), then * \a bytes is a strict \e multiple of sizeof(long). For * instance, if longs are 8 bytes then you can use this * routine with \a bytes=16 but not \a bytes=12. * This restriction is enforced through a compile-time assertion, * but may be lifted in future versions of Regina. * * \pre The given integer is not infinity. * * \ifacespython Not present. * * @param value the new value of this integer. */ template explicit NNativeInteger(const NIntegerBase& value); /** * Returns whether or not this integer is zero. * * @return \c true if and only if this integer is zero. */ bool isZero() const; /** * Returns the sign of this integer. * * @return +1, -1 or 0 according to whether this integer is * positive, negative or zero. */ int sign() const; /** * Returns the value of this integer in its native type. * * @return the value of this integer. */ Native nativeValue() const; /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator =(const NNativeInteger& value); /** * Sets this integer to the given value. * * @param value the new value of this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator =(Native value); /** * Swaps the values of this and the given integer. * * @param other the integer whose value will be swapped with * this. */ void swap(NNativeInteger& other); /** * Determines if this is equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * equal. */ bool operator ==(const NNativeInteger& rhs) const; /** * Determines if this is equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * equal. */ bool operator ==(Native rhs) const; /** * Determines if this is not equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * not equal. */ bool operator !=(const NNativeInteger& rhs) const; /** * Determines if this is not equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this and the given integer are * not equal. */ bool operator !=(Native rhs) const; /** * Determines if this is less than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than the given * integer. */ bool operator <(const NNativeInteger& rhs) const; /** * Determines if this is less than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than the given * integer. */ bool operator <(Native rhs) const; /** * Determines if this is greater than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than the given * integer. */ bool operator >(const NNativeInteger& rhs) const; /** * Determines if this is greater than the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than the given * integer. */ bool operator >(Native rhs) const; /** * Determines if this is less than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than or equal to * the given integer. */ bool operator <=(const NNativeInteger& rhs) const; /** * Determines if this is less than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is less than or equal to * the given integer. */ bool operator <=(Native rhs) const; /** * Determines if this is greater than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than or equal * to the given integer. */ bool operator >=(const NNativeInteger& rhs) const; /** * Determines if this is greater than or equal to the given integer. * * @param rhs the integer with which this will be compared. * @return \c true if and only if this is greater than or equal * to the given integer. */ bool operator >=(Native rhs) const; /** * The preincrement operator. * This operator increments this integer by one, and returns a * reference to the integer \e after the increment. * * \ifacespython Not available. * * @return a reference to this integer after the increment. */ NNativeInteger& operator ++(); /** * The postincrement operator. * This operator increments this integer by one, and returns a * copy of the integer \e before the increment. * * \ifacespython Not available. * * @return a copy of this integer before the * increment took place. */ NNativeInteger operator ++(int); /** * The predecrement operator. * This operator decrements this integer by one, and returns a * reference to the integer \e after the decrement. * * \ifacespython Not available. * * @return a reference to this integer after the decrement. */ NNativeInteger& operator --(); /** * The postdecrement operator. * This operator decrements this integer by one, and returns a * copy of the integer \e before the decrement. * * \ifacespython Not available. * * @return a copy of this integer before the * decrement took place. */ NNativeInteger operator --(int); /** * Adds this to the given integer and returns the result. * This integer is not changed. * * @param other the integer to add to this integer. * @return the sum \a this plus \a other. */ NNativeInteger operator +(const NNativeInteger& other) const; /** * Adds this to the given integer and returns the result. * This integer is not changed. * * @param other the integer to add to this integer. * @return the sum \a this plus \a other. */ NNativeInteger operator +(Native other) const; /** * Subtracts the given integer from this and returns the result. * This integer is not changed. * * @param other the integer to subtract from this integer. * @return the difference \a this minus \a other. */ NNativeInteger operator -(const NNativeInteger& other) const; /** * Subtracts the given integer from this and returns the result. * This integer is not changed. * * @param other the integer to subtract from this integer. * @return the difference \a this minus \a other. */ NNativeInteger operator -(Native other) const; /** * Multiplies this by the given integer and returns the * result. * This integer is not changed. * * @param other the integer to multiply by this integer. * @return the product \a this times \a other. */ NNativeInteger operator *(const NNativeInteger& other) const; /** * Multiplies this by the given integer and returns the * result. * This integer is not changed. * * @param other the integer to multiply by this integer. * @return the product \a this times \a other. */ NNativeInteger operator *(Native other) const; /** * Divides this by the given integer and returns the result. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is not changed. * * For a division routine that always rounds down, see divisionAlg(). * * \pre \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NNativeInteger operator /(const NNativeInteger& other) const; /** * Divides this by the given integer and returns the result. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is not changed. * * For a division routine that always rounds down, see divisionAlg(). * * \pre \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NNativeInteger operator /(Native other) const; /** * Divides this by the given integer and returns the result. * For native integers, this is identical to operator /. * * \pre \a other is not zero. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NNativeInteger divExact(const NNativeInteger& other) const; /** * Divides this by the given integer and returns the result. * For native integers, this is identical to operator /. * * \pre \a other is not zero. * * @param other the integer to divide this by. * @return the quotient \a this divided by \a other. */ NNativeInteger divExact(Native other) const; /** * Determines the remainder when this integer is divided by the * given integer. If non-zero, the result will have the same sign * as this integer. * This integer is not changed. * * For a division routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer to divide this by. * @return the remainder \a this modulo \a other. */ NNativeInteger operator %(const NNativeInteger& other) const; /** * Determines the remainder when this integer is divided by the * given integer. If non-zero, the result will have the same sign * as this integer. * This integer is not changed. * * For a division routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer to divide this by. * @return the remainder \a this modulo \a other. */ NNativeInteger operator %(Native other) const; /** * Uses the division algorithm to obtain a quotient and * remainder when dividing by the given integer. * * Suppose this integer is \a n and we pass the divisor \a d. * The division algorithm describes the result of * dividing \a n by \a d; in particular, it expresses * n = qd + r, where \a q is the quotient and * \a r is the remainder. * * The division algorithm is precise about which values of \a q * and \a r are chosen; in particular it chooses the unique \a r * in the range 0 <= r < |d|. * * Note that this differs from other division routines in this * class, in that it always rounds to give a non-negative remainder. * Thus NNativeInteger(-7).divisionAlg(3) gives quotient -3 and * remainder 2, whereas (-7)/3 gives quotient -2 and (-7)\%3 gives * remainder -1. * * The two results are passed back to the caller as follows: * The quotient \a q is passed back as the return value of the * function, and the remainder \a r is stored in the reference * argument \a r. * * In the special case where the given divisor is 0 (not * allowed by the usual division algorithm), this routine selects * quotient 0 and remainder \a n. * * @param divisor the divisor \a d. * @param remainder used to store the remainder \a r when the * functon returns. The initial value of this argument is ignored. * @return the quotient \a q. * * @author Ryan Budney & B.B. */ NNativeInteger divisionAlg( const NNativeInteger& divisor, NNativeInteger& remainder) const; /** * Determines the negative of this integer. * This integer is not changed. * * @return the negative of this integer. */ NNativeInteger operator -() const; /** * Adds the given integer to this. * This integer is changed to reflect the result. * * @param other the integer to add to this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator +=(const NNativeInteger& other); /** * Adds the given integer to this. * This integer is changed to reflect the result. * * @param other the integer to add to this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator +=(Native other); /** * Subtracts the given integer from this. * This integer is changed to reflect the result. * * @param other the integer to subtract from this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator -=(const NNativeInteger& other); /** * Subtracts the given integer from this. * This integer is changed to reflect the result. * * @param other the integer to subtract from this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator -=(Native other); /** * Multiplies the given integer by this. * This integer is changed to reflect the result. * * @param other the integer to multiply with this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator *=(const NNativeInteger& other); /** * Multiplies the given integer by this. * This integer is changed to reflect the result. * * @param other the integer to multiply with this integer. * @return a reference to this integer with its new value. */ NNativeInteger& operator *=(Native other); /** * Divides this by the given integer. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is changed to reflect the result. * * For a division routine that always rounds down, see divisionAlg(). * * \pre \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NNativeInteger& operator /=(const NNativeInteger& other); /** * Divides this by the given integer. * The result will be truncated to an integer, i.e. rounded * towards zero. * This integer is changed to reflect the result. * * For a division routine that always rounds down, see divisionAlg(). * * \pre \a other must be non-zero. * * \warning As I understand it, the direction of rounding for * native C/C++ integer division was fixed in the C++11 * specification, but left to the compiler implementation in * earlier versions of the specification; however, any modern * hardware should satisfy the C++11 rounding rule as described above. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NNativeInteger& operator /=(Native other); /** * Divides this by the given integer. * For native integers, this routine is identical to operator /=. * * \pre \a other is not zero. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NNativeInteger& divByExact(const NNativeInteger& other); /** * Divides this by the given integer. * For native integers, this routine is identical to operator /=. * * \pre \a other is not zero. * * @param other the integer to divide this by. * @return a reference to this integer with its new value. */ NNativeInteger& divByExact(Native other); /** * Reduces this integer modulo the given integer. * If non-zero, the result will have the same sign as the original * value of this integer. * This integer is changed to reflect the result. * * For a mod routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer modulo which this integer will be * reduced. * @return a reference to this integer with its new value. */ NNativeInteger& operator %=(const NNativeInteger& other); /** * Reduces this integer modulo the given integer. * If non-zero, the result will have the same sign as the original * value of this integer. * This integer is changed to reflect the result. * * For a mod routine that always returns a non-negative * remainder, see divisionAlg(). * * \pre \a other is not zero. * * \warning As I understand it, the sign of the result under * native C/C++ integer division when the second operand is * negative was fixed in the C++11 specification, but left to the * compiler implementation in earlier versions of the specification; * however, any modern hardware should satisfy the C++11 sign rule * as described above. * * @param other the integer modulo which this integer will be * reduced. * @return a reference to this integer with its new value. */ NNativeInteger& operator %=(Native other); /** * Negates this integer. * This integer is changed to reflect the result. */ void negate(); /** * Sets this integer to be the greatest common divisor of this * and the given integer. * * The result is guaranteed to be non-negative. As a * special case, gcd(0,0) is considered to be zero. * * @param other the integer whose greatest common divisor with * this will be found. */ void gcdWith(const NNativeInteger& other); /** * Determines the greatest common divisor of this and the given * integer. This integer is not changed. * * The result is guaranteed to be non-negative. As a * special case, gcd(0,0) is considered to be zero. * * @param other the integer whose greatest common divisor with * this will be found. * @return the greatest common divisor of this and the given * integer. */ NNativeInteger gcd(const NNativeInteger& other) const; template friend std::ostream& operator << (std::ostream& out, const NNativeInteger& large); }; /** * Writes the given integer to the given output stream. * * @param out the output stream to which to write. * @param i the integer to write. * @return a reference to \a out. */ template REGINA_API std::ostream& operator << (std::ostream& out, const NNativeInteger& i); /** * NNativeLong is a typedef for the NNativeInteger template class whose * underlying integer type is a native long. * * \ifacespython Not present. */ typedef NNativeInteger NNativeLong; /*@}*/ // Inline functions for NIntegerBase template inline NIntegerBase::NIntegerBase() : small_(0), large_(0) { } template inline NIntegerBase::NIntegerBase(int value) : small_(value), large_(0) { } template inline NIntegerBase::NIntegerBase(unsigned value) : small_(value) { // Detect overflow. if (small_ < 0) { large_ = new mpz_t; mpz_init_set_ui(large_, value); } else large_ = 0; } template inline NIntegerBase::NIntegerBase(long value) : small_(value), large_(0) { } template inline NIntegerBase::NIntegerBase(unsigned long value) : small_(value) { // Detect overflow. if (small_ < 0) { large_ = new mpz_t; mpz_init_set_ui(large_, value); } else large_ = 0; } template inline NIntegerBase::NIntegerBase( const NIntegerBase& value) { if (value.isInfinite()) { large_ = 0; makeInfinite(); } else if (value.large_) { large_ = new mpz_t; mpz_init_set(large_, value.large_); } else { small_ = value.small_; large_ = 0; } } template inline NIntegerBase::NIntegerBase( const NIntegerBase& value) { // If value is infinite, we cannot make this infinite. // This is why we insist via preconditions that value is finite. if (value.large_) { large_ = new mpz_t; mpz_init_set(large_, value.large_); } else { small_ = value.small_; large_ = 0; } } template template inline NIntegerBase::NIntegerBase( const NNativeInteger& value) : small_(value.nativeValue()), large_(0) { BOOST_STATIC_ASSERT(bytes % sizeof(long) == 0); if (sizeof(long) < bytes && value.nativeValue() != small_) { // It didn't fit. Take things one long at a time. unsigned blocks = bytes / sizeof(long); large_ = new mpz_t; mpz_init_set_si(large_, static_cast( value.nativeValue() >> ((blocks - 1) * 8 * sizeof(long)))); for (unsigned i = 2; i <= blocks; ++i) { mpz_mul_2exp(large_, large_, 8 * sizeof(long)); mpz_add_ui(large_, large_, static_cast( value.nativeValue() >> ((blocks - i) * 8 * sizeof(long)))); } } } template template inline typename IntOfSize::type NIntegerBase::nativeValue() const { typedef typename IntOfSize::type Native; typedef typename IntOfSize::utype UNative; BOOST_STATIC_ASSERT(bytes % sizeof(long) == 0); if (sizeof(long) >= bytes || ! large_) { // Suppose this integer lies within the given byte range. // If sizeof(long) >= bytes, then it can be made to fit inside a long. // If sizeof(long) < bytes but this is a native long, then we // have already got it inside a long. // Either way, we can just pass the long value across. return static_cast(longValue()); } // From here: this is a GMP integer, and the return type is too // large for a long. Take one long-sized chunk at a time. Native ans = 0; unsigned blocks = bytes / sizeof(long); mpz_t tmp; mpz_init_set(tmp, large_); for (unsigned i = 0; i < blocks - 1; ++i) { ans += (static_cast(mpz_get_ui(tmp)) << (i * 8 * sizeof(long))); mpz_fdiv_q_2exp(tmp, tmp, 8 * sizeof(long)); } ans += (static_cast(mpz_get_si(tmp)) << ((blocks - 1) * 8 * sizeof(long))); mpz_clear(tmp); return ans; } template inline NIntegerBase::~NIntegerBase() { if (large_) { mpz_clear(large_); delete[] large_; } } template inline bool NIntegerBase::isNative() const { return (! isInfinite()) && (! large_); } template inline bool NIntegerBase::isZero() const { return (! isInfinite()) && (((! large_) && (! small_)) || (large_ && mpz_sgn(large_) == 0)); } template inline int NIntegerBase::sign() const { return (isInfinite() ? 1 : large_ ? mpz_sgn(large_) : small_ > 0 ? 1 : small_ < 0 ? -1 : 0); } template <> inline bool NIntegerBase::isInfinite() const { return infinite_; } template <> inline bool NIntegerBase::isInfinite() const { return false; } template <> inline void NIntegerBase::makeInfinite() { infinite_ = true; if (large_) clearLarge(); } template <> inline void NIntegerBase::makeInfinite() { } template inline long NIntegerBase::longValue() const { return (large_ ? mpz_get_si(large_) : small_); } template inline NIntegerBase& NIntegerBase::operator =( const NIntegerBase& value) { if (value.isInfinite()) { makeInfinite(); return *this; } makeFinite(); if (value.large_) { if (large_) mpz_set(large_, value.large_); else { large_ = new mpz_t; mpz_init_set(large_, value.large_); } } else { small_ = value.small_; if (large_) clearLarge(); } return *this; } template inline NIntegerBase& NIntegerBase::operator =( const NIntegerBase& value) { // If value is infinite, we cannot make this infinite. // This is why we insist via preconditions that value is finite. makeFinite(); if (value.large_) { if (large_) mpz_set(large_, value.large_); else { large_ = new mpz_t; mpz_init_set(large_, value.large_); } } else { small_ = value.small_; if (large_) clearLarge(); } return *this; } template inline NIntegerBase& NIntegerBase::operator =(int value) { makeFinite(); small_ = value; if (large_) clearLarge(); return *this; } template inline NIntegerBase& NIntegerBase::operator =(unsigned value) { makeFinite(); small_ = value; // Did we overflow? if (small_ < 0) { // Yes, it's an overflow: just a bit too large for a signed long // (literally). if (large_) mpz_set_ui(large_, value); else { large_ = new mpz_t; mpz_init_set_ui(large_, value); } } else if (large_) { // No overflow, but we must clear out any old large integer value. clearLarge(); } return *this; } template inline NIntegerBase& NIntegerBase::operator =(long value) { makeFinite(); small_ = value; if (large_) clearLarge(); return *this; } template inline NIntegerBase& NIntegerBase::operator =(unsigned long value) { makeFinite(); small_ = value; // Did we overflow? if (small_ < 0) { // Yes, it's an overflow: just a bit too large for a signed long // (literally). if (large_) mpz_set_ui(large_, value); else { large_ = new mpz_t; mpz_init_set_ui(large_, value); } } else if (large_) { // No overflow, but we must clear out any old large integer value. clearLarge(); } return *this; } template inline NIntegerBase& NIntegerBase::operator =(const std::string& value) { return (*this) = value.c_str(); } template <> inline void NIntegerBase::swap(NIntegerBase& other) { // This should just work, since large_ is a pointer. std::swap(infinite_, other.infinite_); std::swap(small_, other.small_); std::swap(large_, other.large_); } template <> inline void NIntegerBase::swap(NIntegerBase& other) { // This should just work, since large_ is a pointer. std::swap(small_, other.small_); std::swap(large_, other.large_); } template inline bool NIntegerBase::operator ==( const NIntegerBase& rhs) const { if (isInfinite() && rhs.isInfinite()) return true; else if (isInfinite() || rhs.isInfinite()) return false; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) == 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) == 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) == 0); else return (small_ == rhs.small_); } } template inline bool NIntegerBase::operator ==( const NIntegerBase& rhs) const { // The types are different, so both cannot be infinity. if (isInfinite() || rhs.isInfinite()) return false; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) == 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) == 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) == 0); else return (small_ == rhs.small_); } } template inline bool NIntegerBase::operator ==(long rhs) const { if (isInfinite()) return false; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) == 0); else return (small_ == rhs); } template inline bool NIntegerBase::operator !=( const NIntegerBase& rhs) const { if (isInfinite() && rhs.isInfinite()) return false; else if (isInfinite() || rhs.isInfinite()) return true; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) != 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) != 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) != 0); else return (small_ != rhs.small_); } } template inline bool NIntegerBase::operator !=( const NIntegerBase& rhs) const { // The types are different, so both cannot be infinity. if (isInfinite() || rhs.isInfinite()) return true; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) != 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) != 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) != 0); else return (small_ != rhs.small_); } } template inline bool NIntegerBase::operator !=(long rhs) const { if (isInfinite()) return true; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) != 0); else return (small_ != rhs); } template inline bool NIntegerBase::operator <( const NIntegerBase& rhs) const { if (isInfinite()) return false; else if (rhs.isInfinite()) return true; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) < 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) < 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) > 0); // back-to-front else return (small_ < rhs.small_); } } template inline bool NIntegerBase::operator <(long rhs) const { if (isInfinite()) return false; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) < 0); else return (small_ < rhs); } template inline bool NIntegerBase::operator >( const NIntegerBase& rhs) const { if (rhs.isInfinite()) return false; else if (isInfinite()) return true; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) > 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) > 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) < 0); // back-to-front else return (small_ > rhs.small_); } } template inline bool NIntegerBase::operator >(long rhs) const { if (isInfinite()) return true; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) > 0); else return (small_ > rhs); } template inline bool NIntegerBase::operator <=( const NIntegerBase& rhs) const { if (rhs.isInfinite()) return true; else if (isInfinite()) return false; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) <= 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) <= 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) >= 0); // back-to-front else return (small_ <= rhs.small_); } } template inline bool NIntegerBase::operator <=(long rhs) const { if (isInfinite()) return false; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) <= 0); else return (small_ <= rhs); } template inline bool NIntegerBase::operator >=( const NIntegerBase& rhs) const { if (isInfinite()) return true; else if (rhs.isInfinite()) return false; else if (large_) { if (rhs.large_) return (mpz_cmp(large_, rhs.large_) >= 0); else return (mpz_cmp_si_cpp(large_, rhs.small_) >= 0); } else { if (rhs.large_) return (mpz_cmp_si_cpp(rhs.large_, small_) <= 0); // back-to-front else return (small_ >= rhs.small_); } } template inline bool NIntegerBase::operator >=(long rhs) const { if (isInfinite()) return true; else if (large_) return (mpz_cmp_si_cpp(large_, rhs) >= 0); else return (small_ >= rhs); } template inline NIntegerBase& NIntegerBase::operator ++() { if (! isInfinite()) { if (large_) mpz_add_ui(large_, large_, 1); else if (small_ != LONG_MAX) ++small_; else { // This is the point at which we overflow. forceLarge(); mpz_add_ui(large_, large_, 1); } } return *this; } template inline NIntegerBase NIntegerBase::operator ++(int) { if (isInfinite()) return *this; // Hrmph, just do the standard thing for now. // It's not clear how much microoptimisation will help..? NIntegerBase ans(*this); ++(*this); return ans; } template inline NIntegerBase& NIntegerBase::operator --() { if (! isInfinite()) { if (large_) mpz_sub_ui(large_, large_, 1); else if (small_ != LONG_MIN) --small_; else { // This is the point at which we overflow. forceLarge(); mpz_sub_ui(large_, large_, 1); } } return *this; } template inline NIntegerBase NIntegerBase::operator --(int) { if (isInfinite()) return *this; // Hrmph, just do the standard thing for now. // It's not clear how much microoptimisation will help..? NIntegerBase ans(*this); --(*this); return ans; } template inline NIntegerBase NIntegerBase::operator +( const NIntegerBase& other) const { if (isInfinite()) return *this; if (other.isInfinite()) return other; // Do the standard thing for now. NIntegerBase ans(*this); return ans += other; } template inline NIntegerBase NIntegerBase::operator +(long other) const { if (isInfinite()) return *this; // Do the standard thing for now. NIntegerBase ans(*this); return ans += other; } template inline NIntegerBase NIntegerBase::operator -( const NIntegerBase& other) const { if (isInfinite()) return *this; if (other.isInfinite()) return other; // Do the standard thing for now. NIntegerBase ans(*this); return ans -= other; } template inline NIntegerBase NIntegerBase::operator -(long other) const { if (isInfinite()) return *this; // Do the standard thing for now. NIntegerBase ans(*this); return ans -= other; } template inline NIntegerBase NIntegerBase::operator *( const NIntegerBase& other) const { if (isInfinite()) return *this; if (other.isInfinite()) return other; // Do the standard thing for now. NIntegerBase ans(*this); return ans *= other; } template inline NIntegerBase NIntegerBase::operator *(long other) const { if (isInfinite()) return *this; // Do the standard thing for now. NIntegerBase ans(*this); return ans *= other; } template inline NIntegerBase NIntegerBase::operator /( const NIntegerBase& other) const { if (isInfinite()) return *this; if (other.isInfinite()) return (long)0; if (other.isZero()) { NIntegerBase ans; ans.makeInfinite(); return ans; } // Do the standard thing for now. NIntegerBase ans(*this); return ans /= other; } template inline NIntegerBase NIntegerBase::operator /(long other) const { if (isInfinite()) return *this; if (other == 0) { NIntegerBase ans; ans.makeInfinite(); return ans; } // Do the standard thing for now. NIntegerBase ans(*this); return ans /= other; } template inline NIntegerBase NIntegerBase::divExact( const NIntegerBase& other) const { // Do the standard thing for now. NIntegerBase ans(*this); return ans.divByExact(other); } template inline NIntegerBase NIntegerBase::divExact(long other) const { // Do the standard thing for now. NIntegerBase ans(*this); return ans.divByExact(other); } template inline NIntegerBase NIntegerBase::operator %( const NIntegerBase& other) const { // Do the standard thing for now. NIntegerBase ans(*this); return ans %= other; } template inline NIntegerBase NIntegerBase::operator %(long other) const { // Do the standard thing for now. NIntegerBase ans(*this); return ans %= other; } template inline NIntegerBase NIntegerBase::operator -() const { if (isInfinite()) return *this; if (large_) { NIntegerBase ans; ans.large_ = new mpz_t; mpz_init(ans.large_); mpz_neg(ans.large_, large_); return ans; } else if (small_ == LONG_MIN) { // Overflow, just. NIntegerBase ans; ans.large_ = new mpz_t; mpz_init_set_si(ans.large_, small_); mpz_neg(ans.large_, ans.large_); return ans; } else return NIntegerBase(-small_); } template inline NIntegerBase& NIntegerBase::operator +=( const NIntegerBase& other) { if (isInfinite()) return *this; else if (other.isInfinite()) { makeInfinite(); return *this; } if (other.large_) { if (! large_) forceLarge(); mpz_add(large_, large_, other.large_); return *this; } else return (*this) += other.small_; } template inline NIntegerBase& NIntegerBase::operator -=( const NIntegerBase& other) { if (isInfinite()) return *this; else if (other.isInfinite()) { makeInfinite(); return *this; } if (other.large_) { if (! large_) forceLarge(); mpz_sub(large_, large_, other.large_); return *this; } else return (*this) -= other.small_; } template inline void NIntegerBase::negate() { if (isInfinite()) return; if (large_) mpz_neg(large_, large_); else if (small_ == LONG_MIN) { // Overflow, just. forceLarge(); mpz_neg(large_, large_); } else small_ = -small_; } template inline NIntegerBase NIntegerBase::abs() const { if (isInfinite()) return *this; if (large_) { NIntegerBase ans; ans.large_ = new mpz_t; mpz_init_set(ans.large_, large_); mpz_abs(ans.large_, large_); return ans; } else if (small_ == LONG_MIN) { // Overflow, just. NIntegerBase ans; ans.large_ = new mpz_t; mpz_init_set_si(ans.large_, small_); mpz_neg(ans.large_, ans.large_); return ans; } else return NIntegerBase(small_ >= 0 ? small_ : - small_); } template NIntegerBase NIntegerBase::gcd( const NIntegerBase& other) const { // Do the standard thing for now. NIntegerBase ans(*this); ans.gcdWith(other); return ans; } template NIntegerBase NIntegerBase::lcm( const NIntegerBase& other) const { // Do the standard thing for now. NIntegerBase ans(*this); ans.lcmWith(other); return ans; } template inline NIntegerBase operator +(long lhs, const NIntegerBase& rhs) { return rhs + lhs; } template inline NIntegerBase operator *(long lhs, const NIntegerBase& rhs) { return rhs * lhs; } template inline void NIntegerBase::setRaw(mpz_srcptr fromData) { makeFinite(); if (! large_) { large_ = new mpz_t; mpz_init_set(large_, fromData); } else { mpz_set(large_, fromData); } } template inline mpz_srcptr NIntegerBase::rawData() const { // Cast away the const, since we are not changing the mathematical value. // We are, however, bulking up the representation. const_cast&>(*this).makeLarge(); return large_; } template inline mpz_ptr NIntegerBase::rawData() { makeLarge(); return large_; } template inline void NIntegerBase::makeLarge() { if (! large_) forceLarge(); } template inline void NIntegerBase::tryReduce() { if (large_ && mpz_cmp_si(large_, LONG_MAX) <= 0 && mpz_cmp_si(large_, LONG_MIN) >= 0) forceReduce(); } template inline void NIntegerBase::forceLarge() { large_ = new mpz_t; mpz_init_set_si(large_, small_); } template inline void NIntegerBase::clearLarge() { mpz_clear(large_); delete[] large_; large_ = 0; } template inline void NIntegerBase::forceReduce() { small_ = mpz_get_si(large_); mpz_clear(large_); delete[] large_; large_ = 0; } template <> inline void NIntegerBase::makeFinite() { infinite_ = false; } template <> inline void NIntegerBase::makeFinite() { } // This definition must come *after* the definition of makeInfinite() // to keep the compiler happy. template <> inline NIntegerBase::NIntegerBase(bool, bool) : large_(0) { // The infinity constructor. makeInfinite(); } // Inline functions for NNativeInteger template inline NNativeInteger::NNativeInteger() : data_(0) { } template inline NNativeInteger::NNativeInteger(Native value) : data_(value) { } template inline NNativeInteger::NNativeInteger( const NNativeInteger& value) : data_(value.data_) { } template template inline NNativeInteger::NNativeInteger( const NIntegerBase& value) : data_(value.template nativeValue()) { } template inline bool NNativeInteger::isZero() const { return (data_ == 0); } template inline int NNativeInteger::sign() const { return (data_ > 0 ? 1 : data_ < 0 ? -1 : 0); } template inline typename NNativeInteger::Native NNativeInteger:: nativeValue() const { return data_; } template inline NNativeInteger& NNativeInteger::operator =( const NNativeInteger& value) { data_ = value.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator =(Native value) { data_ = value; return *this; } template inline void NNativeInteger::swap(NNativeInteger& other) { std::swap(data_, other.data_); } template inline bool NNativeInteger::operator ==( const NNativeInteger& rhs) const { return (data_ == rhs.data_); } template inline bool NNativeInteger::operator ==(Native rhs) const { return (data_ == rhs); } template inline bool NNativeInteger::operator !=( const NNativeInteger& rhs) const { return (data_ != rhs.data_); } template inline bool NNativeInteger::operator !=(Native rhs) const { return (data_ != rhs); } template inline bool NNativeInteger::operator <( const NNativeInteger& rhs) const { return (data_ < rhs.data_); } template inline bool NNativeInteger::operator <(Native rhs) const { return (data_ < rhs); } template inline bool NNativeInteger::operator >( const NNativeInteger& rhs) const { return (data_ > rhs.data_); } template inline bool NNativeInteger::operator >(Native rhs) const { return (data_ > rhs); } template inline bool NNativeInteger::operator <=( const NNativeInteger& rhs) const { return (data_ <= rhs.data_); } template inline bool NNativeInteger::operator <=(Native rhs) const { return (data_ <= rhs); } template inline bool NNativeInteger::operator >=( const NNativeInteger& rhs) const { return (data_ >= rhs.data_); } template inline bool NNativeInteger::operator >=(Native rhs) const { return (data_ >= rhs); } template inline NNativeInteger& NNativeInteger::operator ++() { ++data_; return *this; } template inline NNativeInteger NNativeInteger::operator ++(int) { return NNativeInteger(data_++); } template inline NNativeInteger& NNativeInteger::operator --() { --data_; return *this; } template inline NNativeInteger NNativeInteger::operator --(int) { return NNativeInteger(data_--); } template inline NNativeInteger NNativeInteger::operator +( const NNativeInteger& other) const { return NNativeInteger(data_ + other.data_); } template inline NNativeInteger NNativeInteger::operator +( Native other) const { return NNativeInteger(data_ + other); } template inline NNativeInteger NNativeInteger::operator -( const NNativeInteger& other) const { return NNativeInteger(data_ - other.data_); } template inline NNativeInteger NNativeInteger::operator -( Native other) const { return NNativeInteger(data_ - other); } template inline NNativeInteger NNativeInteger::operator *( const NNativeInteger& other) const { return NNativeInteger(data_ * other.data_); } template inline NNativeInteger NNativeInteger::operator *( Native other) const { return NNativeInteger(data_ * other); } template inline NNativeInteger NNativeInteger::operator /( const NNativeInteger& other) const { return NNativeInteger(data_ / other.data_); } template inline NNativeInteger NNativeInteger::operator /( Native other) const { return NNativeInteger(data_ / other); } template inline NNativeInteger NNativeInteger::divExact( const NNativeInteger& other) const { return NNativeInteger(data_ / other.data_); } template inline NNativeInteger NNativeInteger::divExact( Native other) const { return NNativeInteger(data_ / other); } template inline NNativeInteger NNativeInteger::operator %( const NNativeInteger& other) const { return NNativeInteger(data_ % other.data_); } template inline NNativeInteger NNativeInteger::operator %( Native other) const { return NNativeInteger(data_ % other); } template inline NNativeInteger NNativeInteger::divisionAlg( const NNativeInteger& divisor, NNativeInteger& remainder) const { if (divisor == 0) { remainder.data_ = data_; return 0; } // Native integer division could leave a negative remainder // regardless of the sign of the divisor (I think the standard // indicates that the decision is based on the sign of *this?). NNativeInteger quotient = data_ / divisor.data_; remainder = data_ - (quotient.data_ * divisor.data_); if (remainder.data_ < 0) { if (divisor.data_ > 0) { remainder.data_ += divisor.data_; --quotient.data_; } else { remainder.data_ -= divisor.data_; ++quotient.data_; } } return quotient; } template inline NNativeInteger NNativeInteger::operator -() const { return NNativeInteger(- data_); } template inline NNativeInteger& NNativeInteger::operator += ( const NNativeInteger& other) { data_ += other.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator += ( Native other) { data_ += other; return *this; } template inline NNativeInteger& NNativeInteger::operator -= ( const NNativeInteger& other) { data_ -= other.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator -= ( Native other) { data_ -= other; return *this; } template inline NNativeInteger& NNativeInteger::operator *= ( const NNativeInteger& other) { data_ *= other.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator *= ( Native other) { data_ *= other; return *this; } template inline NNativeInteger& NNativeInteger::operator /= ( const NNativeInteger& other) { data_ /= other.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator /= ( Native other) { data_ /= other; return *this; } template inline NNativeInteger& NNativeInteger::divByExact( const NNativeInteger& other) { data_ /= other.data_; return *this; } template inline NNativeInteger& NNativeInteger::divByExact(Native other) { data_ /= other; return *this; } template inline NNativeInteger& NNativeInteger::operator %= ( const NNativeInteger& other) { data_ %= other.data_; return *this; } template inline NNativeInteger& NNativeInteger::operator %= ( Native other) { data_ %= other; return *this; } template inline void NNativeInteger::negate() { data_ = - data_; } template void NNativeInteger::gcdWith(const NNativeInteger& other) { Native a = data_; Native b = other.data_; if (a < 0) a = -a; if (b < 0) b = -b; /** * Now everything is non-negative. * The following code is based on Stein's binary GCD algorithm. */ if (! a) { data_ = b; return; } if (! b) { data_ = a; return; } // Compute the largest common power of 2. int pow2; for (pow2 = 0; ! ((a | b) & 1); ++pow2) { a >>= 1; b >>= 1; } // Strip out all remaining powers of 2 from a and b. while (! (a & 1)) a >>= 1; while (! (b & 1)) b >>= 1; while (a != b) { // INV: a and b are both odd and non-zero. if (a < b) { b -= a; do b >>= 1; while (! (b & 1)); } else { a -= b; do a >>= 1; while (! (a & 1)); } } data_ = (a << pow2); } template inline NNativeInteger NNativeInteger::gcd( const NNativeInteger& other) const { NNativeInteger ans(data_); ans.gcdWith(other); return ans; } template inline std::ostream& operator << (std::ostream& out, const NNativeInteger& i) { return out << i.data_; } #ifdef INT128_AVAILABLE template <> inline std::ostream& operator << (std::ostream& out, const NNativeInteger<16>& i) { // The standard library does not support 128-bit integers (at least not // always). Go through NInteger and GMP instead (for now). return out << NInteger(i); } #endif } // namespace regina #endif regina-4.95/engine/maths/nlargeinteger.h000644 000765 000024 00000005201 12234011536 020162 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nlargeinteger.h * \brief A deprecated header for dealing with arbitrary precision integers. */ #ifndef __NLARGEINTEGER_H #ifndef __DOXYGEN #define __NLARGEINTEGER_H #endif #warning This header is deprecated; please use maths/ninteger.h instead. #include "maths/ninteger.h" namespace regina { /** * \weakgroup maths * @{ */ /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/nmatrix.h000644 000765 000024 00000063653 12234011536 017035 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nmatrix.h * \brief Deals with matrices of elements of various types. */ #ifndef __NMATRIX_H #ifndef __DOXYGEN #define __NMATRIX_H #endif #include #include #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a matrix of elements of the given type T. * * \pre Type T has a default constructor and overloads the assignment * (=) operator. * \pre An element t of type T can be written to an output stream * out using the standard expression out << t. * * \ifacespython Not present, although the subclass NMatrixInt is. */ template class NMatrix { protected: unsigned long nRows; /**< The number of rows in the matrix. */ unsigned long nCols; /**< The number of columns in the matrix. */ T** data; /**< The actual entries in the matrix. * data[r][c] is the element in row \a r, * column \a c. */ public: /** * Creates a new matrix of the given size. * All entries will be initialised using their default * constructors. * * \pre The given number of rows and columns are * both strictly positive. * * @param rows the number of rows in the new matrix. * @param cols the number of columns in the new matrix. */ NMatrix(unsigned long rows, unsigned long cols) : nRows(rows), nCols(cols), data(new T*[rows]){ for (unsigned long i = 0; i < rows; i++) data[i] = new T[cols]; } /** * Creates a new matrix that is a clone of the given matrix. * * @param cloneMe the matrix to clone. */ NMatrix(const NMatrix& cloneMe) : nRows(cloneMe.nRows), nCols(cloneMe.nCols), data(new T*[cloneMe.nRows]) { unsigned long r, c; for (r = 0; r < nRows; r++) { data[r] = new T[nCols]; for (c = 0; c < nCols; c++) data[r][c] = cloneMe.data[r][c]; } } /** * Destroys this matrix. */ virtual ~NMatrix() { for (unsigned long i = 0; i < nRows; i++) delete[] data[i]; delete[] data; } /** * Sets every entry in the matrix to the given value. * * @param value the value to assign to each entry. */ void initialise(const T& value) { unsigned long r, c; for (r = 0; r < nRows; r++) for (c = 0; c < nCols; c++) data[r][c] = value; } #ifdef __DOXYGEN /** * A Python-only routine that fills the matrix with the given * set of elements. * * The argument \a allValues must be a Python list of length * rows() * columns(). Its values will be inserted into the * matrix row by row (i.e., the first row will be filled, then * the second row, and so on). * * \ifacescpp Not available; this routine is for Python only. * * @param allValues the individual elements to place into the matrix. */ void initialise(List allValues); #endif /** * Returns the number of rows in this matrix. * * @return the number of rows. */ unsigned long rows() const { return nRows; } /** * Returns the number of columns in this matrix. * * @return the number of columns. */ unsigned long columns() const { return nCols; } /** * Returns the entry at the given row and column. * Rows and columns are numbered beginning at zero. * * \pre \a row is between 0 and rows()-1 inclusive. * \pre \a column is between 0 and columns()-1 inclusive. * * \ifacespython Although the entry() routine gives direct * read-write access to matrix elements, the syntax * matrix.entry(row, column) = value still cannot be * used in python to set a matrix element directly. For this, * you can use the syntax matrix.set(row, column, value). * This set() routine returns nothing, and is provided for python * only (i.e., it is not part of the C++ calculation engine). * * @param row the row of the desired entry. * @param column the column of the desired entry. * @return a reference to the entry in the given row and column. */ T& entry(unsigned long row, unsigned long column) { return data[row][column]; } /** * Returns the entry at the given row and column. * Rows and columns are numbered beginning at zero. * * \pre \a row is between 0 and rows()-1 inclusive. * \pre \a column is between 0 and columns()-1 inclusive. * * \ifacespython Not present, although the non-const form of * this routine is. * * @param row the row of the desired entry. * @param column the column of the desired entry. * @return a reference to the entry in the given row and column. */ const T& entry(unsigned long row, unsigned long column) const { return data[row][column]; } /** * Determines whether this and the given matrix are identical. * * Two matrices are identical if and only if (i) their dimensions * are the same, and (ii) the corresponding elements of each * matrix are equal. * * Note that this routine can happily deal with two matrices of * different dimensions (in which case it will always return * \c false). * * This routine returns \c true if and only if the inequality operator * (!=) returns \c false. * * \pre The type \a T provides an equality operator (==). * * @param other the matrix to compare with this. * @return \c true if the matrices are equal as described above, * or \c false otherwise. */ bool operator == (const NMatrix& other) const { if (nRows != other.nRows || nCols != other.nCols) return false; unsigned long r, c; for (r = 0; r < nRows; ++r) for (c = 0; c < nCols; ++c) if (! (data[r][c] == other.data[r][c])) return false; return true; } /** * Determines whether this and the given matrix are different. * * Two matrices are different if either (i) their dimensions * differ, or (ii) the corresponding elements of each matrix differ * in at least one location. * * Note that this routine can happily deal with two matrices of * different dimensions (in which case it will always return * \c true). * * This routine returns \c true if and only if the equality operator * (==) returns \c false. * * \pre The type \a T provides an equality operator (==). * * @param other the matrix to compare with this. * @return \c true if the matrices are different as described above, * or \c false otherwise. */ bool operator != (const NMatrix& other) const { return ! ((*this) == other); } /** * Writes a complete representation of the matrix to the given * output stream. * Each row will be written on a separate line with elements in * each row separated by single spaces. * * \ifacespython Not present, even if a subclass of NMatrix * is mirrored and its inherited routines are mirrored also. * * @param out the output stream to which to write. */ virtual void writeMatrix(std::ostream& out) const { unsigned long r, c; for (r = 0; r < nRows; r++) { for (c = 0; c < nCols; c++) { if (c > 0) out << ' '; out << data[r][c]; } out << '\n'; } } /** * Swaps the elements of the two given rows in the matrix. * * \pre The two given rows are between 0 and rows()-1 inclusive. * * @param first the first row to swap. * @param second the second row to swap. */ void swapRows(unsigned long first, unsigned long second) { T tmp; for (unsigned long i = 0; i < nCols; i++) { tmp = data[first][i]; data[first][i] = data[second][i]; data[second][i] = tmp; } } /** * Swaps the elements of the two given columns in the matrix. * * \pre The two given columns are between 0 and columns()-1 inclusive. * * @param first the first column to swap. * @param second the second column to swap. */ void swapColumns(unsigned long first, unsigned long second) { T tmp; for (unsigned long i = 0; i < nRows; i++) { tmp = data[i][first]; data[i][first] = data[i][second]; data[i][second] = tmp; } } }; /** * Represents a matrix of elements from a given ring T. * * Note that many important functions (such as entry()) are inherited * from the parent class NMatrix, and are not documented again here. * * \pre Type T has a default constructor and overloads the assignment * (=) operator. * \pre An element t of type T can be written to an output stream * out using the standard expression out << t. * \pre Type T provides binary operators +, - and * * and unary operators +=, -= and *=. * \pre Type T has a long integer constructor. That is, if \c a is of type T, * then \c a can be initialised to a long integer \c l using a(l). * Here the value 1 refers to the multiplicative identity in the ring T. * * \ifacespython Not present, although the subclass NMatrixInt is. */ template class NMatrixRing : public NMatrix { public: static T zero; /**< Zero in the underlying ring. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ static T one; /**< One (the multiplicative identity) in the underlying ring. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ public: /** * Creates a new matrix of the given size. * All entries will be initialised using their default * constructors. * * \pre The given number of rows and columns are * both strictly positive. * * @param rows the number of rows in the new matrix. * @param cols the number of columns in the new matrix. */ NMatrixRing(unsigned long rows, unsigned long cols) : NMatrix(rows, cols) { } /** * Creates a new matrix that is a clone of the given matrix. * * @param cloneMe the matrix to clone. */ NMatrixRing(const NMatrix& cloneMe) : NMatrix(cloneMe) { } /** * Turns this matrix into an identity matrix. * This matrix need not be square; after this routine it will * have entry(r,c) equal to one if * r == c and zero otherwise. */ void makeIdentity() { this->initialise(zero); for (unsigned long i = 0; i < this->nRows && i < this->nCols; i++) this->data[i][i] = one; } /** * Determines whether this matrix is a square identity matrix. * * If this matrix is square, isIdentity() will return \c true if * and only if the matrix has ones in the main diagonal and zeroes * everywhere else. * * If this matrix is not square, isIdentity() will always return * \c false (even if makeIdentity() was called earlier). * * @return \c true if and only if this is a square identity matrix. */ bool isIdentity() const { if (this->nRows != this->nCols) return false; unsigned long r, c; for (r = 0; r < this->nRows; ++r) for (c = 0; c < this->nCols; ++c) { if (r == c && this->data[r][c] != one) return false; if (r != c && this->data[r][c] != zero) return false; } return true; } /** * Determines whether this is the zero matrix. * * @return \c true if and only if all entries in the matrix are zero. */ bool isZero() const { for (size_t r=0; rnRows; ++r) for (size_t c=0; cnCols; ++c) if (this->data[r][c] != zero) return false; return true; } /** * Adds the given source row to the given destination row. * * \pre The two given rows are distinct and between 0 and * rows()-1 inclusive. * * @param source the row to add. * @param dest the row that will be added to. */ void addRow(unsigned long source, unsigned long dest) { for (unsigned long i = 0; i < this->nCols; i++) this->data[dest][i] += this->data[source][i]; } /** * Adds the given number of copies of the given source row to * the given destination row. * * Note that \a copies is passed by value in case it is an * element of the row to be changed. * * \pre The two given rows are distinct and between 0 and * rows()-1 inclusive. * * @param source the row to add. * @param dest the row that will be added to. * @param copies the number of copies of \a source to add to * \a dest. */ void addRow(unsigned long source, unsigned long dest, T copies) { for (unsigned long i = 0; i < this->nCols; i++) this->data[dest][i] += copies * this->data[source][i]; } /** * Adds the given source column to the given destination column. * * \pre The two given columns are distinct and between 0 and * columns()-1 inclusive. * * @param source the columns to add. * @param dest the column that will be added to. */ void addCol(unsigned long source, unsigned long dest) { for (unsigned long i = 0; i < this->nRows; i++) this->data[i][dest] += this->data[i][source]; } /** * Adds the given number of copies of the given source column to * the given destination column. * * Note that \a copies is passed by value in case it is an * element of the row to be changed. * * \pre The two given columns are distinct and between 0 and * columns()-1 inclusive. * * @param source the columns to add. * @param dest the column that will be added to. * @param copies the number of copies of \a source to add to * \a dest. */ void addCol(unsigned long source, unsigned long dest, T copies) { for (unsigned long i = 0; i < this->nRows; i++) this->data[i][dest] += copies * this->data[i][source]; } /** * Multiplies the given row by the given factor. * * Note that \a factor is passed by value in case it is an * element of the row to be changed. * * \pre The given row is between 0 and rows()-1 inclusive. * * @param row the row to work with. * @param factor the factor by which to multiply the given row. */ void multRow(unsigned long row, T factor) { for (unsigned long i = 0; i < this->nCols; i++) this->data[row][i] *= factor; } /** * Multiplies the given column by the given factor. * * Note that \a factor is passed by value in case it is an * element of the row to be changed. * * \pre The given column is between 0 and columns()-1 inclusive. * * @param column the column to work with. * @param factor the factor by which to multiply the given column. */ void multCol(unsigned long column, T factor) { for (unsigned long i = 0; i < this->nRows; i++) this->data[i][column] *= factor; } /** * Multiplies this by the given matrix, and returns the result. * This matrix is not changed. * * \pre The number of columns in this matrix equals the number * of rows in the given matrix. * * \warning The returned matrix will be of the exact class * NMatrixRing, even if both this and \a other are of some common * subclass of NMatrixRing. If you need a subclass to be returned, * consider calling multiplyAs() instead. * * \ifacespython The multiplication operator for a subclass (such as * NMatrixInt) will return a new matrix of that same subclass. * That is, the python multiplication operator really calls * multiplyAs(), not this routine. * * @param other the matrix by which to multiply this matrix. * @return a newly allocated matrix representing * this * other. */ std::auto_ptr > operator * (const NMatrixRing& other) const { std::auto_ptr > ans(new NMatrixRing( this->nRows, other.nCols)); unsigned long row, col, k; for (row = 0; row < this->nRows; row++) for (col = 0; col < other.nCols; col++) { ans->data[row][col] = zero; for (k = 0; k < this->nCols; k++) ans->data[row][col] += (this->data[row][k] * other.data[k][col]); } return ans; } /** * Multiplies this by the given matrix, and returns a new matrix of * subclass \a MatrixClass. This matrix is not changed. * * \pre The number of columns in this matrix equals the number * of rows in the given matrix. * \pre The class \a MatrixClass is a subclass of NMatrixRing, * and can be fully initialised by calling the two-argument constructor * (passing the row and column counts) and then settng individual * elements via \a data[r][c]. In particular, there should not be any * new data members that need explicit initialisation. * * \ifacespython Not present, but the python multiplication operator * performs the same task (see the python notes for operator *). * * @param other the matrix by which to multiply this matrix. * @return a newly allocated matrix representing * this * other. */ template std::auto_ptr multiplyAs(const NMatrixRing& other) const { std::auto_ptr ans(new MatrixClass( this->nRows, other.nCols)); unsigned long row, col, k; for (row = 0; row < this->nRows; row++) for (col = 0; col < other.nCols; col++) { ans->data[row][col] = zero; for (k = 0; k < this->nCols; k++) ans->data[row][col] += (this->data[row][k] * other.data[k][col]); } return ans; } /** * Evaluates the determinant of the matrix. * * This algorithm has quartic complexity, and uses the dynamic * programming approach of Mahajan and Vinay. For further * details, see Meena Mahajan and V. Vinay, "Determinant: * Combinatorics, algorithms, and complexity", Chicago J. Theor. * Comput. Sci., Vol. 1997, Article 5. * * \pre This is a square matrix. * * @return the determinant of this matrix. */ T det() const { unsigned long n = this->nRows; // Just in case... if (n != this->nCols || n == 0) return zero; T* partial[2]; partial[0] = new T[n * n]; partial[1] = new T[n * n]; unsigned long len, head, curr, prevHead, prevCurr; // Treat the smallest cases of len = 1 separately. int layer = 0; for (head = 0; head < n; head++) { partial[0][head + head * n] = one; for (curr = head + 1; curr < n; curr++) partial[0][head + curr * n] = zero; } // Work up through incrementing values of len. for (len = 2; len <= n; len++) { layer ^= 1; for (head = 0; head < n; head++) { // If curr == head, we need to open a new clow. partial[layer][head + head * n] = zero; for (prevHead = 0; prevHead < head; prevHead++) for (prevCurr = prevHead; prevCurr < n; prevCurr++) partial[layer][head + head * n] -= (partial[layer ^ 1][prevHead + prevCurr * n] * this->data[prevCurr][prevHead]); // If curr > head, we need to continue an existing clow. for (curr = head + 1; curr < n; curr++) { partial[layer][head + curr * n] = zero; for (prevCurr = head; prevCurr < n; prevCurr++) partial[layer][head + curr * n] += (partial[layer ^ 1][head + prevCurr * n] * this->data[prevCurr][curr]); } } } // All done. Sum up the determinant. T ans = zero; for (head = 0; head < n; head++) for (curr = head; curr < n; curr++) ans += (partial[layer][head + curr * n] * this->data[curr][head]); delete[] partial[0]; delete[] partial[1]; return (n % 2 == 0 ? -ans : ans); } }; template T NMatrixRing::zero(0L); /**< Zero in the underlying ring. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ template T NMatrixRing::one(1L); /**< One (the multiplicative identity) in the underlying ring. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/nmatrix2.cpp000644 000765 000024 00000016446 12234011536 017450 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nmatrix2.h" namespace regina { NMatrix2 NMatrix2::inverse() const { long det = data[0][0] * data[1][1] - data[0][1] * data[1][0]; if (det == 1) return NMatrix2(data[1][1], -data[0][1], -data[1][0], data[0][0]); else if (det == -1) return NMatrix2(-data[1][1], data[0][1], data[1][0], -data[0][0]); else return NMatrix2(); } NMatrix2& NMatrix2::operator *= (const NMatrix2& other) { long tmp00 = data[0][0] * other.data[0][0] + data[0][1] * other.data[1][0]; long tmp01 = data[0][0] * other.data[0][1] + data[0][1] * other.data[1][1]; long tmp10 = data[1][0] * other.data[0][0] + data[1][1] * other.data[1][0]; long tmp11 = data[1][0] * other.data[0][1] + data[1][1] * other.data[1][1]; data[0][0] = tmp00; data[0][1] = tmp01; data[1][0] = tmp10; data[1][1] = tmp11; return *this; } bool NMatrix2::invert() { long det = data[0][0] * data[1][1] - data[0][1] * data[1][0]; if (det == 1) { long tmp = data[0][0]; data[0][0] = data[1][1]; data[1][1] = tmp; data[0][1] = -data[0][1]; data[1][0] = -data[1][0]; return true; } else if (det == -1) { long tmp = data[0][0]; data[0][0] = -data[1][1]; data[1][1] = -tmp; return true; } else return false; } bool simpler(const NMatrix2& m1, const NMatrix2& m2) { long maxAbs1 = 0, maxAbs2 = 0; unsigned nZeroes1 = 0, nZeroes2 = 0; unsigned nNeg1 = 0, nNeg2 = 0; int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { if (m1[i][j] > maxAbs1) maxAbs1 = m1[i][j]; if (m1[i][j] < -maxAbs1) maxAbs1 = -m1[i][j]; if (m2[i][j] > maxAbs2) maxAbs2 = m2[i][j]; if (m2[i][j] < -maxAbs2) maxAbs2 = -m2[i][j]; if (m1[i][j] == 0) nZeroes1++; else if (m1[i][j] < 0) nNeg1++; if (m2[i][j] == 0) nZeroes2++; else if (m2[i][j] < 0) nNeg2++; } if (maxAbs1 < maxAbs2) return true; if (maxAbs1 > maxAbs2) return false; if (nZeroes1 > nZeroes2) return true; if (nZeroes1 < nZeroes2) return false; if (nNeg1 < nNeg2) return true; if (nNeg1 > nNeg2) return false; // Go lexicograhpic. for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (m1[i][j] < m2[i][j]) return true; else if (m1[i][j] > m2[i][j]) return false; // They're the same. return false; } bool simpler(const NMatrix2& pair1first, const NMatrix2& pair1second, const NMatrix2& pair2first, const NMatrix2& pair2second) { long maxAbs0 = 0, maxAbs1 = 0; unsigned nZeroes0 = 0, nZeroes1 = 0; unsigned nNeg0 = 0, nNeg1 = 0; int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { if (pair1first[i][j] > maxAbs0) maxAbs0 = pair1first[i][j]; if (pair1first[i][j] < -maxAbs0) maxAbs0 = -pair1first[i][j]; if (pair1second[i][j] > maxAbs0) maxAbs0 = pair1second[i][j]; if (pair1second[i][j] < -maxAbs0) maxAbs0 = -pair1second[i][j]; if (pair2first[i][j] > maxAbs1) maxAbs1 = pair2first[i][j]; if (pair2first[i][j] < -maxAbs1) maxAbs1 = -pair2first[i][j]; if (pair2second[i][j] > maxAbs1) maxAbs1 = pair2second[i][j]; if (pair2second[i][j] < -maxAbs1) maxAbs1 = -pair2second[i][j]; if (pair1first[i][j] == 0) nZeroes0++; else if (pair1first[i][j] < 0) nNeg0++; if (pair1second[i][j] == 0) nZeroes0++; else if (pair1second[i][j] < 0) nNeg0++; if (pair2first[i][j] == 0) nZeroes1++; else if (pair2first[i][j] < 0) nNeg1++; if (pair2second[i][j] == 0) nZeroes1++; else if (pair2second[i][j] < 0) nNeg1++; } if (maxAbs0 < maxAbs1) return true; if (maxAbs0 > maxAbs1) return false; if (nZeroes0 > nZeroes1) return true; if (nZeroes0 < nZeroes1) return false; if (nNeg0 < nNeg1) return true; if (nNeg0 > nNeg1) return false; // Go lexicograhpic. for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (pair1first[i][j] < pair2first[i][j]) return true; else if (pair1first[i][j] > pair2first[i][j]) return false; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (pair1second[i][j] < pair2second[i][j]) return true; else if (pair1second[i][j] > pair2second[i][j]) return false; // They're the same. return false; } } // namespace regina regina-4.95/engine/maths/nmatrix2.h000644 000765 000024 00000042127 12234011536 017110 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NMATRIX2_H #ifndef __DOXYGEN #define __NMATRIX2_H #endif #include #include "regina-core.h" /*! \file maths/nmatrix2.h * \brief Deals with 2x2 integer matrices. */ namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a 2-by-2 integer matrix. The advantages of using this * class over the larger NMatrixInt and friends is that this class has * less overhead and offers additional mathematical support routines * that the larger classes do not. * * This class only contains four long integers, and so it may be considered * small enough to pass about by value. */ class REGINA_API NMatrix2 { private: long data[2][2]; /**< The four entries in this matrix, indexed by row and then by column. */ public: /** * Initialises to the zero matrix. */ NMatrix2(); /** * Initialises to a copy of the given matrix. * * @param cloneMe the matrix to be copied. */ NMatrix2(const NMatrix2& cloneMe); /** * Initialises to the given integer values. * * Each given integer values[r][c] will be placed in * row \a r, column \a c. * * \ifacespython Not present. * * @param values the four values to insert into the new matrix. */ NMatrix2(const long values[2][2]); /** * Initialises to the given integer values. * * @param val00 the value to place in row 0, column 0. * @param val01 the value to place in row 0, column 1. * @param val10 the value to place in row 1, column 0. * @param val11 the value to place in row 1, column 1. */ NMatrix2(long val00, long val01, long val10, long val11); /** * Sets this matrix to be a copy of the given matrix. * * @param cloneMe the matrix to be copied. * @return a reference to this matrix. */ NMatrix2& operator = (const NMatrix2& cloneMe); /** * Sets the elements of this matrix to the given integer values. * * Each given integer values[r][c] will be placed in * row \a r, column \a c. * * \ifacespython Not present. * * @param values the four values to copy into this matrix. * @return a reference to this matrix. */ NMatrix2& operator = (const long values[2][2]); /** * Returns a single row of this matrix. * * This means that the integer in row \a r, column \a c can be * accessed as myMatrix[r][c] (where \a r and \a c are * each 0 or 1). * * @param row the index of the requested row; this must be 0 or 1. * @return a two-integer array containing the elements of the * requested row. */ const long* operator [] (unsigned row) const; /** * Returns a single row of this matrix. * * This means that the integer in row \a r, column \a c can be * accessed as myMatrix[r][c] (where \a r and \a c are * each 0 or 1). Each such element may be modified directly. * * @param row the index of the requested row; this must be 0 or 1. * @return a two-integer array containing the elements of the * requested row. */ long* operator [] (unsigned row); /** * Calculates the matrix product of this and the given matrix. * Neither this nor the given matrix is changed. * * @param other the matrix that this should be multiplied by. * @return the product \a this * \a other. */ NMatrix2 operator * (const NMatrix2& other) const; /** * Calculates the scalar product of this matrix and the given * integer. This matrix is not changed. * * @param scalar the integer that this matrix should be multiplied by. * @return the product \a this * \a scalar. */ NMatrix2 operator * (long scalar) const; /** * Calculates the sum of two matrices. * Neither this nor the given matrix is changed. * * @param other the matrix to add to this. * @return the sum \a this + \a other. */ NMatrix2 operator + (const NMatrix2& other) const; /** * Calculates the difference of two matrices. * Neither this nor the given matrix is changed. * * @param other the matrix to subtract from this. * @return the difference \a this - \a other. */ NMatrix2 operator - (const NMatrix2& other) const; /** * Determines the negative of this matrix. * This matrix is not changed. * * @return the negative of this matrix. */ NMatrix2 operator - () const; /** * Returns the transpose of this matrix. * This matrix is not changed. * * @return the transpose of this matrix. */ NMatrix2 transpose() const; /** * Calculates the inverse of this matrix. * This matrix is not changed. * * This routine only works for integer matrices whose determinant is * either +1 or -1. * * @return the inverse of this matrix. If this matrix does not * have determinant +1 or -1, the zero matrix will be returned * instead. */ NMatrix2 inverse() const; /** * Adds the given matrix to this. * This matrix is changed to reflect the result. * * @param other the matrix to add to this. * @return a reference to this matrix with its new value. */ NMatrix2& operator += (const NMatrix2& other); /** * Subtracts the given matrix from this. * This matrix is changed to reflect the result. * * @param other the matrix to subtract from this. * @return a reference to this matrix with its new value. */ NMatrix2& operator -= (const NMatrix2& other); /** * Multiplies this by the given matrix. * This matrix is changed to reflect the result. * * @param other the matrix by which this should be multiplied. * @return a reference to this matrix with its new value. */ NMatrix2& operator *= (const NMatrix2& other); /** * Multiplies this by the given scalar. * This matrix is changed to reflect the result. * * @param scalar the scalar by which this should be multiplied. * @return a reference to this matrix with its new value. */ NMatrix2& operator *= (long scalar); /** * Negates this matrix. * This matrix is changed to reflect the result. */ void negate(); /** * Inverts this matrix. * * This routine only works for integer matrices whose determinant is * either +1 or -1. Otherwise this matrix is left unchanged. * * @return \c true if this matrix was successfully inverted * (i.e., its determinant was +1 or -1), or \c false otherwise. */ bool invert(); /** * Determines if this is equal to the given matrix. * * @param compare the matrix with which this will be compared. * @return \c true if and only if this matrix is equal to \a compare. */ bool operator == (const NMatrix2& compare) const; /** * Determines if this is not equal to the given matrix. * * @param compare the matrix with which this will be compared. * @return \c true if and only if this matrix is not equal to * \a compare. */ bool operator != (const NMatrix2& compare) const; /** * Returns the determinant of this matrix. * * @return the determinant of this matrix. */ long determinant() const; /** * Determines if this is the 2x2 identity matrix. * * @return \c true if this is the identity matrix, or \c false * otherwise. */ bool isIdentity() const; /** * Determines if this is the 2x2 zero matrix. * * @return \c true if this is the zero matrix, or \c false * otherwise. */ bool isZero() const; friend std::ostream& operator << (std::ostream& out, const NMatrix2& mat); }; /** * Writes the given matrix to the given output stream. The matrix will * be written entirely on a single line, with the first row followed by the * second row. * * @param out the output stream to which to write. * @param mat the matrix to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NMatrix2& mat); /** * Determines whether the first given matrix is more aesthetically * pleasing than the second. The way in which this judgement is made * is purely aesthetic on the part of the author, and is subject to * change in future versions of Regina. * * @param m1 the first matrix to examine. * @param m2 the second matrix to examine. * @return \c true if \a m1 is deemed to be more pleasing than \a m2, * or \c false if either the matrices are equal or \a m2 is more * pleasing than \a m1. */ REGINA_API bool simpler(const NMatrix2& m1, const NMatrix2& m2); /** * Determines whether the first given pair of matrices is more aesthetically * pleasing than the second pair. The way in which this judgement is made * is purely aesthetic on the part of the author, and is subject to * change in future versions of Regina. * * Note that pairs are ordered, so the pair (\a M, \a N) may be more * (or perhaps less) pleasing than the pair (\a N, \a M). * * @param pair1first the first matrix of the first pair to examine. * @param pair1second the second matrix of the first pair to examine. * @param pair2first the first matrix of the second pair to examine. * @param pair2second the second matrix of the second pair to examine. * @return \c true if the first pair is deemed to be more pleasing than * the second pair, or \c false if either the ordered pairs are equal or * the second pair is more pleasing than the first. */ REGINA_API bool simpler(const NMatrix2& pair1first, const NMatrix2& pair1second, const NMatrix2& pair2first, const NMatrix2& pair2second); /*@}*/ // Inline functions for NMatrix2 inline NMatrix2::NMatrix2() { data[0][0] = data[0][1] = data[1][0] = data[1][1] = 0; } inline NMatrix2::NMatrix2(const NMatrix2& cloneMe) { data[0][0] = cloneMe.data[0][0]; data[0][1] = cloneMe.data[0][1]; data[1][0] = cloneMe.data[1][0]; data[1][1] = cloneMe.data[1][1]; } inline NMatrix2::NMatrix2(const long values[2][2]) { data[0][0] = values[0][0]; data[0][1] = values[0][1]; data[1][0] = values[1][0]; data[1][1] = values[1][1]; } inline NMatrix2::NMatrix2(long val00, long val01, long val10, long val11) { data[0][0] = val00; data[0][1] = val01; data[1][0] = val10; data[1][1] = val11; } inline NMatrix2& NMatrix2::operator = (const NMatrix2& cloneMe) { data[0][0] = cloneMe.data[0][0]; data[0][1] = cloneMe.data[0][1]; data[1][0] = cloneMe.data[1][0]; data[1][1] = cloneMe.data[1][1]; return *this; } inline NMatrix2& NMatrix2::operator = (const long values[2][2]) { data[0][0] = values[0][0]; data[0][1] = values[0][1]; data[1][0] = values[1][0]; data[1][1] = values[1][1]; return *this; } inline const long* NMatrix2::operator [] (unsigned row) const { return data[row]; } inline long* NMatrix2::operator [] (unsigned row) { return data[row]; } inline NMatrix2 NMatrix2::operator * (const NMatrix2& other) const { return NMatrix2( data[0][0] * other.data[0][0] + data[0][1] * other.data[1][0], data[0][0] * other.data[0][1] + data[0][1] * other.data[1][1], data[1][0] * other.data[0][0] + data[1][1] * other.data[1][0], data[1][0] * other.data[0][1] + data[1][1] * other.data[1][1]); } inline NMatrix2 NMatrix2::operator * (long scalar) const { return NMatrix2( data[0][0] * scalar, data[0][1] * scalar, data[1][0] * scalar, data[1][1] * scalar); } inline NMatrix2 NMatrix2::operator + (const NMatrix2& other) const { return NMatrix2( data[0][0] + other.data[0][0], data[0][1] + other.data[0][1], data[1][0] + other.data[1][0], data[1][1] + other.data[1][1]); } inline NMatrix2 NMatrix2::operator - (const NMatrix2& other) const { return NMatrix2( data[0][0] - other.data[0][0], data[0][1] - other.data[0][1], data[1][0] - other.data[1][0], data[1][1] - other.data[1][1]); } inline NMatrix2 NMatrix2::operator - () const { return NMatrix2(-data[0][0], -data[0][1], -data[1][0], -data[1][1]); } inline NMatrix2 NMatrix2::transpose() const { return NMatrix2(data[0][0], data[1][0], data[0][1], data[1][1]); } inline NMatrix2& NMatrix2::operator += (const NMatrix2& other) { data[0][0] += other.data[0][0]; data[0][1] += other.data[0][1]; data[1][0] += other.data[1][0]; data[1][1] += other.data[1][1]; return *this; } inline NMatrix2& NMatrix2::operator -= (const NMatrix2& other) { data[0][0] -= other.data[0][0]; data[0][1] -= other.data[0][1]; data[1][0] -= other.data[1][0]; data[1][1] -= other.data[1][1]; return *this; } inline NMatrix2& NMatrix2::operator *= (long scalar) { data[0][0] *= scalar; data[0][1] *= scalar; data[1][0] *= scalar; data[1][1] *= scalar; return *this; } inline void NMatrix2::negate() { data[0][0] = -data[0][0]; data[0][1] = -data[0][1]; data[1][0] = -data[1][0]; data[1][1] = -data[1][1]; } inline bool NMatrix2::operator == (const NMatrix2& compare) const { return ( data[0][0] == compare.data[0][0] && data[0][1] == compare.data[0][1] && data[1][0] == compare.data[1][0] && data[1][1] == compare.data[1][1]); } inline bool NMatrix2::operator != (const NMatrix2& compare) const { return ( data[0][0] != compare.data[0][0] || data[0][1] != compare.data[0][1] || data[1][0] != compare.data[1][0] || data[1][1] != compare.data[1][1]); } inline long NMatrix2::determinant() const { return data[0][0] * data[1][1] - data[0][1] * data[1][0]; } inline bool NMatrix2::isIdentity() const { return (data[0][0] == 1 && data[0][1] == 0 && data[1][0] == 0 && data[1][1] == 1); } inline bool NMatrix2::isZero() const { return (data[0][0] == 0 && data[0][1] == 0 && data[1][0] == 0 && data[1][1] == 0); } inline std::ostream& operator << (std::ostream& out, const NMatrix2& mat) { return out << "[[ " << mat.data[0][0] << ' ' << mat.data[0][1] << " ] [ " << mat.data[1][0] << ' ' << mat.data[1][1] << " ]]"; } } // namespace regina #endif regina-4.95/engine/maths/nmatrixint.h000644 000765 000024 00000022402 12234011536 017533 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NMATRIXINT_H #ifndef __DOXYGEN #define __NMATRIXINT_H #endif /*! \file maths/nmatrixint.h * \brief Deals with matrices of arbitrary precision integers. */ #include "regina-core.h" #include "shareableobject.h" #include "maths/ninteger.h" #include "maths/nmatrix.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a matrix of arbitrary precision integers. * Calculations will be exact no matter how large the integers become. * * Note that many important functions (such as entry()) are inherited * from the superclasses NMatrix and NMatrixRing, and are not documented * again here. Many other algorithms that work on the NMatrixInt class * are available in the maths/matrixops.h file. * * \ifacespython Most inherited member functions are implemented. * Exceptions are noted in the documentation for each individual member * function. */ class REGINA_API NMatrixInt : public NMatrixRing, public ShareableObject { public: /** * Creates a new matrix of the given size. * All entries will be initialised to zero. * * \pre The given number of rows and columns are * both strictly positive. * * @param rows the number of rows in the new matrix. * @param cols the number of columns in the new matrix. */ NMatrixInt(unsigned long rows, unsigned long cols); /** * Creates a new matrix that is a clone of the given matrix. * * @param cloneMe the matrix to clone. */ NMatrixInt(const NMatrixInt& cloneMe); /** * Divides all elements of the given row by the given integer. * This can only be used when the given integer divides into all * row elements exactly (with no remainder), and is much faster * than ordinary division. * * \pre The argument \a divBy is neither zero nor infinity, and * none of the elements of the given row are infinity. * \pre The argument \a divBy divides exactly into every element * of the given row (i.e., it leaves no remainder). * \pre The given row number is between 0 and rows()-1 inclusive. * * @param row the index of the row whose elements should be * divided by \a divBy. * @param divBy the integer to divide each row element by. */ void divRowExact(unsigned long row, const NLargeInteger& divBy) { for (NLargeInteger* x = this->data[row]; x != this->data[row] + nCols; ++x) x->divByExact(divBy); } /** * Divides all elements of the given column by the given integer. * This can only be used when the given integer divides into all * column elements exactly (with no remainder), and is much faster * than ordinary division. * * \pre The argument \a divBy is neither zero nor infinity, and * none of the elements of the given column are infinity. * \pre The argument \a divBy divides exactly into every element * of the given column (i.e., it leaves no remainder). * \pre The given column number is between 0 and columns()-1 inclusive. * * @param col the index of the column whose elements should be * divided by \a divBy. * @param divBy the integer to divide each column element by. */ void divColExact(unsigned long col, const NLargeInteger& divBy) { for (NLargeInteger** row = this->data; row != this->data + nRows; ++row) (*row)[col].divByExact(divBy); } /** * Computes the greatest common divisor of all elements of the * given row. The value returned is guaranteed to be non-negative. * * \pre The given row number is between 0 and rows()-1 inclusive. * * @param row the index of the row whose gcd should be computed. * @return the greatest common divisor of all elements of this row. */ NLargeInteger gcdRow(unsigned long row) { NLargeInteger* x = this->data[row]; NLargeInteger gcd = *x++; while (x != this->data[row] + nCols && gcd != 1 && gcd != -1) gcd = gcd.gcd(*x++); if (gcd < 0) gcd.negate(); return gcd; } /** * Computes the greatest common divisor of all elements of the * given column. The value returned is guaranteed to be non-negative. * * \pre The given column number is between 0 and columns()-1 inclusive. * * @param col the index of the column whose gcd should be computed. * @return the greatest common divisor of all elements of this column. */ NLargeInteger gcdCol(unsigned long col) { NLargeInteger** row = this->data; NLargeInteger gcd = (*row++)[col]; while (row != this->data + nRows && gcd != 1 && gcd != -1) gcd = gcd.gcd((*row++)[col]); if (gcd < 0) gcd.negate(); return gcd; } /** * Reduces the given row by dividing all its elements by their * greatest common divisor. It is guaranteed that, if the row is * changed at all, it will be divided by a \e positive integer. * * \pre The given row number is between 0 and rows()-1 inclusive. * * @param row the index of the row to reduce. */ void reduceRow(unsigned long row) { NLargeInteger gcd = gcdRow(row); if (gcd != NLargeInteger::zero && gcd != NLargeInteger::one) divRowExact(row, gcd); } /** * Reduces the given column by dividing all its elements by their * greatest common divisor. It is guaranteed that, if the column is * changed at all, it will be divided by a \e positive integer. * * \pre The given column number is between 0 and columns()-1 inclusive. * * @param col the index of the column to reduce. */ void reduceCol(unsigned long col) { NLargeInteger gcd = gcdCol(col); if (gcd != NLargeInteger::zero && gcd != NLargeInteger::one) divColExact(col, gcd); } virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; }; // Inline functions for NMatrixInt inline NMatrixInt::NMatrixInt(unsigned long rows, unsigned long cols) : NMatrixRing(rows, cols), ShareableObject() { } inline NMatrixInt::NMatrixInt(const NMatrixInt& cloneMe) : NMatrixRing(cloneMe), ShareableObject() { } inline void NMatrixInt::writeTextShort(std::ostream& out) const { out << nRows << " x " << nCols << " integer matrix"; } inline void NMatrixInt::writeTextLong(std::ostream& out) const { writeMatrix(out); } /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/nperm3.cpp000644 000765 000024 00000007271 12234011536 017104 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nperm3.h" namespace regina { const NPerm3 NPerm3::S3[6] = { NPerm3((unsigned char)0), NPerm3(1), NPerm3(2), NPerm3(3), NPerm3(4), NPerm3(5) }; const NPerm3* NPerm3::Sn = NPerm3::S3; const int NPerm3::invS3[6] = { 0, 1, 4, 3, 2, 5 }; const NPerm3 NPerm3::orderedS3[6] = { NPerm3(code012), NPerm3(code021), NPerm3(code102), NPerm3(code120), NPerm3(code201), NPerm3(code210) }; const NPerm3* NPerm3::orderedSn = NPerm3::orderedS3; const NPerm3 NPerm3::S2[2] = { NPerm3(code012), NPerm3(code102) }; const NPerm3* NPerm3::Sn_1 = NPerm3::S2; const unsigned char NPerm3::imageTable[6][3] = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 2, 0 }, { 1, 0, 2 }, { 2, 0, 1 }, { 2, 1, 0 } }; const unsigned char NPerm3::productTable[6][6] = { { 0, 1, 2, 3, 4, 5 }, { 1, 0, 5, 4, 3, 2 }, { 2, 3, 4, 5, 0, 1 }, { 3, 2, 1, 0, 5, 4 }, { 4, 5, 0, 1, 2, 3 }, { 5, 4, 3, 2, 1, 0 } }; std::string NPerm3::str() const { char ans[4]; ans[0] = static_cast('0' + imageTable[code_][0]); ans[1] = static_cast('0' + imageTable[code_][1]); ans[2] = static_cast('0' + imageTable[code_][2]); ans[3] = 0; return ans; } std::string NPerm3::trunc2() const { char ans[3]; ans[0] = static_cast('0' + imageTable[code_][0]); ans[1] = static_cast('0' + imageTable[code_][1]); ans[2] = 0; return ans; } } // namespace regina regina-4.95/engine/maths/nperm3.h000644 000765 000024 00000051023 12234011536 016543 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nperm3.h * \brief Deals with permutations of {0,1,2}. */ #ifndef __NPERM3_H #ifndef __DOXYGEN #define __NPERM3_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a permutation of {0,1,2}. * * These objects are small enough to pass about by value instead of by * reference. Moreover, they are extremely fast to work with. * * Each permutation has an internal code, and this code is sufficient to * reconstruct the permutation. * Thus the internal code may be a useful means for passing * permutation objects to and from the engine. * * The internal code is an integer between 0 and 5 inclusive, * representing the index of the permutation in the array NPerm3::S3. * * This class is faster and sleeker than related classes such as NPerm4 and * NPerm5. On the other hand, this class does not offer quite as rich an * interface as the others. * * \testfull */ class REGINA_API NPerm3 { public: /** * Contains all possible permutations of three elements. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm3, NPerm4 and so on), * the S3 array stores the same permutations in the same order * (but of course using different data types). * * Note that these permutations are not necessarily in * lexicographical order. */ static const NPerm3 S3[6]; /** * A dimension-agnostic alias for NPerm3::S3. In general, for * each \a K the class NPermK will define an alias \a Sn * that references the list of all permutations NPermK::SK. */ static const NPerm3* Sn; /** * Contains the inverses of the permutations in the array \a S3. * * Specifically, the inverse of permutation S3[i] is * the permutation S3[ invS3[i] ]. */ static const int invS3[6]; /** * Contains all possible permutations of three elements in * lexicographical order. */ static const NPerm3 orderedS3[6]; /** * A dimension-agnostic alias for NPerm3::orderedS3. In general, for * each \a K the class NPermK will define an alias \a orderedSn * that references the list of all permutations NPermK::orderedSK. */ static const NPerm3* orderedSn; /** * Contains all possible permutations of two elements. * In each permutation, 2 maps to 2 and 3 maps to 3. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm3, NPerm4 and so on), * the S2 array stores the same permutations in the same order * (but of course using different data types). * * Note that these permutations are already in lexicographical order. */ static const NPerm3 S2[2]; /** * A dimension-agnostic alias for NPerm3::S2. In general, for * each \a K the class NPermK will define an alias \a Sn_1 * that references the list of all permutations NPermK::S(K-1). */ static const NPerm3* Sn_1; enum { /** * The total number of permutations on three elements. * This is the size of the array Sn. * * \ifacespython Not present. */ nPerms = 6, /** * The total number of permutations on two elements. * This is the size of the array Sn_1. * * \ifacespython Not present. */ nPerms_1 = 2 }; enum { /** * The internal code for the permutation (0,1,2). * * \ifacespython Not present. */ code012 = 0, /** * The internal code for the permutation (0,2,1). * * \ifacespython Not present. */ code021 = 1, /** * The internal code for the permutation (1,2,0). * * \ifacespython Not present. */ code120 = 2, /** * The internal code for the permutation (1,0,2). * * \ifacespython Not present. */ code102 = 3, /** * The internal code for the permutation (2,0,1). * * \ifacespython Not present. */ code201 = 4, /** * The internal code for the permutation (2,1,0). * * \ifacespython Not present. */ code210 = 5 }; private: unsigned char code_; /**< The internal code representing this permutation. */ public: /** * Creates the identity permutation. */ NPerm3(); /** * Creates the transposition of \a a and \a b. * Note that \a a and \a b need not be distinct. * * \pre \a a and \a b are in {0,1,2}. * * @param a the element to switch with \a b. * @param b the element to switch with \a a. */ NPerm3(int a, int b); /** * Creates a permutation mapping (0,1,2) to * (a,b,c) respectively. * * \pre {a,b,c} = {0,1,2}. * * @param a the desired image of 0. * @param b the desired image of 1. * @param c the desired image of 2. */ NPerm3(int a, int b, int c); /** * Creates a permutation mapping \a i to \a image[i] for each * \a i = 0,1,2. * * \pre The array \a image contains three elements, which are * 0, 1 and 2 in some order. * * \ifacespython Not present. * * @param image the array of images. */ NPerm3(const int* image); /** * Creates a permutation that is a clone of the given * permutation. * * @param cloneMe the permutation to clone. */ NPerm3(const NPerm3& cloneMe); /** * Returns the internal code representing this permutation. * Note that the internal code is sufficient to reproduce the * entire permutation. * * The code returned will be a valid permutation code as * determined by isPermCode(). * * @return the internal code. */ unsigned char getPermCode() const; /** * Sets this permutation to that represented by the given * internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param code the internal code that will determine the * new value of this permutation. */ void setPermCode(unsigned char code); /** * Creates a permutation from the given internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param code the internal code for the new permutation. * @return the permutation represented by the given internal code. */ static NPerm3 fromPermCode(unsigned char code); /** * Determines whether the given integer is a valid internal * permutation code. Valid permutation codes can be passed to * setPermCode() or fromPermCode(), and are returned by getPermCode(). * * @return \c true if and only if the given code is a valid * internal permutation code. */ static bool isPermCode(unsigned char code); /** * Sets this permutation to be equal to the given permutation. * * @param cloneMe the permutation whose value will be assigned * to this permutation. * @return a reference to this permutation. */ NPerm3& operator = (const NPerm3& cloneMe); /** * Returns the composition of this permutation with the given * permutation. If this permutation is p, the * resulting permutation will be p o q, satisfying * (p*q)[x] == p[q[x]]. * * @param q the permutation with which to compose this. * @return the composition of both permutations. */ NPerm3 operator * (const NPerm3& q) const; /** * Finds the inverse of this permutation. * * @return the inverse of this permutation. */ NPerm3 inverse() const; /** * Determines the sign of this permutation. * * @return 1 if this permutation is even, or -1 if this * permutation is odd. */ int sign() const; /** * Determines the image of the given integer under this * permutation. * * @param source the integer whose image we wish to find. This * should be between 0 and 2 inclusive. * @return the image of \a source. */ int operator[](int source) const; /** * Determines the preimage of the given integer under this * permutation. * * @param image the integer whose preimage we wish to find. This * should be between 0 and 2 inclusive. * @return the preimage of \a image. */ int preImageOf(int image) const; /** * Determines if this is equal to the given permutation. * This is true if and only if both permutations have the same * images for 0, 1 and 2. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * are equal. */ bool operator == (const NPerm3& other) const; /** * Determines if this differs from the given permutation. * This is true if and only if the two permutations have * different images for at least one of 0, 1 or 2. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * differ. */ bool operator != (const NPerm3& other) const; /** * Lexicographically compares the images of (0,1,2) under this * and the given permutation. * * @param other the permutation with which to compare this. * @return -1 if this permutation produces a smaller image, 0 if * the permutations are equal and 1 if this permutation produces * a greater image. */ int compareWith(const NPerm3& other) const; /** * Determines if this is the identity permutation. * This is true if and only if each of 0, 1 and 2 is mapped to itself. * * @return \c true if and only if this is the identity permutation. */ bool isIdentity() const; /** * A deprecated alias for str(), which returns a string representation * of this permutation. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type str() instead. * * @return a string representation of this permutation. */ std::string toString() const; /** * Returns a string representation of this permutation. * The representation will consist of three adjacent digits * representing the images of 0, 1 and 2 respectively. An * example of a string representation is 120. * * @return a string representation of this permutation. */ std::string str() const; /** * Returns a string representation of this permutation with only * the images of 0 and 1. The resulting string will therefore * have length two. * * @return a truncated string representation of this permutation. */ std::string trunc2() const; /** * Returns the index of this permutation in the NPerm3::S3 array. * * @return the index \a i for which this permutation is equal to * NPerm3::S3[i]. This will be between 0 and 5 inclusive. */ int S3Index() const; /** * Returns the index of this permutation in the NPerm3::S3 array. * This is a dimension-agnostic alias for S3Index(). * * @return the index \a i for which this permutation is equal to * NPerm3::S3[i]. This will be between 0 and 5 inclusive. */ int SnIndex() const; /** * Returns the index of this permutation in the NPerm3::orderedS3 array. * * @return the index \a i for which this permutation is equal to * NPerm3::orderedS3[i]. This will be between 0 and 5 inclusive. */ int orderedS3Index() const; /** * Returns the index of this permutation in the NPerm3::orderedS3 array. * This is a dimension-agnostic alias for orderedS3Index(). * * @return the index \a i for which this permutation is equal to * NPerm3::orderedS3[i]. This will be between 0 and 5 inclusive. */ int orderedSnIndex() const; private: /** * Contains the images of every element under every possible * permutation. * * Specifically, the image of \a x under the permutation S3[i] * is imageTable[i][x]. */ static const unsigned char imageTable[6][3]; /** * Contains the full multiplication table for all possible * permutations. * * Specifically, the product S3[x] * S3[y] is * the permutation S3[product[x][y]]. */ static const unsigned char productTable[6][6]; private: /** * Creates a permutation from the given internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param code the internal code from which the new * permutation will be created. */ NPerm3(unsigned char code); friend std::ostream& operator << (std::ostream& out, const NPerm3& p); }; /** * Writes a string representation of the given permutation to the given * output stream. The format will be the same as is used by * NPerm3::str(). * * @param out the output stream to which to write. * @param p the permutation to write. * @return a reference to \a out. */ inline REGINA_API std::ostream& operator << (std::ostream& out, const NPerm3& p) { return (out << p.str()); } /*@}*/ // Inline functions for NPerm3 inline NPerm3::NPerm3() : code_(0) { } inline NPerm3::NPerm3(unsigned char code) : code_(code) { } inline NPerm3::NPerm3(int a, int b) { // Transposition. if (a == b) code_ = code012; else switch (a) { case 0: code_ = (b == 1 ? code102 : code210); break; case 1: code_ = (b == 0 ? code102 : code021); break; case 2: code_ = (b == 0 ? code210 : code021); break; } } inline NPerm3::NPerm3(int a, int b, int) { // Images of (0, 1, 2). switch (a) { case 0: code_ = static_cast(b == 1 ? 0 : 1); break; case 1: code_ = static_cast(b == 2 ? 2 : 3); break; case 2: code_ = static_cast(b == 0 ? 4 : 5); break; } } inline NPerm3::NPerm3(const int* image) { switch (image[0]) { case 0: code_ = static_cast(image[1] == 1 ? 0 : 1); break; case 1: code_ = static_cast(image[1] == 2 ? 2 : 3); break; case 2: code_ = static_cast(image[1] == 0 ? 4 : 5); break; } } inline NPerm3::NPerm3(const NPerm3& cloneMe) : code_(cloneMe.code_) { } inline unsigned char NPerm3::getPermCode() const { return code_; } inline void NPerm3::setPermCode(unsigned char code) { code_ = code; } inline NPerm3 NPerm3::fromPermCode(unsigned char code) { return NPerm3(code); } inline bool NPerm3::isPermCode(unsigned char code) { // code >= 0 is a no-op because we are using an unsigned data type. return (code < 6); } inline NPerm3& NPerm3::operator = (const NPerm3& cloneMe) { code_ = cloneMe.code_; return *this; } inline NPerm3 NPerm3::operator * (const NPerm3& q) const { return NPerm3(productTable[code_][q.code_]); } inline NPerm3 NPerm3::inverse() const { return NPerm3(static_cast(invS3[code_])); } inline int NPerm3::sign() const { return (code_ % 2 ? -1 : 1); } inline int NPerm3::operator[](int source) const { return imageTable[code_][source]; } inline int NPerm3::preImageOf(int image) const { return imageTable[invS3[code_]][image]; } inline bool NPerm3::operator == (const NPerm3& other) const { return (code_ == other.code_); } inline bool NPerm3::operator != (const NPerm3& other) const { return (code_ != other.code_); } inline int NPerm3::compareWith(const NPerm3& other) const { // Computing orderedS3Index() is very fast. // Use this instead of comparing images one at a time. int o1 = orderedS3Index(); int o2 = other.orderedS3Index(); return (o1 == o2 ? 0 : o1 < o2 ? -1 : 1); } inline bool NPerm3::isIdentity() const { return (code_ == 0); } inline std::string NPerm3::toString() const { return str(); } inline int NPerm3::S3Index() const { return code_; } inline int NPerm3::SnIndex() const { return code_; } inline int NPerm3::orderedS3Index() const { if (code_ == 2 || code_ == 3) return code_ ^ 1; else return code_; } inline int NPerm3::orderedSnIndex() const { return orderedS3Index(); } } // namespace regina #endif regina-4.95/engine/maths/nperm4.cpp000644 000765 000024 00000021346 12234011536 017104 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nperm4.h" #include "triangulation/nedge.h" // deprecated #include "triangulation/ntriangle.h" // deprecated namespace regina { const NPerm4 NPerm4::S4[24] = { NPerm4((unsigned char)0), NPerm4(1), NPerm4(2), NPerm4(3), NPerm4(4), NPerm4(5), NPerm4(6), NPerm4(7), NPerm4(8), NPerm4(9), NPerm4(10), NPerm4(11), NPerm4(12), NPerm4(13), NPerm4(14), NPerm4(15), NPerm4(16), NPerm4(17), NPerm4(18), NPerm4(19), NPerm4(20), NPerm4(21), NPerm4(22), NPerm4(23) }; const NPerm4* NPerm4::Sn = NPerm4::S4; const NPerm4* allPermsS4 = NPerm4::S4; const unsigned NPerm4::invS4[24] = { 0, 1, 4, 3, 2, 5, 6, 7, 12, 19, 18, 13, 8, 11, 20, 15, 16, 23, 10, 9, 14, 21, 22, 17 }; const unsigned* allPermsS4Inv = NPerm4::invS4; const NPerm4 NPerm4::orderedS4[24] = { NPerm4((unsigned char)0), NPerm4(1), NPerm4(3), NPerm4(2), NPerm4(4), NPerm4(5), NPerm4(7), NPerm4(6), NPerm4(8), NPerm4(9), NPerm4(11), NPerm4(10), NPerm4(12), NPerm4(13), NPerm4(15), NPerm4(14), NPerm4(16), NPerm4(17), NPerm4(19), NPerm4(18), NPerm4(20), NPerm4(21), NPerm4(23), NPerm4(22) }; const NPerm4* NPerm4::orderedSn = NPerm4::orderedS4; const NPerm4* orderedPermsS4 = NPerm4::orderedS4; const NPerm4 NPerm4::S3[6] = { NPerm4(0,1,2,3), NPerm4(0,2,1,3), NPerm4(1,2,0,3), NPerm4(1,0,2,3), NPerm4(2,0,1,3), NPerm4(2,1,0,3) }; const NPerm4* NPerm4::Sn_1 = NPerm4::S3; const NPerm4* allPermsS3 = NPerm4::S3; const unsigned NPerm4::invS3[6] = { 0, 1, 4, 3, 2, 5 }; const unsigned* allPermsS3Inv = NPerm4::invS3; const NPerm4 NPerm4::orderedS3[6] = { NPerm4(0,1,2,3), NPerm4(0,2,1,3), NPerm4(1,0,2,3), NPerm4(1,2,0,3), NPerm4(2,0,1,3), NPerm4(2,1,0,3) }; const NPerm4* orderedPermsS3 = NPerm4::orderedS3; const NPerm4 NPerm4::S2[2] = { NPerm4(0,1,2,3), NPerm4(1,0,2,3) }; const NPerm4* allPermsS2 = NPerm4::S2; const unsigned NPerm4::invS2[2] = { 0, 1 }; const unsigned* allPermsS2Inv = NPerm4::invS2; const unsigned char NPerm4::imageTable[24][4] = { { 0, 1, 2, 3 }, { 0, 1, 3, 2 }, { 0, 2, 3, 1 }, { 0, 2, 1, 3 }, { 0, 3, 1, 2 }, { 0, 3, 2, 1 }, { 1, 0, 3, 2 }, { 1, 0, 2, 3 }, { 1, 2, 0, 3 }, { 1, 2, 3, 0 }, { 1, 3, 2, 0 }, { 1, 3, 0, 2 }, { 2, 0, 1, 3 }, { 2, 0, 3, 1 }, { 2, 1, 3, 0 }, { 2, 1, 0, 3 }, { 2, 3, 0, 1 }, { 2, 3, 1, 0 }, { 3, 0, 2, 1 }, { 3, 0, 1, 2 }, { 3, 1, 0, 2 }, { 3, 1, 2, 0 }, { 3, 2, 1, 0 }, { 3, 2, 0, 1 } }; const unsigned char NPerm4::productTable[24][24] = { // Generated using an older version of Regina in which products were // computed (not simply looked up from a dictionary like the one below). { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 }, { 1,0,5,4,3,2,7,6,11,10,9,8,19,18,21,20,23,22,13,12,15,14,17,16 }, { 2,3,4,5,0,1,12,13,16,17,14,15,18,19,22,23,20,21,6,7,8,9,10,11 }, { 3,2,1,0,5,4,13,12,15,14,17,16,7,6,9,8,11,10,19,18,23,22,21,20 }, { 4,5,0,1,2,3,18,19,20,21,22,23,6,7,10,11,8,9,12,13,16,17,14,15 }, { 5,4,3,2,1,0,19,18,23,22,21,20,13,12,17,16,15,14,7,6,11,10,9,8 }, { 6,7,10,11,8,9,0,1,4,5,2,3,20,21,18,19,22,23,14,15,12,13,16,17 }, { 7,6,9,8,11,10,1,0,3,2,5,4,15,14,13,12,17,16,21,20,19,18,23,22 }, { 8,9,6,7,10,11,14,15,12,13,16,17,0,1,2,3,4,5,20,21,22,23,18,19 }, { 9,8,11,10,7,6,15,14,17,16,13,12,21,20,23,22,19,18,1,0,3,2,5,4 }, { 10,11,8,9,6,7,20,21,22,23,18,19,14,15,16,17,12,13,0,1,4,5,2,3 }, { 11,10,7,6,9,8,21,20,19,18,23,22,1,0,5,4,3,2,15,14,17,16,13,12 }, { 12,13,14,15,16,17,2,3,0,1,4,5,8,9,6,7,10,11,22,23,18,19,20,21 }, { 13,12,17,16,15,14,3,2,5,4,1,0,23,22,19,18,21,20,9,8,7,6,11,10 }, { 14,15,16,17,12,13,8,9,10,11,6,7,22,23,20,21,18,19,2,3,0,1,4,5 }, { 15,14,13,12,17,16,9,8,7,6,11,10,3,2,1,0,5,4,23,22,21,20,19,18 }, { 16,17,12,13,14,15,22,23,18,19,20,21,2,3,4,5,0,1,8,9,10,11,6,7 }, { 17,16,15,14,13,12,23,22,21,20,19,18,9,8,11,10,7,6,3,2,5,4,1,0 }, { 18,19,22,23,20,21,4,5,2,3,0,1,16,17,12,13,14,15,10,11,6,7,8,9 }, { 19,18,21,20,23,22,5,4,1,0,3,2,11,10,7,6,9,8,17,16,13,12,15,14 }, { 20,21,18,19,22,23,10,11,6,7,8,9,4,5,0,1,2,3,16,17,14,15,12,13 }, { 21,20,23,22,19,18,11,10,9,8,7,6,17,16,15,14,13,12,5,4,1,0,3,2 }, { 22,23,20,21,18,19,16,17,14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1 }, { 23,22,19,18,21,20,17,16,13,12,15,14,5,4,3,2,1,0,11,10,9,8,7,6 } }; const unsigned char NPerm4::swapTable[4][4] = { { 0, 7, 15, 21 }, { 7, 0, 3, 5 }, { 15, 3, 0, 1 }, { 21, 5, 1, 0 } }; NPerm4::NPerm4(int a0, int a1, int b0, int b1, int c0, int c1, int d0, int d1) { int image[4]; image[a0] = a1; image[b0] = b1; image[c0] = c1; image[d0] = d1; code_ = static_cast( S4Index(image[0], image[1], image[2], image[3])); } bool NPerm4::isPermCode(unsigned char code) { unsigned mask = 0; for (int i = 0; i < 4; i++) mask |= (1 << ((code >> (2 * i)) & 3)); // mask |= (1 << imageOf(i)); return (mask == 15); } std::string NPerm4::str() const { char ans[5]; for (int i = 0; i < 4; i++) ans[i] = static_cast('0' + imageTable[code_][i]); ans[4] = 0; return ans; } std::string NPerm4::trunc2() const { char ans[3]; ans[0] = static_cast('0' + imageTable[code_][0]); ans[1] = static_cast('0' + imageTable[code_][1]); ans[2] = 0; return ans; } std::string NPerm4::trunc3() const { char ans[4]; ans[0] = static_cast('0' + imageTable[code_][0]); ans[1] = static_cast('0' + imageTable[code_][1]); ans[2] = static_cast('0' + imageTable[code_][2]); ans[3] = 0; return ans; } NPerm4 faceOrdering(int face) { switch(face) { case 0: return NPerm4(1,2,3,0); case 1: return NPerm4(0,2,3,1); case 2: return NPerm4(0,1,3,2); case 3: return NPerm4(0,1,2,3); } return NPerm4(); } NPerm4 edgeOrdering(int edge) { switch(edge) { case 0: return NPerm4(0,1,2,3); case 1: return NPerm4(0,2,3,1); case 2: return NPerm4(0,3,1,2); case 3: return NPerm4(1,2,0,3); case 4: return NPerm4(1,3,2,0); case 5: return NPerm4(2,3,0,1); } return NPerm4(); } std::string faceDescription(int face) { // deprecated return NTriangle::ordering[face].trunc3(); } std::string edgeDescription(int edge) { // deprecated return NEdge::ordering[edge].trunc2(); } } // namespace regina regina-4.95/engine/maths/nperm4.h000644 000765 000024 00000115334 12234011536 016552 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nperm4.h * \brief Deals with permutations of {0,1,2,3}. */ #ifndef __NPERM4_H #ifndef __DOXYGEN #define __NPERM4_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a permutation of {0,1,2,3}. * Amongst other things, such permutations are used in specifying how * simplices of a 3-manifold triangulation are glued together. * NPerm4 objects are small enough to pass about by value instead of by * reference. * * Each permutation has an internal code, and this code is sufficient to * reconstruct the permutation. * Thus the internal code may be a useful means for passing * permutation objects to and from the engine. * * The internal permutation codes have changed as of Regina 4.6.1: * * - \e First-generation codes were used internally in Regina 4.6 and earlier. * These codes were characters whose lowest two bits represented the * image of 0, whose next lowest two bits represented the image of 1, * and so on. The routines getPermCode(), setPermCode(), fromPermCode() * and isPermCode() continue to work with first-generation codes for * backward compatibility. Likewise, the XML data file format * continues to use first-generation codes to describe tetrahedron gluings. * * - \e Second-generation codes are used internally in Regina 4.6.1 and above. * These codes are integers between 0 and 23 inclusive, representing the * index of the permutation in the array NPerm4::S4. The routines * getPermCode2(), setPermCode2(), fromPermCode2() and isPermCode2() * work with second-generation codes. * * It is highly recommended that, if you need to work with permutation * codes at all, you use second-generation codes where possible. This * is because the first-generation routines incur additional overhead * in converting back and forth between the second-generation codes * (which are used internally by NPerm4). * * \testfull */ class REGINA_API NPerm4 { public: /** * Contains all possible permutations of four elements. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S4 array stores the same permutations in the same order (but * of course using different data types). * * Note that the permutations are not necessarily in * lexicographical order. */ static const NPerm4 S4[24]; /** * A dimension-agnostic alias for NPerm4::S4. In general, for * each \a K the class NPermK will define an alias \a Sn * that references the list of all permutations NPermK::SK. */ static const NPerm4* Sn; /** * Contains the inverses of the permutations in the array \a S4. * * Specifically, the inverse of permutation S4[i] is * the permutation S4[ invS4[i] ]. */ static const unsigned invS4[24]; /** * Contains all possible permutations of four elements in * lexicographical order. */ static const NPerm4 orderedS4[24]; /** * A dimension-agnostic alias for NPerm4::orderedS4. In general, for * each \a K the class NPermK will define an alias \a orderedSn * that references the list of all permutations NPermK::orderedSK. */ static const NPerm4* orderedSn; /** * Contains all possible permutations of three elements. * In each permutation, 3 maps to 3. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S3 array stores the same permutations in the same order (but * of course using different data types). * * Note that the permutations are not necessarily in * lexicographical order. For the corresponding inverse array, * see NPerm3::invS3. */ static const NPerm4 S3[6]; /** * A dimension-agnostic alias for NPerm4::S3. In general, for * each \a K the class NPermK will define an alias \a Sn_1 * that references the list of all permutations NPermK::S(K-1). */ static const NPerm4* Sn_1; /** * Contains the inverses of the permutations in the array \a S3. * * Specifically, the inverse of permutation S3[i] is * the permutation S3[ invS3[i] ]. * * \deprecated This is identical to the array NPerm3::invS3. * This unnecessary copy in NPerm4 will be removed in some * future version of Regina. */ static const unsigned invS3[6]; /** * Contains all possible permutations of three elements in * lexicographical order. In each permutation, 3 maps to 3. */ static const NPerm4 orderedS3[6]; /** * Contains all possible permutations of two elements. * In each permutation, 2 maps to 2 and 3 maps to 3. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S2 array stores the same permutations in the same order (but * of course using different data types). * * Note that these permutations are already in lexicographical order. */ static const NPerm4 S2[2]; /** * Contains the inverses of the permutations in the array \a S2. * * Specifically, the inverse of permutation S2[i] is * the permutation S2[ invS2[i] ]. * * \deprecated This array is unnecessary, since all elements of S2 are * their own inverses. This array will be removed in some future * version of Regina. */ static const unsigned invS2[2]; enum { /** * The total number of permutations on four elements. * This is the size of the array Sn. * * \ifacespython Not present. */ nPerms = 24, /** * The total number of permutations on three elements. * This is the size of the array Sn_1. * * \ifacespython Not present. */ nPerms_1 = 6 }; private: unsigned char code_; /**< The internal code representing this permutation. */ public: /** * Creates the identity permutation. */ NPerm4(); /** * Creates the transposition of \a a and \a b. * Note that \a a and \a b need not be distinct. * * \pre \a a and \a b are in {0,1,2,3}. * * @param a the element to switch with \a b. * @param b the element to switch with \a a. */ NPerm4(int a, int b); /** * Creates a permutation mapping (0,1,2,3) to * (a,b,c,d) respectively. * * \pre {a,b,c,d} = {0,1,2,3}. * * @param a the desired image of 0. * @param b the desired image of 1. * @param c the desired image of 2. * @param d the desired image of 3. */ NPerm4(int a, int b, int c, int d); /** * Creates a permutation mapping \a i to \a image[i] for each * \a i = 0,1,2,3. * * \pre The array \a image contains four elements, which are * 0, 1, 2 and 3 in some order. * * \ifacespython Not present. * * @param image the array of images. */ NPerm4(const int* image); /** * Creates a permutation mapping * (a0,b0,c0,d0) to * (a1,b1,c1,d1) respectively. * * \pre {a0,b0,c0,d0} = * {a1,b1,c1,d1} = * {0,1,2,3}. * * @param a0 the desired preimage of a1. * @param b0 the desired preimage of b1. * @param c0 the desired preimage of c1. * @param d0 the desired preimage of d1. * @param a1 the desired image of a0. * @param b1 the desired image of b0. * @param c1 the desired image of c0. * @param d1 the desired image of d0. */ NPerm4(int a0, int a1, int b0, int b1, int c0, int c1, int d0, int d1); /** * Creates a permutation that is a clone of the given * permutation. * * @param cloneMe the permutation to clone. */ NPerm4(const NPerm4& cloneMe); /** * Returns the first-generation code representing this permutation. * This code is sufficient to reproduce the entire permutation. * * The code returned will be a valid first-generation permutation * code as determined by isPermCode(). * * \warning This routine will incur additional overhead, since * NPerm4 now uses second-generation codes internally. * See the class notes and the routine getPermCode2() for details. * * @return the first-generation permutation code. */ unsigned char getPermCode() const; /** * Returns the second-generation code representing this permutation. * This code is sufficient to reproduce the entire permutation. * * The code returned will be a valid second-generation permutation * code as determined by isPermCode2(). * * Second-generation codes are fast to work with, since they are * used internally by the NPerm4 class. * * @return the second-generation permutation code. */ unsigned char getPermCode2() const; /** * Sets this permutation to that represented by the given * first-generation permutation code. * * \pre the given code is a valid first-generation permutation code; * see isPermCode() for details. * * \warning This routine will incur additional overhead, since * NPerm4 now uses second-generation codes internally. * See the class notes and the routine setPermCode2() for details. * * @param code the first-generation code that will determine the * new value of this permutation. */ void setPermCode(unsigned char code); /** * Sets this permutation to that represented by the given * second-generation permutation code. * * Second-generation codes are fast to work with, since they are * used internally by the NPerm4 class. * * \pre the given code is a valid second-generation permutation code; * see isPermCode2() for details. * * @param code the second-generation code that will determine the * new value of this permutation. */ void setPermCode2(unsigned char code); /** * Creates a permutation from the given first-generation * permutation code. * * \pre the given code is a valid first-generation permutation code; * see isPermCode() for details. * * \warning This routine will incur additional overhead, since * NPerm4 now uses second-generation codes internally. * See the class notes and the routine fromPermCode2() for details. * * @param code the first-generation code for the new permutation. * @return the permutation represented by the given code. */ static NPerm4 fromPermCode(unsigned char code); /** * Creates a permutation from the given second-generation * permutation code. * * Second-generation codes are fast to work with, since they are * used internally by the NPerm4 class. * * \pre the given code is a valid second-generation permutation code; * see isPermCode2() for details. * * @param code the second-generation code for the new permutation. * @return the permutation represented by the given code. */ static NPerm4 fromPermCode2(unsigned char code); /** * Determines whether the given character is a valid first-generation * permutation code. Valid first-generation codes can be passed to * setPermCode() or fromPermCode(), and are returned by getPermCode(). * * \warning This routine will incur additional overhead, since * NPerm4 now uses second-generation codes internally. * See the class notes and the routine isPermCode2() for details. * * @param code the permutation code to test. * @return \c true if and only if the given code is a valid * first-generation permutation code. */ static bool isPermCode(unsigned char code); /** * Determines whether the given character is a valid second-generation * permutation code. Valid second-generation codes can be passed * to setPermCode2() or fromPermCode2(), and are returned by * getPermCode2(). * * Second-generation codes are fast to work with, since they are * used internally by the NPerm4 class. * * @param code the permutation code to test. * @return \c true if and only if the given code is a valid * second-generation permutation code. */ static bool isPermCode2(unsigned char code); /** * Sets this permutation to the transposition of * \a a and \a b. * Note that \a a and \a b need not be distinct. * * \pre \a a and \a b are in {0,1,2,3}. * * \deprecated This routine is largely unnecessary, since NPerm4 * objects are tiny and cheap. Just use the assignment operator * instead. This routine will eventually be removed entirely in * a future version of Regina. * * @param a the element to switch with \a b. * @param b the element to switch with \a a. */ void setPerm(int a, int b); /** * Sets this permutation to that mapping (0,1,2,3) to * (a,b,c,d) respectively. * * \pre {a,b,c,d} = {0,1,2,3}. * * \deprecated This routine is largely unnecessary, since NPerm4 * objects are tiny and cheap. Just use the assignment operator * instead. This routine will eventually be removed entirely in * a future version of Regina. * * @param a the desired image of 0. * @param b the desired image of 1. * @param c the desired image of 2. * @param d the desired image of 3. */ void setPerm(int a, int b, int c, int d); /** * Sets this permutation to be equal to the given permutation. * * @param cloneMe the permutation whose value will be assigned * to this permutation. * @return a reference to this permutation. */ NPerm4& operator = (const NPerm4& cloneMe); /** * Returns the composition of this permutation with the given * permutation. If this permutation is p, the * resulting permutation will be p o q, satisfying * (p*q)[x] == p[q[x]]. * * @param q the permutation with which to compose this. * @return the composition of both permutations. */ NPerm4 operator *(const NPerm4& q) const; /** * Finds the inverse of this permutation. * * @return the inverse of this permutation. */ NPerm4 inverse() const; /** * Determines the sign of this permutation. * * @return 1 if this permutation is even, or -1 if this * permutation is odd. */ int sign() const; /** * Determines the image of the given integer under this * permutation. * * @param source the integer whose image we wish to find. This * should be between 0 and 3 inclusive. * @return the image of \a source. */ int operator[](int source) const; /** * Determines the preimage of the given integer under this * permutation. * * @param image the integer whose preimage we wish to find. This * should be between 0 and 3 inclusive. * @return the preimage of \a image. */ int preImageOf(int image) const; /** * Determines if this is equal to the given permutation. * This is true if and only if both permutations have the same * images for 0, 1, 2 and 3. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * are equal. */ bool operator == (const NPerm4& other) const; /** * Determines if this differs from the given permutation. * This is true if and only if the two permutations have * different images for at least one of 0, 1, 2 or 3. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * differ. */ bool operator != (const NPerm4& other) const; /** * Lexicographically compares the images of (0,1,2,3) under this * and the given permutation. * * @param other the permutation with which to compare this. * @return -1 if this permutation produces a smaller image, 0 if * the permutations are equal and 1 if this permutation produces * a greater image. */ int compareWith(const NPerm4& other) const; /** * Determines if this is the identity permutation. * This is true if and only if each of 0, 1, 2 and 3 is * mapped to itself. * * @return \c true if and only if this is the identity * permutation. */ bool isIdentity() const; /** * A deprecated alias for str(), which returns a string representation * of this permutation. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type str() instead. * * @return a string representation of this permutation. */ std::string toString() const; /** * Returns a string representation of this permutation. * The representation will consist of four adjacent digits * representing the images of 0, 1, 2 and 3 respectively. An * example of a string representation is 1302. * * @return a string representation of this permutation. */ std::string str() const; /** * Returns a string representation of this permutation with only * the images of 0 and 1. The resulting string will therefore * have length two. * * @return a truncated string representation of this permutation. */ std::string trunc2() const; /** * Returns a string representation of this permutation with only * the images of 0, 1 and 2 included. The resulting string will * therefore have length three. * * @return a truncated string representation of this permutation. */ std::string trunc3() const; /** * Returns the index of this permutation in the NPerm4::S4 array. * * @return the index \a i for which this permutation is equal to * NPerm4::S4[i]. This will be between 0 and 23 inclusive. */ int S4Index() const; /** * Returns the index of this permutation in the NPerm4::S4 array. * This is a dimension-agnostic alias for S4Index(). * * @return the index \a i for which this permutation is equal to * NPerm4::S4[i]. This will be between 0 and 23 inclusive. */ int SnIndex() const; /** * Returns the index of this permutation in the NPerm4::orderedS4 array. * * @return the index \a i for which this permutation is equal to * NPerm4::orderedS4[i]. This will be between 0 and 23 inclusive. */ int orderedS4Index() const; /** * Returns the index of this permutation in the NPerm4::orderedS4 array. * This is a dimension-agnostic alias for orderedS4Index(). * * @return the index \a i for which this permutation is equal to * NPerm4::orderedS4[i]. This will be between 0 and 23 inclusive. */ int orderedSnIndex() const; private: /** * Contains the images of every element under every possible * permutation. * * Specifically, the image of \a x under the permutation S4[i] * is imageTable[i][x]. */ static const unsigned char imageTable[24][4]; /** * Contains the full multiplication table for all possible * permutations. * * Specifically, the product S4[x] * S4[y] is the * permutation S4[product[x][y]]. */ static const unsigned char productTable[24][24]; /** * Contains a full table of two-element swaps. * * Specifically, the permutation that swaps \a x and \a y is * S4[swapTable[x][y]]. Here \a x and \a y may be equal. */ static const unsigned char swapTable[4][4]; private: /** * Creates a permutation from the given second-generation * permutation code. * * \pre the given code is a valid second-generation permutation code; * see isPermCode2() for details. * * @param code the second-generation code from which the new * permutation will be created. */ NPerm4(unsigned char code); /** * Returns the index into the NPerm4::S4 array of the permutation that * maps (0,1,2,3) to (a,b,c,d) respectively. * * \pre {a,b,c,d} = {0,1,2,3}. * * @param a the desired image of 0. * @param b the desired image of 1. * @param c the desired image of 2. * @param d the desired image of 3. * @return the index \a i for which the given permutation is equal to * NPerm4::S4[i]. This will be between 0 and 23 inclusive. */ static int S4Index(int a, int b, int c, int d); friend std::ostream& operator << (std::ostream& out, const NPerm4& p); }; /** * Writes a string representation of the given permutation to the given * output stream. The format will be the same as is used by * NPerm4::str(). * * @param out the output stream to which to write. * @param p the permutation to write. * @return a reference to \a out. */ inline REGINA_API std::ostream& operator << (std::ostream& out, const NPerm4& p) { return (out << p.str()); } // Constants /** * An array of size 24 containing all possible permutations of four elements. * * The permutations with even indices in the array are the even permutations, * and those with odd indices in the array are the odd permutations. * * Note that the permutations are not necessarily in lexicographical order. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::S4. The deprecated name * regina::allPermsS4 now just points to NPerm4::S4, and will be removed * in some future version of Regina. */ REGINA_API extern const NPerm4* allPermsS4; /** * An array of size 24 containing the inverses of the permutations in the * array \a allPermsS4. * * Specifically, the inverse of permutation allPermsS4[i] is * the permutation allPermsS4[ allPermsS4Inv[i] ]. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::invS4. The deprecated name * regina::allPermsS4Inv now just points to NPerm4::invS4, and will * be removed in some future version of Regina. */ REGINA_API extern const unsigned* allPermsS4Inv; /** * An array of size 24 containing all possible permutations of four elements * in lexicographical order. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::orderedS4. The deprecated name * regina::orderedPermsS4 now just points to NPerm4::orderedS4, and will be * removed in some future version of Regina. */ REGINA_API extern const NPerm4* orderedPermsS4; /** * An array of size 6 containing all possible permutations of three elements. * In each permutation, 3 maps to 3. * * The permutations with even indices in the array are the even permutations, * and those with odd indices in the array are the odd permutations. * * Note that the permutations are not necessarily in lexicographical order. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::S3. The deprecated name * regina::allPermsS4 now just points to NPerm4::S3, and will be removed * in some future version of Regina. */ REGINA_API extern const NPerm4* allPermsS3; /** * An array of size 6 containing the inverses of the permutations in the * array \a allPermsS3. * * Specifically, the inverse of permutation allPermsS3[i] is * the permutation allPermsS3[ allPermsS3Inv[i] ]. * * \deprecated This array has been moved into the NPerm3 class, and can now * be accessed as the static array NPerm3::invS3. The deprecated name * regina::allPermsS3Inv now just points to NPerm3::invS3, and will be * removed in some future version of Regina. */ REGINA_API extern const unsigned* allPermsS3Inv; /** * An array of size 6 containing all possible permutations of three elements * in lexicographical order. In each permutation, 3 maps to 3. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::orderedS3. The deprecated name * regina::orderedPermsS3 now just points to NPerm4::orderedS3, and will be * removed in some future version of Regina. */ REGINA_API extern const NPerm4* orderedPermsS3; /** * An array of size 2 containing all possible permutations of two elements. * In each permutation, 2 maps to 2 and 3 maps to 3. * * The permutations with even indices in the array are the even permutations, * and those with odd indices in the array are the odd permutations. * * Note that the permutations are also in lexicographical order. * * \deprecated This array has been moved into the NPerm4 class, and can now * be accessed as the static array NPerm4::S2. The deprecated name * regina::allPermsS2 now just points to NPerm4::S2, and will be removed in * some future version of Regina. */ REGINA_API extern const NPerm4* allPermsS2; /** * An array of size 2 containing the inverses of the permutations in the * array \a allPermsS2. * * Specifically, the inverse of permutation allPermsS2[i] is * the permutation allPermsS2[ allPermsS2Inv[i] ]. * * \deprecated This array is unnecessary, since all elements of S2 are * their own inverses. This array will be removed in some future version * of Regina. */ REGINA_API extern const unsigned* allPermsS2Inv; // Routines for constructing the permutations associated to // triangles and edges of the triangulation /** * Returns a permutation mapping (0,1,2) to the vertices of the * given tetrahedron face in their canonical order. The images of * (0,1,2) will be the vertex numbers of the vertices that make up the * given face of a generic tetrahedron. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use the lookup * table NTriangle::ordering instead (which gives identical results). * * @param face a face number in a tetrahedron. This should be between 0 * and 3 inclusive. Note that face i is opposite vertex * i. * @return the permutation representing the canonical ordering of * vertices in the given face. */ REGINA_API NPerm4 faceOrdering(int face); /** * Returns a permutation mapping (0,1) to the vertices of the * given tetrahedron edge in their canonical order. The images of * (0,1) will be the vertex numbers of the vertices that make up the * given edge of a generic tetrahedron. * * The images of 2 and 3 in the returned permutation will be chosen so * that the permutation will be even. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use the lookup * table NEdge::ordering instead (which gives identical results). * * @param edge an edge number in a tetrahedron. This should be between 0 and * 5 inclusive. The constant arrays NEdge::edgeNumber and NEdge::edgeVertex * describe which vertex numbers are joined by which edge numbers. * @return the permutation representing the canonical ordering of * vertices in the given edge. */ REGINA_API NPerm4 edgeOrdering(int edge); /** * Returns a string representation of the permutation mapping * (0,1,2) to the vertices of the given tetrahedron face in their * canonical order, as described in faceOrdering(). * Only the images of 0, 1 and 2 will be put in the string. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use * NTriangle::ordering[face].trunc3() (which gives identical results). * * @param face a face number in a tetrahedron. This should be between 0 * and 3 inclusive. Note that face i is opposite vertex * i. * @return a string representing the * canonical ordering of vertices in the given face. */ REGINA_API std::string faceDescription(int face); /** * Returns a string representation of the given permutation with only * the images of 0, 1 and 2 included. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use NPerm4::trunc3() * instead (which gives identical results). * * @param facePerm the permutation to represent. * @return a restricted string representation of the given permutation. */ REGINA_API std::string faceDescription(const NPerm4& facePerm); /** * Returns a string representation of the permutation mapping * (0,1) to the vertices of the given tetrahedron edge in their * canonical order, as described in edgeOrdering(). * Only the images of 0 and 1 will be put in the string. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use * NEdge::ordering[edge].trunc2() (which gives identical results). * * @param edge an edge number in a tetrahedron. This should be between 0 and * 5 inclusive. The constant arrays NEdge::edgeNumber and NEdge::edgeVertex * describe which vertex numbers are joined by which edge numbers. * @return a string representing the canonical ordering of * vertices in the given edge. */ REGINA_API std::string edgeDescription(int edge); /** * Returns a string representation of the given permutation with only * the images of 0 and 1 included. * * \deprecated This routine is no longer recommended, and will be * removed in some future version of Regina. Please use NPerm4::trunc2() * instead (which gives identical results). * * @param edgePerm the permutation to represent. * @return a restricted string representation of the given permutation. */ REGINA_API std::string edgeDescription(const NPerm4& edgePerm); /*@}*/ // Inline functions for NPerm4 inline NPerm4::NPerm4() : code_(0) { } inline NPerm4::NPerm4(unsigned char code) : code_(code) { } inline NPerm4::NPerm4(int a, int b) : code_(swapTable[a][b]) { } inline NPerm4::NPerm4(int a, int b, int c, int d) : code_(static_cast(S4Index(a, b, c, d))) { } inline NPerm4::NPerm4(const int* image) : code_(static_cast(S4Index( image[0], image[1], image[2], image[3]))) { } inline NPerm4::NPerm4(const NPerm4& cloneMe) : code_(cloneMe.code_) { } inline void NPerm4::setPerm(int a, int b) { code_ = swapTable[a][b]; } inline void NPerm4::setPerm(int a, int b, int c, int d) { code_ = static_cast(S4Index(a, b, c, d)); } inline unsigned char NPerm4::getPermCode() const { return static_cast( imageTable[code_][0] | (imageTable[code_][1] << 2) | (imageTable[code_][2] << 4) | (imageTable[code_][3] << 6)); } inline unsigned char NPerm4::getPermCode2() const { return code_; } inline void NPerm4::setPermCode(unsigned char code) { code_ = static_cast(S4Index( code & 0x03, (code >> 2) & 0x03, (code >> 4) & 0x03, (code >> 6) & 0x03)); } inline void NPerm4::setPermCode2(unsigned char code) { code_ = code; } inline NPerm4 NPerm4::fromPermCode(unsigned char code) { return NPerm4(static_cast(S4Index( code & 0x03, (code >> 2) & 0x03, (code >> 4) & 0x03, (code >> 6) & 0x03))); } inline NPerm4 NPerm4::fromPermCode2(unsigned char code) { return NPerm4(code); } inline bool NPerm4::isPermCode2(unsigned char code) { // code >= 0 is automatic because we are using an unsigned data type. return (code < 24); } inline NPerm4& NPerm4::operator = (const NPerm4& cloneMe) { code_ = cloneMe.code_; return *this; } inline NPerm4 NPerm4::operator *(const NPerm4& q) const { return NPerm4(productTable[code_][q.code_]); } inline NPerm4 NPerm4::inverse() const { return NPerm4(static_cast(invS4[code_])); } inline int NPerm4::sign() const { return (code_ % 2 ? -1 : 1); } inline int NPerm4::operator[](int source) const { return imageTable[code_][source]; } inline int NPerm4::preImageOf(int image) const { return imageTable[invS4[code_]][image]; } inline int NPerm4::compareWith(const NPerm4& other) const { // Computing orderedS4Index() is very fast, now that we use S4 indices // for internal permutation codes. Use this instead of comparing images // one at a time. int o1 = orderedS4Index(); int o2 = other.orderedS4Index(); return (o1 == o2 ? 0 : o1 < o2 ? -1 : 1); } inline bool NPerm4::isIdentity() const { return (code_ == 0); } inline std::string NPerm4::toString() const { return str(); } inline bool NPerm4::operator == (const NPerm4& other) const { return (code_ == other.code_); } inline bool NPerm4::operator != (const NPerm4& other) const { return (code_ != other.code_); } inline int NPerm4::S4Index() const { return code_; } inline int NPerm4::orderedS4Index() const { // S4 is almost the same as orderedS4, except that some pairs // S4[2i] <--> S4[2i+1] have been swapped to ensure that all // permutations S4[2i] are even and all permutations S4[2i+1] are odd. // // Specifically, we must interchange all pairs 4i+2 <--> 4i+3. return ((code_ & 2) ? (code_ ^ 1) : code_); } inline int NPerm4::orderedSnIndex() const { return orderedS4Index(); } inline int NPerm4::S4Index(int a, int b, int c, int d) { int orderedS4Index = 6 * a + 2 * (b - (b > a ? 1 : 0)) + (c > d ? 1 : 0); // As above, to obtain an S4 index, interchange all pairs 4i+2 <--> 4i+3. return ((orderedS4Index & 2) ? (orderedS4Index ^ 1) : orderedS4Index); } inline int NPerm4::SnIndex() const { return S4Index(); } inline std::string faceDescription(const NPerm4& facePerm) { return facePerm.trunc3(); } inline std::string edgeDescription(const NPerm4& edgePerm) { return edgePerm.trunc2(); } } // namespace regina #endif regina-4.95/engine/maths/nperm5.cpp000644 000765 000024 00000031641 12234011536 017104 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nperm5.h" namespace regina { const NPerm5 NPerm5::S5[120] = { NPerm5(0,1,2,3,4), NPerm5(0,1,2,4,3), NPerm5(0,1,3,4,2), NPerm5(0,1,3,2,4), NPerm5(0,1,4,2,3), NPerm5(0,1,4,3,2), NPerm5(0,2,1,4,3), NPerm5(0,2,1,3,4), NPerm5(0,2,3,1,4), NPerm5(0,2,3,4,1), NPerm5(0,2,4,3,1), NPerm5(0,2,4,1,3), NPerm5(0,3,1,2,4), NPerm5(0,3,1,4,2), NPerm5(0,3,2,4,1), NPerm5(0,3,2,1,4), NPerm5(0,3,4,1,2), NPerm5(0,3,4,2,1), NPerm5(0,4,1,3,2), NPerm5(0,4,1,2,3), NPerm5(0,4,2,1,3), NPerm5(0,4,2,3,1), NPerm5(0,4,3,2,1), NPerm5(0,4,3,1,2), NPerm5(1,0,2,4,3), NPerm5(1,0,2,3,4), NPerm5(1,0,3,2,4), NPerm5(1,0,3,4,2), NPerm5(1,0,4,3,2), NPerm5(1,0,4,2,3), NPerm5(1,2,0,3,4), NPerm5(1,2,0,4,3), NPerm5(1,2,3,4,0), NPerm5(1,2,3,0,4), NPerm5(1,2,4,0,3), NPerm5(1,2,4,3,0), NPerm5(1,3,0,4,2), NPerm5(1,3,0,2,4), NPerm5(1,3,2,0,4), NPerm5(1,3,2,4,0), NPerm5(1,3,4,2,0), NPerm5(1,3,4,0,2), NPerm5(1,4,0,2,3), NPerm5(1,4,0,3,2), NPerm5(1,4,2,3,0), NPerm5(1,4,2,0,3), NPerm5(1,4,3,0,2), NPerm5(1,4,3,2,0), NPerm5(2,0,1,3,4), NPerm5(2,0,1,4,3), NPerm5(2,0,3,4,1), NPerm5(2,0,3,1,4), NPerm5(2,0,4,1,3), NPerm5(2,0,4,3,1), NPerm5(2,1,0,4,3), NPerm5(2,1,0,3,4), NPerm5(2,1,3,0,4), NPerm5(2,1,3,4,0), NPerm5(2,1,4,3,0), NPerm5(2,1,4,0,3), NPerm5(2,3,0,1,4), NPerm5(2,3,0,4,1), NPerm5(2,3,1,4,0), NPerm5(2,3,1,0,4), NPerm5(2,3,4,0,1), NPerm5(2,3,4,1,0), NPerm5(2,4,0,3,1), NPerm5(2,4,0,1,3), NPerm5(2,4,1,0,3), NPerm5(2,4,1,3,0), NPerm5(2,4,3,1,0), NPerm5(2,4,3,0,1), NPerm5(3,0,1,4,2), NPerm5(3,0,1,2,4), NPerm5(3,0,2,1,4), NPerm5(3,0,2,4,1), NPerm5(3,0,4,2,1), NPerm5(3,0,4,1,2), NPerm5(3,1,0,2,4), NPerm5(3,1,0,4,2), NPerm5(3,1,2,4,0), NPerm5(3,1,2,0,4), NPerm5(3,1,4,0,2), NPerm5(3,1,4,2,0), NPerm5(3,2,0,4,1), NPerm5(3,2,0,1,4), NPerm5(3,2,1,0,4), NPerm5(3,2,1,4,0), NPerm5(3,2,4,1,0), NPerm5(3,2,4,0,1), NPerm5(3,4,0,1,2), NPerm5(3,4,0,2,1), NPerm5(3,4,1,2,0), NPerm5(3,4,1,0,2), NPerm5(3,4,2,0,1), NPerm5(3,4,2,1,0), NPerm5(4,0,1,2,3), NPerm5(4,0,1,3,2), NPerm5(4,0,2,3,1), NPerm5(4,0,2,1,3), NPerm5(4,0,3,1,2), NPerm5(4,0,3,2,1), NPerm5(4,1,0,3,2), NPerm5(4,1,0,2,3), NPerm5(4,1,2,0,3), NPerm5(4,1,2,3,0), NPerm5(4,1,3,2,0), NPerm5(4,1,3,0,2), NPerm5(4,2,0,1,3), NPerm5(4,2,0,3,1), NPerm5(4,2,1,3,0), NPerm5(4,2,1,0,3), NPerm5(4,2,3,0,1), NPerm5(4,2,3,1,0), NPerm5(4,3,0,2,1), NPerm5(4,3,0,1,2), NPerm5(4,3,1,0,2), NPerm5(4,3,1,2,0), NPerm5(4,3,2,1,0), NPerm5(4,3,2,0,1), }; const NPerm5* NPerm5::Sn = NPerm5::S5; const NPerm5 NPerm5::orderedS5[120] = { NPerm5(0,1,2,3,4), NPerm5(0,1,2,4,3), NPerm5(0,1,3,2,4), NPerm5(0,1,3,4,2), NPerm5(0,1,4,2,3), NPerm5(0,1,4,3,2), NPerm5(0,2,1,3,4), NPerm5(0,2,1,4,3), NPerm5(0,2,3,1,4), NPerm5(0,2,3,4,1), NPerm5(0,2,4,1,3), NPerm5(0,2,4,3,1), NPerm5(0,3,1,2,4), NPerm5(0,3,1,4,2), NPerm5(0,3,2,1,4), NPerm5(0,3,2,4,1), NPerm5(0,3,4,1,2), NPerm5(0,3,4,2,1), NPerm5(0,4,1,2,3), NPerm5(0,4,1,3,2), NPerm5(0,4,2,1,3), NPerm5(0,4,2,3,1), NPerm5(0,4,3,1,2), NPerm5(0,4,3,2,1), NPerm5(1,0,2,3,4), NPerm5(1,0,2,4,3), NPerm5(1,0,3,2,4), NPerm5(1,0,3,4,2), NPerm5(1,0,4,2,3), NPerm5(1,0,4,3,2), NPerm5(1,2,0,3,4), NPerm5(1,2,0,4,3), NPerm5(1,2,3,0,4), NPerm5(1,2,3,4,0), NPerm5(1,2,4,0,3), NPerm5(1,2,4,3,0), NPerm5(1,3,0,2,4), NPerm5(1,3,0,4,2), NPerm5(1,3,2,0,4), NPerm5(1,3,2,4,0), NPerm5(1,3,4,0,2), NPerm5(1,3,4,2,0), NPerm5(1,4,0,2,3), NPerm5(1,4,0,3,2), NPerm5(1,4,2,0,3), NPerm5(1,4,2,3,0), NPerm5(1,4,3,0,2), NPerm5(1,4,3,2,0), NPerm5(2,0,1,3,4), NPerm5(2,0,1,4,3), NPerm5(2,0,3,1,4), NPerm5(2,0,3,4,1), NPerm5(2,0,4,1,3), NPerm5(2,0,4,3,1), NPerm5(2,1,0,3,4), NPerm5(2,1,0,4,3), NPerm5(2,1,3,0,4), NPerm5(2,1,3,4,0), NPerm5(2,1,4,0,3), NPerm5(2,1,4,3,0), NPerm5(2,3,0,1,4), NPerm5(2,3,0,4,1), NPerm5(2,3,1,0,4), NPerm5(2,3,1,4,0), NPerm5(2,3,4,0,1), NPerm5(2,3,4,1,0), NPerm5(2,4,0,1,3), NPerm5(2,4,0,3,1), NPerm5(2,4,1,0,3), NPerm5(2,4,1,3,0), NPerm5(2,4,3,0,1), NPerm5(2,4,3,1,0), NPerm5(3,0,1,2,4), NPerm5(3,0,1,4,2), NPerm5(3,0,2,1,4), NPerm5(3,0,2,4,1), NPerm5(3,0,4,1,2), NPerm5(3,0,4,2,1), NPerm5(3,1,0,2,4), NPerm5(3,1,0,4,2), NPerm5(3,1,2,0,4), NPerm5(3,1,2,4,0), NPerm5(3,1,4,0,2), NPerm5(3,1,4,2,0), NPerm5(3,2,0,1,4), NPerm5(3,2,0,4,1), NPerm5(3,2,1,0,4), NPerm5(3,2,1,4,0), NPerm5(3,2,4,0,1), NPerm5(3,2,4,1,0), NPerm5(3,4,0,1,2), NPerm5(3,4,0,2,1), NPerm5(3,4,1,0,2), NPerm5(3,4,1,2,0), NPerm5(3,4,2,0,1), NPerm5(3,4,2,1,0), NPerm5(4,0,1,2,3), NPerm5(4,0,1,3,2), NPerm5(4,0,2,1,3), NPerm5(4,0,2,3,1), NPerm5(4,0,3,1,2), NPerm5(4,0,3,2,1), NPerm5(4,1,0,2,3), NPerm5(4,1,0,3,2), NPerm5(4,1,2,0,3), NPerm5(4,1,2,3,0), NPerm5(4,1,3,0,2), NPerm5(4,1,3,2,0), NPerm5(4,2,0,1,3), NPerm5(4,2,0,3,1), NPerm5(4,2,1,0,3), NPerm5(4,2,1,3,0), NPerm5(4,2,3,0,1), NPerm5(4,2,3,1,0), NPerm5(4,3,0,1,2), NPerm5(4,3,0,2,1), NPerm5(4,3,1,0,2), NPerm5(4,3,1,2,0), NPerm5(4,3,2,0,1), NPerm5(4,3,2,1,0), }; const NPerm5* NPerm5::orderedSn = NPerm5::orderedS5; const unsigned NPerm5::invS5[120] = { 0, 1, 4, 3, 2, 5, 6, 7, 12, 19, 18, 13, 8, 11, 20, 15, 16, 23, 10, 9, 14, 21, 22, 17, 24, 25, 26, 29, 28, 27, 48, 49, 96, 73, 72, 97, 52, 51, 74, 99,100, 77, 50, 53, 98, 75, 76,101, 30, 31, 42, 37, 36, 43, 54, 55, 78,103,102, 79, 60, 67,108, 85, 90,115, 66, 61, 84,109,114, 91, 34, 33, 38, 45, 46, 41, 56, 59,104, 81, 82,107, 68, 63, 86,111,116, 93, 64, 71,112, 89, 94,119, 32, 35, 44, 39, 40, 47, 58, 57, 80,105,106, 83, 62, 69,110, 87, 92,117, 70, 65, 88,113,118, 95 }; const NPerm5 NPerm5::S4[24] = { NPerm5(0,1,2,3,4), NPerm5(0,1,3,2,4), NPerm5(0,2,3,1,4), NPerm5(0,2,1,3,4), NPerm5(0,3,1,2,4), NPerm5(0,3,2,1,4), NPerm5(1,0,3,2,4), NPerm5(1,0,2,3,4), NPerm5(1,2,0,3,4), NPerm5(1,2,3,0,4), NPerm5(1,3,2,0,4), NPerm5(1,3,0,2,4), NPerm5(2,0,1,3,4), NPerm5(2,0,3,1,4), NPerm5(2,1,3,0,4), NPerm5(2,1,0,3,4), NPerm5(2,3,0,1,4), NPerm5(2,3,1,0,4), NPerm5(3,0,2,1,4), NPerm5(3,0,1,2,4), NPerm5(3,1,0,2,4), NPerm5(3,1,2,0,4), NPerm5(3,2,1,0,4), NPerm5(3,2,0,1,4) }; const NPerm5* NPerm5::Sn_1 = NPerm5::S4; const NPerm5 NPerm5::orderedS4[24] = { NPerm5(0,1,2,3,4), NPerm5(0,1,3,2,4), NPerm5(0,2,1,3,4), NPerm5(0,2,3,1,4), NPerm5(0,3,1,2,4), NPerm5(0,3,2,1,4), NPerm5(1,0,2,3,4), NPerm5(1,0,3,2,4), NPerm5(1,2,0,3,4), NPerm5(1,2,3,0,4), NPerm5(1,3,0,2,4), NPerm5(1,3,2,0,4), NPerm5(2,0,1,3,4), NPerm5(2,0,3,1,4), NPerm5(2,1,0,3,4), NPerm5(2,1,3,0,4), NPerm5(2,3,0,1,4), NPerm5(2,3,1,0,4), NPerm5(3,0,1,2,4), NPerm5(3,0,2,1,4), NPerm5(3,1,0,2,4), NPerm5(3,1,2,0,4), NPerm5(3,2,0,1,4), NPerm5(3,2,1,0,4) }; const NPerm5 NPerm5::S3[6] = { NPerm5(0,1,2,3,4), NPerm5(0,2,1,3,4), NPerm5(1,2,0,3,4), NPerm5(1,0,2,3,4), NPerm5(2,0,1,3,4), NPerm5(2,1,0,3,4) }; const NPerm5 NPerm5::orderedS3[6] = { NPerm5(0,1,2,3,4), NPerm5(0,2,1,3,4), NPerm5(1,0,2,3,4), NPerm5(1,2,0,3,4), NPerm5(2,0,1,3,4), NPerm5(2,1,0,3,4) }; const NPerm5 NPerm5::S2[2] = { NPerm5(0,1,2,3,4), NPerm5(1,0,2,3,4) }; bool NPerm5::isPermCode(unsigned code) { unsigned mask = 0; for (int i = 0; i < 5; i++) mask |= (1 << ((code >> (3 * i)) & 7)); // mask |= (1 << imageOf(i)); return (mask == 31); } int NPerm5::sign() const { // Try to streamline this routine. // Count the number of elements that map to themselves. unsigned matches = 0; if ((code & 7) == 0) ++matches; if ((code & (7 << 3)) == (1 << 3)) ++matches; if ((code & (7 << 6)) == (2 << 6)) ++matches; if ((code & (7 << 9)) == (3 << 9)) ++matches; if ((code & (7 << 12)) == (4 << 12)) ++matches; if (matches == 5) return 1; if (matches == 3) return -1; if (matches == 2) return 1; // We have at most one fixed point. // Now count the number of order two points. unsigned two = 0; if (((code >> (3 * (code & 7))) & 7) == 0) ++two; if (((code >> (3 * ((code >> 3) & 7))) & 7) == 1) ++two; if (((code >> (3 * ((code >> 6) & 7))) & 7) == 2) ++two; if (((code >> (3 * ((code >> 9) & 7))) & 7) == 3) ++two; if (((code >> (3 * ((code >> 12) & 7))) & 7) == 4) ++two; if (matches == 1) { // We have one fixed point, which means we have a permutation // of four elements that leaves nothing fixed. // This means either (a b)(c d) or (a b c d). if (two == 5) return 1; return -1; } // We have no fixed points, which means we have either // (a b)(c d e) or (a b c d e). if (two == 0) return 1; return -1; } int NPerm5::compareWith(const NPerm5& other) const { for (int i = 0; i < 5; i++) { if (imageOf(i) < other.imageOf(i)) return -1; if (imageOf(i) > other.imageOf(i)) return 1; } return 0; } std::string NPerm5::str() const { char ans[6]; for (int i = 0; i < 5; i++) ans[i] = static_cast('0' + imageOf(i)); ans[5] = 0; return ans; } std::string NPerm5::trunc2() const { char ans[3]; ans[0] = static_cast('0' + imageOf(0)); ans[1] = static_cast('0' + imageOf(1)); ans[2] = 0; return ans; } std::string NPerm5::trunc3() const { char ans[4]; ans[0] = static_cast('0' + imageOf(0)); ans[1] = static_cast('0' + imageOf(1)); ans[2] = static_cast('0' + imageOf(2)); ans[3] = 0; return ans; } std::string NPerm5::trunc4() const { char ans[5]; ans[0] = static_cast('0' + imageOf(0)); ans[1] = static_cast('0' + imageOf(1)); ans[2] = static_cast('0' + imageOf(2)); ans[3] = static_cast('0' + imageOf(3)); ans[4] = 0; return ans; } /** * Returns the number n such that NPerm5::orderedS5[n] == *this. */ int NPerm5::orderedS5Index() const { return 24*imageOf(0) + 6*( imageOf(1)-( (imageOf(1) > imageOf(0)) ? 1 : 0) ) + 2*( imageOf(2)-( ( (imageOf(2) > imageOf(1)) ? 1 : 0) + ( (imageOf(2) > imageOf(0)) ? 1 : 0) ) ) + ( (imageOf(3) > imageOf(4)) ? 1 : 0 ); } /** * Returns the number n such that NPerm5::S5[n] == *this. */ int NPerm5::S5Index() const { // S5 is almost the same as orderedS5, except that some pairs // S5[2i] <--> S5[2i+1] have been swapped to ensure that all // permutations S5[2i] are even and all permutations S5[2i+1] are odd. int retval = orderedS5Index(); // Flip between 2i <--> 2i+1 if and only if // one but not both of (retval / 2) and (retval / 24) is even. // Here we use (retval >> 1), which is equivalent to (retval / 2). if (((retval >> 1) ^ (retval / 24)) & 1) retval ^= 1; return retval; } } // namespace regina regina-4.95/engine/maths/nperm5.h000644 000765 000024 00000054265 12234011536 016560 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nperm5.h * \brief Deals with permutations of {0,1,2,3,4}. */ #ifndef __NPERM5_H #ifndef __DOXYGEN #define __NPERM5_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents a permutation of {0,1,2,3,4}. * Amongst other things, such permutations are used in describing * simplex gluings in 4-manifold triangulations. NPerm5 objects are small * enough to pass about by value instead of by reference. * * Each permutation has an internal code, and this code is sufficient to * reconstruct the permutation. * Thus the internal code may be a useful means for passing * permutation objects to and from the engine. * * The internal code is an unsigned integer. The lowest three bits represent * the image of 0, the next lowest three bits represent the image of 1 and so * on. * * \testfull */ class REGINA_API NPerm5 { public: /** * Contains all possible permutations of five elements. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * Note that the permutations are not necessarily in * lexicographical order. */ static const NPerm5 S5[120]; /** * A dimension-agnostic alias for NPerm5::S5. In general, for * each \a K the class NPermK will define an alias \a Sn * that references the list of all permutations NPermK::SK. */ static const NPerm5* Sn; /** * Contains all possible permutations of five elements in * lexicographical order. */ static const NPerm5 orderedS5[120]; /** * A dimension-agnostic alias for NPerm5::orderedS5. In general, for * each \a K the class NPermK will define an alias \a orderedSn * that references the list of all permutations NPermK::orderedSK. */ static const NPerm5* orderedSn; /** * Contains the inverses of the permutations in the array \a S5. * * Specifically, the inverse of permutation S5[i] is * the permutation S5[ invS5[i] ]. */ static const unsigned invS5[120]; /** * Contains all possible permutations of four elements. * In each permutation, 4 maps to 4. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S4 array stores the same permutations in the same order (but * of course using different data types). * * Note that the permutations are not necessarily in * lexicographical order. For the corresponding inverse array, * see NPerm4::invS4. */ static const NPerm5 S4[24]; /** * A dimension-agnostic alias for NPerm5::S4. In general, for * each \a K the class NPermK will define an alias \a Sn_1 * that references the list of all permutations NPermK::S(K-1). */ static const NPerm5* Sn_1; /** * Contains all possible permutations of four elements in * lexicographical order. In each permutation, 4 maps to 4. */ static const NPerm5 orderedS4[24]; /** * Contains all possible permutations of three elements. * In each permutation, 3 maps to 3 and 4 maps to 4. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S3 array stores the same permutations in the same order (but * of course using different data types). * * Note that the permutations are not necessarily in * lexicographical order. For the corresponding inverse array, * see NPerm3::invS3. */ static const NPerm5 S3[6]; /** * Contains all possible permutations of three elements in * lexicographical order. In each permutation, 3 maps to 3 and * 4 maps to 4. */ static const NPerm5 orderedS3[6]; /** * Contains all possible permutations of two elements. * In each permutation, 2 maps to 2, 3 maps to 3, and 4 maps to 4. * * The permutations with even indices in the array are the even * permutations, and those with odd indices in the array are the * odd permutations. * * For all permutation classes (NPerm4, NPerm5 and so on), the * S2 array stores the same permutations in the same order (but * of course using different data types). * * Note that these permutations are already in lexicographical order. */ static const NPerm5 S2[2]; enum { /** * The total number of permutations on five elements. * This is the size of the array Sn. * * \ifacespython Not present. */ nPerms = 120, /** * The total number of permutations on four elements. * This is the size of the array Sn_1. * * \ifacespython Not present. */ nPerms_1 = 24 }; private: unsigned code; /**< The internal code representing this permutation. */ public: /** * Creates the identity permutation. */ NPerm5(); /** * Creates the transposition of \a a and \a b. * Note that \a a and \a b need not be distinct. * * \pre \a a and \a b are in {0,1,2,3,4}. * * @param a the element to switch with \a b. * @param b the element to switch with \a a. */ NPerm5(int a, int b); /** * Creates a permutation mapping (0,1,2,3,4) to * (a,b,c,d,e) respectively. * * \pre {a,b,c,d,e} = {0,1,2,3,4}. * * @param a the desired image of 0. * @param b the desired image of 1. * @param c the desired image of 2. * @param d the desired image of 3. * @param e the desired image of 4. */ NPerm5(int a, int b, int c, int d, int e); /** * Creates a permutation mapping \a i to \a image[i] for each * \a i = 0,1,2,3,4. * * \pre The array \a image contains five elements, which are * 0, 1, 2, 3 and 4 in some order. * * \ifacespython Not present. * * @param image the array of images. */ NPerm5(const int* image); /** * Creates a permutation mapping * (a0,b0,c0,d0,e0) to * (a1,b1,c1,d1,e1) respectively. * * \pre {a0,b0,c0,d0,e0} = * {a1,b1,c1,d1,e1} = * {0,1,2,3,4}. * * @param a0 the desired preimage of a1. * @param b0 the desired preimage of b1. * @param c0 the desired preimage of c1. * @param d0 the desired preimage of d1. * @param e0 the desired preimage of e1. * @param a1 the desired image of a0. * @param b1 the desired image of b0. * @param c1 the desired image of c0. * @param d1 the desired image of d0. * @param e1 the desired image of e0. */ NPerm5(int a0, int a1, int b0, int b1, int c0, int c1, int d0, int d1, int e0, int e1); /** * Creates a permutation that is a clone of the given * permutation. * * @param cloneMe the permutation to clone. */ NPerm5(const NPerm5& cloneMe); /** * Returns the internal code representing this permutation. * Note that the internal code is sufficient to reproduce the * entire permutation. * * The code returned will be a valid permutation code as * determined by isPermCode(). * * @return the internal code. */ unsigned getPermCode() const; /** * Sets this permutation to that represented by the given * internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param newCode the internal code that will determine the * new value of this permutation. */ void setPermCode(unsigned newCode); /** * Creates a permutation from the given internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param newCode the internal code for the new permutation. * @return the permutation reprsented by the given internal code. */ static NPerm5 fromPermCode(unsigned newCode); /** * Determines whether the given integer is a valid internal * permutation code. Valid permutation codes can be passed to * setPermCode() or fromPermCode(), and are returned by getPermCode(). * * @return \c true if and only if the given code is a valid * internal permutation code. */ static bool isPermCode(unsigned newCode); /** * Sets this permutation to be equal to the given permutation. * * @param cloneMe the permutation whose value will be assigned * to this permutation. * @return a reference to this permutation. */ NPerm5& operator = (const NPerm5& cloneMe); /** * Returns the composition of this permutation with the given * permutation. If this permutation is p, the * resulting permutation will be p o q, satisfying * (p*q)[x] == p[q[x]]. * * @param q the permutation with which to compose this. * @return the composition of both permutations. */ NPerm5 operator * (const NPerm5& q) const; /** * Finds the inverse of this permutation. * * @return the inverse of this permutation. */ NPerm5 inverse() const; /** * Determines the sign of this permutation. * * @return 1 if this permutation is even, or -1 if this * permutation is odd. */ int sign() const; /** * Determines the image of the given integer under this * permutation. * * @param source the integer whose image we wish to find. This * should be between 0 and 4 inclusive. * @return the image of \a source. */ int operator[](int source) const; /** * Determines the preimage of the given integer under this * permutation. * * @param image the integer whose preimage we wish to find. This * should be between 0 and 4 inclusive. * @return the preimage of \a image. */ int preImageOf(int image) const; /** * Determines if this is equal to the given permutation. * This is true if and only if both permutations have the same * images for 0, 1, 2, 3 and 4. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * are equal. */ bool operator == (const NPerm5& other) const; /** * Determines if this differs from the given permutation. * This is true if and only if the two permutations have * different images for at least one of 0, 1, 2, 3 or 4. * * @param other the permutation with which to compare this. * @return \c true if and only if this and the given permutation * differ. */ bool operator != (const NPerm5& other) const; /** * Lexicographically compares the images of (0,1,2,3,4) under this * and the given permutation. * * @param other the permutation with which to compare this. * @return -1 if this permutation produces a smaller image, 0 if * the permutations are equal and 1 if this permutation produces * a greater image. */ int compareWith(const NPerm5& other) const; /** * Determines if this is the identity permutation. * This is true if and only if each of 0, 1, 2, 3 and 4 is * mapped to itself. * * @return \c true if and only if this is the identity * permutation. */ bool isIdentity() const; /** * A deprecated alias for str(), which returns a string representation * of this permutation. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type str() instead. * * @return a string representation of this permutation. */ std::string toString() const; /** * Returns a string representation of this permutation. * The representation will consist of five adjacent digits * representing the images of 0, 1, 2, 3 and 4 respectively. * An example of a string representation is 30421. * * @return a string representation of this permutation. */ std::string str() const; /** * Returns a string representation of this permutation with only * the images of 0 and 1. The resulting string will therefore * have length two. * * @return a truncated string representation of this permutation. */ std::string trunc2() const; /** * Returns a string representation of this permutation with only * the images of 0, 1 and 2. The resulting string will therefore * have length three. * * @return a truncated string representation of this permutation. */ std::string trunc3() const; /** * Returns a string representation of this permutation with only * the images of 0, 1, 2 and 3. The resulting string will therefore * have length four. * * @return a truncated string representation of this permutation. */ std::string trunc4() const; /** * Returns the index of this permutation in the NPerm5::S5 array. * * @return the index \a i for which this permutation is equal to * NPerm5::S5[i]. This will be between 0 and 119 inclusive. * * @author Ryan Budney */ int S5Index() const; /** * Returns the index of this permutation in the NPerm5::S5 array. * This is a dimension-agnostic alias for S5Index(). * * @return the index \a i for which this permutation is equal to * NPerm5::S5[i]. This will be between 0 and 119 inclusive. */ int SnIndex() const; /** * Returns the index of this permutation in the NPerm5::orderedS5 array. * * @return the index \a i for which this permutation is equal to * NPerm5::orderedS5[i]. This will be between 0 and 119 inclusive. * * @author Ryan Budney */ int orderedS5Index() const; /** * Returns the index of this permutation in the NPerm5::orderedS5 array. * This is a dimension-agnostic alias for orderedS5Index(). * * @return the index \a i for which this permutation is equal to * NPerm5::orderedS5[i]. This will be between 0 and 119 inclusive. */ int orderedSnIndex() const; private: /** * Creates a permutation from the given internal code. * * \pre the given code is a valid permutation code; see * isPermCode() for details. * * @param newCode the internal code from which the new * permutation will be created. */ NPerm5(unsigned newCode); /** * Determines the image of the given integer under this * permutation. * * @param source the integer whose image we wish to find. This * should be between 0 and 4 inclusive. * @return the image of \a source. */ int imageOf(int source) const; friend std::ostream& operator << (std::ostream& out, const NPerm5& p); }; /** * Writes a string representation of the given permutation to the given * output stream. The format will be the same as is used by * NPerm5::str(). * * @param out the output stream to which to write. * @param p the permutation to write. * @return a reference to \a out. */ inline REGINA_API std::ostream& operator << (std::ostream& out, const NPerm5& p) { return (out << p.str()); } /*@}*/ // Inline functions for NPerm5 inline NPerm5::NPerm5() : code(18056) { } inline NPerm5::NPerm5(unsigned newCode) : code(newCode) { } inline NPerm5::NPerm5(int a, int b) { code = 18056; code += ((a << (3*b)) - (b << (3*b))); code += ((b << (3*a)) - (a << (3*a))); } inline NPerm5::NPerm5(int a, int b, int c, int d, int e) { code = (e << 12) | (d << 9) | (c << 6) | (b << 3) | a; } inline NPerm5::NPerm5(const int* image) { code = (image[4] << 12) | (image[3] << 9) | (image[2] << 6) | (image[1] << 3) | image[0]; } inline NPerm5::NPerm5(int a0, int a1, int b0, int b1, int c0, int c1, int d0, int d1, int e0, int e1) { code = (a1 << (3*a0)) | (b1 << (3*b0)) | (c1 << (3*c0)) | (d1 << (3*d0)) | (e1 << (3*e0)); } inline NPerm5::NPerm5(const NPerm5& cloneMe) : code(cloneMe.code) { } inline unsigned NPerm5::getPermCode() const { return code; } inline void NPerm5::setPermCode(unsigned newCode) { code = newCode; } inline NPerm5 NPerm5::fromPermCode(unsigned newCode) { return NPerm5(newCode); } inline NPerm5& NPerm5::operator = (const NPerm5& cloneMe) { code = cloneMe.code; return *this; } inline NPerm5 NPerm5::operator *(const NPerm5& q) const { return NPerm5(imageOf(q[0]), imageOf(q[1]), imageOf(q[2]), imageOf(q[3]), imageOf(q[4])); } inline NPerm5 NPerm5::inverse() const { // Specify the inverse by its internal code. return NPerm5(static_cast( (1 << (3*imageOf(1))) | (2 << (3*imageOf(2))) | (3 << (3*imageOf(3))) | (4 << (3*imageOf(4))))); } inline int NPerm5::operator[](int source) const { return (code >> (3*source)) & 7; } inline int NPerm5::preImageOf(int image) const { if (( code & 7) == static_cast(image)) return 0; if (((code >> 3) & 7) == static_cast(image)) return 1; if (((code >> 6) & 7) == static_cast(image)) return 2; if (((code >> 9) & 7) == static_cast(image)) return 3; return 4; } inline bool NPerm5::operator == (const NPerm5& other) const { return (code == other.code); } inline bool NPerm5::operator != (const NPerm5& other) const { return (code != other.code); } inline std::string NPerm5::toString() const { return str(); } inline bool NPerm5::isIdentity() const { return (code == 18056); } inline int NPerm5::imageOf(int source) const { return (code >> (3*source)) & 7; } inline int NPerm5::SnIndex() const { return S5Index(); } inline int NPerm5::orderedSnIndex() const { return orderedS5Index(); } } // namespace regina #endif regina-4.95/engine/maths/nprimes.cpp000644 000765 000024 00000015415 12234011536 017354 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nprimes.h" namespace regina { std::vector NPrimes::largePrimes; NLargeInteger NPrimes::prime(unsigned long which, bool autoGrow) { // Can we grab it straight out of the hard-coded seed list? if (which < numPrimeSeeds) return primeSeedList[which]; // Do we even have the requested prime stored? if (which >= numPrimeSeeds + largePrimes.size()) { if (autoGrow) growPrimeList(which - numPrimeSeeds - largePrimes.size() + 1); else return NLargeInteger::zero; } // Got it. return largePrimes[which - numPrimeSeeds]; } void NPrimes::growPrimeList(unsigned long extras) { NLargeInteger lastPrime = (largePrimes.empty() ? primeSeedList[numPrimeSeeds - 1] : largePrimes[largePrimes.size() - 1]); NLargeInteger newPrime; // Since this is all being done through GMP, just bite the bullet // and make them all GMP integers (not native integers). // This means we can call rawData() with abandon. while (extras) { mpz_nextprime(newPrime.rawData(), lastPrime.rawData()); newPrime.tryReduce(); // since rawData() forced it into GMP format largePrimes.push_back(newPrime); lastPrime = newPrime; extras--; } } std::vector NPrimes::primeDecomp(const NLargeInteger& n) { std::vector retval; // Deal with n=0 first. if (n == NLargeInteger::zero) { retval.push_back(NLargeInteger::zero); return retval; } NLargeInteger temp(n); NLargeInteger r,q; // if the number is negative, put -1 as first factor. if (temp < NLargeInteger::zero) { temp.negate(); retval.push_back(NLargeInteger(-1)); } // repeatedly divide the number by the smallest primes until no // longer divisible. // at present the algorithm is only guaranteed to factorize the integer // into its prime factors if none of them are larger than the 500th smallest // prime. it always produces a factorization, but after the 500th it uses // a probabilistic test to speed things up. This algorithm is at present // ad-hoc since the current usage in Regina rarely demands the // factorization of even a 4-digit number. unsigned long cpi=0; // current prime index. unsigned long iterSinceDivision=0; // keeps track of how many iterations // since the last successful division while ( temp != NLargeInteger::one ) { // now cpi > NPrimes::primePowerDecomp(const NLargeInteger& n) { std::vector list1(primeDecomp(n)); std::vector< std::pair > retlist; // go through list1, record number of each prime, put in retlist. if (! list1.empty()) { NLargeInteger cp(list1.front()); // current prime unsigned long cc(1); // current count std::vector::const_iterator it = list1.begin(); for (++it; it != list1.end(); ++it) { if (*it == cp) cc++; else { // a new prime is coming up. retlist.push_back(std::make_pair( cp, cc ) ); cp = *it; cc = 1; } } retlist.push_back(std::make_pair( cp, cc ) ); } return retlist; } } // namespace regina regina-4.95/engine/maths/nprimes.h000644 000765 000024 00000030467 12234011536 017025 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NPRIMES_H #ifndef __DOXYGEN #define __NPRIMES_H #endif /*! \file maths/nprimes.h * \brief Support for finding primes and factorising integers. */ #include "regina-core.h" #include "maths/ninteger.h" #include namespace regina { /** * \weakgroup maths * @{ */ /** * A helper class for finding primes and factorising integers. * * This class has two functions: (i) to maintain a list of known primes, * and (ii) to use this list to factorise integers into prime factors. * * The primes stored by this class will always be the smallest \a k * suspected primes, where \a k may grow dynamically as the program runs. * Specifically: * * - An initial hard-coded list of seed primes is loaded into the class on * startup. This list contains precisely the smallest 10,000 primes (the * size of this list is subject to change in future versions of Regina). * * - Whenever a prime beyond the known list is requested (e.g., when a * number greater than the largest stored prime is to be factorised), the * list is extended on the fly. The extension uses the probabilistic * algorithm shipped with GMP (hence the phrase "suspected primes" above); * regarding this algorithm, the GMP documentation states that "for * practical purposes it's adequate, the chance of a composite passing * will be extremely small." * * This list is used by the high-level factorisation routines in this * class, such as primeDecomp() and primePowerDecomp(). For users only * interested in these high-level routines, there is no need to worry * about the size of the list; the high-level routines will extend it if * necessary. * * \testfull * * @author Ryan Budney, B.B. */ class REGINA_API NPrimes { private: static const unsigned long numPrimeSeeds; /**< The size of the hard-coded list of seed primes. */ static const unsigned long primeSeedList[]; /**< The full hard-coded list of seed primes. */ static std::vector largePrimes; /**< Primes (or suspected primes) that have been found thus far, not including the initial seed primes. This list begins empty, and is expanded as required throughout the life of the program. */ public: /** * Returns the number of primes (or suspected primes) currently * stored. * * Primes that are already stored can be accessed instantly; * primes larger than those currently stored must be generated * on the fly (which takes time). * * This number may increase as the program runs (according to * whether larger primes are requested), but it will never * decrease. * * @return the number of primes or suspected primes currently stored. */ static unsigned long size(); /** * Returns the requested prime (or suspected prime). More * specifically, this routine returns the (\a which + 1)th * smallest prime. Thus prime(0) returns 2, prime(1) returns 3, * prime(2) returns 5, and so on. * * If \a which is smaller than the number of initial seed primes, * the result is guaranteed to be the (\a which + 1)th smallest * prime (see the NPrimes class notes for the size of the initial * seed list). If \a which is larger, a probabilistic algorithm * is used and so there is a possibility that non-primes are * included in the list. * * If \a which < size() then this routine is essentially * instantaneous, since the (\a which + 1)th smallest (suspected) * prime is already stored. Otherwise the behaviour depends on * the argument \a autoGrow. If \a autoGrow is \c true (the * default) then this routine calculates the requested prime, * which might take some time. If \a autoGrow is \c false then * this routine returns zero. * * @param which indicates which prime is requested. * @param autoGrow specifies what to do if the requested * prime lies beyond the list currently stored (see above). * @return the requested prime (or suspected prime), or zero if * \a which was too large and \a autoGrow was \c false. */ static NLargeInteger prime(unsigned long which, bool autoGrow = true); /** * Returns the prime factorisation of the given integer as a list * of individual primes (or suspected primes). * * Prime factors are returned in increasing order. Where * a prime power appears in the factorisation, the relevant * prime will appear several times in the list. * * For very large integers, the factorisation becomes * probabilistic: (i) this routine examines suspected primes * instead of primes (see the class notes), and (ii) if the * routine is having trouble finding factors then it will run a * probabilistic prime test on whatever portion of \a n still * remains (and will assume that portion to be prime if the test * passes). * * The given integer may be negative, in which case -1 will be * listed as the first factor (even though -1 is not prime). * If 0 is passed then a single factor of 0 will be returned; * if 1 is passed then an empty list will be returned. * In all cases, the given integer \a n will be the product of * all elements of the final list (where an empty product is * assumed to be 1). * * As an example, the prime factors of 54 will be listed as * (2, 3, 3, 3), and the prime factors of -90 will be listed as * (-1, 2, 3, 3, 5). * * Note that the internal list of known primes and suspected * primes will be expanded as necessary; there is no need for * the caller to manage this list manually. * * \todo \opt Add a version that does not return the factors by value. * * \ifacespython In addition to this routine, the routine * primeDecompInt() is also available. The routine * primeDecompInt() behaves identically to this routine except * that the (i) return values are of ordinary integer type, not * NLargeInteger; (ii) the input value \a n must lie within * the C++ long integer range (otherwise the behaviour is undefined). * * @param n the integer to factorise. * @return the list of prime factors as described above. */ static std::vector primeDecomp(const NLargeInteger& n); /** * Returns the prime factorisation of the given integer as a * list of prime powers (or suspected prime powers). * * Factors are returned as (prime, exponent) pairs. Different * pairs describe different primes, and the pairs are sorted * in order from smallest prime to largest. All exponents are * strictly positive. * * For very large integers, the factorisation becomes * probabilistic: (i) this routine examines suspected primes * instead of primes (see the class notes), and (ii) if the * routine is having trouble finding factors then it will run a * probabilistic prime test on whatever portion of \a n still * remains (and will assume that portion to be prime if the test * passes). * * The given integer may be negative, in which case (-1,1) will * be listed as the first prime power (even though -1 is not prime). * If 0 is passed then a single pair (0,1) will be returned; * if 1 is passed then an empty list will be returned. * In all cases, the given integer \a n will be the product of * all powers described by the final list (where an empty product * is assumed to be 1). * * As an example, the factorisation of 54 will be reported as * [(2,1) (3,3)], and the factorisation of -90 will be reported * as [(-1,1) (2,1) (3,2) (5,1)]. * * Note that the internal list of known primes and suspected * primes will be expanded as necessary; there is no need for * the caller to manage this list manually. * * The current implementation of this routine merely calls * primeDecomp() and rewrites the list of factors by grouping primes. * * \todo \opt Implement this routine natively to avoid the overhead * of the temporary primeDecomp() vector. * * \todo \opt Add a version that does not return the factors by value. * * \ifacespython In addition to this routine, the routine * primePowerDecompInt() is also available. The routine * primePowerDecompInt() behaves identically to this routine except * that the (i) return values are of ordinary integer type, not * NLargeInteger; (ii) the input value \a n must lie within * the C++ long integer range (otherwise the behaviour is undefined). * * @param n the integer to factorise. * @return the list of prime power factors as described above. */ static std::vector > primePowerDecomp(const NLargeInteger& n); private: /** * Private constructor. No instance of this class is allowed, * since everything of interest is static. */ NPrimes(); /** * Adds the given number of primes (or suspected primes) to the * list already stored. * * @param extras the number of additional suspected primes to * calculate. */ static void growPrimeList(unsigned long extras = 1); }; /*@}*/ // Inline functions for NPrimes inline NPrimes::NPrimes() { } inline unsigned long NPrimes::size() { return numPrimeSeeds + largePrimes.size(); } } // namespace regina #endif regina-4.95/engine/maths/nrational.cpp000644 000765 000024 00000026514 12234011536 017670 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nrational.h" #include #include namespace regina { const NRational NRational::zero; const NRational NRational::one(1); const NRational NRational::infinity(1, 0); const NRational NRational::undefined(0, 0); // These two constants are initialised to their intended values in // initDoubleBounds(). const NRational NRational::maxDouble(0, 0); const NRational NRational::minDouble(0, 0); NRational::NRational(long newNum, unsigned long newDen) { mpq_init(data); if (newDen == 0) { if (newNum == 0) flavour = f_undefined; else flavour = f_infinity; } else { flavour = f_normal; mpq_set_si(data, newNum, newDen); } } NInteger NRational::getNumerator() const { if (flavour == f_infinity) return NInteger::one; else if (flavour == f_undefined) return NInteger::zero; NInteger ans; ans.setRaw(mpq_numref(data)); return ans; } NInteger NRational::getDenominator() const { if (flavour != f_normal) return NInteger::zero; NInteger ans; ans.setRaw(mpq_denref(data)); return ans; } NRational NRational::operator *(const NRational& r) const { if (flavour == f_undefined || r.flavour == f_undefined) return undefined; if (flavour == f_infinity) { if (r == zero) return undefined; return infinity; } if (r.flavour == f_infinity) { if (*this == zero) return undefined; return infinity; } NRational ans; mpq_mul(ans.data, data, r.data); return ans; } NRational NRational::operator /(const NRational& r) const { if (flavour == f_undefined || r.flavour == f_undefined) return undefined; if (flavour == f_infinity) { if (r.flavour == f_infinity) return undefined; return infinity; } if (r.flavour == f_infinity) return zero; if (r == zero) { if (*this == zero) return undefined; return infinity; } NRational ans; mpq_div(ans.data, data, r.data); return ans; } NRational NRational::operator +(const NRational& r) const { if (flavour == f_undefined || r.flavour == f_undefined) return undefined; if (flavour == f_infinity || r.flavour == f_infinity) return infinity; NRational ans; mpq_add(ans.data, data, r.data); return ans; } NRational NRational::operator -(const NRational& r) const { if (flavour == f_undefined || r.flavour == f_undefined) return undefined; if (flavour == f_infinity || r.flavour == f_infinity) return infinity; NRational ans; mpq_sub(ans.data, data, r.data); return ans; } NRational NRational::operator - () const { if (flavour != f_normal) return *this; NRational ans; mpq_neg(ans.data, data); return ans; } NRational NRational::inverse() const { if (flavour == f_undefined) return undefined; if (flavour == f_infinity) return zero; if (*this == zero) return infinity; NRational ans; mpq_inv(ans.data, data); return ans; } NRational NRational::abs() const { if (flavour != f_normal || mpq_cmp(data, zero.data) >= 0) return *this; NRational ans; mpq_neg(ans.data, data); return ans; } NRational& NRational::operator += (const NRational& other) { if (flavour == f_undefined || other.flavour == f_undefined) flavour = f_undefined; else if (flavour == f_infinity || other.flavour == f_infinity) flavour = f_infinity; else mpq_add(data, data, other.data); return *this; } NRational& NRational::operator -= (const NRational& other) { if (flavour == f_undefined || other.flavour == f_undefined) flavour = f_undefined; else if (flavour == f_infinity || other.flavour == f_infinity) flavour = f_infinity; else mpq_sub(data, data, other.data); return *this; } NRational& NRational::operator *= (const NRational& other) { if (flavour == f_undefined || other.flavour == f_undefined) flavour = f_undefined; else if (flavour == f_infinity) { if (other == zero) flavour = f_undefined; else flavour = f_infinity; } else if (other.flavour == f_infinity) { if (*this == zero) flavour = f_undefined; else flavour = f_infinity; } else mpq_mul(data, data, other.data); return *this; } NRational& NRational::operator /= (const NRational& other) { if (flavour == f_undefined || other.flavour == f_undefined) flavour = f_undefined; else if (flavour == f_infinity) { if (other.flavour == f_infinity) flavour = f_undefined; else flavour = f_infinity; } else if (other.flavour == f_infinity) mpq_set(data, zero.data); else if (other == zero) { if (*this == zero) flavour = f_undefined; else flavour = f_infinity; } else mpq_div(data, data, other.data); return *this; } void NRational::invert() { if (flavour == f_undefined) return; else if (flavour == f_infinity) { flavour = f_normal; mpq_set(data, zero.data); } else if (*this == zero) { flavour = f_infinity; } else mpq_inv(data, data); } bool NRational::operator == (const NRational& compare) const { if (flavour != compare.flavour) return false; if (flavour != f_normal) return true; return mpq_equal(data, compare.data); } bool NRational::operator < (const NRational& compare) const { if (flavour == f_infinity || compare.flavour == f_undefined) return false; if (flavour == f_undefined || compare.flavour == f_infinity) return (compare.flavour != flavour); return (mpq_cmp(data, compare.data) < 0); } bool NRational::operator > (const NRational& compare) const { if (flavour == f_undefined || compare.flavour == f_infinity) return false; if (flavour == f_infinity || compare.flavour == f_undefined) return (compare.flavour != flavour); return (mpq_cmp(data, compare.data) > 0); } std::ostream& operator << (std::ostream& out, const NRational& rat) { if (rat.flavour == NRational::f_infinity) out << "Inf"; else if (rat.flavour == NRational::f_undefined) out << "Undef"; else if (rat.getDenominator() == 1) out << rat.getNumerator(); else out << rat.getNumerator() << '/' << rat.getDenominator(); return out; } std::string NRational::getTeX() const { std::ostringstream out; writeTeX(out); return out.str(); } std::ostream& NRational::writeTeX(std::ostream &out) const { if (flavour == NRational::f_infinity) out << "\\infty"; else if (flavour == NRational::f_undefined) out << "0/0"; else if (getDenominator() == 1) out << getNumerator(); else out << "\\frac{" << getNumerator() << "}{" << getDenominator() << "}"; return out; } double NRational::doubleApprox(bool* inRange) const { // Initialise maxDouble and minDouble if this has not already been done. // Do this even if the current doubleApprox() call is trivial, since we // promise this initialisation on the very first call to doubleApprox(). if (maxDouble.flavour == f_undefined) initDoubleBounds(); // Trivial cases. if (flavour == NRational::f_infinity || flavour == NRational::f_undefined) { if (inRange) *inRange = false; return 0.0; } // Treat zero separately so that "abs < minDouble" is meaningful later on. if (*this == zero) { if (inRange) *inRange = true; return 0.0; } // In bounds or out of bounds? NRational magnitude = this->abs(); if (magnitude < minDouble || magnitude > maxDouble) { if (inRange) *inRange = false; return 0.0; } // The rational is in range. Use GMP's native conversion routines, // since GMP knows best. if (inRange) *inRange = true; return mpq_get_d(data); } void NRational::initDoubleBounds() { // The largest and smallest possible (positive) doubles should be: // FLT_RADIX ^ DBL_MAX_EXP (minus a small amount) // FLT_RADIX ^ (DBL_MIN_EXP - 1) // // However, I have also seen the following crop up in some places: // FLT_RADIX ^ (DBL_MAX_EXP + 1) (minus a small amount) // FLT_RADIX ^ DBL_MIN_EXP // // Best to be conservative here and choose the weaker in each case: // FLT_RADIX ^ DBL_MAX_EXP (minus a small amount) // FLT_RADIX ^ DBL_MIN_EXP // // In fact, we'll be even more conservative and divide by an extra // factor of FLT_RADIX to account for "minus a small amount". NInteger maxNum = FLT_RADIX; maxNum.raiseToPower(DBL_MAX_EXP - 1); NInteger minNum = FLT_RADIX; minNum.raiseToPower(- DBL_MIN_EXP); // Cast away constness so we can actually change these variables. const_cast(maxDouble) = NRational(maxNum, NInteger(1)); const_cast(minDouble) = NRational(NInteger(1), minNum); } } // namespace regina regina-4.95/engine/maths/nrational.h000644 000765 000024 00000053011 12234011536 017325 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NRATIONAL_H #ifndef __DOXYGEN #define __NRATIONAL_H #endif /*! \file maths/nrational.h * \brief Deals with artibrary precision rational numbers. */ #include "regina-core.h" #include "maths/ninteger.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Represents an arbitrary precision rational number. * Calculations with NRational objects will be exact. * * Infinity (1/0) and undefined (0/0) are catered for. (-1/0) is considered * the same as (1/0), and is represented as (1/0). * Any operation involving (0/0) will return (0/0). * * Since infinity is the same as negative infinity, both infinity plus * infinity and infinity minus infinity will return infinity. Infinity * divided by infinity returns undefined, as does infinity times zero. * * For the purposes of ordering, undefined is the smallest rational and * infinity is the largest. Undefined is always equal to itself, and * infinity is always equal to itself. * * Rationals will always be stored in lowest terms with non-negative * denominator. * * \testpart */ class REGINA_API NRational { public: static const NRational zero; /**< Globally available zero. */ static const NRational one; /**< Globally available one. */ static const NRational infinity; /**< Globally available infinity. Note that both 1/0 and * -1/0 evaluate to this same rational. When queried, * the representation 1/0 will be returned. */ static const NRational undefined; /**< Globally available undefined. This is represented as * 0/0. */ private: /** * Represents the available flavours of rational number. */ enum flavourType { f_infinity, /**< Infinity; there is only one rational of this type. */ f_undefined, /**< Undefined; there is only one rational of this type. */ f_normal /**< An ordinary rational (the denominator is non-zero). */ }; flavourType flavour; /**< Stores whether this rational is infinity, undefined or * normal (non-zero denominator). */ mpq_t data; /**< Contains the arbitrary precision rational data for normal * (non-zero denominator) rationals. * This is initialised even if the rational is infinite. */ static const NRational maxDouble; /**< The largest positive rational number that can be converted * to a finite double. This begins as undefined, and is set * to its correct value on the first call to doubleApprox(). */ static const NRational minDouble; /**< The smallest positive rational number that can be converted * to a non-zero double. This begins as undefined, and is set * to its correct value on the first call to doubleApprox(). */ public: /** * Initialises to 0/1. */ NRational(); /** * Initialises to the given rational value. * * @param value the new rational value of this rational. */ NRational(const NRational& value); /** * Initialises to the given integer value. * The given integer may be infinite. * * @param value the new integer value of this rational. */ template NRational(const NIntegerBase& value); /** * Initialises to the given integer value. * * @param value the new integer value of this rational. */ NRational(long value); /** * Initialises to newNum/newDen. * * \pre gcd(newNum, newDen) = 1 or newDen = 0. * \pre \a newDen is non-negative. * * \warning Failing to meet the preconditions above can result * in misleading or even undefined behaviour. As an example, * NRational(4,4) (which breaks the gcd requirement) is * considered different from NRational(1,1) (a valid rational), * which is different again from NRational(-1,-1) (which breaks * the non-negativity requirement). * * \pre Neither of the given integers is infinite. * * @param newNum the new numerator. * @param newDen the new denominator. */ template NRational(const NIntegerBase& newNum, const NIntegerBase& newDen); /** * Initialises to newNum/newDen. * * \pre gcd(newNum, newDen) = 1 or newDen = 0. * \pre \a newDen is non-negative. * * \warning Failing to meet the preconditions above can result * in misleading or even undefined behaviour. As an example, * NRational(4,4) (which breaks the gcd requirement) is * considered different from NRational(1,1) (a valid rational), * which is different again from NRational(-1,-1) (which breaks * the non-negativity requirement). * * @param newNum the new numerator. * @param newDen the new denominator. */ NRational(long newNum, unsigned long newDen); /** * Destroys this rational. */ virtual ~NRational(); /** * Sets this rational to the given rational value. * * @param value the new value of this rational. * @return a reference to this rational with its new value. */ NRational& operator = (const NRational& value); /** * Sets this rational to the given integer value. * The given integer may be infinite. * * @param value the new value of this rational. * @return a reference to this rational with its new value. */ template NRational& operator = (const NIntegerBase& value); /** * Sets this rational to the given integer value. * * @param value the new value of this rational. * @return a reference to this rational with its new value. */ NRational& operator = (long value); /** * Swaps the values of this and the given rational. * * @param other the rational whose value will be swapped with * this. */ void swap(NRational& other); /** * Returns the numerator of this rational. * Note that rationals are always stored in lowest terms with * non-negative denominator. Infinity will be stored as 1/0. * * @return the numerator. */ NInteger getNumerator() const; /** * Returns the denominator of this rational. * Note that rationals are always stored in lowest terms with * non-negative denominator. Infinity will be stored as 1/0. * * @return the denominator. */ NInteger getDenominator() const; /** * Calculates the product of two rationals. * This rational is not changed. * * @param r the rational with which to multiply this. * @return the product \a this * \a r. */ NRational operator *(const NRational& r) const; /** * Calculates the ratio of two rationals. * This rational is not changed. * * @param r the rational to divide this by. * @return the ratio \a this / \a r. */ NRational operator /(const NRational& r) const; /** * Calculates the sum of two rationals. * This rational is not changed. * * @param r the rational to add to this. * @return the sum \a this + \a r. */ NRational operator +(const NRational& r) const; /** * Calculates the difference of two rationals. * This rational is not changed. * * @param r the rational to subtract from this. * @return the difference \a this - \a r. */ NRational operator -(const NRational& r) const; /** * Determines the negative of this rational. * This rational is not changed. * * @return the negative of this rational. */ NRational operator - () const; /** * Calculates the inverse of this rational. * This rational is not changed. * * @return the inverse 1 / \a this. */ NRational inverse() const; /** * Determines the absolute value of this rational. * This rational is not changed. * * @return the absolute value of this rational. */ NRational abs() const; /** * Adds the given rational to this. * This rational is changed to reflect the result. * * @param other the rational to add to this. * @return a reference to this rational with its new value. */ NRational& operator += (const NRational& other); /** * Subtracts the given rational from this. * This rational is changed to reflect the result. * * @param other the rational to subtract from this. * @return a reference to this rational with its new value. */ NRational& operator -= (const NRational& other); /** * Multiplies the given rational by this. * This rational is changed to reflect the result. * * @param other the rational to multiply by this. * @return a reference to this rational with its new value. */ NRational& operator *= (const NRational& other); /** * Divides this by the given rational. * This rational is changed to reflect the result. * * @param other the rational to divide this by. * @return a reference to this rational with its new value. */ NRational& operator /= (const NRational& other); /** * Negates this rational. * This rational is changed to reflect the result. */ void negate(); /** * Inverts this rational. * This rational is changed to reflect the result. */ void invert(); /** * Determines if this is equal to the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is equal to * \a compare. */ bool operator == (const NRational& compare) const; /** * Determines if this is not equal to the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is not equal to * \a compare. */ bool operator != (const NRational& compare) const; /** * Determines if this is less than the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is less than * \a compare. */ bool operator < (const NRational& compare) const; /** * Determines if this is greater than the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is greater than * \a compare. */ bool operator > (const NRational& compare) const; /** * Determines if this is less than or equal to the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is less than or * equal to \a compare. */ bool operator <= (const NRational& compare) const; /** * Determines if this is greater than or equal to the given rational. * * @param compare the rational with which this will be compared. * @return \c true if and only if this rational is greater than * or equal to \a compare. */ bool operator >= (const NRational& compare) const; /** * Attempts to convert this rational to a real number. * * If this rational can be approximated by a double * (specifically, if it lies within double's allowable range) * then a such an approximation is returned. Otherwise zero is * returned instead. * * The optional \a inRange argument allows the result of range * checking to be returned explicitly as a boolean * (*inRange will be set to \c true if a double * approximation is possible and \c false otherwise). * * It is safe to pass \a inRange as \c null, in which case this * boolean is not returned. Range checking is still performed * internally however, i.e., zero is still returned if the rational * is out of range. * * Note that "lies with double's allowable range" is * machine-dependent, and may vary between different installations. * Infinity and undefined are always considered out of range. * Otherwise a rational is out of range if its absolute value is * finite but too large (e.g., 10^10000) or non-zero but too small * (e.g., 10^-10000). * * @param inRange returns the result of range checking as * described above; this pointer may be passed as \c null if * the caller does not care about this result. * @return the double approximation to this rational, or zero if * this rational lies outside double's allowable range. * * \ifacespython The \a inRange argument is not present. * Instead there are two versions of this routine. * The first is \a doubleApprox(), which returns a single real * number. The second is \a doubleApproxCheck(), which returns * a (real, bool) pair containing the converted real number * followed by the result of range checking. * * @author Ryan Budney, B.B. */ double doubleApprox(bool* inRange = 0) const; /** * Returns this rational as written using TeX formatting. * No leading or trailing dollar signs will be included. * * @return this rational as written using TeX formatting. * * @author Ryan Budney */ std::string getTeX() const; /** * Writes this rational in TeX format to the given output stream. * No leading or trailing dollar signs will be included. * * \ifacespython The parameter \a out does not exist; instead * standard output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. * * @author Ryan Budney */ std::ostream& writeTeX(std::ostream& out) const; private: /** * Initialises the class constants \a maxDouble and \a minDouble. * These constants are used by doubleApprox(), and so this routine * is called the first time that doubleApprox() is run. */ static void initDoubleBounds(); friend std::ostream& operator << (std::ostream& out, const NRational& rat); }; /** * Writes the given rational to the given output stream. * Infinity will be written as Inf. Undefined will be written * as Undef. A rational with denominator one will be written * as a single integer. All other rationals will be written in the form * r/s. * * @param out the output stream to which to write. * @param rat the rational to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NRational& rat); /*@}*/ // Inline functions for NRational inline NRational::NRational() : flavour(f_normal) { mpq_init(data); } inline NRational::NRational(const NRational& value) : flavour(value.flavour) { mpq_init(data); if (flavour == f_normal) mpq_set(data, value.data); } template inline NRational::NRational(const NIntegerBase& value) : flavour(f_normal) { mpq_init(data); if (value.isInfinite()) flavour = f_infinity; else if (value.isNative()) mpq_set_si(data, value.longValue(), 1); else mpq_set_z(data, value.rawData()); } inline NRational::NRational(long value) : flavour(f_normal) { mpq_init(data); mpq_set_si(data, value, 1); } template NRational::NRational(const NIntegerBase& newNum, const NIntegerBase& newDen) { mpq_init(data); if (newDen.isZero()) { if (newNum.isZero()) flavour = f_undefined; else flavour = f_infinity; } else { flavour = f_normal; if (newNum.isNative() && newDen.isNative()) mpq_set_si(data, newNum.longValue(), newDen.longValue()); else if (newNum.isNative()) { // Avoid bloating newNum with a GMP representation. NIntegerBase tmp(newNum); mpz_set(mpq_numref(data), tmp.rawData()); mpz_set(mpq_denref(data), newDen.rawData()); } else if (newDen.isNative()) { // Avoid bloating newDen with a GMP representation. NIntegerBase tmp(newDen); mpz_set(mpq_numref(data), newNum.rawData()); mpz_set(mpq_denref(data), tmp.rawData()); } else { mpz_set(mpq_numref(data), newNum.rawData()); mpz_set(mpq_denref(data), newDen.rawData()); } } } inline NRational::~NRational() { mpq_clear(data); } inline NRational& NRational::operator = (const NRational& value) { flavour = value.flavour; if (flavour == f_normal) mpq_set(data, value.data); return *this; } template inline NRational& NRational::operator = ( const NIntegerBase& value) { if (value.isInfinite()) flavour = f_infinity; else if (value.isNative()) { flavour = f_normal; mpq_set_si(data, value.longValue(), 1); } else { flavour = f_normal; mpq_set_z(data, value.rawData()); } return *this; } inline NRational& NRational::operator = (long value) { flavour = f_normal; mpq_set_si(data, value, 1); return *this; } inline void NRational::swap(NRational& other) { std::swap(flavour, other.flavour); mpq_swap(data, other.data); } inline void NRational::negate() { if (flavour == f_normal) mpq_neg(data, data); } inline bool NRational::operator <= (const NRational& compare) const { return ! (*this > compare); } inline bool NRational::operator >= (const NRational& compare) const { return ! (*this < compare); } inline bool NRational::operator != (const NRational& compare) const { return ! (*this == compare); } } // namespace regina #endif regina-4.95/engine/maths/nray.cpp000644 000765 000024 00000005506 12234011536 016650 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nray.h" namespace regina { void NRay::scaleDown() { NLargeInteger gcd; // Initialised to 0. NLargeInteger* e; for (e = elements; e < end; ++e) { if (e->isInfinite() || (*e) == zero) continue; gcd = gcd.gcd(*e); if (gcd < 0) gcd.negate(); if (gcd == one) return; } if (gcd == zero) return; for (e = elements; e < end; ++e) if ((! e->isInfinite()) && (*e) != zero) { e->divByExact(gcd); e->tryReduce(); } } } // namespace regina regina-4.95/engine/maths/nray.h000644 000765 000024 00000012545 12234011536 016316 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nray.h * \brief Provides a fast class for rational rays rooted at the origin. */ #ifndef __NRAY_H #ifndef __DOXYGEN #define __NRAY_H #endif #include "regina-core.h" #include "maths/ninteger.h" #include "maths/nvector.h" namespace regina { /** * \weakgroup maths * @{ */ /** * A fast class for storing a ray rooted at the origin whose * coordinates are rational. Such a ray is a half-line beginning at * the origin and is represented by an integer point that it passes through. * Positive scalar multiples of a ray are considered to represent the same ray. * * This class is intended for serious computation, and as a result it * has a streamlined implementation with no virtual methods. It can be * subclassed, but since there are no virtual methods, type information * must generally be known at compile time. Nevertheless, in many respects, * different subclasses of NRay can happily interact with one another. * * \warning As of Regina 4.90, this class merges the old functionality * of NFastRay and NRay from Regina 4.6. Since functions are no longer * virtual, the old clone() method and intersect() function are gone * completely. Instead you can just use the copy constructor and standard * linear operators respectively. * * \ifacespython Not present. */ class REGINA_API NRay : public NVector { public: /** * Creates a new ray all of whose coordinates are initialised to zero. * * @param length the number of elements in the new vector. */ NRay(size_t length); /** * Creates a new ray that is a clone of the given ray. * * @param cloneMe the ray to clone. */ NRay(const NVector& cloneMe); /** * Scales this vector down by the greatest common divisor of all * its elements. The resulting vector will be the smallest * multiple of the original that maintains integral entries, and * these entries will have the same signs as the originals. * * This routine thus reduces a ray to its smallest possible * representation. * * This routine poses no problem for vectors containing infinite * elements; such elements are simply ignored and left at * infinity. */ void scaleDown(); /** * Negates every element of this vector. * * This is an optimised implementation that overrides * NVector::negate(). */ inline void negate(); }; /*@}*/ // Inline functions for NRay inline NRay::NRay(size_t length) : NVector(length) { // Don't bother passing zero to the parent constructor, since the // default NLargeInteger constructor already sets elements to zero. } inline NRay::NRay(const NVector& cloneMe) : NVector(cloneMe) { } inline void NRay::negate() { // Slightly more efficient than the default implementation. for (NLargeInteger* e = elements; e < end; ++e) e->negate(); } } // namespace regina #endif regina-4.95/engine/maths/numbertheory.cpp000644 000765 000024 00000015675 12234011536 020432 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "maths/numbertheory.h" #include "utilities/stlutils.h" namespace regina { long reducedMod(long k, long modBase) { long ans = k % modBase; if (ans < 0) { if ((ans + modBase) <= (-ans)) return (ans + modBase); } else if (-(ans - modBase) < ans) return (ans - modBase); return ans; } long gcd(long a, long b) { long tmp; while (a != b && b != 0) { tmp = a; a = b; b = tmp % b; } return (a >= 0 ? a : -a); } namespace { long gcdWithCoeffsInternal(long a, long b, long& u, long& v) { // This routine assumes a and b to be non-negative. long a_orig = a; long b_orig = b; u = 1; v = 0; long uu = 0; long vv = 1; long tmp1, tmp2, q; while (a != b && b != 0) { // At each stage: // u*(a_orig) + v*(b_orig) = a_curr; // uu*(a_orig) + vv*(b_orig) = b_curr. tmp1 = u; tmp2 = v; u = uu; v = vv; q = a / b; uu = tmp1 - (q * uu); vv = tmp2 - (q * vv); tmp1 = a; a = b; b = tmp1 % b; } // a is now our gcd. // Put u and v in the correct range. if (b_orig == 0) return a; // We are allowed to add (b_orig/d, -a_orig/d) to (u,v). a_orig = -(a_orig / a); b_orig = b_orig / a; // Now we are allowed to add (b_orig, a_orig), where b_orig >= 0. // Add enough copies to put u between 1 and b_orig inclusive. long k; if (u > 0) k = -((u-1) / b_orig); else k = (b_orig-u) / b_orig; if (k) { u += k * b_orig; v += k * a_orig; } return a; } } long gcdWithCoeffs(long a, long b, long& u, long& v) { long signA = (a > 0 ? 1 : a == 0 ? 0 : -1); long signB = (b > 0 ? 1 : b == 0 ? 0 : -1); long ans = gcdWithCoeffsInternal(a >= 0 ? a : -a, b >= 0 ? b : -b, u, v); u *= signA; v *= signB; return ans; } long lcm(long a, long b) { if (a == 0 || b == 0) return 0; long tmp = regina::gcd(a, b); tmp = (a / tmp) * b; return (tmp >= 0 ? tmp : -tmp); } unsigned long modularInverse(unsigned long n, unsigned long k) { if (n == 1) return 0; long u, v; gcdWithCoeffs(n, k % n, u, v); // GCD should equal 1, so u*n + k*v = 1. // Inverse is v; note that -n < v <= 0. // Since n >= 2 now and (n,k) = 1, we know v != 0. return v + n; } namespace { /** * Finds the smallest prime factor of the given odd integer. * You may specify a known lower bound for this smallest prime factor. * If the given integer is prime, 0 is returned. * * \pre \a n is odd. * \pre The smallest prime factor of \a n is known to * be at least as large as (and possibly equal to) \a lowerBound. * \pre \a lowerBound is odd. * * @param n the integer whose smallest prime factor we wish to find. * @param lowerBound a known lower bound for this smallest prime factor. * @return the smallest prime factor of \a n, or 0 if \a n is prime. */ unsigned long smallestPrimeFactor(unsigned long n, unsigned long lowerBound = 1) { while (lowerBound * lowerBound <= n) { if (n % lowerBound == 0) return lowerBound; lowerBound += 2; } return 0; } } void factorise(unsigned long n, std::list& factors) { // n > 0 is a precondition, but the effects are too unpleasant if // it's broken (infinite memory consumption). if (n == 0) return; // First take out all factors of 2. while (n % 2 == 0) { n = n / 2; factors.push_back(2); } // Run through finding smallest factors. unsigned long factor = 3; while ((factor = smallestPrimeFactor(n, factor))) { factors.push_back(factor); n = n / factor; } // Anything left is prime. if (n > 1) factors.push_back(n); } void primesUpTo(const NLargeInteger& roof, std::list& primes) { // First check 2. if (roof < 2) return; primes.push_back(NLargeInteger(2)); // Run through the rest. NLargeInteger current(3); while (current <= roof) { // Is current prime? if (find_if(primes.begin(), primes.end(), regina::stl::compose1( bind2nd(std::equal_to(), NLargeInteger::zero), bind1st(std::modulus(), current))) == primes.end()) primes.push_back(current); current += 2; } } } // namespace regina regina-4.95/engine/maths/numbertheory.h000644 000765 000024 00000017400 12234011536 020063 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NUMBERTHEORY_H #ifndef __DOXYGEN #define __NUMBERTHEORY_H #endif /*! \file maths/numbertheory.h * \brief Provides miscellaneous number theory routines. */ #include #include "regina-core.h" #include "maths/ninteger.h" namespace regina { /** * \addtogroup maths Mathematical Support * Underlying mathematical gruntwork. * @{ */ /** * Reduces \a k modulo \a modBase to give the smallest possible * absolute value. For instance, reducedMod(4,10) = 4 but * reducedMod(6,10) = -4. In the case of a tie, the positive * solution is taken. * * \pre \a modBase is strictly positive. * * \testfull * * @param k the number to reduce modulo \a modBase. * @param modBase the modular base in which to work. */ REGINA_API long reducedMod(long k, long modBase); /** * Calculates the greatest common divisor of two signed integers. * This routine is not recursive. * * Although the arguments may be negative, the result is guaranteed to * be non-negative. As a special case, gcd(0,0) is considered to be zero. * * \testfull * * @param a one of the two integers to work with. * @param b the other integer with which to work. * @return the greatest common divisor of \a a and \a b. */ REGINA_API long gcd(long a, long b); /** * Calculates the greatest common divisor of two given integers and finds the * smallest coefficients with which these integers combine to give their * gcd. This routine is not recursive. * * Note that the given integers need not be non-negative. However, the * gcd returned is guaranteed to be non-negative. * As a special case, gcd(0,0) is considered to be zero. * * If \a d is the gcd of \a a and \a b, the values placed in \a u and \a v * will be those for which * u*a + v*b = d, * -abs(a)/d < v*sign(b) <= 0 and * 1 <= u*sign(a) <= abs(b)/d. * * In the special case where one of the given integers is zero, the * corresponding coefficient will also be zero and the other coefficient * will be 1 or -1 so that u*a + v*b = d still holds. If both * given integers are zero, both of the coefficients will be set to zero. * * \testfull * * @param a one of the integers to work with. * @param b the other integer with which to work. * @param u a variable into which the final coefficient of \a a will be * placed. * @param v a variable into which the final coefficient of \a b will be * placed. * @return the greatest common divisor of \a a and \a b. */ REGINA_API long gcdWithCoeffs(long a, long b, long& u, long& v); /** * Calculates the lowest common multiple of two signed integers. * Although the arguments may be negative, the result is guaranteed to * be non-negative. * * If either of the arguments is zero, the return value will also be zero. * * Regarding possible overflow: This routine does not create any temporary * integers that are larger than the final LCM. * * @param a one of the two integers to work with. * @param b the other integer with which to work. * @return the lowest common multiple of \a a and \a b. */ REGINA_API long lcm(long a, long b); /** * Calculates the multiplicative inverse of one integer modulo another. * The inverse returned will be between 0 and n-1 inclusive. * * \pre \a n and \a k are both strictly positive; * \pre \a n and \a k have no common factors. * * \testfull * * @param n the modular base in which to work. * @param k the number whose multiplicative inverse should be found. * @return the inverse \a v for which k * v == 1 (mod n). */ REGINA_API unsigned long modularInverse(unsigned long n, unsigned long k); /** * Calculates the prime factorisation of the given integer. * All the prime factors will be inserted into the given list. * The algorithm used is very neanderthal and should only be used with * reasonably sized integers. Don't use it to do RSA! * * If a prime factor is repeated, it will be inserted multiple times into * the list. The primes in the list are not guaranteed to appear in any * specific order, nor are multiple occurrences of the same prime * guaranteed to appear together. * * Note that once finished the list will contain the prime factors as well * as whatever happened to be in the list before this function was * called. * * \pre The given integer is at least 1. * * \deprecated This routine is old and slow; please consider using the * much faster routines from the NPrimes class instead. * * \ifacespython Argument \a factors is not present; instead this * routine returns a python list containing the prime factors. * * @param n the integer to factorise. * @param factors the list into which prime factors will be inserted. */ REGINA_API void factorise(unsigned long n, std::list& factors); /** * Determines all primes up to and including the given upper bound. * All the primes found will be inserted into the given list in * increasing order. * * The algorithm currently used is fairly neanderthal. * * \pre The given list is empty. * * \deprecated This routine is old and slow; please consider using the * much faster routines from the NPrimes class instead. * * \ifacespython Argument \a primes is not present; instead this routine * returns a python list containing the primes up to and including \a roof. * * @param roof the upper bound up to which primes will be found. * @param primes the list into which the primes will be inserted. */ REGINA_API void primesUpTo(const NLargeInteger& roof, std::list& primes); /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/nvector.h000644 000765 000024 00000035443 12234011536 017027 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/nvector.h * \brief Provides a fast and generic vector class. */ #ifndef __NVECTOR_H #ifndef __DOXYGEN #define __NVECTOR_H #endif #include #include #include "regina-core.h" namespace regina { /** * \weakgroup maths * @{ */ /** * An optimised vector class of elements from a given ring T. * Various mathematical vector operations are available. * * This class is intended for serious computation, and as a result it has a * streamlined implementation with no virtual methods. It can be subclassed, * but since there are no virtual methods, type information must generally * be known at compile time. Nevertheless, in many respects, different * subclasses of NVector can happily interact with one another. * * This class is written with bulky types in mind (such as * arbitrary precision integers), and so creations and operations are kept * to a minimum. * * \warning As of Regina 4.90, this class merges the old functionality of * NFastVector and the NVector hierarchy from Regina 4.6. As a side-effect, * the hierarchy has been compressed into just one class (NVectorUnit, * NVectorMatrix and NVectorDense are gone), elements are always stored as * dense vectors, and functions are no longer virtual (since the storage * model is now fixed). The virtual clone() method is gone completely * (since there are no longer virtual functions you should use the copy * constructor instead), and the old makeLinComb() method is also gone * (just use operator *= and addCopies()). * * \pre Type T has a copy constructor. That is, * if \c a and \c b are of type T, then \c a can be initialised to the value * of \c b using a(b). * \pre Type T has a default constructor. That is, * an object of type T can be declared with no arguments. No specific * default value is required. * \pre Type T allows for operators =, ==, +=, * -= and *=. * \pre Type T has a long integer constructor. That is, if \c a is of type T, * then \c a can be initialised to a long integer \c l using a(l). * \pre An element \c t of type T can be written to an output stream * \c out using the standard expression out << t. * * \ifacespython Not present. */ template class NVector { public: static T zero; /**< Zero in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ static T one; /**< One in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ static T minusOne; /**< Negative one in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ protected: T* elements; /**< The internal array containing all vector elements. */ T* end; /**< A pointer just beyond the end of the internal array. The size of the vector can be computed as (end - elements). */ public: /** * Creates a new vector. * Its elements will not be initialised. * * @param newVectorSize the number of elements in the new * vector; this must be strictly positive. */ inline NVector(size_t newVectorSize) : elements(new T[newVectorSize]), end(elements + newVectorSize) { } /** * Creates a new vector and initialises every element to the * given value. * * @param newVectorSize the number of elements in the new * vector; this must be strictly positive. * @param initValue the value to assign to every element of the * vector. */ inline NVector(size_t newVectorSize, const T& initValue) : elements(new T[newVectorSize]), end(elements + newVectorSize) { std::fill(elements, end, initValue); } /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ inline NVector(const NVector& cloneMe) : elements(new T[cloneMe.end - cloneMe.elements]), end(elements + (cloneMe.end - cloneMe.elements)) { std::copy(cloneMe.elements, cloneMe.end, elements); } /** * Destroys this vector. */ inline ~NVector() { delete[] elements; } /** * Returns the number of elements in the vector. * * @return the vector size. */ inline size_t size() const { return end - elements; } /** * Returns the element at the given index in the vector. * A constant reference to the element is returned; the element * may not be altered. * * \pre \c index is between 0 and size()-1 inclusive. * * @param index the vector index to examine. * @return the vector element at the given index. */ inline const T& operator[](size_t index) const { return elements[index]; } /** * Sets the element at the given index in the vector to the * given value. * * \pre \c index is between 0 and size()-1 inclusive. * * @param index the vector index to examine. * @param value the new value to assign to the element. * @return the vector element at the given index. */ inline void setElement(size_t index, const T& value) { elements[index] = value; } /** * Determines if this vector is equal to the given vector. * * \pre This and the given vector have the same size. * * @param compare the vector with which this will be compared. * @return \c true if and only if the this and the given vector * are equal. */ inline bool operator == (const NVector& compare) const { return std::equal(elements, end, compare.elements); } /** * Sets this vector equal to the given vector. * * \pre This and the given vector have the same size. * * @param cloneMe the vector whose value shall be assigned to this * vector. */ inline NVector& operator = (const NVector& cloneMe) { std::copy(cloneMe.elements, cloneMe.end, elements); return *this; } /** * Adds the given vector to this vector. * * \pre This and the given vector have the same size. * * @param other the vector to add to this vector. */ inline void operator += (const NVector& other) { T* e = elements; const T* o = other.elements; for ( ; e < end; ++e, ++o) *e += *o; } /** * Subtracts the given vector from this vector. * * \pre This and the given vector have the same size. * * @param other the vector to subtract from this vector. */ inline void operator -= (const NVector& other) { T* e = elements; const T* o = other.elements; for ( ; e < end; ++e, ++o) *e -= *o; } /** * Multiplies this vector by the given scalar. * * @param factor the scalar with which this will be multiplied. */ inline void operator *= (const T& factor) { if (factor == NVector::one) return; for (T* e = elements; e < end; ++e) *e *= factor; } /** * Calculates the dot product of this vector and the given vector. * * \pre This and the given vector have the same size. * * @param other the vector with which this will be multiplied. * @return the dot product of this and the given vector. */ inline T operator * (const NVector& other) const { T ans(zero); const T* e = elements; const T* o = other.elements; for ( ; e < end; ++e, ++o) ans += (*e) * (*o); return ans; } /** * Negates every element of this vector. */ inline void negate() { for (T* e = elements; e < end; ++e) *e = -*e; } /** * Returns the norm of this vector. * This is the dot product of the vector with itself. * * @return the norm of this vector. */ inline T norm() const { T ans(zero); for (const T* e = elements; e < end; ++e) ans += (*e) * (*e); return ans; } /** * Returns the sum of all elements of this vector. * * @return the sum of the elements of this vector. */ inline T elementSum() const { T ans(zero); for (const T* e = elements; e < end; ++e) ans += *e; return ans; } /** * Adds the given multiple of the given vector to this vector. * * \pre This and the given vector have the same size. * * @param other the vector a multiple of which will be added to * this vector. * @param multiple the multiple of \a other to be added to this * vector. */ void addCopies(const NVector& other, const T& multiple) { if (multiple == NVector::zero) return; if (multiple == NVector::one) { (*this) += other; return; } if (multiple == NVector::minusOne) { (*this) -= other; return; } T* e = elements; const T* o = other.elements; for ( ; e < end; ++e, ++o) *e += *o * multiple; } /** * Subtracts the given multiple of the given vector to this vector. * * \pre This and the given vector have the same size. * * @param other the vector a multiple of which will be * subtracted from this vector. * @param multiple the multiple of \a other to be subtracted * from this vector. */ void subtractCopies(const NVector& other, const T& multiple) { if (multiple == NVector::zero) return; if (multiple == NVector::one) { (*this) -= other; return; } if (multiple == NVector::minusOne) { (*this) += other; return; } T* e = elements; const T* o = other.elements; for ( ; e < end; ++e, ++o) *e -= *o * multiple; } }; /** * Writes the given vector to the given output stream. * The vector will be written on a single line with elements separated * by a single space. No newline will be written. * * \ifacespython Not present. * * @param out the output stream to which to write. * @param vector the vector to write. * @return a reference to \a out. */ template std::ostream& operator << (std::ostream& out, const NVector& vector) { size_t size = vector.size(); if (size == 0) return out; out << vector[0]; for (size_t i=1; i T NVector::zero(0L); /**< Zero in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ template T NVector::one(1L); /**< One in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ template T NVector::minusOne(-1L); /**< Negative one in the underlying number system. * This would be \c const if it weren't for the fact that * some compilers don't like this. It should never be * modified! */ /*@}*/ } // namespace regina #endif regina-4.95/engine/maths/permconv.h000644 000765 000024 00000012056 12234011536 017173 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file maths/permconv.h * \brief Conversions between various permutation classes. */ #ifndef __PERMCONV_H #ifndef __DOXYGEN #define __PERMCONV_H #endif #include "regina-core.h" #include "maths/nperm3.h" #include "maths/nperm4.h" #include "maths/nperm5.h" namespace regina { /** * \weakgroup maths * @{ */ /** * Converts the given 4-element permutation to a 5-element permutation. * The resulting 5-element permutation will map 4 to 4, and will map * 0, 1, 2 and 3 to their respective images under \a p. * * @param p the given 4-element permutation. * @return the permutation \a p expressed as a permutation of five * elements, not four. */ REGINA_API NPerm5 perm4to5(const NPerm4& p); /** * Expresses the given 5-element permutation as a 4-element permutation. * The resulting 4-element permutation will map 0, 1, 2 and 3 to their * respective images under \a p. It is assumed that the image of 4 is 4 * under \a p; otherwise this conversion cannot be performed. * * \pre The given permutation maps 4 to 4. * * @param p the given 5-element permutation. * @return the permutation \a p expressed as a permutation of four * elements, not five. */ REGINA_API NPerm4 perm5to4(const NPerm5& p); /** * Converts the given 3-element permutation to a 4-element permutation. * The resulting 4-element permutation will map 3 to 3, and will map * 0, 1 and 2 to their respective images under \a p. * * @param p the given 3-element permutation. * @return the permutation \a p expressed as a permutation of four * elements, not three. */ REGINA_API NPerm4 perm3to4(const NPerm3& p); /** * Expresses the given 4-element permutation as a 3-element permutation. * The resulting 3-element permutation will map 0, 1 and 2 to their * respective images under \a p. It is assumed that the image of 3 is 3 * under \a p; otherwise this conversion cannot be performed. * * \pre The given permutation maps 3 to 3. * * @param p the given 4-element permutation. * @return the permutation \a p expressed as a permutation of three * elements, not four. */ REGINA_API NPerm3 perm4to3(const NPerm4& p); /*@}*/ // Inline conversion functions inline NPerm5 perm4to5(const NPerm4& p) { return NPerm5(p[0], p[1], p[2], p[3], 4); } inline NPerm4 perm5to4(const NPerm5& p) { unsigned code = p.getPermCode(); return NPerm4(code & 0x03, (code >> 3) & 0x03, (code >> 6) & 0x03, (code >> 9) & 0x03); } inline NPerm4 perm3to4(const NPerm3& p) { // Code map: 0,1,2,3,4,5 -> 0,3,8,7,12,15. unsigned char c = p.getPermCode(); return NPerm4::fromPermCode2(c == 2 ? 8 : c == 3 ? 7 : 3 * c); } inline NPerm3 perm4to3(const NPerm4& p) { // Code map: 0,3,8,7,12,15 -> 0,1,2,3,4,5. unsigned char c = p.getPermCode2(); return NPerm3::fromPermCode(c == 8 ? 2 : c == 7 ? 3 : c / 3); } } // namespace regina #endif regina-4.95/engine/maths/seedprimes.cpp000644 000765 000024 00000243166 12234011536 020045 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nprimes.h" namespace regina { // Keep the seed primes in a separate file so they're nicely out of the // way of everything else. const unsigned long NPrimes::numPrimeSeeds = 10000; const unsigned long NPrimes::primeSeedList[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, 7927, 7933, 7937, 7949, 7951, 7963, 7993, 8009, 8011, 8017, 8039, 8053, 8059, 8069, 8081, 8087, 8089, 8093, 8101, 8111, 8117, 8123, 8147, 8161, 8167, 8171, 8179, 8191, 8209, 8219, 8221, 8231, 8233, 8237, 8243, 8263, 8269, 8273, 8287, 8291, 8293, 8297, 8311, 8317, 8329, 8353, 8363, 8369, 8377, 8387, 8389, 8419, 8423, 8429, 8431, 8443, 8447, 8461, 8467, 8501, 8513, 8521, 8527, 8537, 8539, 8543, 8563, 8573, 8581, 8597, 8599, 8609, 8623, 8627, 8629, 8641, 8647, 8663, 8669, 8677, 8681, 8689, 8693, 8699, 8707, 8713, 8719, 8731, 8737, 8741, 8747, 8753, 8761, 8779, 8783, 8803, 8807, 8819, 8821, 8831, 8837, 8839, 8849, 8861, 8863, 8867, 8887, 8893, 8923, 8929, 8933, 8941, 8951, 8963, 8969, 8971, 8999, 9001, 9007, 9011, 9013, 9029, 9041, 9043, 9049, 9059, 9067, 9091, 9103, 9109, 9127, 9133, 9137, 9151, 9157, 9161, 9173, 9181, 9187, 9199, 9203, 9209, 9221, 9227, 9239, 9241, 9257, 9277, 9281, 9283, 9293, 9311, 9319, 9323, 9337, 9341, 9343, 9349, 9371, 9377, 9391, 9397, 9403, 9413, 9419, 9421, 9431, 9433, 9437, 9439, 9461, 9463, 9467, 9473, 9479, 9491, 9497, 9511, 9521, 9533, 9539, 9547, 9551, 9587, 9601, 9613, 9619, 9623, 9629, 9631, 9643, 9649, 9661, 9677, 9679, 9689, 9697, 9719, 9721, 9733, 9739, 9743, 9749, 9767, 9769, 9781, 9787, 9791, 9803, 9811, 9817, 9829, 9833, 9839, 9851, 9857, 9859, 9871, 9883, 9887, 9901, 9907, 9923, 9929, 9931, 9941, 9949, 9967, 9973, 10007, 10009, 10037, 10039, 10061, 10067, 10069, 10079, 10091, 10093, 10099, 10103, 10111, 10133, 10139, 10141, 10151, 10159, 10163, 10169, 10177, 10181, 10193, 10211, 10223, 10243, 10247, 10253, 10259, 10267, 10271, 10273, 10289, 10301, 10303, 10313, 10321, 10331, 10333, 10337, 10343, 10357, 10369, 10391, 10399, 10427, 10429, 10433, 10453, 10457, 10459, 10463, 10477, 10487, 10499, 10501, 10513, 10529, 10531, 10559, 10567, 10589, 10597, 10601, 10607, 10613, 10627, 10631, 10639, 10651, 10657, 10663, 10667, 10687, 10691, 10709, 10711, 10723, 10729, 10733, 10739, 10753, 10771, 10781, 10789, 10799, 10831, 10837, 10847, 10853, 10859, 10861, 10867, 10883, 10889, 10891, 10903, 10909, 10937, 10939, 10949, 10957, 10973, 10979, 10987, 10993, 11003, 11027, 11047, 11057, 11059, 11069, 11071, 11083, 11087, 11093, 11113, 11117, 11119, 11131, 11149, 11159, 11161, 11171, 11173, 11177, 11197, 11213, 11239, 11243, 11251, 11257, 11261, 11273, 11279, 11287, 11299, 11311, 11317, 11321, 11329, 11351, 11353, 11369, 11383, 11393, 11399, 11411, 11423, 11437, 11443, 11447, 11467, 11471, 11483, 11489, 11491, 11497, 11503, 11519, 11527, 11549, 11551, 11579, 11587, 11593, 11597, 11617, 11621, 11633, 11657, 11677, 11681, 11689, 11699, 11701, 11717, 11719, 11731, 11743, 11777, 11779, 11783, 11789, 11801, 11807, 11813, 11821, 11827, 11831, 11833, 11839, 11863, 11867, 11887, 11897, 11903, 11909, 11923, 11927, 11933, 11939, 11941, 11953, 11959, 11969, 11971, 11981, 11987, 12007, 12011, 12037, 12041, 12043, 12049, 12071, 12073, 12097, 12101, 12107, 12109, 12113, 12119, 12143, 12149, 12157, 12161, 12163, 12197, 12203, 12211, 12227, 12239, 12241, 12251, 12253, 12263, 12269, 12277, 12281, 12289, 12301, 12323, 12329, 12343, 12347, 12373, 12377, 12379, 12391, 12401, 12409, 12413, 12421, 12433, 12437, 12451, 12457, 12473, 12479, 12487, 12491, 12497, 12503, 12511, 12517, 12527, 12539, 12541, 12547, 12553, 12569, 12577, 12583, 12589, 12601, 12611, 12613, 12619, 12637, 12641, 12647, 12653, 12659, 12671, 12689, 12697, 12703, 12713, 12721, 12739, 12743, 12757, 12763, 12781, 12791, 12799, 12809, 12821, 12823, 12829, 12841, 12853, 12889, 12893, 12899, 12907, 12911, 12917, 12919, 12923, 12941, 12953, 12959, 12967, 12973, 12979, 12983, 13001, 13003, 13007, 13009, 13033, 13037, 13043, 13049, 13063, 13093, 13099, 13103, 13109, 13121, 13127, 13147, 13151, 13159, 13163, 13171, 13177, 13183, 13187, 13217, 13219, 13229, 13241, 13249, 13259, 13267, 13291, 13297, 13309, 13313, 13327, 13331, 13337, 13339, 13367, 13381, 13397, 13399, 13411, 13417, 13421, 13441, 13451, 13457, 13463, 13469, 13477, 13487, 13499, 13513, 13523, 13537, 13553, 13567, 13577, 13591, 13597, 13613, 13619, 13627, 13633, 13649, 13669, 13679, 13681, 13687, 13691, 13693, 13697, 13709, 13711, 13721, 13723, 13729, 13751, 13757, 13759, 13763, 13781, 13789, 13799, 13807, 13829, 13831, 13841, 13859, 13873, 13877, 13879, 13883, 13901, 13903, 13907, 13913, 13921, 13931, 13933, 13963, 13967, 13997, 13999, 14009, 14011, 14029, 14033, 14051, 14057, 14071, 14081, 14083, 14087, 14107, 14143, 14149, 14153, 14159, 14173, 14177, 14197, 14207, 14221, 14243, 14249, 14251, 14281, 14293, 14303, 14321, 14323, 14327, 14341, 14347, 14369, 14387, 14389, 14401, 14407, 14411, 14419, 14423, 14431, 14437, 14447, 14449, 14461, 14479, 14489, 14503, 14519, 14533, 14537, 14543, 14549, 14551, 14557, 14561, 14563, 14591, 14593, 14621, 14627, 14629, 14633, 14639, 14653, 14657, 14669, 14683, 14699, 14713, 14717, 14723, 14731, 14737, 14741, 14747, 14753, 14759, 14767, 14771, 14779, 14783, 14797, 14813, 14821, 14827, 14831, 14843, 14851, 14867, 14869, 14879, 14887, 14891, 14897, 14923, 14929, 14939, 14947, 14951, 14957, 14969, 14983, 15013, 15017, 15031, 15053, 15061, 15073, 15077, 15083, 15091, 15101, 15107, 15121, 15131, 15137, 15139, 15149, 15161, 15173, 15187, 15193, 15199, 15217, 15227, 15233, 15241, 15259, 15263, 15269, 15271, 15277, 15287, 15289, 15299, 15307, 15313, 15319, 15329, 15331, 15349, 15359, 15361, 15373, 15377, 15383, 15391, 15401, 15413, 15427, 15439, 15443, 15451, 15461, 15467, 15473, 15493, 15497, 15511, 15527, 15541, 15551, 15559, 15569, 15581, 15583, 15601, 15607, 15619, 15629, 15641, 15643, 15647, 15649, 15661, 15667, 15671, 15679, 15683, 15727, 15731, 15733, 15737, 15739, 15749, 15761, 15767, 15773, 15787, 15791, 15797, 15803, 15809, 15817, 15823, 15859, 15877, 15881, 15887, 15889, 15901, 15907, 15913, 15919, 15923, 15937, 15959, 15971, 15973, 15991, 16001, 16007, 16033, 16057, 16061, 16063, 16067, 16069, 16073, 16087, 16091, 16097, 16103, 16111, 16127, 16139, 16141, 16183, 16187, 16189, 16193, 16217, 16223, 16229, 16231, 16249, 16253, 16267, 16273, 16301, 16319, 16333, 16339, 16349, 16361, 16363, 16369, 16381, 16411, 16417, 16421, 16427, 16433, 16447, 16451, 16453, 16477, 16481, 16487, 16493, 16519, 16529, 16547, 16553, 16561, 16567, 16573, 16603, 16607, 16619, 16631, 16633, 16649, 16651, 16657, 16661, 16673, 16691, 16693, 16699, 16703, 16729, 16741, 16747, 16759, 16763, 16787, 16811, 16823, 16829, 16831, 16843, 16871, 16879, 16883, 16889, 16901, 16903, 16921, 16927, 16931, 16937, 16943, 16963, 16979, 16981, 16987, 16993, 17011, 17021, 17027, 17029, 17033, 17041, 17047, 17053, 17077, 17093, 17099, 17107, 17117, 17123, 17137, 17159, 17167, 17183, 17189, 17191, 17203, 17207, 17209, 17231, 17239, 17257, 17291, 17293, 17299, 17317, 17321, 17327, 17333, 17341, 17351, 17359, 17377, 17383, 17387, 17389, 17393, 17401, 17417, 17419, 17431, 17443, 17449, 17467, 17471, 17477, 17483, 17489, 17491, 17497, 17509, 17519, 17539, 17551, 17569, 17573, 17579, 17581, 17597, 17599, 17609, 17623, 17627, 17657, 17659, 17669, 17681, 17683, 17707, 17713, 17729, 17737, 17747, 17749, 17761, 17783, 17789, 17791, 17807, 17827, 17837, 17839, 17851, 17863, 17881, 17891, 17903, 17909, 17911, 17921, 17923, 17929, 17939, 17957, 17959, 17971, 17977, 17981, 17987, 17989, 18013, 18041, 18043, 18047, 18049, 18059, 18061, 18077, 18089, 18097, 18119, 18121, 18127, 18131, 18133, 18143, 18149, 18169, 18181, 18191, 18199, 18211, 18217, 18223, 18229, 18233, 18251, 18253, 18257, 18269, 18287, 18289, 18301, 18307, 18311, 18313, 18329, 18341, 18353, 18367, 18371, 18379, 18397, 18401, 18413, 18427, 18433, 18439, 18443, 18451, 18457, 18461, 18481, 18493, 18503, 18517, 18521, 18523, 18539, 18541, 18553, 18583, 18587, 18593, 18617, 18637, 18661, 18671, 18679, 18691, 18701, 18713, 18719, 18731, 18743, 18749, 18757, 18773, 18787, 18793, 18797, 18803, 18839, 18859, 18869, 18899, 18911, 18913, 18917, 18919, 18947, 18959, 18973, 18979, 19001, 19009, 19013, 19031, 19037, 19051, 19069, 19073, 19079, 19081, 19087, 19121, 19139, 19141, 19157, 19163, 19181, 19183, 19207, 19211, 19213, 19219, 19231, 19237, 19249, 19259, 19267, 19273, 19289, 19301, 19309, 19319, 19333, 19373, 19379, 19381, 19387, 19391, 19403, 19417, 19421, 19423, 19427, 19429, 19433, 19441, 19447, 19457, 19463, 19469, 19471, 19477, 19483, 19489, 19501, 19507, 19531, 19541, 19543, 19553, 19559, 19571, 19577, 19583, 19597, 19603, 19609, 19661, 19681, 19687, 19697, 19699, 19709, 19717, 19727, 19739, 19751, 19753, 19759, 19763, 19777, 19793, 19801, 19813, 19819, 19841, 19843, 19853, 19861, 19867, 19889, 19891, 19913, 19919, 19927, 19937, 19949, 19961, 19963, 19973, 19979, 19991, 19993, 19997, 20011, 20021, 20023, 20029, 20047, 20051, 20063, 20071, 20089, 20101, 20107, 20113, 20117, 20123, 20129, 20143, 20147, 20149, 20161, 20173, 20177, 20183, 20201, 20219, 20231, 20233, 20249, 20261, 20269, 20287, 20297, 20323, 20327, 20333, 20341, 20347, 20353, 20357, 20359, 20369, 20389, 20393, 20399, 20407, 20411, 20431, 20441, 20443, 20477, 20479, 20483, 20507, 20509, 20521, 20533, 20543, 20549, 20551, 20563, 20593, 20599, 20611, 20627, 20639, 20641, 20663, 20681, 20693, 20707, 20717, 20719, 20731, 20743, 20747, 20749, 20753, 20759, 20771, 20773, 20789, 20807, 20809, 20849, 20857, 20873, 20879, 20887, 20897, 20899, 20903, 20921, 20929, 20939, 20947, 20959, 20963, 20981, 20983, 21001, 21011, 21013, 21017, 21019, 21023, 21031, 21059, 21061, 21067, 21089, 21101, 21107, 21121, 21139, 21143, 21149, 21157, 21163, 21169, 21179, 21187, 21191, 21193, 21211, 21221, 21227, 21247, 21269, 21277, 21283, 21313, 21317, 21319, 21323, 21341, 21347, 21377, 21379, 21383, 21391, 21397, 21401, 21407, 21419, 21433, 21467, 21481, 21487, 21491, 21493, 21499, 21503, 21517, 21521, 21523, 21529, 21557, 21559, 21563, 21569, 21577, 21587, 21589, 21599, 21601, 21611, 21613, 21617, 21647, 21649, 21661, 21673, 21683, 21701, 21713, 21727, 21737, 21739, 21751, 21757, 21767, 21773, 21787, 21799, 21803, 21817, 21821, 21839, 21841, 21851, 21859, 21863, 21871, 21881, 21893, 21911, 21929, 21937, 21943, 21961, 21977, 21991, 21997, 22003, 22013, 22027, 22031, 22037, 22039, 22051, 22063, 22067, 22073, 22079, 22091, 22093, 22109, 22111, 22123, 22129, 22133, 22147, 22153, 22157, 22159, 22171, 22189, 22193, 22229, 22247, 22259, 22271, 22273, 22277, 22279, 22283, 22291, 22303, 22307, 22343, 22349, 22367, 22369, 22381, 22391, 22397, 22409, 22433, 22441, 22447, 22453, 22469, 22481, 22483, 22501, 22511, 22531, 22541, 22543, 22549, 22567, 22571, 22573, 22613, 22619, 22621, 22637, 22639, 22643, 22651, 22669, 22679, 22691, 22697, 22699, 22709, 22717, 22721, 22727, 22739, 22741, 22751, 22769, 22777, 22783, 22787, 22807, 22811, 22817, 22853, 22859, 22861, 22871, 22877, 22901, 22907, 22921, 22937, 22943, 22961, 22963, 22973, 22993, 23003, 23011, 23017, 23021, 23027, 23029, 23039, 23041, 23053, 23057, 23059, 23063, 23071, 23081, 23087, 23099, 23117, 23131, 23143, 23159, 23167, 23173, 23189, 23197, 23201, 23203, 23209, 23227, 23251, 23269, 23279, 23291, 23293, 23297, 23311, 23321, 23327, 23333, 23339, 23357, 23369, 23371, 23399, 23417, 23431, 23447, 23459, 23473, 23497, 23509, 23531, 23537, 23539, 23549, 23557, 23561, 23563, 23567, 23581, 23593, 23599, 23603, 23609, 23623, 23627, 23629, 23633, 23663, 23669, 23671, 23677, 23687, 23689, 23719, 23741, 23743, 23747, 23753, 23761, 23767, 23773, 23789, 23801, 23813, 23819, 23827, 23831, 23833, 23857, 23869, 23873, 23879, 23887, 23893, 23899, 23909, 23911, 23917, 23929, 23957, 23971, 23977, 23981, 23993, 24001, 24007, 24019, 24023, 24029, 24043, 24049, 24061, 24071, 24077, 24083, 24091, 24097, 24103, 24107, 24109, 24113, 24121, 24133, 24137, 24151, 24169, 24179, 24181, 24197, 24203, 24223, 24229, 24239, 24247, 24251, 24281, 24317, 24329, 24337, 24359, 24371, 24373, 24379, 24391, 24407, 24413, 24419, 24421, 24439, 24443, 24469, 24473, 24481, 24499, 24509, 24517, 24527, 24533, 24547, 24551, 24571, 24593, 24611, 24623, 24631, 24659, 24671, 24677, 24683, 24691, 24697, 24709, 24733, 24749, 24763, 24767, 24781, 24793, 24799, 24809, 24821, 24841, 24847, 24851, 24859, 24877, 24889, 24907, 24917, 24919, 24923, 24943, 24953, 24967, 24971, 24977, 24979, 24989, 25013, 25031, 25033, 25037, 25057, 25073, 25087, 25097, 25111, 25117, 25121, 25127, 25147, 25153, 25163, 25169, 25171, 25183, 25189, 25219, 25229, 25237, 25243, 25247, 25253, 25261, 25301, 25303, 25307, 25309, 25321, 25339, 25343, 25349, 25357, 25367, 25373, 25391, 25409, 25411, 25423, 25439, 25447, 25453, 25457, 25463, 25469, 25471, 25523, 25537, 25541, 25561, 25577, 25579, 25583, 25589, 25601, 25603, 25609, 25621, 25633, 25639, 25643, 25657, 25667, 25673, 25679, 25693, 25703, 25717, 25733, 25741, 25747, 25759, 25763, 25771, 25793, 25799, 25801, 25819, 25841, 25847, 25849, 25867, 25873, 25889, 25903, 25913, 25919, 25931, 25933, 25939, 25943, 25951, 25969, 25981, 25997, 25999, 26003, 26017, 26021, 26029, 26041, 26053, 26083, 26099, 26107, 26111, 26113, 26119, 26141, 26153, 26161, 26171, 26177, 26183, 26189, 26203, 26209, 26227, 26237, 26249, 26251, 26261, 26263, 26267, 26293, 26297, 26309, 26317, 26321, 26339, 26347, 26357, 26371, 26387, 26393, 26399, 26407, 26417, 26423, 26431, 26437, 26449, 26459, 26479, 26489, 26497, 26501, 26513, 26539, 26557, 26561, 26573, 26591, 26597, 26627, 26633, 26641, 26647, 26669, 26681, 26683, 26687, 26693, 26699, 26701, 26711, 26713, 26717, 26723, 26729, 26731, 26737, 26759, 26777, 26783, 26801, 26813, 26821, 26833, 26839, 26849, 26861, 26863, 26879, 26881, 26891, 26893, 26903, 26921, 26927, 26947, 26951, 26953, 26959, 26981, 26987, 26993, 27011, 27017, 27031, 27043, 27059, 27061, 27067, 27073, 27077, 27091, 27103, 27107, 27109, 27127, 27143, 27179, 27191, 27197, 27211, 27239, 27241, 27253, 27259, 27271, 27277, 27281, 27283, 27299, 27329, 27337, 27361, 27367, 27397, 27407, 27409, 27427, 27431, 27437, 27449, 27457, 27479, 27481, 27487, 27509, 27527, 27529, 27539, 27541, 27551, 27581, 27583, 27611, 27617, 27631, 27647, 27653, 27673, 27689, 27691, 27697, 27701, 27733, 27737, 27739, 27743, 27749, 27751, 27763, 27767, 27773, 27779, 27791, 27793, 27799, 27803, 27809, 27817, 27823, 27827, 27847, 27851, 27883, 27893, 27901, 27917, 27919, 27941, 27943, 27947, 27953, 27961, 27967, 27983, 27997, 28001, 28019, 28027, 28031, 28051, 28057, 28069, 28081, 28087, 28097, 28099, 28109, 28111, 28123, 28151, 28163, 28181, 28183, 28201, 28211, 28219, 28229, 28277, 28279, 28283, 28289, 28297, 28307, 28309, 28319, 28349, 28351, 28387, 28393, 28403, 28409, 28411, 28429, 28433, 28439, 28447, 28463, 28477, 28493, 28499, 28513, 28517, 28537, 28541, 28547, 28549, 28559, 28571, 28573, 28579, 28591, 28597, 28603, 28607, 28619, 28621, 28627, 28631, 28643, 28649, 28657, 28661, 28663, 28669, 28687, 28697, 28703, 28711, 28723, 28729, 28751, 28753, 28759, 28771, 28789, 28793, 28807, 28813, 28817, 28837, 28843, 28859, 28867, 28871, 28879, 28901, 28909, 28921, 28927, 28933, 28949, 28961, 28979, 29009, 29017, 29021, 29023, 29027, 29033, 29059, 29063, 29077, 29101, 29123, 29129, 29131, 29137, 29147, 29153, 29167, 29173, 29179, 29191, 29201, 29207, 29209, 29221, 29231, 29243, 29251, 29269, 29287, 29297, 29303, 29311, 29327, 29333, 29339, 29347, 29363, 29383, 29387, 29389, 29399, 29401, 29411, 29423, 29429, 29437, 29443, 29453, 29473, 29483, 29501, 29527, 29531, 29537, 29567, 29569, 29573, 29581, 29587, 29599, 29611, 29629, 29633, 29641, 29663, 29669, 29671, 29683, 29717, 29723, 29741, 29753, 29759, 29761, 29789, 29803, 29819, 29833, 29837, 29851, 29863, 29867, 29873, 29879, 29881, 29917, 29921, 29927, 29947, 29959, 29983, 29989, 30011, 30013, 30029, 30047, 30059, 30071, 30089, 30091, 30097, 30103, 30109, 30113, 30119, 30133, 30137, 30139, 30161, 30169, 30181, 30187, 30197, 30203, 30211, 30223, 30241, 30253, 30259, 30269, 30271, 30293, 30307, 30313, 30319, 30323, 30341, 30347, 30367, 30389, 30391, 30403, 30427, 30431, 30449, 30467, 30469, 30491, 30493, 30497, 30509, 30517, 30529, 30539, 30553, 30557, 30559, 30577, 30593, 30631, 30637, 30643, 30649, 30661, 30671, 30677, 30689, 30697, 30703, 30707, 30713, 30727, 30757, 30763, 30773, 30781, 30803, 30809, 30817, 30829, 30839, 30841, 30851, 30853, 30859, 30869, 30871, 30881, 30893, 30911, 30931, 30937, 30941, 30949, 30971, 30977, 30983, 31013, 31019, 31033, 31039, 31051, 31063, 31069, 31079, 31081, 31091, 31121, 31123, 31139, 31147, 31151, 31153, 31159, 31177, 31181, 31183, 31189, 31193, 31219, 31223, 31231, 31237, 31247, 31249, 31253, 31259, 31267, 31271, 31277, 31307, 31319, 31321, 31327, 31333, 31337, 31357, 31379, 31387, 31391, 31393, 31397, 31469, 31477, 31481, 31489, 31511, 31513, 31517, 31531, 31541, 31543, 31547, 31567, 31573, 31583, 31601, 31607, 31627, 31643, 31649, 31657, 31663, 31667, 31687, 31699, 31721, 31723, 31727, 31729, 31741, 31751, 31769, 31771, 31793, 31799, 31817, 31847, 31849, 31859, 31873, 31883, 31891, 31907, 31957, 31963, 31973, 31981, 31991, 32003, 32009, 32027, 32029, 32051, 32057, 32059, 32063, 32069, 32077, 32083, 32089, 32099, 32117, 32119, 32141, 32143, 32159, 32173, 32183, 32189, 32191, 32203, 32213, 32233, 32237, 32251, 32257, 32261, 32297, 32299, 32303, 32309, 32321, 32323, 32327, 32341, 32353, 32359, 32363, 32369, 32371, 32377, 32381, 32401, 32411, 32413, 32423, 32429, 32441, 32443, 32467, 32479, 32491, 32497, 32503, 32507, 32531, 32533, 32537, 32561, 32563, 32569, 32573, 32579, 32587, 32603, 32609, 32611, 32621, 32633, 32647, 32653, 32687, 32693, 32707, 32713, 32717, 32719, 32749, 32771, 32779, 32783, 32789, 32797, 32801, 32803, 32831, 32833, 32839, 32843, 32869, 32887, 32909, 32911, 32917, 32933, 32939, 32941, 32957, 32969, 32971, 32983, 32987, 32993, 32999, 33013, 33023, 33029, 33037, 33049, 33053, 33071, 33073, 33083, 33091, 33107, 33113, 33119, 33149, 33151, 33161, 33179, 33181, 33191, 33199, 33203, 33211, 33223, 33247, 33287, 33289, 33301, 33311, 33317, 33329, 33331, 33343, 33347, 33349, 33353, 33359, 33377, 33391, 33403, 33409, 33413, 33427, 33457, 33461, 33469, 33479, 33487, 33493, 33503, 33521, 33529, 33533, 33547, 33563, 33569, 33577, 33581, 33587, 33589, 33599, 33601, 33613, 33617, 33619, 33623, 33629, 33637, 33641, 33647, 33679, 33703, 33713, 33721, 33739, 33749, 33751, 33757, 33767, 33769, 33773, 33791, 33797, 33809, 33811, 33827, 33829, 33851, 33857, 33863, 33871, 33889, 33893, 33911, 33923, 33931, 33937, 33941, 33961, 33967, 33997, 34019, 34031, 34033, 34039, 34057, 34061, 34123, 34127, 34129, 34141, 34147, 34157, 34159, 34171, 34183, 34211, 34213, 34217, 34231, 34253, 34259, 34261, 34267, 34273, 34283, 34297, 34301, 34303, 34313, 34319, 34327, 34337, 34351, 34361, 34367, 34369, 34381, 34403, 34421, 34429, 34439, 34457, 34469, 34471, 34483, 34487, 34499, 34501, 34511, 34513, 34519, 34537, 34543, 34549, 34583, 34589, 34591, 34603, 34607, 34613, 34631, 34649, 34651, 34667, 34673, 34679, 34687, 34693, 34703, 34721, 34729, 34739, 34747, 34757, 34759, 34763, 34781, 34807, 34819, 34841, 34843, 34847, 34849, 34871, 34877, 34883, 34897, 34913, 34919, 34939, 34949, 34961, 34963, 34981, 35023, 35027, 35051, 35053, 35059, 35069, 35081, 35083, 35089, 35099, 35107, 35111, 35117, 35129, 35141, 35149, 35153, 35159, 35171, 35201, 35221, 35227, 35251, 35257, 35267, 35279, 35281, 35291, 35311, 35317, 35323, 35327, 35339, 35353, 35363, 35381, 35393, 35401, 35407, 35419, 35423, 35437, 35447, 35449, 35461, 35491, 35507, 35509, 35521, 35527, 35531, 35533, 35537, 35543, 35569, 35573, 35591, 35593, 35597, 35603, 35617, 35671, 35677, 35729, 35731, 35747, 35753, 35759, 35771, 35797, 35801, 35803, 35809, 35831, 35837, 35839, 35851, 35863, 35869, 35879, 35897, 35899, 35911, 35923, 35933, 35951, 35963, 35969, 35977, 35983, 35993, 35999, 36007, 36011, 36013, 36017, 36037, 36061, 36067, 36073, 36083, 36097, 36107, 36109, 36131, 36137, 36151, 36161, 36187, 36191, 36209, 36217, 36229, 36241, 36251, 36263, 36269, 36277, 36293, 36299, 36307, 36313, 36319, 36341, 36343, 36353, 36373, 36383, 36389, 36433, 36451, 36457, 36467, 36469, 36473, 36479, 36493, 36497, 36523, 36527, 36529, 36541, 36551, 36559, 36563, 36571, 36583, 36587, 36599, 36607, 36629, 36637, 36643, 36653, 36671, 36677, 36683, 36691, 36697, 36709, 36713, 36721, 36739, 36749, 36761, 36767, 36779, 36781, 36787, 36791, 36793, 36809, 36821, 36833, 36847, 36857, 36871, 36877, 36887, 36899, 36901, 36913, 36919, 36923, 36929, 36931, 36943, 36947, 36973, 36979, 36997, 37003, 37013, 37019, 37021, 37039, 37049, 37057, 37061, 37087, 37097, 37117, 37123, 37139, 37159, 37171, 37181, 37189, 37199, 37201, 37217, 37223, 37243, 37253, 37273, 37277, 37307, 37309, 37313, 37321, 37337, 37339, 37357, 37361, 37363, 37369, 37379, 37397, 37409, 37423, 37441, 37447, 37463, 37483, 37489, 37493, 37501, 37507, 37511, 37517, 37529, 37537, 37547, 37549, 37561, 37567, 37571, 37573, 37579, 37589, 37591, 37607, 37619, 37633, 37643, 37649, 37657, 37663, 37691, 37693, 37699, 37717, 37747, 37781, 37783, 37799, 37811, 37813, 37831, 37847, 37853, 37861, 37871, 37879, 37889, 37897, 37907, 37951, 37957, 37963, 37967, 37987, 37991, 37993, 37997, 38011, 38039, 38047, 38053, 38069, 38083, 38113, 38119, 38149, 38153, 38167, 38177, 38183, 38189, 38197, 38201, 38219, 38231, 38237, 38239, 38261, 38273, 38281, 38287, 38299, 38303, 38317, 38321, 38327, 38329, 38333, 38351, 38371, 38377, 38393, 38431, 38447, 38449, 38453, 38459, 38461, 38501, 38543, 38557, 38561, 38567, 38569, 38593, 38603, 38609, 38611, 38629, 38639, 38651, 38653, 38669, 38671, 38677, 38693, 38699, 38707, 38711, 38713, 38723, 38729, 38737, 38747, 38749, 38767, 38783, 38791, 38803, 38821, 38833, 38839, 38851, 38861, 38867, 38873, 38891, 38903, 38917, 38921, 38923, 38933, 38953, 38959, 38971, 38977, 38993, 39019, 39023, 39041, 39043, 39047, 39079, 39089, 39097, 39103, 39107, 39113, 39119, 39133, 39139, 39157, 39161, 39163, 39181, 39191, 39199, 39209, 39217, 39227, 39229, 39233, 39239, 39241, 39251, 39293, 39301, 39313, 39317, 39323, 39341, 39343, 39359, 39367, 39371, 39373, 39383, 39397, 39409, 39419, 39439, 39443, 39451, 39461, 39499, 39503, 39509, 39511, 39521, 39541, 39551, 39563, 39569, 39581, 39607, 39619, 39623, 39631, 39659, 39667, 39671, 39679, 39703, 39709, 39719, 39727, 39733, 39749, 39761, 39769, 39779, 39791, 39799, 39821, 39827, 39829, 39839, 39841, 39847, 39857, 39863, 39869, 39877, 39883, 39887, 39901, 39929, 39937, 39953, 39971, 39979, 39983, 39989, 40009, 40013, 40031, 40037, 40039, 40063, 40087, 40093, 40099, 40111, 40123, 40127, 40129, 40151, 40153, 40163, 40169, 40177, 40189, 40193, 40213, 40231, 40237, 40241, 40253, 40277, 40283, 40289, 40343, 40351, 40357, 40361, 40387, 40423, 40427, 40429, 40433, 40459, 40471, 40483, 40487, 40493, 40499, 40507, 40519, 40529, 40531, 40543, 40559, 40577, 40583, 40591, 40597, 40609, 40627, 40637, 40639, 40693, 40697, 40699, 40709, 40739, 40751, 40759, 40763, 40771, 40787, 40801, 40813, 40819, 40823, 40829, 40841, 40847, 40849, 40853, 40867, 40879, 40883, 40897, 40903, 40927, 40933, 40939, 40949, 40961, 40973, 40993, 41011, 41017, 41023, 41039, 41047, 41051, 41057, 41077, 41081, 41113, 41117, 41131, 41141, 41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, 41213, 41221, 41227, 41231, 41233, 41243, 41257, 41263, 41269, 41281, 41299, 41333, 41341, 41351, 41357, 41381, 41387, 41389, 41399, 41411, 41413, 41443, 41453, 41467, 41479, 41491, 41507, 41513, 41519, 41521, 41539, 41543, 41549, 41579, 41593, 41597, 41603, 41609, 41611, 41617, 41621, 41627, 41641, 41647, 41651, 41659, 41669, 41681, 41687, 41719, 41729, 41737, 41759, 41761, 41771, 41777, 41801, 41809, 41813, 41843, 41849, 41851, 41863, 41879, 41887, 41893, 41897, 41903, 41911, 41927, 41941, 41947, 41953, 41957, 41959, 41969, 41981, 41983, 41999, 42013, 42017, 42019, 42023, 42043, 42061, 42071, 42073, 42083, 42089, 42101, 42131, 42139, 42157, 42169, 42179, 42181, 42187, 42193, 42197, 42209, 42221, 42223, 42227, 42239, 42257, 42281, 42283, 42293, 42299, 42307, 42323, 42331, 42337, 42349, 42359, 42373, 42379, 42391, 42397, 42403, 42407, 42409, 42433, 42437, 42443, 42451, 42457, 42461, 42463, 42467, 42473, 42487, 42491, 42499, 42509, 42533, 42557, 42569, 42571, 42577, 42589, 42611, 42641, 42643, 42649, 42667, 42677, 42683, 42689, 42697, 42701, 42703, 42709, 42719, 42727, 42737, 42743, 42751, 42767, 42773, 42787, 42793, 42797, 42821, 42829, 42839, 42841, 42853, 42859, 42863, 42899, 42901, 42923, 42929, 42937, 42943, 42953, 42961, 42967, 42979, 42989, 43003, 43013, 43019, 43037, 43049, 43051, 43063, 43067, 43093, 43103, 43117, 43133, 43151, 43159, 43177, 43189, 43201, 43207, 43223, 43237, 43261, 43271, 43283, 43291, 43313, 43319, 43321, 43331, 43391, 43397, 43399, 43403, 43411, 43427, 43441, 43451, 43457, 43481, 43487, 43499, 43517, 43541, 43543, 43573, 43577, 43579, 43591, 43597, 43607, 43609, 43613, 43627, 43633, 43649, 43651, 43661, 43669, 43691, 43711, 43717, 43721, 43753, 43759, 43777, 43781, 43783, 43787, 43789, 43793, 43801, 43853, 43867, 43889, 43891, 43913, 43933, 43943, 43951, 43961, 43963, 43969, 43973, 43987, 43991, 43997, 44017, 44021, 44027, 44029, 44041, 44053, 44059, 44071, 44087, 44089, 44101, 44111, 44119, 44123, 44129, 44131, 44159, 44171, 44179, 44189, 44201, 44203, 44207, 44221, 44249, 44257, 44263, 44267, 44269, 44273, 44279, 44281, 44293, 44351, 44357, 44371, 44381, 44383, 44389, 44417, 44449, 44453, 44483, 44491, 44497, 44501, 44507, 44519, 44531, 44533, 44537, 44543, 44549, 44563, 44579, 44587, 44617, 44621, 44623, 44633, 44641, 44647, 44651, 44657, 44683, 44687, 44699, 44701, 44711, 44729, 44741, 44753, 44771, 44773, 44777, 44789, 44797, 44809, 44819, 44839, 44843, 44851, 44867, 44879, 44887, 44893, 44909, 44917, 44927, 44939, 44953, 44959, 44963, 44971, 44983, 44987, 45007, 45013, 45053, 45061, 45077, 45083, 45119, 45121, 45127, 45131, 45137, 45139, 45161, 45179, 45181, 45191, 45197, 45233, 45247, 45259, 45263, 45281, 45289, 45293, 45307, 45317, 45319, 45329, 45337, 45341, 45343, 45361, 45377, 45389, 45403, 45413, 45427, 45433, 45439, 45481, 45491, 45497, 45503, 45523, 45533, 45541, 45553, 45557, 45569, 45587, 45589, 45599, 45613, 45631, 45641, 45659, 45667, 45673, 45677, 45691, 45697, 45707, 45737, 45751, 45757, 45763, 45767, 45779, 45817, 45821, 45823, 45827, 45833, 45841, 45853, 45863, 45869, 45887, 45893, 45943, 45949, 45953, 45959, 45971, 45979, 45989, 46021, 46027, 46049, 46051, 46061, 46073, 46091, 46093, 46099, 46103, 46133, 46141, 46147, 46153, 46171, 46181, 46183, 46187, 46199, 46219, 46229, 46237, 46261, 46271, 46273, 46279, 46301, 46307, 46309, 46327, 46337, 46349, 46351, 46381, 46399, 46411, 46439, 46441, 46447, 46451, 46457, 46471, 46477, 46489, 46499, 46507, 46511, 46523, 46549, 46559, 46567, 46573, 46589, 46591, 46601, 46619, 46633, 46639, 46643, 46649, 46663, 46679, 46681, 46687, 46691, 46703, 46723, 46727, 46747, 46751, 46757, 46769, 46771, 46807, 46811, 46817, 46819, 46829, 46831, 46853, 46861, 46867, 46877, 46889, 46901, 46919, 46933, 46957, 46993, 46997, 47017, 47041, 47051, 47057, 47059, 47087, 47093, 47111, 47119, 47123, 47129, 47137, 47143, 47147, 47149, 47161, 47189, 47207, 47221, 47237, 47251, 47269, 47279, 47287, 47293, 47297, 47303, 47309, 47317, 47339, 47351, 47353, 47363, 47381, 47387, 47389, 47407, 47417, 47419, 47431, 47441, 47459, 47491, 47497, 47501, 47507, 47513, 47521, 47527, 47533, 47543, 47563, 47569, 47581, 47591, 47599, 47609, 47623, 47629, 47639, 47653, 47657, 47659, 47681, 47699, 47701, 47711, 47713, 47717, 47737, 47741, 47743, 47777, 47779, 47791, 47797, 47807, 47809, 47819, 47837, 47843, 47857, 47869, 47881, 47903, 47911, 47917, 47933, 47939, 47947, 47951, 47963, 47969, 47977, 47981, 48017, 48023, 48029, 48049, 48073, 48079, 48091, 48109, 48119, 48121, 48131, 48157, 48163, 48179, 48187, 48193, 48197, 48221, 48239, 48247, 48259, 48271, 48281, 48299, 48311, 48313, 48337, 48341, 48353, 48371, 48383, 48397, 48407, 48409, 48413, 48437, 48449, 48463, 48473, 48479, 48481, 48487, 48491, 48497, 48523, 48527, 48533, 48539, 48541, 48563, 48571, 48589, 48593, 48611, 48619, 48623, 48647, 48649, 48661, 48673, 48677, 48679, 48731, 48733, 48751, 48757, 48761, 48767, 48779, 48781, 48787, 48799, 48809, 48817, 48821, 48823, 48847, 48857, 48859, 48869, 48871, 48883, 48889, 48907, 48947, 48953, 48973, 48989, 48991, 49003, 49009, 49019, 49031, 49033, 49037, 49043, 49057, 49069, 49081, 49103, 49109, 49117, 49121, 49123, 49139, 49157, 49169, 49171, 49177, 49193, 49199, 49201, 49207, 49211, 49223, 49253, 49261, 49277, 49279, 49297, 49307, 49331, 49333, 49339, 49363, 49367, 49369, 49391, 49393, 49409, 49411, 49417, 49429, 49433, 49451, 49459, 49463, 49477, 49481, 49499, 49523, 49529, 49531, 49537, 49547, 49549, 49559, 49597, 49603, 49613, 49627, 49633, 49639, 49663, 49667, 49669, 49681, 49697, 49711, 49727, 49739, 49741, 49747, 49757, 49783, 49787, 49789, 49801, 49807, 49811, 49823, 49831, 49843, 49853, 49871, 49877, 49891, 49919, 49921, 49927, 49937, 49939, 49943, 49957, 49991, 49993, 49999, 50021, 50023, 50033, 50047, 50051, 50053, 50069, 50077, 50087, 50093, 50101, 50111, 50119, 50123, 50129, 50131, 50147, 50153, 50159, 50177, 50207, 50221, 50227, 50231, 50261, 50263, 50273, 50287, 50291, 50311, 50321, 50329, 50333, 50341, 50359, 50363, 50377, 50383, 50387, 50411, 50417, 50423, 50441, 50459, 50461, 50497, 50503, 50513, 50527, 50539, 50543, 50549, 50551, 50581, 50587, 50591, 50593, 50599, 50627, 50647, 50651, 50671, 50683, 50707, 50723, 50741, 50753, 50767, 50773, 50777, 50789, 50821, 50833, 50839, 50849, 50857, 50867, 50873, 50891, 50893, 50909, 50923, 50929, 50951, 50957, 50969, 50971, 50989, 50993, 51001, 51031, 51043, 51047, 51059, 51061, 51071, 51109, 51131, 51133, 51137, 51151, 51157, 51169, 51193, 51197, 51199, 51203, 51217, 51229, 51239, 51241, 51257, 51263, 51283, 51287, 51307, 51329, 51341, 51343, 51347, 51349, 51361, 51383, 51407, 51413, 51419, 51421, 51427, 51431, 51437, 51439, 51449, 51461, 51473, 51479, 51481, 51487, 51503, 51511, 51517, 51521, 51539, 51551, 51563, 51577, 51581, 51593, 51599, 51607, 51613, 51631, 51637, 51647, 51659, 51673, 51679, 51683, 51691, 51713, 51719, 51721, 51749, 51767, 51769, 51787, 51797, 51803, 51817, 51827, 51829, 51839, 51853, 51859, 51869, 51871, 51893, 51899, 51907, 51913, 51929, 51941, 51949, 51971, 51973, 51977, 51991, 52009, 52021, 52027, 52051, 52057, 52067, 52069, 52081, 52103, 52121, 52127, 52147, 52153, 52163, 52177, 52181, 52183, 52189, 52201, 52223, 52237, 52249, 52253, 52259, 52267, 52289, 52291, 52301, 52313, 52321, 52361, 52363, 52369, 52379, 52387, 52391, 52433, 52453, 52457, 52489, 52501, 52511, 52517, 52529, 52541, 52543, 52553, 52561, 52567, 52571, 52579, 52583, 52609, 52627, 52631, 52639, 52667, 52673, 52691, 52697, 52709, 52711, 52721, 52727, 52733, 52747, 52757, 52769, 52783, 52807, 52813, 52817, 52837, 52859, 52861, 52879, 52883, 52889, 52901, 52903, 52919, 52937, 52951, 52957, 52963, 52967, 52973, 52981, 52999, 53003, 53017, 53047, 53051, 53069, 53077, 53087, 53089, 53093, 53101, 53113, 53117, 53129, 53147, 53149, 53161, 53171, 53173, 53189, 53197, 53201, 53231, 53233, 53239, 53267, 53269, 53279, 53281, 53299, 53309, 53323, 53327, 53353, 53359, 53377, 53381, 53401, 53407, 53411, 53419, 53437, 53441, 53453, 53479, 53503, 53507, 53527, 53549, 53551, 53569, 53591, 53593, 53597, 53609, 53611, 53617, 53623, 53629, 53633, 53639, 53653, 53657, 53681, 53693, 53699, 53717, 53719, 53731, 53759, 53773, 53777, 53783, 53791, 53813, 53819, 53831, 53849, 53857, 53861, 53881, 53887, 53891, 53897, 53899, 53917, 53923, 53927, 53939, 53951, 53959, 53987, 53993, 54001, 54011, 54013, 54037, 54049, 54059, 54083, 54091, 54101, 54121, 54133, 54139, 54151, 54163, 54167, 54181, 54193, 54217, 54251, 54269, 54277, 54287, 54293, 54311, 54319, 54323, 54331, 54347, 54361, 54367, 54371, 54377, 54401, 54403, 54409, 54413, 54419, 54421, 54437, 54443, 54449, 54469, 54493, 54497, 54499, 54503, 54517, 54521, 54539, 54541, 54547, 54559, 54563, 54577, 54581, 54583, 54601, 54617, 54623, 54629, 54631, 54647, 54667, 54673, 54679, 54709, 54713, 54721, 54727, 54751, 54767, 54773, 54779, 54787, 54799, 54829, 54833, 54851, 54869, 54877, 54881, 54907, 54917, 54919, 54941, 54949, 54959, 54973, 54979, 54983, 55001, 55009, 55021, 55049, 55051, 55057, 55061, 55073, 55079, 55103, 55109, 55117, 55127, 55147, 55163, 55171, 55201, 55207, 55213, 55217, 55219, 55229, 55243, 55249, 55259, 55291, 55313, 55331, 55333, 55337, 55339, 55343, 55351, 55373, 55381, 55399, 55411, 55439, 55441, 55457, 55469, 55487, 55501, 55511, 55529, 55541, 55547, 55579, 55589, 55603, 55609, 55619, 55621, 55631, 55633, 55639, 55661, 55663, 55667, 55673, 55681, 55691, 55697, 55711, 55717, 55721, 55733, 55763, 55787, 55793, 55799, 55807, 55813, 55817, 55819, 55823, 55829, 55837, 55843, 55849, 55871, 55889, 55897, 55901, 55903, 55921, 55927, 55931, 55933, 55949, 55967, 55987, 55997, 56003, 56009, 56039, 56041, 56053, 56081, 56087, 56093, 56099, 56101, 56113, 56123, 56131, 56149, 56167, 56171, 56179, 56197, 56207, 56209, 56237, 56239, 56249, 56263, 56267, 56269, 56299, 56311, 56333, 56359, 56369, 56377, 56383, 56393, 56401, 56417, 56431, 56437, 56443, 56453, 56467, 56473, 56477, 56479, 56489, 56501, 56503, 56509, 56519, 56527, 56531, 56533, 56543, 56569, 56591, 56597, 56599, 56611, 56629, 56633, 56659, 56663, 56671, 56681, 56687, 56701, 56711, 56713, 56731, 56737, 56747, 56767, 56773, 56779, 56783, 56807, 56809, 56813, 56821, 56827, 56843, 56857, 56873, 56891, 56893, 56897, 56909, 56911, 56921, 56923, 56929, 56941, 56951, 56957, 56963, 56983, 56989, 56993, 56999, 57037, 57041, 57047, 57059, 57073, 57077, 57089, 57097, 57107, 57119, 57131, 57139, 57143, 57149, 57163, 57173, 57179, 57191, 57193, 57203, 57221, 57223, 57241, 57251, 57259, 57269, 57271, 57283, 57287, 57301, 57329, 57331, 57347, 57349, 57367, 57373, 57383, 57389, 57397, 57413, 57427, 57457, 57467, 57487, 57493, 57503, 57527, 57529, 57557, 57559, 57571, 57587, 57593, 57601, 57637, 57641, 57649, 57653, 57667, 57679, 57689, 57697, 57709, 57713, 57719, 57727, 57731, 57737, 57751, 57773, 57781, 57787, 57791, 57793, 57803, 57809, 57829, 57839, 57847, 57853, 57859, 57881, 57899, 57901, 57917, 57923, 57943, 57947, 57973, 57977, 57991, 58013, 58027, 58031, 58043, 58049, 58057, 58061, 58067, 58073, 58099, 58109, 58111, 58129, 58147, 58151, 58153, 58169, 58171, 58189, 58193, 58199, 58207, 58211, 58217, 58229, 58231, 58237, 58243, 58271, 58309, 58313, 58321, 58337, 58363, 58367, 58369, 58379, 58391, 58393, 58403, 58411, 58417, 58427, 58439, 58441, 58451, 58453, 58477, 58481, 58511, 58537, 58543, 58549, 58567, 58573, 58579, 58601, 58603, 58613, 58631, 58657, 58661, 58679, 58687, 58693, 58699, 58711, 58727, 58733, 58741, 58757, 58763, 58771, 58787, 58789, 58831, 58889, 58897, 58901, 58907, 58909, 58913, 58921, 58937, 58943, 58963, 58967, 58979, 58991, 58997, 59009, 59011, 59021, 59023, 59029, 59051, 59053, 59063, 59069, 59077, 59083, 59093, 59107, 59113, 59119, 59123, 59141, 59149, 59159, 59167, 59183, 59197, 59207, 59209, 59219, 59221, 59233, 59239, 59243, 59263, 59273, 59281, 59333, 59341, 59351, 59357, 59359, 59369, 59377, 59387, 59393, 59399, 59407, 59417, 59419, 59441, 59443, 59447, 59453, 59467, 59471, 59473, 59497, 59509, 59513, 59539, 59557, 59561, 59567, 59581, 59611, 59617, 59621, 59627, 59629, 59651, 59659, 59663, 59669, 59671, 59693, 59699, 59707, 59723, 59729, 59743, 59747, 59753, 59771, 59779, 59791, 59797, 59809, 59833, 59863, 59879, 59887, 59921, 59929, 59951, 59957, 59971, 59981, 59999, 60013, 60017, 60029, 60037, 60041, 60077, 60083, 60089, 60091, 60101, 60103, 60107, 60127, 60133, 60139, 60149, 60161, 60167, 60169, 60209, 60217, 60223, 60251, 60257, 60259, 60271, 60289, 60293, 60317, 60331, 60337, 60343, 60353, 60373, 60383, 60397, 60413, 60427, 60443, 60449, 60457, 60493, 60497, 60509, 60521, 60527, 60539, 60589, 60601, 60607, 60611, 60617, 60623, 60631, 60637, 60647, 60649, 60659, 60661, 60679, 60689, 60703, 60719, 60727, 60733, 60737, 60757, 60761, 60763, 60773, 60779, 60793, 60811, 60821, 60859, 60869, 60887, 60889, 60899, 60901, 60913, 60917, 60919, 60923, 60937, 60943, 60953, 60961, 61001, 61007, 61027, 61031, 61043, 61051, 61057, 61091, 61099, 61121, 61129, 61141, 61151, 61153, 61169, 61211, 61223, 61231, 61253, 61261, 61283, 61291, 61297, 61331, 61333, 61339, 61343, 61357, 61363, 61379, 61381, 61403, 61409, 61417, 61441, 61463, 61469, 61471, 61483, 61487, 61493, 61507, 61511, 61519, 61543, 61547, 61553, 61559, 61561, 61583, 61603, 61609, 61613, 61627, 61631, 61637, 61643, 61651, 61657, 61667, 61673, 61681, 61687, 61703, 61717, 61723, 61729, 61751, 61757, 61781, 61813, 61819, 61837, 61843, 61861, 61871, 61879, 61909, 61927, 61933, 61949, 61961, 61967, 61979, 61981, 61987, 61991, 62003, 62011, 62017, 62039, 62047, 62053, 62057, 62071, 62081, 62099, 62119, 62129, 62131, 62137, 62141, 62143, 62171, 62189, 62191, 62201, 62207, 62213, 62219, 62233, 62273, 62297, 62299, 62303, 62311, 62323, 62327, 62347, 62351, 62383, 62401, 62417, 62423, 62459, 62467, 62473, 62477, 62483, 62497, 62501, 62507, 62533, 62539, 62549, 62563, 62581, 62591, 62597, 62603, 62617, 62627, 62633, 62639, 62653, 62659, 62683, 62687, 62701, 62723, 62731, 62743, 62753, 62761, 62773, 62791, 62801, 62819, 62827, 62851, 62861, 62869, 62873, 62897, 62903, 62921, 62927, 62929, 62939, 62969, 62971, 62981, 62983, 62987, 62989, 63029, 63031, 63059, 63067, 63073, 63079, 63097, 63103, 63113, 63127, 63131, 63149, 63179, 63197, 63199, 63211, 63241, 63247, 63277, 63281, 63299, 63311, 63313, 63317, 63331, 63337, 63347, 63353, 63361, 63367, 63377, 63389, 63391, 63397, 63409, 63419, 63421, 63439, 63443, 63463, 63467, 63473, 63487, 63493, 63499, 63521, 63527, 63533, 63541, 63559, 63577, 63587, 63589, 63599, 63601, 63607, 63611, 63617, 63629, 63647, 63649, 63659, 63667, 63671, 63689, 63691, 63697, 63703, 63709, 63719, 63727, 63737, 63743, 63761, 63773, 63781, 63793, 63799, 63803, 63809, 63823, 63839, 63841, 63853, 63857, 63863, 63901, 63907, 63913, 63929, 63949, 63977, 63997, 64007, 64013, 64019, 64033, 64037, 64063, 64067, 64081, 64091, 64109, 64123, 64151, 64153, 64157, 64171, 64187, 64189, 64217, 64223, 64231, 64237, 64271, 64279, 64283, 64301, 64303, 64319, 64327, 64333, 64373, 64381, 64399, 64403, 64433, 64439, 64451, 64453, 64483, 64489, 64499, 64513, 64553, 64567, 64577, 64579, 64591, 64601, 64609, 64613, 64621, 64627, 64633, 64661, 64663, 64667, 64679, 64693, 64709, 64717, 64747, 64763, 64781, 64783, 64793, 64811, 64817, 64849, 64853, 64871, 64877, 64879, 64891, 64901, 64919, 64921, 64927, 64937, 64951, 64969, 64997, 65003, 65011, 65027, 65029, 65033, 65053, 65063, 65071, 65089, 65099, 65101, 65111, 65119, 65123, 65129, 65141, 65147, 65167, 65171, 65173, 65179, 65183, 65203, 65213, 65239, 65257, 65267, 65269, 65287, 65293, 65309, 65323, 65327, 65353, 65357, 65371, 65381, 65393, 65407, 65413, 65419, 65423, 65437, 65447, 65449, 65479, 65497, 65519, 65521, 65537, 65539, 65543, 65551, 65557, 65563, 65579, 65581, 65587, 65599, 65609, 65617, 65629, 65633, 65647, 65651, 65657, 65677, 65687, 65699, 65701, 65707, 65713, 65717, 65719, 65729, 65731, 65761, 65777, 65789, 65809, 65827, 65831, 65837, 65839, 65843, 65851, 65867, 65881, 65899, 65921, 65927, 65929, 65951, 65957, 65963, 65981, 65983, 65993, 66029, 66037, 66041, 66047, 66067, 66071, 66083, 66089, 66103, 66107, 66109, 66137, 66161, 66169, 66173, 66179, 66191, 66221, 66239, 66271, 66293, 66301, 66337, 66343, 66347, 66359, 66361, 66373, 66377, 66383, 66403, 66413, 66431, 66449, 66457, 66463, 66467, 66491, 66499, 66509, 66523, 66529, 66533, 66541, 66553, 66569, 66571, 66587, 66593, 66601, 66617, 66629, 66643, 66653, 66683, 66697, 66701, 66713, 66721, 66733, 66739, 66749, 66751, 66763, 66791, 66797, 66809, 66821, 66841, 66851, 66853, 66863, 66877, 66883, 66889, 66919, 66923, 66931, 66943, 66947, 66949, 66959, 66973, 66977, 67003, 67021, 67033, 67043, 67049, 67057, 67061, 67073, 67079, 67103, 67121, 67129, 67139, 67141, 67153, 67157, 67169, 67181, 67187, 67189, 67211, 67213, 67217, 67219, 67231, 67247, 67261, 67271, 67273, 67289, 67307, 67339, 67343, 67349, 67369, 67391, 67399, 67409, 67411, 67421, 67427, 67429, 67433, 67447, 67453, 67477, 67481, 67489, 67493, 67499, 67511, 67523, 67531, 67537, 67547, 67559, 67567, 67577, 67579, 67589, 67601, 67607, 67619, 67631, 67651, 67679, 67699, 67709, 67723, 67733, 67741, 67751, 67757, 67759, 67763, 67777, 67783, 67789, 67801, 67807, 67819, 67829, 67843, 67853, 67867, 67883, 67891, 67901, 67927, 67931, 67933, 67939, 67943, 67957, 67961, 67967, 67979, 67987, 67993, 68023, 68041, 68053, 68059, 68071, 68087, 68099, 68111, 68113, 68141, 68147, 68161, 68171, 68207, 68209, 68213, 68219, 68227, 68239, 68261, 68279, 68281, 68311, 68329, 68351, 68371, 68389, 68399, 68437, 68443, 68447, 68449, 68473, 68477, 68483, 68489, 68491, 68501, 68507, 68521, 68531, 68539, 68543, 68567, 68581, 68597, 68611, 68633, 68639, 68659, 68669, 68683, 68687, 68699, 68711, 68713, 68729, 68737, 68743, 68749, 68767, 68771, 68777, 68791, 68813, 68819, 68821, 68863, 68879, 68881, 68891, 68897, 68899, 68903, 68909, 68917, 68927, 68947, 68963, 68993, 69001, 69011, 69019, 69029, 69031, 69061, 69067, 69073, 69109, 69119, 69127, 69143, 69149, 69151, 69163, 69191, 69193, 69197, 69203, 69221, 69233, 69239, 69247, 69257, 69259, 69263, 69313, 69317, 69337, 69341, 69371, 69379, 69383, 69389, 69401, 69403, 69427, 69431, 69439, 69457, 69463, 69467, 69473, 69481, 69491, 69493, 69497, 69499, 69539, 69557, 69593, 69623, 69653, 69661, 69677, 69691, 69697, 69709, 69737, 69739, 69761, 69763, 69767, 69779, 69809, 69821, 69827, 69829, 69833, 69847, 69857, 69859, 69877, 69899, 69911, 69929, 69931, 69941, 69959, 69991, 69997, 70001, 70003, 70009, 70019, 70039, 70051, 70061, 70067, 70079, 70099, 70111, 70117, 70121, 70123, 70139, 70141, 70157, 70163, 70177, 70181, 70183, 70199, 70201, 70207, 70223, 70229, 70237, 70241, 70249, 70271, 70289, 70297, 70309, 70313, 70321, 70327, 70351, 70373, 70379, 70381, 70393, 70423, 70429, 70439, 70451, 70457, 70459, 70481, 70487, 70489, 70501, 70507, 70529, 70537, 70549, 70571, 70573, 70583, 70589, 70607, 70619, 70621, 70627, 70639, 70657, 70663, 70667, 70687, 70709, 70717, 70729, 70753, 70769, 70783, 70793, 70823, 70841, 70843, 70849, 70853, 70867, 70877, 70879, 70891, 70901, 70913, 70919, 70921, 70937, 70949, 70951, 70957, 70969, 70979, 70981, 70991, 70997, 70999, 71011, 71023, 71039, 71059, 71069, 71081, 71089, 71119, 71129, 71143, 71147, 71153, 71161, 71167, 71171, 71191, 71209, 71233, 71237, 71249, 71257, 71261, 71263, 71287, 71293, 71317, 71327, 71329, 71333, 71339, 71341, 71347, 71353, 71359, 71363, 71387, 71389, 71399, 71411, 71413, 71419, 71429, 71437, 71443, 71453, 71471, 71473, 71479, 71483, 71503, 71527, 71537, 71549, 71551, 71563, 71569, 71593, 71597, 71633, 71647, 71663, 71671, 71693, 71699, 71707, 71711, 71713, 71719, 71741, 71761, 71777, 71789, 71807, 71809, 71821, 71837, 71843, 71849, 71861, 71867, 71879, 71881, 71887, 71899, 71909, 71917, 71933, 71941, 71947, 71963, 71971, 71983, 71987, 71993, 71999, 72019, 72031, 72043, 72047, 72053, 72073, 72077, 72089, 72091, 72101, 72103, 72109, 72139, 72161, 72167, 72169, 72173, 72211, 72221, 72223, 72227, 72229, 72251, 72253, 72269, 72271, 72277, 72287, 72307, 72313, 72337, 72341, 72353, 72367, 72379, 72383, 72421, 72431, 72461, 72467, 72469, 72481, 72493, 72497, 72503, 72533, 72547, 72551, 72559, 72577, 72613, 72617, 72623, 72643, 72647, 72649, 72661, 72671, 72673, 72679, 72689, 72701, 72707, 72719, 72727, 72733, 72739, 72763, 72767, 72797, 72817, 72823, 72859, 72869, 72871, 72883, 72889, 72893, 72901, 72907, 72911, 72923, 72931, 72937, 72949, 72953, 72959, 72973, 72977, 72997, 73009, 73013, 73019, 73037, 73039, 73043, 73061, 73063, 73079, 73091, 73121, 73127, 73133, 73141, 73181, 73189, 73237, 73243, 73259, 73277, 73291, 73303, 73309, 73327, 73331, 73351, 73361, 73363, 73369, 73379, 73387, 73417, 73421, 73433, 73453, 73459, 73471, 73477, 73483, 73517, 73523, 73529, 73547, 73553, 73561, 73571, 73583, 73589, 73597, 73607, 73609, 73613, 73637, 73643, 73651, 73673, 73679, 73681, 73693, 73699, 73709, 73721, 73727, 73751, 73757, 73771, 73783, 73819, 73823, 73847, 73849, 73859, 73867, 73877, 73883, 73897, 73907, 73939, 73943, 73951, 73961, 73973, 73999, 74017, 74021, 74027, 74047, 74051, 74071, 74077, 74093, 74099, 74101, 74131, 74143, 74149, 74159, 74161, 74167, 74177, 74189, 74197, 74201, 74203, 74209, 74219, 74231, 74257, 74279, 74287, 74293, 74297, 74311, 74317, 74323, 74353, 74357, 74363, 74377, 74381, 74383, 74411, 74413, 74419, 74441, 74449, 74453, 74471, 74489, 74507, 74509, 74521, 74527, 74531, 74551, 74561, 74567, 74573, 74587, 74597, 74609, 74611, 74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719, 74729, 74731, 74747, 74759, 74761, 74771, 74779, 74797, 74821, 74827, 74831, 74843, 74857, 74861, 74869, 74873, 74887, 74891, 74897, 74903, 74923, 74929, 74933, 74941, 74959, 75011, 75013, 75017, 75029, 75037, 75041, 75079, 75083, 75109, 75133, 75149, 75161, 75167, 75169, 75181, 75193, 75209, 75211, 75217, 75223, 75227, 75239, 75253, 75269, 75277, 75289, 75307, 75323, 75329, 75337, 75347, 75353, 75367, 75377, 75389, 75391, 75401, 75403, 75407, 75431, 75437, 75479, 75503, 75511, 75521, 75527, 75533, 75539, 75541, 75553, 75557, 75571, 75577, 75583, 75611, 75617, 75619, 75629, 75641, 75653, 75659, 75679, 75683, 75689, 75703, 75707, 75709, 75721, 75731, 75743, 75767, 75773, 75781, 75787, 75793, 75797, 75821, 75833, 75853, 75869, 75883, 75913, 75931, 75937, 75941, 75967, 75979, 75983, 75989, 75991, 75997, 76001, 76003, 76031, 76039, 76079, 76081, 76091, 76099, 76103, 76123, 76129, 76147, 76157, 76159, 76163, 76207, 76213, 76231, 76243, 76249, 76253, 76259, 76261, 76283, 76289, 76303, 76333, 76343, 76367, 76369, 76379, 76387, 76403, 76421, 76423, 76441, 76463, 76471, 76481, 76487, 76493, 76507, 76511, 76519, 76537, 76541, 76543, 76561, 76579, 76597, 76603, 76607, 76631, 76649, 76651, 76667, 76673, 76679, 76697, 76717, 76733, 76753, 76757, 76771, 76777, 76781, 76801, 76819, 76829, 76831, 76837, 76847, 76871, 76873, 76883, 76907, 76913, 76919, 76943, 76949, 76961, 76963, 76991, 77003, 77017, 77023, 77029, 77041, 77047, 77069, 77081, 77093, 77101, 77137, 77141, 77153, 77167, 77171, 77191, 77201, 77213, 77237, 77239, 77243, 77249, 77261, 77263, 77267, 77269, 77279, 77291, 77317, 77323, 77339, 77347, 77351, 77359, 77369, 77377, 77383, 77417, 77419, 77431, 77447, 77471, 77477, 77479, 77489, 77491, 77509, 77513, 77521, 77527, 77543, 77549, 77551, 77557, 77563, 77569, 77573, 77587, 77591, 77611, 77617, 77621, 77641, 77647, 77659, 77681, 77687, 77689, 77699, 77711, 77713, 77719, 77723, 77731, 77743, 77747, 77761, 77773, 77783, 77797, 77801, 77813, 77839, 77849, 77863, 77867, 77893, 77899, 77929, 77933, 77951, 77969, 77977, 77983, 77999, 78007, 78017, 78031, 78041, 78049, 78059, 78079, 78101, 78121, 78137, 78139, 78157, 78163, 78167, 78173, 78179, 78191, 78193, 78203, 78229, 78233, 78241, 78259, 78277, 78283, 78301, 78307, 78311, 78317, 78341, 78347, 78367, 78401, 78427, 78437, 78439, 78467, 78479, 78487, 78497, 78509, 78511, 78517, 78539, 78541, 78553, 78569, 78571, 78577, 78583, 78593, 78607, 78623, 78643, 78649, 78653, 78691, 78697, 78707, 78713, 78721, 78737, 78779, 78781, 78787, 78791, 78797, 78803, 78809, 78823, 78839, 78853, 78857, 78877, 78887, 78889, 78893, 78901, 78919, 78929, 78941, 78977, 78979, 78989, 79031, 79039, 79043, 79063, 79087, 79103, 79111, 79133, 79139, 79147, 79151, 79153, 79159, 79181, 79187, 79193, 79201, 79229, 79231, 79241, 79259, 79273, 79279, 79283, 79301, 79309, 79319, 79333, 79337, 79349, 79357, 79367, 79379, 79393, 79397, 79399, 79411, 79423, 79427, 79433, 79451, 79481, 79493, 79531, 79537, 79549, 79559, 79561, 79579, 79589, 79601, 79609, 79613, 79621, 79627, 79631, 79633, 79657, 79669, 79687, 79691, 79693, 79697, 79699, 79757, 79769, 79777, 79801, 79811, 79813, 79817, 79823, 79829, 79841, 79843, 79847, 79861, 79867, 79873, 79889, 79901, 79903, 79907, 79939, 79943, 79967, 79973, 79979, 79987, 79997, 79999, 80021, 80039, 80051, 80071, 80077, 80107, 80111, 80141, 80147, 80149, 80153, 80167, 80173, 80177, 80191, 80207, 80209, 80221, 80231, 80233, 80239, 80251, 80263, 80273, 80279, 80287, 80309, 80317, 80329, 80341, 80347, 80363, 80369, 80387, 80407, 80429, 80447, 80449, 80471, 80473, 80489, 80491, 80513, 80527, 80537, 80557, 80567, 80599, 80603, 80611, 80621, 80627, 80629, 80651, 80657, 80669, 80671, 80677, 80681, 80683, 80687, 80701, 80713, 80737, 80747, 80749, 80761, 80777, 80779, 80783, 80789, 80803, 80809, 80819, 80831, 80833, 80849, 80863, 80897, 80909, 80911, 80917, 80923, 80929, 80933, 80953, 80963, 80989, 81001, 81013, 81017, 81019, 81023, 81031, 81041, 81043, 81047, 81049, 81071, 81077, 81083, 81097, 81101, 81119, 81131, 81157, 81163, 81173, 81181, 81197, 81199, 81203, 81223, 81233, 81239, 81281, 81283, 81293, 81299, 81307, 81331, 81343, 81349, 81353, 81359, 81371, 81373, 81401, 81409, 81421, 81439, 81457, 81463, 81509, 81517, 81527, 81533, 81547, 81551, 81553, 81559, 81563, 81569, 81611, 81619, 81629, 81637, 81647, 81649, 81667, 81671, 81677, 81689, 81701, 81703, 81707, 81727, 81737, 81749, 81761, 81769, 81773, 81799, 81817, 81839, 81847, 81853, 81869, 81883, 81899, 81901, 81919, 81929, 81931, 81937, 81943, 81953, 81967, 81971, 81973, 82003, 82007, 82009, 82013, 82021, 82031, 82037, 82039, 82051, 82067, 82073, 82129, 82139, 82141, 82153, 82163, 82171, 82183, 82189, 82193, 82207, 82217, 82219, 82223, 82231, 82237, 82241, 82261, 82267, 82279, 82301, 82307, 82339, 82349, 82351, 82361, 82373, 82387, 82393, 82421, 82457, 82463, 82469, 82471, 82483, 82487, 82493, 82499, 82507, 82529, 82531, 82549, 82559, 82561, 82567, 82571, 82591, 82601, 82609, 82613, 82619, 82633, 82651, 82657, 82699, 82721, 82723, 82727, 82729, 82757, 82759, 82763, 82781, 82787, 82793, 82799, 82811, 82813, 82837, 82847, 82883, 82889, 82891, 82903, 82913, 82939, 82963, 82981, 82997, 83003, 83009, 83023, 83047, 83059, 83063, 83071, 83077, 83089, 83093, 83101, 83117, 83137, 83177, 83203, 83207, 83219, 83221, 83227, 83231, 83233, 83243, 83257, 83267, 83269, 83273, 83299, 83311, 83339, 83341, 83357, 83383, 83389, 83399, 83401, 83407, 83417, 83423, 83431, 83437, 83443, 83449, 83459, 83471, 83477, 83497, 83537, 83557, 83561, 83563, 83579, 83591, 83597, 83609, 83617, 83621, 83639, 83641, 83653, 83663, 83689, 83701, 83717, 83719, 83737, 83761, 83773, 83777, 83791, 83813, 83833, 83843, 83857, 83869, 83873, 83891, 83903, 83911, 83921, 83933, 83939, 83969, 83983, 83987, 84011, 84017, 84047, 84053, 84059, 84061, 84067, 84089, 84121, 84127, 84131, 84137, 84143, 84163, 84179, 84181, 84191, 84199, 84211, 84221, 84223, 84229, 84239, 84247, 84263, 84299, 84307, 84313, 84317, 84319, 84347, 84349, 84377, 84389, 84391, 84401, 84407, 84421, 84431, 84437, 84443, 84449, 84457, 84463, 84467, 84481, 84499, 84503, 84509, 84521, 84523, 84533, 84551, 84559, 84589, 84629, 84631, 84649, 84653, 84659, 84673, 84691, 84697, 84701, 84713, 84719, 84731, 84737, 84751, 84761, 84787, 84793, 84809, 84811, 84827, 84857, 84859, 84869, 84871, 84913, 84919, 84947, 84961, 84967, 84977, 84979, 84991, 85009, 85021, 85027, 85037, 85049, 85061, 85081, 85087, 85091, 85093, 85103, 85109, 85121, 85133, 85147, 85159, 85193, 85199, 85201, 85213, 85223, 85229, 85237, 85243, 85247, 85259, 85297, 85303, 85313, 85331, 85333, 85361, 85363, 85369, 85381, 85411, 85427, 85429, 85439, 85447, 85451, 85453, 85469, 85487, 85513, 85517, 85523, 85531, 85549, 85571, 85577, 85597, 85601, 85607, 85619, 85621, 85627, 85639, 85643, 85661, 85667, 85669, 85691, 85703, 85711, 85717, 85733, 85751, 85781, 85793, 85817, 85819, 85829, 85831, 85837, 85843, 85847, 85853, 85889, 85903, 85909, 85931, 85933, 85991, 85999, 86011, 86017, 86027, 86029, 86069, 86077, 86083, 86111, 86113, 86117, 86131, 86137, 86143, 86161, 86171, 86179, 86183, 86197, 86201, 86209, 86239, 86243, 86249, 86257, 86263, 86269, 86287, 86291, 86293, 86297, 86311, 86323, 86341, 86351, 86353, 86357, 86369, 86371, 86381, 86389, 86399, 86413, 86423, 86441, 86453, 86461, 86467, 86477, 86491, 86501, 86509, 86531, 86533, 86539, 86561, 86573, 86579, 86587, 86599, 86627, 86629, 86677, 86689, 86693, 86711, 86719, 86729, 86743, 86753, 86767, 86771, 86783, 86813, 86837, 86843, 86851, 86857, 86861, 86869, 86923, 86927, 86929, 86939, 86951, 86959, 86969, 86981, 86993, 87011, 87013, 87037, 87041, 87049, 87071, 87083, 87103, 87107, 87119, 87121, 87133, 87149, 87151, 87179, 87181, 87187, 87211, 87221, 87223, 87251, 87253, 87257, 87277, 87281, 87293, 87299, 87313, 87317, 87323, 87337, 87359, 87383, 87403, 87407, 87421, 87427, 87433, 87443, 87473, 87481, 87491, 87509, 87511, 87517, 87523, 87539, 87541, 87547, 87553, 87557, 87559, 87583, 87587, 87589, 87613, 87623, 87629, 87631, 87641, 87643, 87649, 87671, 87679, 87683, 87691, 87697, 87701, 87719, 87721, 87739, 87743, 87751, 87767, 87793, 87797, 87803, 87811, 87833, 87853, 87869, 87877, 87881, 87887, 87911, 87917, 87931, 87943, 87959, 87961, 87973, 87977, 87991, 88001, 88003, 88007, 88019, 88037, 88069, 88079, 88093, 88117, 88129, 88169, 88177, 88211, 88223, 88237, 88241, 88259, 88261, 88289, 88301, 88321, 88327, 88337, 88339, 88379, 88397, 88411, 88423, 88427, 88463, 88469, 88471, 88493, 88499, 88513, 88523, 88547, 88589, 88591, 88607, 88609, 88643, 88651, 88657, 88661, 88663, 88667, 88681, 88721, 88729, 88741, 88747, 88771, 88789, 88793, 88799, 88801, 88807, 88811, 88813, 88817, 88819, 88843, 88853, 88861, 88867, 88873, 88883, 88897, 88903, 88919, 88937, 88951, 88969, 88993, 88997, 89003, 89009, 89017, 89021, 89041, 89051, 89057, 89069, 89071, 89083, 89087, 89101, 89107, 89113, 89119, 89123, 89137, 89153, 89189, 89203, 89209, 89213, 89227, 89231, 89237, 89261, 89269, 89273, 89293, 89303, 89317, 89329, 89363, 89371, 89381, 89387, 89393, 89399, 89413, 89417, 89431, 89443, 89449, 89459, 89477, 89491, 89501, 89513, 89519, 89521, 89527, 89533, 89561, 89563, 89567, 89591, 89597, 89599, 89603, 89611, 89627, 89633, 89653, 89657, 89659, 89669, 89671, 89681, 89689, 89753, 89759, 89767, 89779, 89783, 89797, 89809, 89819, 89821, 89833, 89839, 89849, 89867, 89891, 89897, 89899, 89909, 89917, 89923, 89939, 89959, 89963, 89977, 89983, 89989, 90001, 90007, 90011, 90017, 90019, 90023, 90031, 90053, 90059, 90067, 90071, 90073, 90089, 90107, 90121, 90127, 90149, 90163, 90173, 90187, 90191, 90197, 90199, 90203, 90217, 90227, 90239, 90247, 90263, 90271, 90281, 90289, 90313, 90353, 90359, 90371, 90373, 90379, 90397, 90401, 90403, 90407, 90437, 90439, 90469, 90473, 90481, 90499, 90511, 90523, 90527, 90529, 90533, 90547, 90583, 90599, 90617, 90619, 90631, 90641, 90647, 90659, 90677, 90679, 90697, 90703, 90709, 90731, 90749, 90787, 90793, 90803, 90821, 90823, 90833, 90841, 90847, 90863, 90887, 90901, 90907, 90911, 90917, 90931, 90947, 90971, 90977, 90989, 90997, 91009, 91019, 91033, 91079, 91081, 91097, 91099, 91121, 91127, 91129, 91139, 91141, 91151, 91153, 91159, 91163, 91183, 91193, 91199, 91229, 91237, 91243, 91249, 91253, 91283, 91291, 91297, 91303, 91309, 91331, 91367, 91369, 91373, 91381, 91387, 91393, 91397, 91411, 91423, 91433, 91453, 91457, 91459, 91463, 91493, 91499, 91513, 91529, 91541, 91571, 91573, 91577, 91583, 91591, 91621, 91631, 91639, 91673, 91691, 91703, 91711, 91733, 91753, 91757, 91771, 91781, 91801, 91807, 91811, 91813, 91823, 91837, 91841, 91867, 91873, 91909, 91921, 91939, 91943, 91951, 91957, 91961, 91967, 91969, 91997, 92003, 92009, 92033, 92041, 92051, 92077, 92083, 92107, 92111, 92119, 92143, 92153, 92173, 92177, 92179, 92189, 92203, 92219, 92221, 92227, 92233, 92237, 92243, 92251, 92269, 92297, 92311, 92317, 92333, 92347, 92353, 92357, 92363, 92369, 92377, 92381, 92383, 92387, 92399, 92401, 92413, 92419, 92431, 92459, 92461, 92467, 92479, 92489, 92503, 92507, 92551, 92557, 92567, 92569, 92581, 92593, 92623, 92627, 92639, 92641, 92647, 92657, 92669, 92671, 92681, 92683, 92693, 92699, 92707, 92717, 92723, 92737, 92753, 92761, 92767, 92779, 92789, 92791, 92801, 92809, 92821, 92831, 92849, 92857, 92861, 92863, 92867, 92893, 92899, 92921, 92927, 92941, 92951, 92957, 92959, 92987, 92993, 93001, 93047, 93053, 93059, 93077, 93083, 93089, 93097, 93103, 93113, 93131, 93133, 93139, 93151, 93169, 93179, 93187, 93199, 93229, 93239, 93241, 93251, 93253, 93257, 93263, 93281, 93283, 93287, 93307, 93319, 93323, 93329, 93337, 93371, 93377, 93383, 93407, 93419, 93427, 93463, 93479, 93481, 93487, 93491, 93493, 93497, 93503, 93523, 93529, 93553, 93557, 93559, 93563, 93581, 93601, 93607, 93629, 93637, 93683, 93701, 93703, 93719, 93739, 93761, 93763, 93787, 93809, 93811, 93827, 93851, 93871, 93887, 93889, 93893, 93901, 93911, 93913, 93923, 93937, 93941, 93949, 93967, 93971, 93979, 93983, 93997, 94007, 94009, 94033, 94049, 94057, 94063, 94079, 94099, 94109, 94111, 94117, 94121, 94151, 94153, 94169, 94201, 94207, 94219, 94229, 94253, 94261, 94273, 94291, 94307, 94309, 94321, 94327, 94331, 94343, 94349, 94351, 94379, 94397, 94399, 94421, 94427, 94433, 94439, 94441, 94447, 94463, 94477, 94483, 94513, 94529, 94531, 94541, 94543, 94547, 94559, 94561, 94573, 94583, 94597, 94603, 94613, 94621, 94649, 94651, 94687, 94693, 94709, 94723, 94727, 94747, 94771, 94777, 94781, 94789, 94793, 94811, 94819, 94823, 94837, 94841, 94847, 94849, 94873, 94889, 94903, 94907, 94933, 94949, 94951, 94961, 94993, 94999, 95003, 95009, 95021, 95027, 95063, 95071, 95083, 95087, 95089, 95093, 95101, 95107, 95111, 95131, 95143, 95153, 95177, 95189, 95191, 95203, 95213, 95219, 95231, 95233, 95239, 95257, 95261, 95267, 95273, 95279, 95287, 95311, 95317, 95327, 95339, 95369, 95383, 95393, 95401, 95413, 95419, 95429, 95441, 95443, 95461, 95467, 95471, 95479, 95483, 95507, 95527, 95531, 95539, 95549, 95561, 95569, 95581, 95597, 95603, 95617, 95621, 95629, 95633, 95651, 95701, 95707, 95713, 95717, 95723, 95731, 95737, 95747, 95773, 95783, 95789, 95791, 95801, 95803, 95813, 95819, 95857, 95869, 95873, 95881, 95891, 95911, 95917, 95923, 95929, 95947, 95957, 95959, 95971, 95987, 95989, 96001, 96013, 96017, 96043, 96053, 96059, 96079, 96097, 96137, 96149, 96157, 96167, 96179, 96181, 96199, 96211, 96221, 96223, 96233, 96259, 96263, 96269, 96281, 96289, 96293, 96323, 96329, 96331, 96337, 96353, 96377, 96401, 96419, 96431, 96443, 96451, 96457, 96461, 96469, 96479, 96487, 96493, 96497, 96517, 96527, 96553, 96557, 96581, 96587, 96589, 96601, 96643, 96661, 96667, 96671, 96697, 96703, 96731, 96737, 96739, 96749, 96757, 96763, 96769, 96779, 96787, 96797, 96799, 96821, 96823, 96827, 96847, 96851, 96857, 96893, 96907, 96911, 96931, 96953, 96959, 96973, 96979, 96989, 96997, 97001, 97003, 97007, 97021, 97039, 97073, 97081, 97103, 97117, 97127, 97151, 97157, 97159, 97169, 97171, 97177, 97187, 97213, 97231, 97241, 97259, 97283, 97301, 97303, 97327, 97367, 97369, 97373, 97379, 97381, 97387, 97397, 97423, 97429, 97441, 97453, 97459, 97463, 97499, 97501, 97511, 97523, 97547, 97549, 97553, 97561, 97571, 97577, 97579, 97583, 97607, 97609, 97613, 97649, 97651, 97673, 97687, 97711, 97729, 97771, 97777, 97787, 97789, 97813, 97829, 97841, 97843, 97847, 97849, 97859, 97861, 97871, 97879, 97883, 97919, 97927, 97931, 97943, 97961, 97967, 97973, 97987, 98009, 98011, 98017, 98041, 98047, 98057, 98081, 98101, 98123, 98129, 98143, 98179, 98207, 98213, 98221, 98227, 98251, 98257, 98269, 98297, 98299, 98317, 98321, 98323, 98327, 98347, 98369, 98377, 98387, 98389, 98407, 98411, 98419, 98429, 98443, 98453, 98459, 98467, 98473, 98479, 98491, 98507, 98519, 98533, 98543, 98561, 98563, 98573, 98597, 98621, 98627, 98639, 98641, 98663, 98669, 98689, 98711, 98713, 98717, 98729, 98731, 98737, 98773, 98779, 98801, 98807, 98809, 98837, 98849, 98867, 98869, 98873, 98887, 98893, 98897, 98899, 98909, 98911, 98927, 98929, 98939, 98947, 98953, 98963, 98981, 98993, 98999, 99013, 99017, 99023, 99041, 99053, 99079, 99083, 99089, 99103, 99109, 99119, 99131, 99133, 99137, 99139, 99149, 99173, 99181, 99191, 99223, 99233, 99241, 99251, 99257, 99259, 99277, 99289, 99317, 99347, 99349, 99367, 99371, 99377, 99391, 99397, 99401, 99409, 99431, 99439, 99469, 99487, 99497, 99523, 99527, 99529, 99551, 99559, 99563, 99571, 99577, 99581, 99607, 99611, 99623, 99643, 99661, 99667, 99679, 99689, 99707, 99709, 99713, 99719, 99721, 99733, 99761, 99767, 99787, 99793, 99809, 99817, 99823, 99829, 99833, 99839, 99859, 99871, 99877, 99881, 99901, 99907, 99923, 99929, 99961, 99971, 99989, 99991, 100003, 100019, 100043, 100049, 100057, 100069, 100103, 100109, 100129, 100151, 100153, 100169, 100183, 100189, 100193, 100207, 100213, 100237, 100267, 100271, 100279, 100291, 100297, 100313, 100333, 100343, 100357, 100361, 100363, 100379, 100391, 100393, 100403, 100411, 100417, 100447, 100459, 100469, 100483, 100493, 100501, 100511, 100517, 100519, 100523, 100537, 100547, 100549, 100559, 100591, 100609, 100613, 100621, 100649, 100669, 100673, 100693, 100699, 100703, 100733, 100741, 100747, 100769, 100787, 100799, 100801, 100811, 100823, 100829, 100847, 100853, 100907, 100913, 100927, 100931, 100937, 100943, 100957, 100981, 100987, 100999, 101009, 101021, 101027, 101051, 101063, 101081, 101089, 101107, 101111, 101113, 101117, 101119, 101141, 101149, 101159, 101161, 101173, 101183, 101197, 101203, 101207, 101209, 101221, 101267, 101273, 101279, 101281, 101287, 101293, 101323, 101333, 101341, 101347, 101359, 101363, 101377, 101383, 101399, 101411, 101419, 101429, 101449, 101467, 101477, 101483, 101489, 101501, 101503, 101513, 101527, 101531, 101533, 101537, 101561, 101573, 101581, 101599, 101603, 101611, 101627, 101641, 101653, 101663, 101681, 101693, 101701, 101719, 101723, 101737, 101741, 101747, 101749, 101771, 101789, 101797, 101807, 101833, 101837, 101839, 101863, 101869, 101873, 101879, 101891, 101917, 101921, 101929, 101939, 101957, 101963, 101977, 101987, 101999, 102001, 102013, 102019, 102023, 102031, 102043, 102059, 102061, 102071, 102077, 102079, 102101, 102103, 102107, 102121, 102139, 102149, 102161, 102181, 102191, 102197, 102199, 102203, 102217, 102229, 102233, 102241, 102251, 102253, 102259, 102293, 102299, 102301, 102317, 102329, 102337, 102359, 102367, 102397, 102407, 102409, 102433, 102437, 102451, 102461, 102481, 102497, 102499, 102503, 102523, 102533, 102539, 102547, 102551, 102559, 102563, 102587, 102593, 102607, 102611, 102643, 102647, 102653, 102667, 102673, 102677, 102679, 102701, 102761, 102763, 102769, 102793, 102797, 102811, 102829, 102841, 102859, 102871, 102877, 102881, 102911, 102913, 102929, 102931, 102953, 102967, 102983, 103001, 103007, 103043, 103049, 103067, 103069, 103079, 103087, 103091, 103093, 103099, 103123, 103141, 103171, 103177, 103183, 103217, 103231, 103237, 103289, 103291, 103307, 103319, 103333, 103349, 103357, 103387, 103391, 103393, 103399, 103409, 103421, 103423, 103451, 103457, 103471, 103483, 103511, 103529, 103549, 103553, 103561, 103567, 103573, 103577, 103583, 103591, 103613, 103619, 103643, 103651, 103657, 103669, 103681, 103687, 103699, 103703, 103723, 103769, 103787, 103801, 103811, 103813, 103837, 103841, 103843, 103867, 103889, 103903, 103913, 103919, 103951, 103963, 103967, 103969, 103979, 103981, 103991, 103993, 103997, 104003, 104009, 104021, 104033, 104047, 104053, 104059, 104087, 104089, 104107, 104113, 104119, 104123, 104147, 104149, 104161, 104173, 104179, 104183, 104207, 104231, 104233, 104239, 104243, 104281, 104287, 104297, 104309, 104311, 104323, 104327, 104347, 104369, 104381, 104383, 104393, 104399, 104417, 104459, 104471, 104473, 104479, 104491, 104513, 104527, 104537, 104543, 104549, 104551, 104561, 104579, 104593, 104597, 104623, 104639, 104651, 104659, 104677, 104681, 104683, 104693, 104701, 104707, 104711, 104717, 104723, 104729 }; } // namespace regina regina-4.95/engine/packet/CMakeLists.txt000644 000765 000024 00000001317 12236546232 020072 0ustar00babstaff000000 000000 # packet # Files to compile SET ( FILES ncontainer npacket npacketlistener npdf nscript ntext nxmlpacketreader nxmlpacketreaders ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} packet/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES ncontainer.h npacket.h npacketlistener.h npdf.h nscript.h ntext.h nxmlpacketreader.h nxmlpacketreaders.h nxmltreeresolver.h packetregistry.h packetregistry-impl.h packettype.h DESTINATION ${INCLUDEDIR}/packet COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/packet/ncontainer.h000644 000765 000024 00000007723 12236524106 017646 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/ncontainer.h * \brief Contains a packet whose entire life purpose is to contain * other packets. */ #ifndef __NCONTAINER_H #ifndef __DOXYGEN #define __NCONTAINER_H #endif #include "regina-core.h" #include "packet/npacket.h" namespace regina { class NXMLPacketReader; class NContainer; /** * \weakgroup packet * @{ */ /** * Stores information about the container packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NContainer Class; inline static const char* name() { return "Container"; } }; /** * A packet that simply contains other packets. Such * a packet contains no real data. */ class REGINA_API NContainer : public NPacket { REGINA_PACKET(NContainer, PACKET_CONTAINER) public: /** * Default constructor. */ NContainer(); virtual void writeTextShort(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; }; /*@}*/ // Inline functions for NContainer inline NContainer::NContainer() { } inline void NContainer::writeTextShort(std::ostream& o) const { o << "Container"; } inline bool NContainer::dependsOnParent() const { return false; } inline NPacket* NContainer::internalClonePacket(NPacket*) const { return new NContainer(); } inline void NContainer::writeXMLPacketData(std::ostream&) const { } } // namespace regina #endif regina-4.95/engine/packet/npacket.cpp000644 000765 000024 00000053553 12236524106 017470 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "engine.h" #include "packet/npacket.h" #include "packet/npacketlistener.h" #include "packet/nscript.h" #include "utilities/base64.h" #include "utilities/xmlutils.h" namespace regina { NPacket::~NPacket() { inDestructor = true; // Orphan this packet before doing anything else. // The destructor can lead to callbacks for packet listeners, which // might in turn involve tree traversal. It can't be good for // anyone to start querying packets whose destructors are already // being carried out. if (treeParent) makeOrphan(); // Destroy all descendants. // Note that the NPacket destructor now orphans the packet as well. while(firstTreeChild) delete firstTreeChild; // Fire a packet event and unregister all listeners. fireDestructionEvent(); } std::string NPacket::getFullName() const { return getHumanLabel() + " (" + getPacketTypeName() + ")"; } void NPacket::setPacketLabel(const std::string& newLabel) { fireEvent(&NPacketListener::packetToBeRenamed); packetLabel = newLabel; fireEvent(&NPacketListener::packetWasRenamed); } bool NPacket::listen(NPacketListener* listener) { if (! listeners.get()) listeners.reset(new std::set()); listener->packets.insert(this); return listeners->insert(listener).second; } bool NPacket::unlisten(NPacketListener* listener) { if (! listeners.get()) return false; listener->packets.erase(this); return listeners->erase(listener); } NPacket* NPacket::getTreeMatriarch() const { NPacket* p = const_cast(this); while (p->treeParent) p = p->treeParent; return p; } void NPacket::insertChildFirst(NPacket* child) { fireEvent(&NPacketListener::childToBeAdded, child); child->treeParent = this; child->prevTreeSibling = 0; child->nextTreeSibling = firstTreeChild; if (firstTreeChild) { firstTreeChild->prevTreeSibling = child; firstTreeChild = child; } else { firstTreeChild = child; lastTreeChild = child; } fireEvent(&NPacketListener::childWasAdded, child); } void NPacket::insertChildLast(NPacket* child) { fireEvent(&NPacketListener::childToBeAdded, child); child->treeParent = this; child->prevTreeSibling = lastTreeChild; child->nextTreeSibling = 0; if (lastTreeChild) { lastTreeChild->nextTreeSibling = child; lastTreeChild = child; } else { firstTreeChild = child; lastTreeChild = child; } fireEvent(&NPacketListener::childWasAdded, child); } void NPacket::insertChildAfter(NPacket* newChild, NPacket* prevChild) { fireEvent(&NPacketListener::childToBeAdded, newChild); if (prevChild == 0) insertChildFirst(newChild); else { newChild->treeParent = this; newChild->nextTreeSibling = prevChild->nextTreeSibling; newChild->prevTreeSibling = prevChild; prevChild->nextTreeSibling = newChild; if (newChild->nextTreeSibling) newChild->nextTreeSibling->prevTreeSibling = newChild; else lastTreeChild = newChild; } fireEvent(&NPacketListener::childWasAdded, newChild); } void NPacket::makeOrphan() { if (! treeParent) return; NPacket* oldParent = treeParent; oldParent->fireEvent(&NPacketListener::childToBeRemoved, this, inDestructor); if (treeParent->firstTreeChild == this) treeParent->firstTreeChild = nextTreeSibling; else prevTreeSibling->nextTreeSibling = nextTreeSibling; if (treeParent->lastTreeChild == this) treeParent->lastTreeChild = prevTreeSibling; else nextTreeSibling->prevTreeSibling = prevTreeSibling; treeParent = 0; oldParent->fireEvent(&NPacketListener::childWasRemoved, this, inDestructor); } void NPacket::reparent(NPacket* newParent, bool first) { if (treeParent) makeOrphan(); if (first) newParent->insertChildFirst(this); else newParent->insertChildLast(this); } void NPacket::moveUp(unsigned steps) { if (steps == 0 || ! prevTreeSibling) return; // This packet is not the first packet in the child list. treeParent->fireEvent(&NPacketListener::childrenToBeReordered); NPacket* prev = prevTreeSibling; while (prev && steps) { prev = prev->prevTreeSibling; steps--; } // Pull us out of the tree. if (nextTreeSibling) nextTreeSibling->prevTreeSibling = prevTreeSibling; else treeParent->lastTreeChild = prevTreeSibling; prevTreeSibling->nextTreeSibling = nextTreeSibling; // Reinsert ourselves into the tree. prevTreeSibling = prev; nextTreeSibling = (prev ? prev->nextTreeSibling : treeParent->firstTreeChild); nextTreeSibling->prevTreeSibling = this; if (prev) prev->nextTreeSibling = this; else treeParent->firstTreeChild = this; treeParent->fireEvent(&NPacketListener::childrenWereReordered); } void NPacket::moveDown(unsigned steps) { if (steps == 0 || ! nextTreeSibling) return; // This packet is not the last packet in the child list. treeParent->fireEvent(&NPacketListener::childrenToBeReordered); NPacket* next = nextTreeSibling; while (next && steps) { next = next->nextTreeSibling; steps--; } // Pull us out of the tree. if (prevTreeSibling) prevTreeSibling->nextTreeSibling = nextTreeSibling; else treeParent->firstTreeChild = nextTreeSibling; nextTreeSibling->prevTreeSibling = prevTreeSibling; // Reinsert ourselves into the tree. nextTreeSibling = next; prevTreeSibling = (next ? next->prevTreeSibling : treeParent->lastTreeChild); prevTreeSibling->nextTreeSibling = this; if (next) next->prevTreeSibling = this; else treeParent->lastTreeChild = this; treeParent->fireEvent(&NPacketListener::childrenWereReordered); } void NPacket::moveToFirst() { if (! prevTreeSibling) return; // This packet is not the first packet in the child list. treeParent->fireEvent(&NPacketListener::childrenToBeReordered); // Pull us out of the tree. if (nextTreeSibling) nextTreeSibling->prevTreeSibling = prevTreeSibling; else treeParent->lastTreeChild = prevTreeSibling; prevTreeSibling->nextTreeSibling = nextTreeSibling; // Reinsert ourselves into the tree. treeParent->firstTreeChild->prevTreeSibling = this; nextTreeSibling = treeParent->firstTreeChild; prevTreeSibling = 0; treeParent->firstTreeChild = this; treeParent->fireEvent(&NPacketListener::childrenWereReordered); } void NPacket::moveToLast() { if (! nextTreeSibling) return; // This packet is not the last packet in the child list. treeParent->fireEvent(&NPacketListener::childrenToBeReordered); // Pull us out of the tree. if (prevTreeSibling) prevTreeSibling->nextTreeSibling = nextTreeSibling; else treeParent->firstTreeChild = nextTreeSibling; nextTreeSibling->prevTreeSibling = prevTreeSibling; // Reinsert ourselves into the tree. treeParent->lastTreeChild->nextTreeSibling = this; prevTreeSibling = treeParent->lastTreeChild; nextTreeSibling = 0; treeParent->lastTreeChild = this; treeParent->fireEvent(&NPacketListener::childrenWereReordered); } void NPacket::sortChildren() { // Run through the packets from largest to smallest, moving each to // the beginning of the child list in turn. NPacket* endpoint = 0; NPacket* current; NPacket* largest; fireEvent(&NPacketListener::childrenToBeReordered); while (1) { // Put current at the beginning of the clump of yet-unsorted children. if (! endpoint) current = firstTreeChild; else current = endpoint->nextTreeSibling; if (! current) break; // Find the largest amongst the yet-unsorted children. largest = current; current = current->nextTreeSibling; while (current) { if (current->getPacketLabel() > largest->getPacketLabel()) largest = current; current = current->nextTreeSibling; } // Move the largest to the front of the list. if (firstTreeChild != largest) { // We know that largest has a previous sibling. largest->prevTreeSibling->nextTreeSibling = largest->nextTreeSibling; if (largest->nextTreeSibling) largest->nextTreeSibling->prevTreeSibling = largest->prevTreeSibling; else lastTreeChild = largest->prevTreeSibling; firstTreeChild->prevTreeSibling = largest; largest->nextTreeSibling = firstTreeChild; largest->prevTreeSibling = 0; firstTreeChild = largest; } if (! endpoint) endpoint = largest; } fireEvent(&NPacketListener::childrenWereReordered); } void NPacket::swapWithNextSibling() { if (! nextTreeSibling) return; treeParent->fireEvent(&NPacketListener::childrenToBeReordered); if (prevTreeSibling) prevTreeSibling->nextTreeSibling = nextTreeSibling; else treeParent->firstTreeChild = nextTreeSibling; if (nextTreeSibling->nextTreeSibling) nextTreeSibling->nextTreeSibling->prevTreeSibling = this; else treeParent->lastTreeChild = this; NPacket* other = nextTreeSibling; nextTreeSibling = other->nextTreeSibling; other->prevTreeSibling = prevTreeSibling; prevTreeSibling = other; other->nextTreeSibling = this; treeParent->fireEvent(&NPacketListener::childrenWereReordered); } NPacket* NPacket::nextTreePacket() { if (firstTreeChild) return firstTreeChild; if (nextTreeSibling) return nextTreeSibling; NPacket* tmp = treeParent; while (tmp) { if (tmp->nextTreeSibling) return tmp->nextTreeSibling; tmp = tmp->treeParent; } return 0; } const NPacket* NPacket::nextTreePacket() const { if (firstTreeChild) return firstTreeChild; if (nextTreeSibling) return nextTreeSibling; NPacket* tmp = treeParent; while (tmp) { if (tmp->nextTreeSibling) return tmp->nextTreeSibling; tmp = tmp->treeParent; } return 0; } NPacket* NPacket::firstTreePacket(const std::string& type) { if (getPacketTypeName() == type) return this; return nextTreePacket(type); } const NPacket* NPacket::firstTreePacket(const std::string& type) const { if (getPacketTypeName() == type) return this; return nextTreePacket(type); } NPacket* NPacket::nextTreePacket(const std::string& type) { NPacket* ans = nextTreePacket(); while (ans) { if (ans->getPacketTypeName() == type) return ans; ans = ans->nextTreePacket(); } return 0; } const NPacket* NPacket::nextTreePacket(const std::string& type) const { const NPacket* ans = nextTreePacket(); while (ans) { if (ans->getPacketTypeName() == type) return ans; ans = ans->nextTreePacket(); } return 0; } NPacket* NPacket::findPacketLabel(const std::string& label) { if (packetLabel == label) return this; NPacket* tmp = firstTreeChild; NPacket* ans; while (tmp) { ans = tmp->findPacketLabel(label); if (ans) return ans; tmp = tmp->nextTreeSibling; } return 0; } const NPacket* NPacket::findPacketLabel(const std::string& label) const { if (packetLabel == label) return this; NPacket* tmp = firstTreeChild; NPacket* ans; while (tmp) { ans = tmp->findPacketLabel(label); if (ans) return ans; tmp = tmp->nextTreeSibling; } return 0; } unsigned NPacket::levelsDownTo(const NPacket* descendant) const { unsigned levels = 0; while (descendant != this) { descendant = descendant->treeParent; levels++; } return levels; } bool NPacket::isGrandparentOf(const NPacket* descendant) const { while (descendant) { if (descendant == this) return true; descendant = descendant->treeParent; } return false; } unsigned long NPacket::getNumberOfChildren() const { unsigned long tot = 0; for (NPacket* tmp = firstTreeChild; tmp; tmp = tmp->nextTreeSibling) tot++; return tot; } unsigned long NPacket::getTotalTreeSize() const { unsigned long tot = 1; for (NPacket* tmp = firstTreeChild; tmp; tmp = tmp->nextTreeSibling) tot += tmp->getTotalTreeSize(); return tot; } bool NPacket::isPacketEditable() const { NPacket* tmp = firstTreeChild; while (tmp) { if (tmp->dependsOnParent()) return false; tmp = tmp->nextTreeSibling; } return true; } NPacket* NPacket::clone(bool cloneDescendants, bool end) const { if (treeParent == 0) return 0; NPacket* ans = internalClonePacket(treeParent); ans->setPacketLabel(packetLabel + " - clone"); if (end) treeParent->insertChildLast(ans); else treeParent->insertChildAfter(ans, const_cast(this)); if (cloneDescendants) internalCloneDescendants(ans); return ans; } void NPacket::internalCloneDescendants(NPacket* parent) const { NPacket* child = firstTreeChild; NPacket* clone; while (child) { clone = child->internalClonePacket(parent); clone->setPacketLabel(child->packetLabel + " - clone"); parent->insertChildLast(clone); child->internalCloneDescendants(clone); child = child->nextTreeSibling; } } std::string NPacket::makeUniqueLabel(const std::string& base) const { const NPacket* topLevel = this; while (topLevel->treeParent) topLevel = topLevel->treeParent; if (! topLevel->findPacketLabel(base)) return base; std::string ans; unsigned long extraInt = 2; while(1) { std::ostringstream out; out << ' ' << extraInt; ans = base + out.str(); if (! topLevel->findPacketLabel(ans)) return ans; else extraInt++; } return ""; } bool NPacket::makeUniqueLabels(NPacket* reference) { NPacket* tree[3]; if (reference) { tree[0] = reference; tree[1] = this; tree[2] = 0; } else { tree[0] = this; tree[1] = 0; } std::set labels; int whichTree; NPacket* p; std::string label, newLabel; unsigned long extraInt; bool changed = false; for (whichTree = 0; tree[whichTree]; whichTree++) for (p = tree[whichTree]; p; p = p->nextTreePacket()) { label = p->getPacketLabel(); if (! labels.insert(label).second) { extraInt = 1; do { extraInt++; std::ostringstream out; out << ' ' << extraInt; newLabel = label + out.str(); } while (! labels.insert(newLabel).second); p->setPacketLabel(newLabel); changed = true; } } return changed; } bool NPacket::addTag(const std::string& tag) { fireEvent(&NPacketListener::packetToBeRenamed); if (! tags.get()) tags.reset(new std::set()); bool ans = tags->insert(tag).second; fireEvent(&NPacketListener::packetWasRenamed); return ans; } bool NPacket::removeTag(const std::string& tag) { if (! tags.get()) return false; fireEvent(&NPacketListener::packetToBeRenamed); bool ans = tags->erase(tag); fireEvent(&NPacketListener::packetWasRenamed); return ans; } void NPacket::removeAllTags() { if (tags.get() && ! tags->empty()) { fireEvent(&NPacketListener::packetToBeRenamed); tags->clear(); fireEvent(&NPacketListener::packetWasRenamed); } } void NPacket::writeXMLFile(std::ostream& out) const { // Write the XML header. out << "\n"; // Write the regina data opening tag including engine version. out << "\n"; // Write the packet tree. writeXMLPacketTree(out); // Write the regina data closing tag. out << "\n"; } void NPacket::fireEvent(void (NPacketListener::*event)(NPacket*)) { if (listeners.get()) { std::set::const_iterator it = listeners->begin(); while (it != listeners->end()) ((*it++)->*event)(this); } } void NPacket::fireEvent(void (NPacketListener::*event)(NPacket*, NPacket*), NPacket* arg2) { if (listeners.get()) { std::set::const_iterator it = listeners->begin(); while (it != listeners->end()) ((*it++)->*event)(this, arg2); } } void NPacket::fireEvent(void (NPacketListener::*event)(NPacket*, NPacket*, bool), NPacket* arg2, bool arg3) { if (listeners.get()) { std::set::const_iterator it = listeners->begin(); while (it != listeners->end()) ((*it++)->*event)(this, arg2, arg3); } } void NPacket::fireDestructionEvent() { if (listeners.get()) { std::set::const_iterator it; NPacketListener* tmp; while (! listeners->empty()) { it = listeners->begin(); tmp = *it; // Unregister *before* we fire the event for each listener. // If we have a listener that deletes itself (or other listeners), // we don't want things to get nasty. listeners->erase(it); tmp->packets.erase(this); tmp->packetToBeDestroyed(this); } } } void NPacket::writeXMLPacketTree(std::ostream& out) const { using regina::xml::xmlEncodeSpecialChars; using regina::xml::xmlEncodeComment; // Write the packet opening tag including packet label and type. out << "::const_iterator it = listeners->begin(); it != listeners->end(); ++it) if (dynamic_cast(*it)) { out << "\tid=\"" << internalID() << "\"\n"; break; } out << "\tparent=\""; if (treeParent) out << xmlEncodeSpecialChars(treeParent->packetLabel); out << "\">\n"; // Write the internal packet data. writeXMLPacketData(out); // Write any packet tags. if (tags.get()) for (std::set::const_iterator it = tags->begin(); it != tags->end(); it++) out << " \n"; // Write the child packets. for (NPacket* p = firstTreeChild; p; p = p->nextTreeSibling) p->writeXMLPacketTree(out); // Write the packet closing tag. out << " \n"; } std::string NPacket::internalID() const { char ptrAsBytes[sizeof(NPacket*)]; *(reinterpret_cast(&ptrAsBytes)) = this; char* id = 0; base64Encode(ptrAsBytes, sizeof(NPacket*), &id); std::string ans = id; delete[] id; return ans; } } // namespace regina regina-4.95/engine/packet/npacket.h000644 000765 000024 00000146500 12236524106 017130 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/npacket.h * \brief Deals with packets of information that form the working data * objects. */ #ifndef __NPACKET_H #ifndef __DOXYGEN #define __NPACKET_H #endif #include #include #include #include "regina-core.h" #include "shareableobject.h" #include "packet/npacketlistener.h" #include "packet/packettype.h" #include "utilities/boostutils.h" namespace regina { class NPacketListener; class NXMLPacketReader; class NXMLTreeResolver; /** * \addtogroup packet Basic Packet Types * Packet administration and some basic packet types. * @{ */ /** * A template that stores information about a particular type of packet. * Much of this information is given in the form of compile-time constants * and types. * * To iterate through cases for a given value of PacketInfo that is not * known until runtime, see the various forPacket() routines defined in * packetregistry.h. * * At a bare minimum, each specialisation of this template must provide: * * - a typedef \a Class that represents the corresponding NPacket subclass; * - a static function name() that returns a C-style string giving the * human-readable name of the packet type. * * \ifacespython Not present. */ template struct PacketInfo; /** * Defines various constants, types and virtual functions for a * subclass of NPacket. * * Every subclass of NPacket \a must include REGINA_PACKET at the beginning * of the class definition. * * This macro provides the class with: * * - a compile-time enum constant \a packetType, which is equal to the * corresponding PacketType constant; * - declarations and implementations of the virtual functions * NPacket::getPacketType() and NPacket::getPacketTypeName(). * * @param class_ the name of this descendant class of NSurfaceFilter. * @param id the corresponding PacketType constant. */ #define REGINA_PACKET(class_, id) \ public: \ enum { packetType = id }; \ inline virtual PacketType getPacketType() const { \ return id; \ } \ inline virtual std::string getPacketTypeName() const { \ return PacketInfo::name(); \ } /** * Represents a packet of information that may be individually edited or * operated upon. Packets are stored in a dependency tree, * where child packets fit within the context of (or otherwise * cannot live without) parent packets. * * When deriving classes from NPacket: *
    *
  • A new value must be added to the PacketType enum in packettype.h * to represent the new packet type.
  • *
  • The file packetregistry-impl.h must be updated to reflect the new * packet type (the file itself contains instructions on how to do * this).
  • *
  • A corresponding specialisation of PacketInfo<> must be defined, * typically in the same header as the new packet class.
  • *
  • The macro REGINA_PACKET must be added to the beginning of the new * packet class. This will declare and define various constants, typedefs * and virtual functions (see the REGINA_PACKET macro documentation for * details). *
  • All abstract functions must be implemented, except for those * already provided by REGINA_PACKET.
  • *
  • A public function * static NXMLPacketReader* getXMLReader(NPacket* parent, * NXMLTreeResolver& resolver) * must be declared and implemented. See the notes for getXMLReader() * for further details.
  • *
  • Whenever the contents of the packet are changed, a local * ChangeEventSpan must be declared on the stack to notify listeners of * the change.
  • *
* * Note that external objects can listen for events on packets, such as * when packets are changed or about to be destroyed. See the * NPacketListener class notes for details. * * \todo \feature Provide automatic name selection/specification upon * child packet insertion. */ class REGINA_API NPacket : public ShareableObject { private: std::string packetLabel; /**< The unique label for this individual packet of information. */ NPacket* treeParent; /**< Parent packet in the tree structure (0 if none). */ NPacket* firstTreeChild; /**< First child packet in the tree structure (0 if none). */ NPacket* lastTreeChild; /**< Last child packet in the tree structure (0 if none). */ NPacket* prevTreeSibling; /**< Previous sibling packet in the tree structure (0 if none). */ NPacket* nextTreeSibling; /**< Next sibling packet in the tree structure (0 if none). */ std::auto_ptr > tags; /**< The set of all tags associated with this packet. */ std::auto_ptr > listeners; /**< All objects listening for events on this packet. */ unsigned changeEventSpans; /**< The number of change event spans currently registered. Change events will only be fired when this count is zero. */ bool inDestructor; /**< Have we entered the packet destructor? */ public: /** * \name Constructors and Destructors */ /*@{*/ /** * Constructor that inserts the new packet into the * overall tree structure. The new packet will be inserted as * the last child of the given parent, and will be initialised * with no children of its own. * * Note that NPacket is an abstract class and cannot be * instantiated directly. * * \ifacespython Not present. * * @param parent the parent beneath which to insert this packet, * or 0 if this packet is to be the matriarch of a new tree. */ NPacket(NPacket* parent = 0); /** * Destructor that also orphans this packet and destroys * all of its descendants. */ virtual ~NPacket(); /*@}*/ /** * \name Packet Identification */ /*@{*/ /** * Returns the unique integer ID representing this type of packet. * This is the same for all packets of this class. * * @return the packet type ID. */ virtual PacketType getPacketType() const = 0; /** * Returns an English name for this type of packet. * An example is \c NTriangulation. * This is the same for all packets of this class. * * @return the packet type name. */ virtual std::string getPacketTypeName() const = 0; /** * Returns the label associated with this individual packet. * An example is \c MyTriangulation. * Each individual packet in the overall tree structure must * have a unique label. * * @return this individual packet's label. */ const std::string& getPacketLabel() const; /** * Returns the label associated with this individual packet, * adjusted if necessary for human-readable output. * * In particular, if the packet has no label assigned then this * routine will return "(no label)", not the empty string. * * \warning The method by which this routine adjusts packet labels * is subject to change in future versions of Regina. * * @return this individual packet's label. */ std::string getHumanLabel() const; /** * Sets the label associated with this individual packet. * * \pre No other packet in the overall tree * structure has the same label. * * @param newLabel the new label to give this packet. */ void setPacketLabel(const std::string& newLabel); /** * Returns a descriptive text string for the packet. * The string is of the form label (packet-type). * * The packet label will be adjusted for human-readable output * according to the behaviour of getHumanLabel(). * * @return the descriptive text string. */ std::string getFullName() const; /** * Returns a new label that cannot be found anywhere in the * entire tree structure. This packet need not be the tree * matriarch; this routine will search the entire tree to which * this packet belongs. * * The new label will consist of the given base, possibly * followed by a space and a number. * * \deprecated This routine is deprecated, since (as of Regina 4.95) * packet labels in a data file are no longer required to be distinct. * * @param base a string upon which the new label will be based. * @return a new unique label. */ std::string makeUniqueLabel(const std::string& base) const; /** * Ensures that all packet labels in both this and the given * packet tree combined are distinct. If two packets have the * same label, one will be renamed by adding a space and a number. * * Packets in the given packet tree will be given priority over * the labels; that is, if a packet in this tree has the same * label as a packet in the given tree, it will be the packet in * this tree that is renamed. * * The given packet tree may be \c null, in which case only this * tree will be examined. * * \deprecated This routine is deprecated, since (as of Regina 4.95) * packet labels in a data file are no longer required to be distinct. * * \pre This and the given packet belong to different packet * trees, and are each matriarchs in their respective trees. * * @param reference the packet tree with which to compare this * tree. * @return \c true if and only if any of the packets were * relabelled. */ bool makeUniqueLabels(NPacket* reference); /*@}*/ /** * \name Tags */ /*@{*/ /** * Determines whether this packet has the given associated tag. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. * * @param tag the tag to search for. * @return \c true if the given tag is found, \c false otherwise. */ bool hasTag(const std::string& tag) const; /** * Determines whether this packet has any associated tags at all. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. * * @return \c true if this packet has any tags, \c false otherwise. */ bool hasTags() const; /** * Associates the given tag with this packet. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. * * \pre The given tag is not the empty string. * * @param tag the tag to add. * @return \c true if the given tag was successfully added, * or \c false if the given tag was already present beforehand. */ bool addTag(const std::string& tag); /** * Removes the association of the given tag with this packet. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. * * @param tag the tag to remove. * @return \c true if the given tag was removed, or \c false if the * given tag was not actually associated with this packet. */ bool removeTag(const std::string& tag); /** * Removes all associated tags from this packet. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. */ void removeAllTags(); /** * Returns the set of all tags associated with this packet. * * Each packet can have an arbitrary set of string tags associated * with it. The tags are not used by this calculation engine; the * feature is provided for whatever use a developer or user chooses * to make of it. * * Tags are case-sensitive. Tags associated with a single packet * must be distinct, i.e., a particular tag cannot be associated * more than once with the same packet. * * \ifacespython This routine returns a python list of strings. * * @return the set of all tags associated with this packet. */ const std::set& getTags() const; /*@}*/ /** * \name Event Handling */ /*@{*/ /** * Registers the given packet listener to listen for events on * this packet. See the NPacketListener class notes for * details. * * \ifacespython Not present. * * @param listener the listener to register. * @return \c true if the given listener was successfully registered, * or \c false if the given listener was already registered * beforehand. */ bool listen(NPacketListener* listener); /** * Determines whether the given packet listener is currently * listening for events on this packet. See the NPacketListener * class notes for details. * * \ifacespython Not present. * * @param listener the listener to search for. * @return \c true if the given listener is currently registered * with this packet, or \c false otherwise. */ bool isListening(NPacketListener* listener); /** * Unregisters the given packet listener so that it no longer * listens for events on this packet. See the NPacketListener * class notes for details. * * \ifacespython Not present. * * @param listener the listener to unregister. * @return \c true if the given listener was successfully unregistered, * or \c false if the given listener was not registered in the * first place. */ bool unlisten(NPacketListener* listener); /*@}*/ /** * \name Tree Queries */ /*@{*/ /** * Determines the parent packet in the tree structure. * * This routine takes small constant time. * * @return the parent packet, or 0 if there is none. */ NPacket* getTreeParent() const; /** * Determines the first child of this packet in the tree * structure. * * This routine takes small constant time. * * @return the first child packet, or 0 if there is none. */ NPacket* getFirstTreeChild() const; /** * Determines the last child of this packet in the tree * structure. * * This routine takes small constant time. * * @return the last child packet, or 0 if there is none. */ NPacket* getLastTreeChild() const; /** * Determines the next sibling of this packet in the tree * structure. This is the child of the parent that follows this * packet. * * This routine takes small constant time. * * @return the next sibling of this packet, or 0 if there is * none. */ NPacket* getNextTreeSibling() const; /** * Determines the previous sibling of this packet in the tree * structure. This is the child of the parent that precedes * this packet. * * This routine takes small constant time. * * @return the previous sibling of this packet, or 0 if there is * none. */ NPacket* getPrevTreeSibling() const; /** * Determines the matriarch (the root) of the tree to which this * packet belongs. * * @return the matriarch of the packet tree. */ NPacket* getTreeMatriarch() const; /** * Counts the number of levels between this packet and its given * descendant in the tree structure. If \c descendant is this * packet, the number of levels is zero. * * \pre This packet is equal to \c descendant, or * can be obtained from \c descendant using only child-to-parent * steps. * * @param descendant the packet whose relationship with this * packet we are examining. * @return the number of levels difference. */ unsigned levelsDownTo(const NPacket* descendant) const; /** * Counts the number of levels between this packet and its given * ancestor in the tree structure. If \c ancestor is this * packet, the number of levels is zero. * * \pre This packet is equal to \c ancestor, or * can be obtained from \c ancestor using only parent-to-child * steps. * * @param ancestor the packet whose relationship with this * packet we are examining. * @return the number of levels difference. */ unsigned levelsUpTo(const NPacket* ancestor) const; /** * Determines if this packet is equal to or an ancestor of * the given packet in the tree structure. * * @param descendant the other packet whose relationships we are * examining. * @return \c true if and only if this packet is equal to or an * ancestor of \c descendant. */ bool isGrandparentOf(const NPacket* descendant) const; /** * Returns the number of immediate children of this packet. * Grandchildren and so on are not counted. * * @return the number of immediate children. */ unsigned long getNumberOfChildren() const; /** * Returns the total number of descendants of this packet. This * includes children, grandchildren and so on. This packet is not * included in the count. * * @return the total number of descendants. */ unsigned long getNumberOfDescendants() const; /** * Determines the total number of packets in the tree or subtree * for which this packet is matriarch. This packet is included * in the count. * * @return the total tree or subtree size. */ unsigned long getTotalTreeSize() const; /*@}*/ /** * \name Tree Manipulation */ /*@{*/ /** * Inserts the given packet as the first child of this packet. * * This routine takes small constant time. * * \pre The given child has no parent packet. * \pre This packet is not a descendant of the given child. * * \ifacespython Since this packet takes ownership of the given * child packet, the python object containing the given child * packet becomes a null object and should no longer be used. * See reparent() for a way of avoiding these problems in some cases. * * @param child the child to insert. */ void insertChildFirst(NPacket* child); /** * Inserts the given packet as the last child of this packet. * * This routine takes small constant time. * * \pre The given child has no parent packet. * \pre This packet is not a descendant of the given child. * * \ifacespython Since this packet takes ownership of the given * child packet, the python object containing the given child * packet becomes a null object and should no longer be used. * See reparent() for a way of avoiding these problems in some cases. * * @param child the child to insert. */ void insertChildLast(NPacket* child); /** * Inserts the given packet as a child of this packet at the * given location in this packet's child list. * * This routine takes small constant time. * * \pre Parameter \a newChild has no parent packet. * \pre Parameter \a prevChild is already a child of this packet. * \pre This packet is not a descendant of \a newChild. * * \ifacespython Since this packet takes ownership of the given * child packet, the python object containing the given child * packet becomes a null object and should no longer be used. * See reparent() for a way of avoiding these problems in some cases. * * @param newChild the child to insert. * @param prevChild the preexisting child of this packet after * which \a newChild will be inserted, or 0 if \a newChild * is to be the first child of this packet. */ void insertChildAfter(NPacket* newChild, NPacket* prevChild); /** * Cuts this packet away from its parent in the tree structure * and instead makes it matriarch of its own tree. The tree * information for both this packet and its parent will be * updated. * * This routine takes small constant time. * * \pre This packet has a parent. * \pre This packet does not depend on its parent; see * dependsOnParent() for details. * * \ifacespython As of Regina 4.6.1, this routine returns the packet * itself, and the ownership of this packet becomes the responsibility * of whoever takes this return value. In particular, if you call * makeOrphan() and ignore the return value then the entire * packet subtree is automatically destroyed. The reason for * this behaviour is to avoid memory leaks where subtrees are * orphaned and then silently forgotten. */ void makeOrphan(); /** * Cuts this packet away from its parent in the tree structure, * and inserts it as a child of the given packet instead. * * This routine is essentially a combination of makeOrphan() * followed by either insertChildFirst() or insertChildLast(). * * This routine takes small constant time. It is safe to use * regardless of whether this packet has a parent or not. * * \pre This packet does not depend on its parent; see * dependsOnParent() for details. * \pre The given parent is not a descendant of this packet. * * \ifacespython This routine is much simpler than combinations of * makeOrphan() and insertChildFirst() / insertChildLast(), since * there are no unpleasant ownership issues to deal with. * However, if this packet currently has no parent then the ownership * issues are unavoidable; in this case reparent() will do nothing, * and one of the insertChild...() routines must be used instead. * * @param newParent the new parent of this packet, i.e., the * packet beneath which this packet will be inserted. * @param first \c true if this packet should be inserted as the * first child of the given parent, or \c false (the default) if * it should be inserted as the last child. */ void reparent(NPacket* newParent, bool first = false); /** * Swaps this packet with its next sibling in the sequence of * children beneath their common parent packet. Calling this * routine is equivalent to calling moveDown(). * * This routine takes small constant time. * * If this packet has no next sibling then this routine does * nothing. */ void swapWithNextSibling(); /** * Moves this packet the given number of steps towards the * beginning of its sibling list. If the number of steps is * larger than the greatest possible movement, the packet will * be moved to the very beginning of its sibling list. * * This routine takes time proportional to the number of steps. * * \pre The given number of steps is strictly positive. */ void moveUp(unsigned steps = 1); /** * Moves this packet the given number of steps towards the * end of its sibling list. If the number of steps is * larger than the greatest possible movement, the packet will * be moved to the very end of its sibling list. * * This routine takes time proportional to the number of steps. * * \pre The given number of steps is strictly positive. */ void moveDown(unsigned steps = 1); /** * Moves this packet to be the first in its sibling list. * * This routine takes small constant time. */ void moveToFirst(); /** * Moves this packet to be the last in its sibling list. * * This routine takes small constant time. */ void moveToLast(); /** * Sorts the immediate children of this packet according to * their packet labels. Note that this routine is not * recursive (for instance, grandchildren will not be sorted * within each child packet). * * This routine takes quadratic time in the number of * immediate children (and it's slow quadratic at that). */ void sortChildren(); /*@}*/ /** * \name Searching and Iterating */ /*@{*/ /** * Finds the next packet after this in a complete depth-first * iteration of the entire tree structure to which this packet * belongs. Note that this packet need not be the tree * matriarch. * * A parent packet is always reached before its children. The * tree matriarch will be the first packet visited in a complete * depth-first iteration. * * @return the next packet, or 0 if this is the last packet in * such an iteration. */ NPacket* nextTreePacket(); /** * Finds the next packet after this in a complete depth-first * iteration of the entire tree structure to which this packet * belongs. Note that this packet need not be the tree * matriarch. * * A parent packet is always reached before its children. The * tree matriarch will be the first packet visited in a complete * depth-first iteration. * * @return the next packet, or 0 if this is the last packet in * such an iteration. */ const NPacket* nextTreePacket() const; /** * Finds the first packet of the requested type in a complete * depth-first iteration of the tree structure. * Note that this packet must be the matriarch of the * entire tree. * * A parent packet is always reached before its children. The * tree matriarch will be the first packet visited in a complete * depth-first iteration. * * @param type the type of packet to search for, as returned by * getPacketTypeName(). Note that string comparisons are case * sensitive. * @return the first such packet, or 0 if there are no packets of * the requested type. */ NPacket* firstTreePacket(const std::string& type); /** * Finds the first packet of the requested type in a complete * depth-first iteration of the tree structure. * Note that this packet must be the matriarch of the * entire tree. * * A parent packet is always reached before its children. The * tree matriarch will be the first packet visited in a complete * depth-first iteration. * * @param type the type of packet to search for, as returned by * getPacketTypeName(). Note that string comparisons are case * sensitive. * @return the first such packet, or 0 if there are no packets of * the requested type. */ const NPacket* firstTreePacket(const std::string& type) const; /** * Finds the next packet after this of the requested type in a * complete depth-first iteration of the entire tree structure. * Note that this packet need not be the tree matriarch. * The order of tree searching is described in * firstTreePacket(). * * @param type the type of packet to search for, as returned by * getPacketTypeName(). Note that string comparisons are case * sensitive. * @return the next such packet, or 0 if this is the last packet * of the requested type in such an iteration. */ NPacket* nextTreePacket(const std::string& type); /** * Finds the next packet after this of the requested type in a * complete depth-first iteration of the entire tree structure. * Note that this packet need not be the tree matriarch. * The order of tree searching is described in * firstTreePacket(). * * @param type the type of packet to search for, as returned by * getPacketTypeName(). Note that string comparisons are case * sensitive. * @return the next such packet, or 0 if this is the last packet * of the requested type in such an iteration. */ const NPacket* nextTreePacket(const std::string& type) const; /** * Finds the packet with the requested label in the tree or * subtree for which this packet is matriarch. Note that label * comparisons are case sensitive. * * @param label the label to search for. * @return the packet with the requested label, or 0 if there is * no such packet. */ NPacket* findPacketLabel(const std::string& label); /** * Finds the packet with the requested label in the tree or * subtree for which this packet is matriarch. Note that label * comparisons are case sensitive. * * @param label the label to search for. * @return the packet with the requested label, or 0 if there is * no such packet. */ const NPacket* findPacketLabel(const std::string& label) const; /*@}*/ /** * \name Packet Dependencies */ /*@{*/ /** * Determines if this packet depends upon its parent. * This is true if the parent cannot be altered without * invalidating or otherwise upsetting this packet. * * @return \c true if and only if this packet depends on * its parent. */ virtual bool dependsOnParent() const = 0; /** * Determines whether this packet can be altered without * invalidating or otherwise upsetting any of its immediate * children. Descendants further down the packet tree are not * (and should not need to be) considered. * * @return \c true if and only if this packet may be edited. */ bool isPacketEditable() const; /*@}*/ /** * \name Cloning */ /*@{*/ /** * Clones this packet (and possibly its descendants), assigns to it * a suitable unused label and * inserts the clone into the tree as a sibling of this packet. * * Note that any string tags associated with this packet will * \e not be cloned. * * If this packet has no parent in the tree structure, no clone * will be created and 0 will be returned. * * @param cloneDescendants \c true if the descendants of this * packet should also be cloned and inserted as descendants of * the new packet. If this is passed as \c false (the default), * only this packet will be cloned. * @param end \c true if the new packet should be inserted at * the end of the parent's list of children (the default), or * \c false if the new packet should be inserted as the sibling * immediately after this packet. * @return the newly inserted packet, or 0 if this packet has no * parent. */ NPacket* clone(bool cloneDescendants = false, bool end = true) const; /*@}*/ /** * \name File I/O */ /*@{*/ /** * Writes a complete XML file containing the subtree with this * packet as matriarch. This is the preferred way of writing * a packet tree to file. * * The output from this routine cannot be used as a piece of an * XML file; it must be the entire XML file. For a piece of an * XML file, see routine writeXMLPacketTree() instead. * * For a handy wrapper to this routine that handles file I/O and * compression, see regina::writeXMLFile(). * * \pre This packet does not depend upon its parent. * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ void writeXMLFile(std::ostream& out) const; /** * Returns a unique string ID that identifies this packet. * * The user has no control over this ID, and it is not human * readable. It is guaranteed to remain fixed throughout * the lifetime of the program for a given packet, and it is * guaranteed not to clash with the ID of any other packet. * * If you change the contents of a packet, its ID will not change. * * If you clone a packet, the new clone will receive a different ID. * If you save and then load a packet to/from file, the ID will change. * These behaviours are necessary to ensure that IDs remain unique * (since, for instance, you could load several copies of the same * data file into memory simultaneously). * * The ID is implemented as an encoding of the underlying C++ pointer. * This encoding is subject to change in later versions of Regina. * * @return a unique ID that identifies this packet. */ std::string internalID() const; /*@}*/ /** * Returns a newly created XML element reader that will read the * contents of a single XML packet element. You may assume that * the packet to be read is of the same type as the class in which * you are implementing this routine. * * The XML element reader should read exactly what * writeXMLPacketData() writes, and vice versa. * * \a parent represents the packet which will become the new * packet's parent in the tree structure, and may be assumed to * have already been read from the file. This information is * for reference only, and does not need to be used. The XML * element reader can either insert or not insert the new packet * beneath \a parent in the tree structure as it pleases. Note * however that \a parent will be 0 if the new packet is to * become a tree matriarch. * * If the new packet needs to store pointers to other packets that * might not have been read yet (such as a script packet that * needs pointers to its variables), then it should queue a new * NXMLTreeResolutionTask to the given NXMLTreeResolver. After the * complete data file has been read, NXMLTreeResolver::resolve() * will run all of its queued tasks, at which point the new packet can * resolve any dangling references. * * This routine is not actually provided for NPacket itself, but * must be declared and implemented for every packet subclass that * will be instantiated. * * \ifacespython Not present. * * @param parent the packet which will become the new packet's * parent in the tree structure, or 0 if the new packet is to be * tree matriarch. * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. * @return the newly created XML element reader. */ #ifdef __DOXYGEN static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); #endif /** * An object that facilitates firing packetToBeChanged() and * packetWasChanged() events. * * Objects of this type should be created on the stack before * data within a packet is changed. On creation, this object * will fire a NPacketListener::packetToBeChanged() event to all * registered listeners. On destruction (i.e., when the object * goes out of scope), it will fire a * NPacketListener::packetWasChanged() event. * * It may be the case that several objects of this type all * exist at the same time for the same packet. In this case, only * the outermost object will fire events; that is, only the first * object to be constructed will fire * NPacketListener::packetToBeChanged(), and only the last * object to be destroyed will fire * NPacketListener::packetWasChanged(). This is because the * "inner" ChangeEventSpan objects earlier represent smaller events * that are part of a larger suite of changes. * * If you are writing code that makes a large number of changes * to a packet, it is highly recommended that you declare a * ChangeEventSpan at the beginning of your code. This will ensure * that listeners only receive one pair of events for the * entire change set, instead of many events representing each * individual modification. */ class ChangeEventSpan : public regina::boost::noncopyable { private: NPacket* packet_; /**< The packet for which change events are fired. */ public: /** * Creates a new change event object for the given * packet. * * If this is the only ChangeEventSpan currently in existence * for the given packet, this constructor will call * NPacketListener::packetToBeChanged() for all * registered listeners for the given packet. * * @param packet the packet whose data is about to change. */ ChangeEventSpan(NPacket* packet); /** * Destroys this change event object. * * If this is the only ChangeEventSpan currently in existence * for the given packet, this destructor will call * NPacketListener::packetWasChanged() for all * registered listeners for the given packet. */ ~ChangeEventSpan(); }; /** * A deprecated typedef for ChangeEventSpan. * * \deprecated ChangeEventSpan is now the correct way to fire a * "packet changed" event. The class ChangeEventSpan is similar * to the old ChangeEventBlock except that it fires both * NPacketListener::packetToBeChanged() and * NPacketListener::packetWasChanged() (on construction and * destruction respectively), and the old boolean argument * \a fireOnDestruction is gone (events are now fired always). */ typedef ChangeEventSpan ChangeEventBlock; protected: /** * Makes a newly allocated copy of this packet. * This routine should not insert the new packet into the * tree structure, clone the packet's associated tags or give the * packet a label. It should also not clone any descendants of * this packet. * * You may assume that the new packet will eventually be * inserted into the tree beneath either the same parent as this * packet or a clone of that parent. * * @param parent the parent beneath which the new packet will * eventually be inserted. * @return the newly allocated packet. */ virtual NPacket* internalClonePacket(NPacket* parent) const = 0; /** * Writes a chunk of XML containing the subtree with this packet * as matriarch. This is the preferred way of writing a packet * tree to file. * * The output from this routine is only a piece of XML; it * should not be used as a complete XML file. For a complete * XML file, see routine writeXMLFile() instead. * * @param out the output stream to which the XML should be written. */ void writeXMLPacketTree(std::ostream& out) const; /** * Writes a chunk of XML containing the data for this packet * only. * * You may assume that the packet opening tag (including * the packet type and label) has already been written, and that * all child packets followed by the corresponding packet closing * tag will be written immediately after this routine is called. * This routine need only write the internal data stored in * this specific packet. * * @param out the output stream to which the XML should be written. */ virtual void writeXMLPacketData(std::ostream& out) const = 0; private: /** * Clones the descendants of this packet and inserts them as * descendants of the given parent. The entire descendant tree * will be cloned recursively, and suitable labels will be * assigned to the new clones. * * \pre The given parent is a clone of this packet. * * @param parent the parent beneath which the descendant clones * will be inserted. */ void internalCloneDescendants(NPacket* parent) const; /** * Calls the given NPacketListener event for all registered * packet listeners. The first argument to the event function * will be this packet. * * Calling this routine is better than iterating through listeners * manually, since it behaves correctly even if listeners unregister * themselves as they handle the event. * * @param event the member function of NPacketListener to be called * for each listener. */ void fireEvent(void (NPacketListener::*event)(NPacket*)); /** * Calls the given NPacketListener event for all registered * packet listeners. The first argument to the event function * will be this packet. * * Calling this routine is better than iterating through listeners * manually, since it behaves correctly even if listeners unregister * themselves as they handle the event. * * @param event the member function of NPacketListener to be called * for each listener. * @param arg2 the second argument to pass to the event function. */ void fireEvent(void (NPacketListener::*event)(NPacket*, NPacket*), NPacket* arg2); /** * Calls the given NPacketListener event for all registered * packet listeners. The first argument to the event function * will be this packet * * Calling this routine is better than iterating through listeners * manually, since it behaves correctly even if listeners unregister * themselves as they handle the event. * * @param event the member function of NPacketListener to be called * for each listener. * @param arg2 the second argument to pass to the event function. * @param arg3 the third argument to pass to the event function. */ void fireEvent(void (NPacketListener::*event)(NPacket*, NPacket*, bool), NPacket* arg2, bool arg3); /** * Calls NPacketListener::packetToBeDestroyed() for all registered * packet listeners. * * This routine unregisters each listener just before it calls * packetToBeDestroyed() for that listener. * * Calling this routine is better than iterating through listeners * manually, since it behaves correctly even if listeners unregister * themselves or even destroy themselves and/or other listeners as * they handle the event. */ void fireDestructionEvent(); }; /*@}*/ // Inline functions for NPacket inline NPacket::NPacket(NPacket* parent) : firstTreeChild(0), lastTreeChild(0), prevTreeSibling(0), nextTreeSibling(0), changeEventSpans(0), inDestructor(false) { if (parent) parent->insertChildLast(this); else treeParent = 0; } inline const std::string& NPacket::getPacketLabel() const { return packetLabel; } inline std::string NPacket::getHumanLabel() const { if (packetLabel.empty()) return "(no label)"; return packetLabel; } inline bool NPacket::hasTag(const std::string& tag) const { if (! tags.get()) return false; return tags->count(tag); } inline bool NPacket::hasTags() const { if (! tags.get()) return false; return (! tags->empty()); } inline const std::set& NPacket::getTags() const { if (! tags.get()) const_cast(this)->tags.reset(new std::set()); return *tags; } inline bool NPacket::isListening(NPacketListener* listener) { if (! listeners.get()) return false; return listeners->count(listener); } inline NPacket* NPacket::getTreeParent() const { return treeParent; } inline NPacket* NPacket::getFirstTreeChild() const { return firstTreeChild; } inline NPacket* NPacket::getLastTreeChild() const { return lastTreeChild; } inline NPacket* NPacket::getPrevTreeSibling() const { return prevTreeSibling; } inline NPacket* NPacket::getNextTreeSibling() const { return nextTreeSibling; } inline unsigned NPacket::levelsUpTo(const NPacket* ancestor) const { return ancestor->levelsDownTo(this); } inline unsigned long NPacket::getNumberOfDescendants() const { return getTotalTreeSize() - 1; } inline NPacket::ChangeEventSpan::ChangeEventSpan(NPacket* packet) : packet_(packet) { if (! packet_->changeEventSpans) packet_->fireEvent(&NPacketListener::packetToBeChanged); packet_->changeEventSpans++; } inline NPacket::ChangeEventSpan::~ChangeEventSpan() { packet_->changeEventSpans--; if (! packet_->changeEventSpans) packet_->fireEvent(&NPacketListener::packetWasChanged); } } // namespace regina #endif regina-4.95/engine/packet/npacketlistener.cpp000644 000765 000024 00000005751 12234011536 021227 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "packet/npacket.h" #include "packet/npacketlistener.h" namespace regina { NPacketListener::~NPacketListener() { unregisterFromAllPackets(); } void NPacketListener::unregisterFromAllPackets() { std::set::iterator it, next; it = packets.begin(); next = it; while (it != packets.end()) { // INV: next == it. // Step forwards before we actually deregister (*it), since // the deregistration will remove (*it) from the set and // invalidate the iterator. next++; // This deregistration removes (*it) from the set, but other // iterators (i.e., next) are not invalidated. (*it)->unlisten(this); it = next; } } } // namespace regina regina-4.95/engine/packet/npacketlistener.h000644 000765 000024 00000031547 12234011536 020676 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/npacketlistener.h * \brief Deals with objects that can listen for packet events. */ #ifndef __NPACKETLISTENER_H #ifndef __DOXYGEN #define __NPACKETLISTENER_H #endif #include #include "regina-core.h" namespace regina { class NPacket; /** * \weakgroup packet * @{ */ /** * An object that can be registered to listen for packet events. * * A packet listener can be registered to listen for events on a * packet by calling NPacket::listen(). * * Each time that one of the events listed in this class occurs, * the packet will call the appropriate routine for all registered * packet listeners. * * These events come in future/past pairs: packetToBeChanged() and * packetWasChanged(), childToBeAdded() and childWasAdded(), and so on. * These event pairs are mutually exclusive: any event will * cause at most one pair of routines to be called for each listener. For * instance, if a packet is renamed then packetToBeRenamed() and * packetWasRenamed() will be called but packetToBeChanged() and * packetWasChanged() will not. * * As a special case, when a packet is destroyed there is only the future * event packetToBeDestroyed() with no matching "past" event, since \e after * the packet has been destroyed the set of listeners is no longer available. * * No guarantees are made as to the order in which the different packet * listeners are notified of an event. * * When a listener is destroyed, it is automatically unregistered * from any packets to which it is currently listening. Similarly, when * a packet is destroyed all listeners are automatically unregistered. * * When using multiple threads, there are restrictions upon what any thread * other than the main thread may do. If these restrictions are properly * adhered to, packet listeners may assume that no routines other than * childWasAdded() will be called from a non-main thread. Of course it is * up to the multithreading code to ensure that these restrictions are in * fact met; see the NThread class notes for further information. * * \warning If the multithreading restrictions noted above are \e not * adhered to, this can result in the GUI crashing within either Qt or * Xlib. Again, see the NThread class notes for further information. * * \ifacespython Not present. */ class REGINA_API NPacketListener { private: std::set packets; /**< The set of packets upon which this object is currently listening. */ public: /** * Destroys this listener. This listener will be unregistered * from any packets to which it is currently listening. */ virtual ~NPacketListener(); /** * Unregisters this listener from any packets to which it is * currently listening. */ void unregisterFromAllPackets(); /** * Called before the contents of the packet are to be changed. * Once the contents are changed, packetWasChanged() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void packetToBeChanged(NPacket* packet); /** * Called after the contents of the packet have been changed. * Before the contents are changed, packetToBeChanged() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void packetWasChanged(NPacket* packet); /** * Called before the packet label or tags are to be changed. * Once the label or tags are changed, packetWasRenamed() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void packetToBeRenamed(NPacket* packet); /** * Called after the packet label or tags have been changed. * Before the label or tags are changed, packetToBeRenamed() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void packetWasRenamed(NPacket* packet); /** * Called before the packet is about to be destroyed. Note that * there is no matching function called \e after the * packet is destroyed, since the set of listeners will no * longer be available at that stage. * * When an entire packet subtree is to be destroyed, child packets * will notify their listeners of the impending destruction * before parent packets will. * * Note that the packet will forcibly unregister this listener * immediately \e before packetToBeDestroyed() is called, to avoid * any unpleasant consequences if this listener should also try to * unregister itself. This means that, by the time this routine is * called, this listener will no longer be registered with the * packet in question (and any attempt to unregister it again * will be harmless). * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void packetToBeDestroyed(NPacket* packet); /** * Called before a child packet is to be inserted directly beneath * the packet. * Once the child is removed, childWasAdded() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. * @param child the child packet to be added. */ virtual void childToBeAdded(NPacket* packet, NPacket* child); /** * Called after a child packet has been inserted directly beneath * the packet. * Before this child is added, childToBeAdded() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. * @param child the child packet that was added. */ virtual void childWasAdded(NPacket* packet, NPacket* child); /** * Called before a child packet is to be removed from directly beneath * the packet. Note that the child packet may be about to be * destroyed (although this destruction will not have happened yet). * Once the child is removed, childWasRemoved() will be * called also. * * Note also that this packet (the parent) may have already * entered its destructor (which removes and destroys all child * packets as a matter of course). In this situation it may be * unsafe to query or update this packet, and so the third argument * \a inParentDestructor is provided to indicate such a situation. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. * @param child the child packet to be removed. * @param inParentDestructor set to \c true if the parent packet * is in fact being destroyed, and the child was simply removed * as part of the standard subtree destruction. */ virtual void childToBeRemoved(NPacket* packet, NPacket* child, bool inParentDestructor); /** * Called after a child packet has been removed from directly beneath * the packet. Note that the child packet may be about to be * destroyed (although this destruction will not have happened yet). * Before this child is removed, childToBeRemoved() will be * called also. * * Note also that this packet (the parent) may have already * entered its destructor (which removes and destroys all child * packets as a matter of course). In this situation it may be * unsafe to query or update this packet, and so the third argument * \a inParentDestructor is provided to indicate such a situation. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. * @param child the child packet that was removed. * @param inParentDestructor set to \c true if the parent packet * is in fact being destroyed, and the child was simply removed * as part of the standard subtree destruction. */ virtual void childWasRemoved(NPacket* packet, NPacket* child, bool inParentDestructor); /** * Called before the child packets directly beneath the packet * are to be reordered. * Once the reordering is done, childrenWereReordered() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void childrenToBeReordered(NPacket* packet); /** * Called after the child packets directly beneath the packet * have been reordered. * Before this reordering is done, childrenToBeReordered() will be * called also. * * The default implementation of this routine is to do nothing. * * @param packet the packet being listened to. */ virtual void childrenWereReordered(NPacket* packet); /** * Allow packets to automatically deregister listeners as they are * destroyed. */ friend class NPacket; }; /*@}*/ // Inline functions for NPacketListener inline void NPacketListener::packetToBeChanged(NPacket*) { } inline void NPacketListener::packetWasChanged(NPacket*) { } inline void NPacketListener::packetToBeRenamed(NPacket*) { } inline void NPacketListener::packetWasRenamed(NPacket*) { } inline void NPacketListener::packetToBeDestroyed(NPacket*) { } inline void NPacketListener::childToBeAdded(NPacket*, NPacket*) { } inline void NPacketListener::childWasAdded(NPacket*, NPacket*) { } inline void NPacketListener::childToBeRemoved(NPacket*, NPacket*, bool) { } inline void NPacketListener::childWasRemoved(NPacket*, NPacket*, bool) { } inline void NPacketListener::childrenToBeReordered(NPacket*) { } inline void NPacketListener::childrenWereReordered(NPacket*) { } } // namespace regina #endif regina-4.95/engine/packet/npdf.cpp000644 000765 000024 00000010005 12234011536 016747 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "packet/npdf.h" #include "utilities/base64.h" #include "utilities/xmlutils.h" #define BASE64_LINE_LEN 76 namespace regina { void NPDF::reset() { ChangeEventSpan span(this); if (data_) { if (alloc_ == OWN_MALLOC) ::free(data_); else delete[] data_; } data_ = 0; size_ = 0; alloc_ = OWN_NEW; } void NPDF::reset(char* data, size_t size, OwnershipPolicy alloc) { ChangeEventSpan span(this); // Out with the old data. if (data_) { if (alloc_ == OWN_MALLOC) ::free(data_); else delete[] data_; } // In with the new. if (data) { data_ = data; size_ = size; if (alloc == DEEP_COPY) { data_ = static_cast(::malloc(size_)); ::memcpy(data_, static_cast(data), size_); alloc_ = OWN_MALLOC; } else alloc_ = alloc; } else { data_ = 0; size_ = 0; alloc_ = OWN_NEW; } } void NPDF::writeXMLPacketData(std::ostream& out) const { if (! data_) { // We have an empty PDF packet. out << " \n"; return; } char* base64; size_t len64 = base64Encode(data_, size_, &base64); if (! base64) { out << " \n"; return; } out << " \n"; const char* pos = base64; while (len64 > BASE64_LINE_LEN) { out.write(pos, BASE64_LINE_LEN); out << std::endl; pos += BASE64_LINE_LEN; len64 -= BASE64_LINE_LEN; } if (len64 > 0) { out.write(pos, len64); out << std::endl; } out << " \n"; delete[] base64; } } // namespace regina regina-4.95/engine/packet/npdf.h000644 000765 000024 00000022017 12236524106 016426 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/npdf.h * \brief A packet that contains a PDF document. */ #ifndef __NPDF_H #ifndef __DOXYGEN #define __NPDF_H #endif #include #include #include "regina-core.h" #include "packet/npacket.h" namespace regina { class NXMLPacketReader; class NPDF; /** * \weakgroup packet * @{ */ /** * Stores information about the PDF packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NPDF Class; inline static const char* name() { return "PDF"; } }; /** * A packet that can hold a PDF document. * * This packet may or may not contain a PDF document at any given time. * This state can be changed by calling reset(). */ class REGINA_API NPDF : public NPacket { REGINA_PACKET(NPDF, PACKET_PDF) public: /** * Describes how a PDF packet should claim ownership of a block * of binary data. * * \ifacespython Not present. */ enum OwnershipPolicy { /** * The packet should claim ownership of the block, and * should assume that it was allocated using \a malloc(). */ OWN_MALLOC, /** * The packet should claim ownership of the block, and * should assume that it was allocated using \c new[]. */ OWN_NEW, /** * The packet should not claim ownership of the block, but * should instead make its own deep copy. */ DEEP_COPY }; private: char* data_; /**< The binary data of the PDF document that is stored in this packet, or 0 if no PDF document is currently stored. */ size_t size_; /**< The size of the binary data in bytes, or 0 if no PDF document is currently stored. */ OwnershipPolicy alloc_; /**< Describes how the binary data (if any) was allocated; this must be either \a OWN_MALLOC or \a OWN_NEW. */ public: /** * Creates a PDF packet with no document stored. */ NPDF(); /** * Creates a packet to store the given PDF data. * * The \a data array must contain a full PDF document as a * block of binary data. * * The \a alloc argument shows if/how this packet claims ownership of * the data. In particular, unless \a alloc is \a DEEP_COPY, this * packet will claim ownership of the given data block and will * deallocate it when the packet is destroyed. If \a alloc is * \a DEEP_COPY then the given block of data will not be modified in * any way. * * It is possible to pass a null pointer as the data array, in * which case the new packet will have no PDF document stored. * * \ifacespython Not present. * * @param data the block of binary data that forms the PDF document, * or \c null if no document is to be stored. * @param size the number of bytes in this block of binary data; * if actual data is passed (i.e., \a data is not \c null) then * this must be strictly positive. * @param alloc describes if/how this packet should claim ownership * of the given block of data; see the notes above for details. */ NPDF(char* data, size_t size, OwnershipPolicy alloc); /** * Destroys this PDF packet and deallocates data if required. */ ~NPDF(); /** * Returns a pointer to the block of raw data that forms this * PDF document. The number of bytes in this block can be found * by calling size(). * * If no PDF document is currently stored, this routine will * return a null pointer. * * \ifacespython Not present. * * @return the raw PDF data. */ const char* data() const; /** * Returns the size of this PDF document in bytes. * * If no PDF document is currently stored, this routine will * return zero. * * @return the number of bytes. */ size_t size() const; /** * Empties this PDF packet so that no document is stored. * * The old data will be deallocated if required. */ void reset(); /** * Refills this PDF packet with the given PDF data. * The old data will be deallocated if required. * * This routine behaves like the class constructor; see the * constructor documentation for details. * * \ifacespython Not present. * * @param data the block of binary data that forms the new PDF * document, or \c null if no document is to be stored. * @param size the number of bytes in this new block of binary data; * if actual data is passed (i.e., \a data is not \c null) then * this must be strictly positive. * @param alloc describes if/how this packet should claim ownership * of the given block of data; see the notes above for details. */ void reset(char* data, size_t size, OwnershipPolicy alloc); virtual void writeTextShort(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; }; /*@}*/ // Inline functions for NPDF inline NPDF::NPDF() : data_(0), size_(0), alloc_(OWN_NEW) { } inline NPDF::NPDF(char* data, size_t size, OwnershipPolicy alloc) : data_(data), size_(size), alloc_(alloc) { if (alloc_ == DEEP_COPY) { if (data_) { data_ = static_cast(::malloc(size_)); ::memcpy(data_, static_cast(data), size_); } alloc_ = OWN_MALLOC; } else if (! data_) size_ = 0; } inline NPDF::~NPDF() { if (data_) { if (alloc_ == OWN_MALLOC) ::free(data_); else delete[] data_; } } inline const char* NPDF::data() const { return data_; } inline size_t NPDF::size() const { return size_; } inline void NPDF::writeTextShort(std::ostream& o) const { o << "PDF packet (" << size_ << (size_ == 1 ? " byte)" : " bytes)"); } inline bool NPDF::dependsOnParent() const { return false; } inline NPacket* NPDF::internalClonePacket(NPacket*) const { return new NPDF(data_, size_, DEEP_COPY); } } // namespace regina #endif regina-4.95/engine/packet/nscript.cpp000644 000765 000024 00000012156 12236524106 017517 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "packet/nscript.h" #include "utilities/xmlutils.h" #define PROP_VARIABLE 1 namespace regina { const std::string& NScript::getVariableName(unsigned long index) const { std::map::const_iterator it = variables.begin(); advance(it, index); return (*it).first; } NPacket* NScript::getVariableValue(unsigned long index) const { std::map::const_iterator it = variables.begin(); advance(it, index); return (*it).second; } NPacket* NScript::getVariableValue(const std::string& name) const { std::map::const_iterator it = variables.find(name); if (it == variables.end()) return 0; return (*it).second; } void NScript::removeVariable(const std::string& name) { std::map::iterator it = variables.find(name); if (it == variables.end()) return; if (it->second) it->second->unlisten(this); ChangeEventSpan span(this); variables.erase(it); } void NScript::writeTextLong(std::ostream& o) const { if (variables.empty()) o << "No variables.\n"; else { for (std::map::const_iterator vit = variables.begin(); vit != variables.end(); vit++) { o << "Variable: " << vit->first << " = "; if (vit->second) o << vit->second->getPacketLabel() << '\n'; else o << "(null)" << '\n'; } } o << '\n'; copy(lines.begin(), lines.end(), std::ostream_iterator(o, "\n")); } NPacket* NScript::internalClonePacket(NPacket*) const { NScript* ans = new NScript(); ans->lines = lines; ans->variables = variables; return ans; } void NScript::writeXMLPacketData(std::ostream& out) const { using regina::xml::xmlEncodeSpecialChars; for (std::map::const_iterator vit = variables.begin(); vit != variables.end(); vit++) { out << " second) out << vit->second->internalID(); out << "\" value=\""; if (vit->second) out << xmlEncodeSpecialChars(vit->second->getPacketLabel()); out << "\"/>\n"; } for (std::vector::const_iterator it = lines.begin(); it != lines.end(); it++) out << " " << xmlEncodeSpecialChars(*it) << "\n"; } void NScript::packetToBeDestroyed(NPacket* packet) { // We know the script will change, because one of our variables is // listening on this packet. ChangeEventSpan span(this); for (std::map::iterator vit = variables.begin(); vit != variables.end(); vit++) if (vit->second == packet) vit->second = 0; } } // namespace regina regina-4.95/engine/packet/nscript.h000644 000765 000024 00000026057 12236524106 017171 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/nscript.h * \brief Contains a packet representing a script. */ #ifndef __NSCRIPT_H #ifndef __DOXYGEN #define __NSCRIPT_H #endif #include #include #include #include "regina-core.h" #include "packet/npacket.h" namespace regina { class NXMLPacketReader; class NScript; /** * \weakgroup packet * @{ */ /** * Stores information about the script packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NScript Class; inline static const char* name() { return "Script"; } }; /** * A packet representing a Python script that can be run. * Accessor methods for a script work a line at a time. * * As of Regina 4.95, variables are now stored as pointers to packets, not * packet labels. This affects how variables react to changes in the * packets that they point to. In particular, if a variable \a V points to * some packet \a P, then as of Regina 4.95: * * - if \a P is renamed then \a V will still point to it and the script will * \e not notify listeners of any changes (though of course \a P will * still notify its own listeners); * - if \a P is deleted then \a V will take the value \c None, and the script * \e will notify listeners of the change. */ class REGINA_API NScript : public NPacket, public NPacketListener { REGINA_PACKET(NScript, PACKET_SCRIPT) private: std::vector lines; /**< An array storing the lines of this script; none of * these strings should contain newlines. */ std::map variables; /**< A map storing the variables with which this script * is to be run. Variable names are mapped to their * corresponding values. */ public: /** * Initialises to a script with no lines and no variables. */ NScript(); /** * Returns the number of lines in this script. * * @return the number of lines. */ unsigned long getNumberOfLines() const; /** * Returns the requested line of this script. * * @param index the index of the requested line; this must be * between 0 and getNumberOfLines()-1 inclusive. * @return the requested line. */ const std::string& getLine(unsigned long index) const; /** * Adds the given line to the beginning of this script. * * @param line the line to insert. */ void addFirst(const std::string& line); /** * Adds the given line to the end of this script. * * @param line the line to add. */ void addLast(const std::string& line); /** * Inserts the given line into the given position in this script. * All subsequent lines will be shifted down to make room. * * @param line the line to insert. * @param index the index at which the new line will be placed; * this must be between 0 and getNumberOfLines() inclusive. */ void insertAtPosition(const std::string& line, unsigned long index); /** * Replaces a line of this script with the given line. * * @param line the line to replace the currently stored line. * @param index the index of the line to replace; this must be * between 0 and getNumberOfLines()-1 inclusive. */ void replaceAtPosition(const std::string& line, unsigned long index); /** * Removes the requested line from this script. * * @param index the index of the requested line; this must be * between 0 and getNumberOfLines()-1 inclusive. */ void removeLineAt(unsigned long index); /** * Removes all lines from this script. */ void removeAllLines(); /** * Returns the number of variables associated with this script. * * @return the number of variables. */ unsigned long getNumberOfVariables() const; /** * Returns the name of the requested variable associated with * this script. * * @param index the index of the requested variable; this must * be between 0 and getNumberOfVariables()-1 inclusive. * @return the name of the requested variable. */ const std::string& getVariableName(unsigned long index) const; /** * Returns the value of the requested variable associated with * this script. Variables may take the value \c null. * * @param index the index of the requested variable; this must * be between 0 and getNumberOfVariables()-1 inclusive. * @return the value of the requested variable. */ NPacket* getVariableValue(unsigned long index) const; /** * Returns the value of the variable stored with the given * name. Variables may take the value \c null. * * If no variable is stored with the given name, then \c null * will likewise be returned. * * @param name the name of the requested variable; note that * names are case sensitive. * @return the value of the requested variable. */ NPacket* getVariableValue(const std::string& name) const; /** * Adds a new variable to be associated with this script. * If a variable with the given name is already stored, this * routine will do nothing. * * @param name the name of the new variable. * @param value the value of the new variable; this is allowed * to be \c null. * @return \c true if the variable was successfully added, or * \c false if a variable with the given name was already stored. */ bool addVariable(const std::string& name, NPacket* value); /** * Removes the variable stored with the given name. * Note that the indices of other variables may change as a * result of this action. * * If no variable is stored with the given name, this routine * will do nothing (but waste time!). * * @param name the name of the variable to remove; note that * names are case sensitive. */ void removeVariable(const std::string& name); /** * Removes all variables associated with this script. */ void removeAllVariables(); virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; virtual void packetToBeDestroyed(NPacket* packet); protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; }; /*@}*/ // Inline functions for NScript inline NScript::NScript() { } inline unsigned long NScript::getNumberOfLines() const { return lines.size(); } inline const std::string& NScript::getLine(unsigned long index) const { return lines[index]; } inline void NScript::addFirst(const std::string& line) { ChangeEventSpan span(this); lines.insert(lines.begin(), line); } inline void NScript::addLast(const std::string& line) { ChangeEventSpan span(this); lines.push_back(line); } inline void NScript::insertAtPosition(const std::string& line, unsigned long index) { ChangeEventSpan span(this); lines.insert(lines.begin() + index, line); } inline void NScript::replaceAtPosition(const std::string& line, unsigned long index) { ChangeEventSpan span(this); lines[index] = line; } inline void NScript::removeLineAt(unsigned long index) { ChangeEventSpan span(this); lines.erase(lines.begin() + index); } inline void NScript::removeAllLines() { ChangeEventSpan span(this); lines.clear(); } inline unsigned long NScript::getNumberOfVariables() const { return variables.size(); } inline bool NScript::addVariable(const std::string& name, NPacket* value) { ChangeEventSpan span(this); bool ans = variables.insert(std::make_pair(name, value)).second; if (value) value->listen(this); return ans; } inline void NScript::removeAllVariables() { unregisterFromAllPackets(); ChangeEventSpan span(this); variables.clear(); } inline void NScript::writeTextShort(std::ostream& o) const { o << "Script with " << lines.size() << " line"; if (lines.size() != 1) o << 's'; } inline bool NScript::dependsOnParent() const { return false; } } // namespace regina #endif regina-4.95/engine/packet/ntext.cpp000644 000765 000024 00000004770 12234011536 017176 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "packet/ntext.h" #include "utilities/xmlutils.h" namespace regina { void NText::writeXMLPacketData(std::ostream& out) const { out << " " << regina::xml::xmlEncodeSpecialChars(text) << "\n"; } } // namespace regina regina-4.95/engine/packet/ntext.h000644 000765 000024 00000012460 12236524106 016642 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/ntext.h * \brief Contains a packet representing a text string. */ #ifndef __NTEXT_H #ifndef __DOXYGEN #define __NTEXT_H #endif #include "regina-core.h" #include "packet/npacket.h" namespace regina { class NXMLPacketReader; class NText; /** * \weakgroup packet * @{ */ /** * Stores information about the text packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NText Class; inline static const char* name() { return "Text"; } }; /** * A packet representing a text string. */ class REGINA_API NText : public NPacket { REGINA_PACKET(NText, PACKET_TEXT) private: std::string text; /**< The text string stored in this packet. */ public: /** * Initialises the packet to the empty string. */ NText(); /** * Initialises the packet to the given string. * * @param newText the new value for the packet. */ NText(const std::string& newText); /** * Initialises the packet to the given string. * * @param newText the new value for the packet. */ NText(const char* newText); /** * Returns the string stored in the packet. * * @return the stored string. */ const std::string& getText() const; /** * Sets the packet data to the given string. * * @param newText the new value for the packet. */ void setText(const std::string& newText); /** * Sets the packet data to the given string. * * @param newText the new value for the packet. */ void setText(const char* newText); virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; }; /*@}*/ // Inline functions for NText inline NText::NText() { } inline NText::NText(const std::string& newText) : text(newText) { } inline NText::NText(const char* newText) : text(newText) { } inline const std::string& NText::getText() const { return text; } inline void NText::setText(const std::string& newText) { ChangeEventSpan span(this); text = newText; } inline void NText::setText(const char* newText) { ChangeEventSpan span(this); text = newText; } inline void NText::writeTextShort(std::ostream& o) const { o << "Text packet"; } inline void NText::writeTextLong(std::ostream& o) const { o << text << '\n'; } inline bool NText::dependsOnParent() const { return false; } inline NPacket* NText::internalClonePacket(NPacket*) const { return new NText(text); } } // namespace regina #endif regina-4.95/engine/packet/nxmlpacketreader.cpp000644 000765 000024 00000012455 12236524106 021370 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "packet/npacket.h" #include "packet/nxmlpacketreader.h" #include "packet/nxmltreeresolver.h" #include "packet/packetregistry.h" #include "utilities/stringutils.h" namespace regina { namespace { struct XMLReaderFunction : public Returns { NPacket* me_; NXMLTreeResolver& resolver_; XMLReaderFunction(NPacket* me, NXMLTreeResolver& resolver) : me_(me), resolver_(resolver) {} template inline NXMLElementReader* operator() (Packet) { return Packet::Class::getXMLReader(me_, resolver_); } }; } NXMLElementReader* NXMLPacketReader::startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps) { if (subTagName == "packet") { NPacket* me = getPacket(); if (! me) return new NXMLPacketReader(resolver_); regina::xml::XMLPropertyDict::const_iterator it = subTagProps.find("label"); if (it == subTagProps.end()) childLabel = ""; else childLabel = it->second; it = subTagProps.find("id"); if (it == subTagProps.end()) childID = ""; else childID = it->second; it = subTagProps.find("typeid"); if (it == subTagProps.end()) return new NXMLPacketReader(resolver_); long typeID; if (! valueOf((*it).second, typeID)) return new NXMLPacketReader(resolver_); if (typeID <= 0) return new NXMLPacketReader(resolver_); NXMLElementReader* ans = forPacket( static_cast(typeID), XMLReaderFunction(me, resolver_), 0); if (ans) return ans; else return new NXMLPacketReader(resolver_); } else if (subTagName == "tag") { if (NPacket* me = getPacket()) { std::string packetTag = subTagProps.lookup("name"); if (! packetTag.empty()) me->addTag(packetTag); } return new NXMLElementReader(); } else return startContentSubElement(subTagName, subTagProps); } void NXMLPacketReader::endSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "packet") { NPacket* child = dynamic_cast(subReader)->getPacket(); if (child) { NPacket* me = getPacket(); if (me) { child->setPacketLabel(childLabel); if (! childID.empty()) resolver_.storeID(childID, child); if (! child->getTreeParent()) me->insertChildLast(child); } else delete child; } } else if (subTagName == "tag") return; else endContentSubElement(subTagName, subReader); } void NXMLPacketReader::abort(NXMLElementReader* /* subReader */) { NPacket* me = getPacket(); if (me) if (! me->getTreeParent()) delete me; } } // namespace regina regina-4.95/engine/packet/nxmlpacketreader.h000644 000765 000024 00000021035 12236524106 021027 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/nxmlpacketreader.h * \brief Deals with parsing XML data for individual packets. */ #ifndef __NXMLPACKETREADER_H #ifndef __DOXYGEN #define __NXMLPACKETREADER_H #endif #include "regina-core.h" #include "file/nxmlelementreader.h" namespace regina { class NPacket; class NXMLTreeResolver; /** * \weakgroup packet * @{ */ /** * An XML element reader that reads the data for an individual packet. * * Generally a subclass of NXMLPacketReader will be used to receive and * store packets that you care about. However, if you simply wish to * ignore a particular packet (and all of its descendants), you can use * class NXMLPacketReader itself for the packet(s) you wish to ignore. * * Routine getPacket() is used to return the packet that was read; see * its documentation for further notes on how the packet should be * constructed. * * Routines startSubElement() and endSubElement() should \e not be * overridden by derived classes. They determine whether the subelement * is another packet element or a packet tag; if so then they deal with * the subelement themselves (packet elements will be read using a new * NXMLPacketReader of the correct type), and if not then they call * startContentSubElement() and endContentSubElement() which \e should * be overridden for processing of non-packet XML subelements. * * If routine abort() is overridden, it \e must at some point call * NXMLPacketReader::abort() which will destroy whatever new packets * have already been created. * * \ifacespython Not present. */ class REGINA_API NXMLPacketReader : public NXMLElementReader { private: std::string childLabel; /**< The packet label to give the child packet currently being read. */ std::string childID; /**< The internal ID stored in the data file for the child packet currently being read. */ protected: NXMLTreeResolver& resolver_; /**< The master resolver that will be used to fix dangling packet references after the entire XML file has been read. */ public: /** * Creates a new packet element reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLPacketReader(NXMLTreeResolver& resolver); /** * Returns the newly allocated packet that has been read by * this element reader. * * Deallocation of this new packet is not the responsibility of * this class. Once this routine gives a non-zero return value, * it should continue to give the same non-zero return value * from this point onwards. * * If this routine is ever to give a non-zero return value, it * \e must be giving that non-zero return value by the time the * first child packet or packet tag is encountered; otherwise * child packets will not be inserted into the packet tree and/or * packet tags will not be added. * * The newly allocated packet should not be given a packet * label. This will be done by NXMLPacketReader::endSubElement(). * * The newly allocated packet may or may not be inserted in the * packet tree structure; this does not matter (although if it * is inserted it must be inserted in the correct place). * * The newly allocated packet should not be given any associated * packet tags. This will be done by * NXMLPacketReader::startSubElement(). * * The default implementation returns 0. * * @return the packet that has been read, or 0 if packet reading * is incomplete, the packet should be ignored or an error * occurred. */ virtual NPacket* getPacket(); /** * Used instead of startSubElement() for XML subelements that * are not child packets or packet tags. * * The default implementation returns a new NXMLElementReader * which can be used to ignore the subelement completely. * * @param subTagName the name of the subelement opening tag. * @param subTagProps the properties associated with the * subelement opening tag. * @return a newly created element reader that will be used to * parse the subelement. This class should \e not take care of * the new reader's destruction; that will be done by the parser. */ virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); /** * Used instead of endSubElement() for XML subelements that are * not child packets or packet tags. * * The default implementation does nothing. * * @param subTagName the name of the subelement closing tag. * @param subReader the child reader that was used to parse the * subelement (this is the reader that was returned by the * corresponding startContentSubElement() call). It is guaranteed * that endElement() has already been called upon this child reader * and that the child reader has not yet been destroyed. */ virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endSubElement(const std::string& subTagName, NXMLElementReader* subReader); virtual void abort(NXMLElementReader *subReader); }; /*@}*/ // Inline functions for NXMLPacketReader inline NXMLPacketReader::NXMLPacketReader(NXMLTreeResolver& resolver) : resolver_(resolver) { } inline NPacket* NXMLPacketReader::getPacket() { return 0; } inline NXMLElementReader* NXMLPacketReader::startContentSubElement( const std::string&, const regina::xml::XMLPropertyDict&) { return new NXMLElementReader(); } inline void NXMLPacketReader::endContentSubElement(const std::string&, NXMLElementReader*) { } } // namespace regina #endif regina-4.95/engine/packet/nxmlpacketreaders.cpp000644 000765 000024 00000016706 12236524106 021556 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "packet/nxmlpacketreaders.h" #include "packet/nxmltreeresolver.h" #include "utilities/base64.h" #include #include namespace regina { /** * A unique namespace containing various task-specific packet readers. */ namespace { /** * Reads a single script variable and its value. */ class NScriptVarReader : public NXMLElementReader { private: std::string name, valueID, valueLabel; public: inline void startElement(const std::string& /* tagName */, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { name = props.lookup("name"); valueID = props.lookup("valueid"); valueLabel = props.lookup("value"); } inline const std::string& getName() { return name; } inline const std::string& getValueID() { return valueID; } inline const std::string& getValueLabel() { return valueLabel; } }; /** * A resolution task that, after the entire XML file has been read, * will bind a script variable to its corresponding packet reference. */ class VariableResolutionTask : public NXMLTreeResolutionTask { private: NScript* script_; std::string name_; std::string valueID_; /**< An internal packet ID. Used by Regina >= 4.95. */ std::string valueLabel_; /**< A packet label. Used by Regina <= 4.94. */ public: inline VariableResolutionTask(NScript* script, const std::string& name, const std::string& valueID, const std::string& valueLabel) : script_(script), name_(name), valueID_(valueID), valueLabel_(valueLabel) { } inline void resolve(const NXMLTreeResolver& resolver) { NPacket* resolution = 0; if (! valueID_.empty()) { NXMLTreeResolver::IDMap::const_iterator it = resolver.ids().find(valueID_); resolution = (it == resolver.ids().end() ? 0 : it->second); } if ((! resolution) && (! valueLabel_.empty())) resolution = script_->getTreeMatriarch()-> findPacketLabel(valueLabel_); script_->addVariable(name_, resolution); } }; } NXMLElementReader* NXMLPDFReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "pdf") return new NXMLCharsReader(); else return new NXMLElementReader(); } void NXMLPDFReader::endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "pdf") { std::string base64 = dynamic_cast(subReader)-> getChars(); // Strip out whitespace. std::string::iterator in = base64.begin(); std::string::iterator out = base64.begin(); while (in != base64.end()) { if (::isspace(*in)) ++in; else { if (in == out) { ++in; ++out; } else *out++ = *in++; } } // Is there any data at all? if (out == base64.begin()) { pdf->reset(); return; } // Convert from base64. char* data; size_t dataLen; if (base64Decode(base64.c_str(), out - base64.begin(), &data, &dataLen)) pdf->reset(data, dataLen, NPDF::OWN_NEW); else pdf->reset(); } } NXMLElementReader* NXMLScriptReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "line") return new NXMLCharsReader(); else if (subTagName == "var") return new NScriptVarReader(); else return new NXMLElementReader(); } void NXMLScriptReader::endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "line") script->addLast(dynamic_cast(subReader)->getChars()); else if (subTagName == "var") { NScriptVarReader* var = dynamic_cast(subReader); if (! var->getName().empty()) resolver_.queueTask(new VariableResolutionTask( script, var->getName(), var->getValueID(), var->getValueLabel())); } } NXMLPacketReader* NContainer::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLContainerReader(resolver); } NXMLPacketReader* NPDF::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLPDFReader(resolver); } NXMLPacketReader* NScript::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLScriptReader(resolver); } NXMLPacketReader* NText::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLTextReader(resolver); } } // namespace regina regina-4.95/engine/packet/nxmlpacketreaders.h000644 000765 000024 00000016400 12236524106 021212 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/nxmlpacketreaders.h * \brief Deals with parsing XML data for various basic packet types. */ #ifndef __NXMLPACKETREADERS_H #ifndef __DOXYGEN #define __NXMLPACKETREADERS_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "packet/ncontainer.h" #include "packet/npdf.h" #include "packet/nscript.h" #include "packet/ntext.h" namespace regina { /** * \weakgroup packet * @{ */ /** * An XML packet reader that reads a single container. * * \ifacespython Not present. */ class REGINA_API NXMLContainerReader : public NXMLPacketReader { private: NContainer* container; /**< The container currently being read. */ public: /** * Creates a new container reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLContainerReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); }; /** * An XML packet reader that reads a single PDF packet. * * \ifacespython Not present. */ class REGINA_API NXMLPDFReader : public NXMLPacketReader { private: NPDF* pdf; /**< The PDF packet currently being read. */ public: /** * Creates a new PDF reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLPDFReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /** * An XML packet reader that reads a single script. * * \ifacespython Not present. */ class REGINA_API NXMLScriptReader : public NXMLPacketReader { private: NScript* script; /**< The script currently being read. */ public: /** * Creates a new script reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLScriptReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /** * An XML packet reader that reads a single text packet. * * \ifacespython Not present. */ class REGINA_API NXMLTextReader : public NXMLPacketReader { private: NText* text; /**< The text packet currently being read. */ public: /** * Creates a new text packet reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLTextReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLContainerReader inline NXMLContainerReader::NXMLContainerReader(NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), container(new NContainer()) { } inline NPacket* NXMLContainerReader::getPacket() { return container; } // Inline functions for NXMLPDFReader inline NXMLPDFReader::NXMLPDFReader(NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), pdf(new NPDF()) { } inline NPacket* NXMLPDFReader::getPacket() { return pdf; } // Inline functions for NXMLScriptReader inline NXMLScriptReader::NXMLScriptReader(NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), script(new NScript()) { } inline NPacket* NXMLScriptReader::getPacket() { return script; } // Inline functions for NXMLTextReader inline NXMLTextReader::NXMLTextReader(NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), text(new NText()) { } inline NPacket* NXMLTextReader::getPacket() { return text; } inline NXMLElementReader* NXMLTextReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "text") return new NXMLCharsReader(); else return new NXMLElementReader(); } inline void NXMLTextReader::endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "text") text->setText(dynamic_cast(subReader)->getChars()); } } // namespace regina #endif regina-4.95/engine/packet/nxmltreeresolver.h000644 000765 000024 00000024702 12236713237 021127 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/nxmltreeresolver.h * \brief Support for resolving dangling packet references after a * complete packet tree has been read from file. */ #ifndef __NXMLTREERESOLVER_H #ifndef __DOXYGEN #define __NXMLTREERESOLVER_H #endif #include "regina-core.h" #include #include namespace regina { class NPacket; /** * \weakgroup packet * @{ */ class NXMLTreeResolver; /** * An individual task for resolving dangling packet references after an * XML data file has been read. * * See the NXMLTreeResolver class notes for an overview of how dangling * references and related issues are resolved, and the role that * NXMLTreeResolutionTask plays in this process. * * Specifically, if an individual NXMLPacketReader cannot * completely flesh out the internal data for a packet as the packet is * being read, it should construct a new NXMLTreeResolutionTask and * queue it to the master NXMLTreeResolver. The NXMLTreeResolver will * then call resolve() for each queued task after the complete data file * has been read, at which point whatever information was missing when the * packet was initially read should now be available. * * Each packet reader that requires this machinery should subclass * NXMLTreeResolutionTask, and override resolve() to perform whatever * "fleshing out" procedure is required for its particular type of packet. */ class REGINA_API NXMLTreeResolutionTask { public: /** * A default construct that does nothing. */ virtual ~NXMLTreeResolutionTask(); /** * Called by NXMLTreeResolver after the entire data file has * been read. Subclasses should override this routine to * perform whatever "fleshing out" is necessary for a packet * whose internal data is not yet complete. * * @param resolver the master resolver managing the resolution * process, as outlined in the NXMLTreeResolver class notes. */ virtual void resolve(const NXMLTreeResolver& resolver) = 0; }; /** * Provides a mechanism to resolve dangling packet references after a * complete packet tree has been read from an XML data file. * * There are situations in which, when reading an XML data file, the data * stored in an individual packet cannot be fully constructed until after * the entire data file has been read. For instance, a packet might need to * store pointers or references to other packets that could appear later in * the packet tree (e.g., a script storing pointers to its variables). * * This problem is solved by the NXMLTreeResolver class. The complete * process of reading an XML data file works as follows: * * - The top-level routine managing the file I/O constructs a new * NXMLTreeResolver. This resolver is then passed to each * NXMLPacketReader in turn as each individual packet is read. * * - If an NXMLPacketReader is not able to fully flesh out its data * because it requires information that is not yet available, it * should create a new NXMLTreeResolutionTask and queue this task to * the resolver via NXMLTreeResolver::queueTask(). * * - Once the entire packet tree has been read, the top-level file I/O * manager will call NXMLTreeResolver::resolve(). This will run * NXMLTreeResolutionTask::resolve() for each task in turn, whereby any * missing data for individual packets can be resolved. * * Each task should be an instance of a subclass of NXMLTreeResolutionTask, * whose virtual resolve() function is overridden to perform whatever * "fleshing out" work is required for the type of packet under consideration. */ class REGINA_API NXMLTreeResolver { public: typedef std::map IDMap; /**< A type that maps internal IDs from the data file to the corresponding packets. See ids() for details. */ private: IDMap ids_; /**< Maps internal IDs from the data file to the corresponding packets. */ std::list tasks_; /**< The list of tasks that have been queued for processing. */ public: /** * Constructs a resolver with no tasks queued. */ NXMLTreeResolver(); /** * Destroys any tasks that were queued but not performed. */ ~NXMLTreeResolver(); /** * Queues a task for processing. When the file I/O manager * calls resolve(), this will call NXMLTreeResolutionTask::resolve() * for each task that has been queued. * * This object will claim ownership of the given task, and will * destroy it after resolve() has been called (or, if resolve() * is never called, when this NXMLTreeResolver is destroyed). * * @param task the task to be queued. */ void queueTask(NXMLTreeResolutionTask* task); /** * Stores the fact that the given packet is stored in the data * file using the given internal ID. Associations between IDs * and packets can be queried through the ids() function. * See ids() for further information on internal IDs. * * This will be called automatically by NXMLPacketReader as it * processes packet tags in the data file. Users and/or subclasses * of NXMLPacketReader do not need to call this function themselves. * * @param id the internal ID of the given packet, as stored in * the data file. * @param packet the corresponding packet. */ void storeID(const std::string& id, NPacket* packet); /** * Returns the map from internal IDs to packets, as stored in * the data file. * * Packets in a data file may have individual string IDs stored * alongside them, in the \a id attribute of the * <packet> tag. These strings are optional, * and do not need to be human-readable. * Although packets are not required to have IDs, any IDs that \e are * stored must be unique (i.e., two different packets cannot * share the same ID). * * Note that IDs read from the data file need not bear any * relation to the IDs that are returned from NPacket::internalID(), * although this is typically how they are constructed when a * file is saved. * * This map will be fleshed out as the data file is read. In * particular, since each task runs NXMLTreeResolutionTask::resolve() * only after the entire tree has been read, tasks may assume that * this map contains all IDs that were explicitly stored in the * data file. * * Only packets with IDs will appear in this map (i.e., there may well * be packets in the data file that do not appear in this map at all). * * @return the map from internal file IDs to packets. */ const IDMap& ids() const; /** * Calls NXMLTreeResolutionTask::resolve() for all queued tasks. * * The tasks will then be destroyed and removed from the queue * (so subsequent calls to resolve() are safe and will do nothing). */ void resolve(); }; /*@}*/ // Inline functions for NXMLTreeResolutionTask inline NXMLTreeResolutionTask::~NXMLTreeResolutionTask() { } // Inline functions for NXMLTreeResolver inline NXMLTreeResolver::NXMLTreeResolver() { } inline NXMLTreeResolver::~NXMLTreeResolver() { for (std::list::iterator it = tasks_.begin(); it != tasks_.end(); ++it) delete *it; } inline void NXMLTreeResolver::queueTask(NXMLTreeResolutionTask* task) { tasks_.push_back(task); } inline void NXMLTreeResolver::storeID(const std::string& id, NPacket* packet) { ids_.insert(std::make_pair(id, packet)); } inline const NXMLTreeResolver::IDMap& NXMLTreeResolver::ids() const { return ids_; } inline void NXMLTreeResolver::resolve() { for (std::list::iterator it = tasks_.begin(); it != tasks_.end(); ++it) { (*it)->resolve(*this); delete *it; } tasks_.clear(); } } // namespace regina #endif regina-4.95/engine/packet/packetregistry-impl.h000644 000765 000024 00000012226 12236713375 021507 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/packetregistry-impl.h * \brief Contains the registry of all packet types known to Regina. * * Each time a new packet type is created, this registry must be updated to: * * - add a #include line for the corresponding packet class; * - add a corresponding case to each implementation of forPacket(). * * See packetregistry.h for how other routines can use this registry. */ #ifndef __PACKETREGISTRY_IMPL_H #ifndef __DOXYGEN #define __PACKETREGISTRY_IMPL_H #endif #include "packet/ncontainer.h" #include "packet/ntext.h" #include "triangulation/ntriangulation.h" #include "surfaces/nnormalsurfacelist.h" #include "packet/nscript.h" #include "surfaces/nsurfacefilter.h" #include "angle/nanglestructurelist.h" #include "packet/npdf.h" #include "dim2/dim2triangulation.h" namespace regina { template inline typename FunctionObject::ReturnType forPacket( PacketType packetType, FunctionObject func, typename FunctionObject::ReturnType defaultReturn) { switch (packetType) { case PACKET_CONTAINER : return func(PacketInfo()); case PACKET_TEXT : return func(PacketInfo()); case PACKET_TRIANGULATION : return func(PacketInfo()); case PACKET_NORMALSURFACELIST : return func(PacketInfo()); case PACKET_SCRIPT : return func(PacketInfo()); case PACKET_SURFACEFILTER : return func(PacketInfo()); case PACKET_ANGLESTRUCTURELIST : return func(PacketInfo()); case PACKET_PDF : return func(PacketInfo()); case PACKET_DIM2TRIANGULATION : return func(PacketInfo()); default: return defaultReturn; } } template inline void forPacket(PacketType packetType, VoidFunctionObject func) { switch (packetType) { case PACKET_CONTAINER : func(PacketInfo()); break; case PACKET_TEXT : func(PacketInfo()); break; case PACKET_TRIANGULATION : func(PacketInfo()); break; case PACKET_NORMALSURFACELIST : func(PacketInfo()); break; case PACKET_SCRIPT : func(PacketInfo()); break; case PACKET_SURFACEFILTER : func(PacketInfo()); break; case PACKET_ANGLESTRUCTURELIST : func(PacketInfo()); break; case PACKET_PDF : func(PacketInfo()); break; case PACKET_DIM2TRIANGULATION : func(PacketInfo()); break; default: break; } } } // namespace regina #endif regina-4.95/engine/packet/packetregistry.h000644 000765 000024 00000015333 12234011536 020536 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/packetregistry.h * \brief Provides access to a registry of all packet types known to Regina. * * Each time a new packet type is created, the file packetregistry-impl.h * must be updated to include it. Instructions on how to do this are * included in packetregistry-impl.h. * * External routines can access the registry by calling one of the * forPacket() template functions defined in packetregistry.h. * * \warning You should not include this header unless it is necessary, * since it will automatically import every header for every packet type * in the registry. */ // The old registry macros will silently compile but do nothing. // This could lead to nasty surprises, so throw an error if it looks like // people are still trying to use them. #ifdef __PACKET_REGISTRY_BODY #error "The old REGISTER_PACKET macros have been removed. Use forPacket() instead." #endif #ifndef __PACKETREGISTRY_H #ifndef __DOXYGEN #define __PACKETREGISTRY_H #endif #include "packet/packettype.h" #include "utilities/registryutils.h" namespace regina { /** * \weakgroup packet * @{ */ /** * Allows the user to call a template function whose template parameter * matches a given value of PacketType, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible packet types known to Regina. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new packet type is added then only a small amount of code * needs to be extended to incorporate it. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(PacketInfo) is * defined for any valid PacketType enum value \a t. Then, * when the user calls forPacket(packetType, func, defaultReturn), * this routine will call func(PacketInfo) and pass * back the corresponding return value. If \a packetType does not denote a * valid packet type, then forPacket() will pass back \a defaultReturn instead. * * There is also a two-argument variant of forPacket() that works with * void functions. * * \pre The function object must have a typedef \a ReturnType indicating * the return type of the corresponding templated unary bracket operator. * Inheriting from Returns<...> is a convenient way to ensure this. * * \ifacespython Not present. * * @param packetType the given packet type. * @param func the function object whose unary bracket operator we will * call with a PacketInfo object. * @param defaultReturn the value to return if the given packet type * is not valid. * @return the return value from the corresponding unary bracket * operator of \a func, or \a defaultReturn if the given packet type * is not valid. */ template typename FunctionObject::ReturnType forPacket( PacketType packetType, FunctionObject func, typename FunctionObject::ReturnType defaultReturn); /** * Allows the user to call a template function whose template parameter * matches a given value of PacketType, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible packet types known to Regina. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new packet type is added then only a small amount of code * needs to be extended to incorporate it. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(PacketInfo) is * defined for any valid PacketType enum value \a t. Then, * when the user calls forPacket(packetType, func), * this routine will call func(PacketInfo) in turn. * If \a packetType does not denote a valid packet type, then forPacket() * will do nothing. * * There is also a three-argument variant of forPacket() that works with * functions with return values. * * \ifacespython Not present. * * @param packetType the given packet type. * @param func the function object whose unary bracket operator we will * call with a PacketInfo object. */ template void forPacket(PacketType packetType, VoidFunctionObject func); /*@}*/ } // namespace regina // Import template implementations: #include "packet/packetregistry-impl.h" #endif regina-4.95/engine/packet/packettype.h000644 000765 000024 00000007431 12236713375 017663 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file packet/packettype.h * \brief Defines constants for the various packet types known to Regina. */ #ifndef __PACKETTYPE_H #ifndef __DOXYGEN #define __PACKETTYPE_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup packet * @{ */ /** * Represents the different types of packet that are available in Regina. * * IDs 0-9999 are reserved for future use by Regina. If you are extending * Regina to include your own packet type, you should choose an ID >= 10000. */ enum PacketType { /** * Represents a container packet, of class NContainer. */ PACKET_CONTAINER = 1, /** * Represents a text packet, of class NText. */ PACKET_TEXT = 2, /** * Represents a 3-manifold triangulation, of class NTriangulation. */ PACKET_TRIANGULATION = 3, /** * Represents a normal surface list, of class NNormalSurfaceList. */ PACKET_NORMALSURFACELIST = 6, /** * Represents a script packet, of class NScript. */ PACKET_SCRIPT = 7, /** * Represents a normal surface filter, of class NSurfaceFilter or * one of its descendant classes. */ PACKET_SURFACEFILTER = 8, /** * Represents an angle structure list, of class NAngleStructureList. */ PACKET_ANGLESTRUCTURELIST = 9, /** * Represents a PDF document, of class NPDF. */ PACKET_PDF = 10, /** * Represents a 2-manifold triangulation, of class Dim2Triangulation. */ PACKET_DIM2TRIANGULATION = 15 }; /*@}*/ } // namespace regina #endif regina-4.95/engine/progress/CMakeLists.txt000644 000765 000024 00000000761 12234011536 020461 0ustar00babstaff000000 000000 # progress # Files to compile SET ( FILES nprogresstypes ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} progress/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nprogress.h nprogressmanager.h nprogresstypes.h nprogresstracker.h DESTINATION ${INCLUDEDIR}/progress COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/progress/nprogress.h000644 000765 000024 00000036364 12234011536 020124 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file progress/nprogress.h * \brief Allows external interfaces to obtain progress reports when * running long calculations. */ #ifndef __NPROGRESS_H #ifndef __DOXYGEN #define __NPROGRESS_H #endif #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/nthread.h" namespace regina { /** * \addtogroup progress Progress Management * Progress reports during long calculations. * @{ */ /** * An object through which external interfaces can obtain progress * reports when running long calculations. * The running calculation writes to this object to * store the current state of progress, and the external interface reads * from this object from a different thread. * * When writing progress information to an NProgress object, the last * call should be to setFinished(). This informs all threads * that the operation is finished and that the NProgress object can be * deleted without the risk that the writing thread will attempt to * access it again. * * If the operation allows it, the reading thread * may at any time request that the operation be cancelled by calling * cancel(). The writing thread should regularly poll isCancelled(), * and if it detects a cancellation request should exit cleanly as soon * as possible. Note that the writing thread should still call * setFinished() in this situation. * * NProgress contains multithreading support; a mutex is used to ensure * that the reading and writing threads do not interfere. * * NProgress also contains timing support, with measurements in both * real time and CPU time. See the routines getRealTime() and * totalCPUTime() for details. * * Subclasses of NProgress represent the various ways in which progress * can be internally stored. Note that subclass member functions * must lock the mutex whenever internal data is being * accessed or modified (see NMutex::MutexLock for how this is done). * Any public subclass member function that changes the state of * progress must set the \a changed flag to \c true, and all public * subclass query functions must set the \a changed flag to \c false. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ class REGINA_API NProgress : public ShareableObject, protected NMutex { protected: mutable bool changed; /**< Has the state of progress changed since the last query? */ bool cancelled; /**< Has this operation been cancelled? */ private: bool finished; /**< Is the operation whose progress we are reporting * completely finished? */ time_t startReal; /**< A real time marker representing when this progress object was constructed. */ clock_t startCPU; /**< A CPU time marker representing when this progress object was constructed. */ time_t endReal; /**< A real time marker representing when this progress object was marked finished. */ clock_t endCPU; /**< A CPU time marker representing when this progress object was marked finished. */ public: /** * Performs basic initialisation. * Note that the internal mutex is not locked during construction. * * The internal state-has-changed flag is set to \c true. * * \ifacespython Not present; NProgress objects should only be * created within calculation engine routines whose progress is * being watched. */ NProgress(); /** * Destroys this object. */ virtual ~NProgress(); /** * Determines if the state of progress has changed since the * last query. A query is defined to be a call to * getDescription(), getPercent() or any of the * subclass-specific query routines. * * This routine allows interfaces to avoid calls to the slower * query routines when they can avoid it. * * If no query has yet been made, this routine will return \c true. * * @return \c true if and only if the state of progress has * changed since the last query. */ bool hasChanged() const; /** * Is the operation whose progress we are reporting completely * finished? * * Once this routine returns \c true, it will always return \c * true; thus there will be no need to call it again. * * @return \c true if and only if the operation is finished. */ bool isFinished() const; /** * Signifies that the operation whose progress we are reporting * is completely finished. This must be the final * member function call to this NProgress object made by the thread * performing the corresponding operation. It notifies all * other threads that the operation is complete and that this * NProgress object can be safely deleted. * * This routine should still be called by the operation thread * if it cancels itself in response to a request by an external * interface (see cancel()). * * \ifacespython Not present; this should only be called from * within the calculation engine routine whose progress is being * watched. */ void setFinished(); /** * Called by an external interface to request that the operation * whose progress we are reporting be cancelled. * The operation itself should regularly poll * isCancelled() to check if an external interface has made this * request. * * Note that if cancellation is not sensible or appropriate, the * operation may freely ignore such cancellation requests and need * not poll isCancelled() at all. * * This routine is made const since an external interface should be * able to cancel an operation even though it should never * modify the state of progress. */ void cancel() const; /** * Determines whether an external interface has requested that * the operation whose progress we are reporting be cancelled. * * If the operation is polling for cancellation requests and it finds * that isCancelled() returns \c true, it should generally exit * (cleanly) as soon as possible with only partial or no results. * However, if cancellation is not sensible or appropriate, the * operation may freely ignore such cancellation requests. * * Note that even if the underlying operation cancels itself, it * should still call setFinished(). * * @return \c true if and only if an external interface has * requested that the operation be cancelled. */ bool isCancelled() const; /** * Returns a string description of the current state of * progress. Note that subclasses must override * internalGetDescription(), not this routine. * * @return the current state of progress. */ std::string getDescription() const; /** * Determines if the state of progress can be expressed as a * percentage. * * The default implementation returns \c false. * * @return \c true if and only if progress can be expressed as a * percentage. */ virtual bool isPercent() const; /** * Returns the current state of progress as a percentage. * Note that subclasses must override internalGetPercent(), * not this routine. * * \pre Progress can be expressed as a percentage (see isPercent()). * * @return the current state of progress as a percentage. */ double getPercent() const; /** * Returns the real time elapsed since this operation began. * This routine may be called both during and after the * operation. * * If the operation has been marked as finished, the total * elapsed time from start to finish will be reported. * Otherwise the time elasped thus far will be reported. * * @return the total elapsed real time, measured in seconds. * * @see totalCPUTime() */ long getRealTime() const; /** * Returns the total CPU time consumed by the program from the * beginning to the end of this operation. This routine will * only return useful results after the operation has finished. * * If the operation has not yet been marked as finished, this * routine will return 0. * * \warning For CPU time calculations to be correct, the same * thread that constructs this progress object must also mark it * finished. * * @return the total CPU time consumed, measured in seconds. * * @see getRealTime() */ long totalCPUTime() const; void writeTextShort(std::ostream& out) const; protected: /** * Returns a string description of the current state of progress. * * This function must not touch the mutex, and is not required * to alter the \a changed flag. The getDescription() routine takes * care of all of these issues. * * \pre The mutex is currently locked. * * @return the current state of progress. */ virtual std::string internalGetDescription() const = 0; /** * Returns the current state of progress as a percentage. * The default implementation returns 0. * * This function must not touch the mutex, and is not required * to alter the \a changed flag. The getDescription() routine takes * care of all of these issues. * * \pre Progress can be expressed as a percentage (see isPercent()). * \pre The mutex is currently locked. * * @return the current state of progress as a percentage. */ virtual double internalGetPercent() const; }; /** * A progress report that immediately claims it is finished. * There is no need to call setFinished(); this will be done * automatically by the constructor. * * \ifacespython Not present; all progress classes communicate with * external interfaces through the NProgress interface. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ class REGINA_API NProgressFinished : public NProgress { public: /** * Creates a new finished progress report. * This constructor will automatically call setFinished(). */ NProgressFinished(); virtual bool isPercent() const; protected: virtual std::string internalGetDescription() const; virtual double internalGetPercent() const; }; /*@}*/ // Inline functions for NProgress inline NProgress::NProgress() : changed(true), finished(false), cancelled(false) { startReal = std::time(0); startCPU = std::clock(); } inline NProgress::~NProgress() { } inline bool NProgress::hasChanged() const { MutexLock(this); return changed; } inline bool NProgress::isFinished() const { MutexLock(this); return finished; } inline void NProgress::setFinished() { MutexLock(this); endReal = std::time(0); endCPU = std::clock(); finished = true; } inline void NProgress::cancel() const { MutexLock(this); const_cast(this)->cancelled = true; } inline bool NProgress::isCancelled() const { MutexLock(this); return cancelled; } inline std::string NProgress::getDescription() const { MutexLock(this); changed = false; return internalGetDescription(); } inline bool NProgress::isPercent() const { return false; } inline double NProgress::getPercent() const { MutexLock(this); changed = false; return internalGetPercent(); } inline void NProgress::writeTextShort(std::ostream& out) const { out << "Progress: " << getDescription(); } inline double NProgress::internalGetPercent() const { return 0; } inline long NProgress::getRealTime() const { MutexLock(this); return (finished ? endReal : std::time(0)) - startReal; } inline long NProgress::totalCPUTime() const { MutexLock(this); return (finished ? (endCPU - startCPU) / CLOCKS_PER_SEC : 0); }; // Inline functions for NProgressFinished inline NProgressFinished::NProgressFinished() { setFinished(); } inline bool NProgressFinished::isPercent() const { return true; } inline std::string NProgressFinished::internalGetDescription() const { return "Finished."; } inline double NProgressFinished::internalGetPercent() const { return 100; } } // namespace regina #endif regina-4.95/engine/progress/nprogressmanager.h000644 000765 000024 00000017750 12234011536 021455 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file progress/nprogressmanager.h * \brief Facilitates sharing NProgress objects between an * operation thread and an external interface. */ #ifndef __NPROGRESSMANAGER_H #ifndef __DOXYGEN #define __NPROGRESSMANAGER_H #endif #include "regina-core.h" #include "progress/nprogress.h" namespace regina { /** * \weakgroup progress * @{ */ /** * Manages the sharing of an NProgress object between reading and * writing threads. * * The life cycle of an NProgressManager and the corresponding NProgress * is as follows. Note that the reading thread is the interface * thread that is querying the state of progress, and the writing * thread is the thread in which the operation is actually being * performed. * * - Before the operation begins, an NProgressManager is created and * both the reading and writing threads have access to it. This can * be achieved for instance by having the reading thread create an * NProgressManager and pass it to the writing thread as a parameter * of the actual operation function call. * - The writing thread: * - creates a new NProgress and stores it in the NProgressManager * using setProgress(); * - updates the NProgress throughout the operation; * - finally calls NProgress::setFinished() when the operation is * complete; * - does not touch either the NProgress or the NProgressManager * again. * - The reading thread: * - repeatedly calls isStarted() and waits until this returns \c true; * - now knows the NProgress has been created and begins querying it * using getProgress() and displaying progress reports; * - whilst querying, repeatedly calls isFinished() to test if the * operation is complete; * - once isFinished() returns \c true, displays the final progress * message to the user and destroys the NProgressManager. * - Note that the NProgressManager destructor will automatically destroy the * NProgress. It is imperative that the writing thread does not touch * either the NProgress or the NProgressManager after calling * NProgress::setFinished(), since once isFinished() * returns \c true the reading * thread might destroy the NProgressManager and thus the NProgress at * any time. The writing thread cannot destroy these objects because * it has no guarantee that the reading thread is not still reading * progress reports from them. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ class NProgressManager : public ShareableObject { private: NProgress* progress; /**< The progress report object that we are managing. */ public: /** * Creates a new progress manager with no NProgress to manage. */ NProgressManager(); /** * Destroys this manager as well as the corresponding NProgress. * * \pre There is an NProgress assigned to this manager; that is, * isStarted() returns \c true. * \pre The NProgress that we are managing has finished, that is, * isFinished() returns \c true. */ ~NProgressManager(); /** * Determines if an NProgress has been assigned to this * manager yet. * * Once this routine returns \c true, it will always return \c * true; thus there will be no need to call it again. * * @return \c true if and only if an NProgress has been assigned * to this manager. */ bool isStarted() const; /** * Determines if the NProgress that we are managing has finished. * That is, this routine determines if NProgress::isFinished() * returns \c true. * * Once this routine returns \c true, it will always return \c * true; thus there will be no need to call it again. * * \pre There is an NProgress assigned to this manager; that is, * isStarted() returns \c true. * * @return \c true if and only if the NProgress that we are * managing has finished. */ bool isFinished() const; /** * Returns the NProgress that this manager is managing. * If isStarted() returns \c true, you are guaranteed that * this routine will not return zero. * * @return the NProgress that this manager is managing, or 0 if * an NProgress has not yet been assigned to this manager. */ const NProgress* getProgress() const; /** * Assigns the given NProgress to this manager to manage. * * \pre setProgress() has not already been called. * * \ifacespython Not present; this routine should only be called * from within calculation engine routines whose progress is * being watched. * * @param newProgress the NProgress that this manager will manage. */ void setProgress(NProgress* newProgress); void writeTextShort(std::ostream& out) const; }; /*@}*/ // Inline functions for NProgressManager inline NProgressManager::NProgressManager() : progress(0) { } inline NProgressManager::~NProgressManager() { if (progress) delete progress; } inline bool NProgressManager::isStarted() const { return (progress != 0); } inline bool NProgressManager::isFinished() const { return progress->isFinished(); } inline const NProgress* NProgressManager::getProgress() const { return progress; } inline void NProgressManager::setProgress(NProgress* newProgress) { progress = newProgress; } inline void NProgressManager::writeTextShort(std::ostream& out) const { out << "[Progress Manager]"; } } // namespace regina #endif regina-4.95/engine/progress/nprogresstracker.h000644 000765 000024 00000035002 12234011536 021464 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file progress/nprogresstracker.h * \brief Facilitates progress tracking and cancellation for long operations. */ #ifndef __NPROGRESSTRACKER_H #ifndef __DOXYGEN #define __NPROGRESSTRACKER_H #endif #include "regina-core.h" #include "utilities/nthread.h" #include namespace regina { /** * \weakgroup progress * @{ */ /** * Manages progress tracking and cancellation polling for long operations. * A typical progress tracker is simultaneously used by a \e writing thread, * which is performing the long calculations, and a \e reading thread, * which displays progress updates to the user and/or takes cancellation * requests from the user. * * Progress works through a series of \e stages. Each stage has a text * description, as well as a percentage progress that rises from 0 to 100 * as the stage progresses. Each stage also has a fractional weight * (between 0 and 1 inclusive), and the percentage progress of the entire * calculation is taken to be the weighted sum of the progress of the * individual stages. The weights of all stages should sum to 1. * * The life cycle of an NProgressTracker is as follows. * * - The reading thread creates an NProgressTracker, and passes it to * the writing thread when the calculation begins. * * - The writing thread: * - creates the first stage by calling newStage(); * - as the stage progresses, repeatedly updates the percentage progress of * this stage by calling setPercent() and polls for cancellation by calling * isCancelled(); * - moves through any additional stages in a similar fashion, by * calling newStage() and then repeatedly calling setPercent() and * isCancelled(); * - calls setFinished() once all processing is complete (regardless * of whether the operation finished naturally or was cancelled by * the user); * - never touches the progress tracker again. * * - The reading thread: * - passes the progress tracker to the writing thread; * - repeatedly polls the state of the tracker by calling * percentChanged(), descriptionChanged() and/or isFinished(); * - if percentChanged() returns \c true, collects the total * percentage progress by calling percent() and displays this to the user; * - if descriptionChanged() returns \c true, collects the description of * the current stage by calling description() and displays this to the user; * - if the user ever chooses to cancel the operation, calls cancel() * but continues polling the state of the tracker as above; * - once isFinished() returns \c true, displays any final information * to the user and destroys the NProgressTracker. * * It is imperative that the writing thread does not access the tracker * after calling setFinished(), and it is imperative that the reading * thread does not destroy the tracker until after isFinished() returns * \c true. In particular, even if the reading thread has called cancel(), * it must still wait upon isFinished() before destroying the tracker. * Until isFinished() returns \c true, there is no guarantee that the * writing thread has detected and honoured the cancellation request. */ class REGINA_API NProgressTracker { private: double percent_; /**< The percentage progress through the current stage. Note that this is typically not the same as the percentage progress through the entire operation. */ std::string desc_; /**< The human-readable description of the current stage. */ bool percentChanged_; /**< Has the percentage progress changed since the last call to percentChanged()? */ bool descChanged_; /**< Has the description changed since the last call to descriptionChanged()? */ bool cancelled_; /**< Has the reading thread requested that the operation be cancelled? */ bool finished_; /**< Has the writing thread declared that it has finished all processing? */ double prevPercent_; /**< The total percentage progress of all previous stages, measured as a percentage of the entire operation. */ double currWeight_; /**< The fractional weight assigned to the current stage; this must be between 0 and 1 inclusive. */ NMutex lock_; /**< A mutex to stop the reading and writing threads from interfering with each other. */ public: /** * Creates a new progress tracker. * This sets a sensible state description (which declares that * the operation is initialising), and marks the current * progress as zero percent complete. * * This is typically called by the reading thread. */ NProgressTracker(); /** * Queries whether the writing thread has finished all * processing. This will eventually return \c true regardless * of whether the processing finished naturally or was cancelled * by the reading thread. * * This is typically called by the reading thread. * * @return \c true if and only if the writing thread has * finished all processing. */ bool isFinished() const; /** * Queries whether the percentage progress has changed since the * last call to percentChanged(). If this is the first time * percentChanged() is called, the result will be \c true. * * This is typically called by the reading thread. * * @return \c true if and only if the percentage progress has changed. */ bool percentChanged() const; /** * Queries whether the stage description has changed since the * last call to descriptionChanged(). If this is the first time * descriptionChanged() is called, the result will be \c true. * * This is typically called by the reading thread. * * @return \c true if and only if the stage description has changed. */ bool descriptionChanged() const; /** * Returns the percentage progress through the entire operation. * This combines the progress through the current stage with all * previous stages, taking into account the relative weights that the * writing thread has passed to newStage(). * * This is typically called by the reading thread. * * @return \c the current percentage progress. */ double percent() const; /** * Returns the human-readable description of the current stage. * * This is typically called by the reading thread. * * @return \c the current stage description. */ std::string description() const; /** * Indicates to the writing thread that the user wishes to * cancel the operation. The writing thread might not detect * and/or respond to this request immediately (or indeed ever), * and so the reading thread should continue to wait until * isFinished() returns \c true before it cleans up and destroys * this progress tracker. * * This is typically called by the reading thread. */ void cancel(); /** * Used by the writing thread to indicate that it has moved on * to a new stage of processing. The percentage progress through * the current stage will automatically be set to 100. * * This is typically called by the writing thread. * * @param desc a human-readable description of the new stage. * Typically this begins with a capital and does not include a * final period (full stop). * @param weight the relative weight of this stage as a fraction * of the entire operation. This weight must be between 0 and 1 * inclusive, and the weights of \e all stages must sum to 1 * in total. */ void newStage(const char* desc, double weight = 1); /** * Queries whether the reading thread has made a request for the * writing thread to cancel the operation; in other words, whether * cancel() has been called. * * This is typically called by the writing thread. * * @return \c true if and only if a cancellation request has * been made. */ bool isCancelled() const; /** * Used by the writing thread to indicate the level of progress * through the current stage. * * Unlike percent(), which measures progress in the context of * the entire operation, this routine takes a percentage that * is strictly relative to the current stage (i.e., the stage most * recently declared through a call to newStage()). When the * stage begins, setPercent() would typically be given a figure * close to 0; when the stage is finished, setPercent() would * typically be given a figure close to 100. * * There is no actual need to call setPercent(0) at the * beginning of the stage or setPercent(100) at the end of the * stage, since other routines (such as the constructor, * newStage() and setFinished()) will take care of this for you. * * This is typically called by the writing thread. * * @param percent the percentage progress through this stage, as * a number between 0 and 100 inclusive. * @return \c true if there has been no cancellation request, or * \c false if cancel() has been called (typically by the reading * thread). */ bool setPercent(double percent); /** * Used by the writing thread to indicate that it has finished * all processing. The percentage progress through both the * current stage and the entire operation will automatically * be set to 100, and the stage description will be updated to * indicate that the operation is finished. * * This is typically called by the writing thread. */ void setFinished(); }; /*@}*/ // Inline functions for NProgressTracker inline NProgressTracker::NProgressTracker() : percent_(0), percentChanged_(true), descChanged_(true), cancelled_(false), finished_(false), prevPercent_(0), currWeight_(0) { desc_ = "Initialising"; } inline bool NProgressTracker::isFinished() const { NMutex::MutexLock lock(lock_); return finished_; } inline bool NProgressTracker::percentChanged() const { NMutex::MutexLock lock(lock_); if (percentChanged_) { const_cast(this)->percentChanged_ = false; return true; } else return false; } inline bool NProgressTracker::descriptionChanged() const { NMutex::MutexLock lock(lock_); if (descChanged_) { const_cast(this)->descChanged_ = false; return true; } else return false; } inline double NProgressTracker::percent() const { NMutex::MutexLock lock(lock_); return prevPercent_ + currWeight_ * percent_; } inline std::string NProgressTracker::description() const { NMutex::MutexLock lock(lock_); return desc_; } inline void NProgressTracker::cancel() { NMutex::MutexLock lock(lock_); cancelled_ = true; } inline void NProgressTracker::newStage(const char* desc, double weight) { NMutex::MutexLock lock(lock_); desc_ = desc; percent_ = 0; prevPercent_ += 100 * currWeight_; currWeight_ = weight; percentChanged_ = descChanged_ = true; } inline bool NProgressTracker::isCancelled() const { NMutex::MutexLock lock(lock_); return cancelled_; } inline bool NProgressTracker::setPercent(double percent) { NMutex::MutexLock lock(lock_); percent_ = percent; percentChanged_ = true; return ! cancelled_; } inline void NProgressTracker::setFinished() { NMutex::MutexLock lock(lock_); prevPercent_ = 100; currWeight_ = 0; percent_ = 0; desc_ = "Finished"; finished_ = true; percentChanged_ = descChanged_ = true; } } // namespace regina #endif regina-4.95/engine/progress/nprogresstypes.cpp000644 000765 000024 00000005033 12234011536 021531 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "progress/nprogresstypes.h" #include namespace regina { std::string NProgressNumber::internalGetDescription() const { std::ostringstream out; out << completed; if (outOf >= 0) out << '/' << outOf; return out.str(); } } // namespace regina regina-4.95/engine/progress/nprogresstypes.h000644 000765 000024 00000025400 12234011536 021176 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file progress/nprogresstypes.h * \brief Provides specific methods of representing progress reports. */ #ifndef __NPROGRESSTYPES_H #ifndef __DOXYGEN #define __NPROGRESSTYPES_H #endif #include "regina-core.h" #include "progress/nprogress.h" namespace regina { /** * \weakgroup progress * @{ */ /** * A progress report in which the current state of progress is stored as a * string message. * * \ifacespython Not present; all progress classes communicate with * external interfaces through the NProgress interface. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ class REGINA_API NProgressMessage : public NProgress { private: std::string message; /**< The current state of progress. */ public: /** * Creates a new progress report with an empty progress message. * Note that the internal mutex is not locked during construction. */ NProgressMessage(); /** * Creates a new progress report with the given progress message. * Note that the internal mutex is not locked during construction. * * @param newMessage the current state of progress. */ NProgressMessage(const std::string& newMessage); /** * Creates a new progress report with the given progress message. * Note that the internal mutex is not locked during construction. * * @param newMessage the current state of progress. */ NProgressMessage(const char* newMessage); /** * Returns a reference to the current progress message. * * @return the current progress message. */ std::string getMessage() const; /** * Sets the current progress message to the given string. * * @param newMessage the new state of progress. */ void setMessage(const std::string& newMessage); /** * Sets the current progress message to the given string. * * @param newMessage the new state of progress. */ void setMessage(const char* newMessage); protected: virtual std::string internalGetDescription() const; }; /** * A simple structure used for passing around a numeric state of * progress. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ struct REGINA_API NProgressStateNumeric { long completed; /**< The number of items that have already been completed. */ long outOf; /**< The expected total number of items, or -1 if this is not known. */ /** * Initialises a new structure using the given values. * * @param newCompleted the number of items that have already been * completed. * @param newOutOf the expected total number of items, or -1 if this * is not known. */ NProgressStateNumeric(long newCompleted = 0, long newOutOf = -1); }; /** * A progress report in which the current state of progress is stored as * a number of items completed. * The expected total number of items can be optionally specified. * * \ifacespython Not present; all progress classes communicate with * external interfaces through the NProgress interface. * * \deprecated This class is deprecated. Please use the more flexible * and more streamlined NProgressTracker class instead. */ class REGINA_API NProgressNumber : public NProgress { private: long completed; /**< The number of items completed. */ long outOf; /**< The expected total number of items, or -1 if this * total is not known. */ public: /** * Creates a new progress report containing the given details. * Note that the internal mutex is not locked during construction. * * \pre The new number of items completed is non-negative. * \pre If the new expected total is non-negative, then the new * number of items completed is at most the new expected total. * * @param newCompleted the number of items completed; this * defaults to 0. * @param newOutOf the expected total number of items, or -1 if * this total is not known (the default). */ NProgressNumber(long newCompleted = 0, long newOutOf = -1); /** * Returns the number of items completed. * * @return the number of items completed. */ long getCompleted() const; /** * Returns the expected total number of items. * * @return the expected total number of items, or -1 if this * total is not known. */ long getOutOf() const; /** * Returns both the number of items completed and the expected * total number of items. * * @return the current state of progress. */ NProgressStateNumeric getNumericState() const; /** * Sets the number of items completed. * * \pre The new number of items completed is non-negative. * \pre If the expected total is non-negative, then the new * number of items completed is at most the expected total. * * @param newCompleted the number of items completed. */ void setCompleted(long newCompleted); /** * Increases the number of items completed by the given amount. * * \pre If the expected total is non-negative, then the new * total number of items completed is at most the expected total. * * @param extraCompleted the number of items to add to the number of * items already completed. */ void incCompleted(unsigned long extraCompleted = 1); /** * Sets the expected total number of items. * * \pre If the new expected total is non-negative, then the * new expected total is at least the number of items completed. * * @param newOutOf the expected total number of items, or -1 if * this total is not known. */ void setOutOf(long newOutOf); virtual bool isPercent() const; protected: virtual std::string internalGetDescription() const; virtual double internalGetPercent() const; }; /*@}*/ // Inline functions inline NProgressMessage::NProgressMessage() : NProgress() { } inline NProgressMessage::NProgressMessage(const std::string& newMessage) : NProgress(), message(newMessage) { } inline NProgressMessage::NProgressMessage(const char* newMessage) : NProgress(), message(newMessage) { } inline std::string NProgressMessage::getMessage() const { MutexLock(this); changed = false; return message; } inline void NProgressMessage::setMessage(const std::string& newMessage) { MutexLock(this); message = newMessage; changed = true; } inline void NProgressMessage::setMessage(const char* newMessage) { MutexLock(this); message = newMessage; changed = true; } inline std::string NProgressMessage::internalGetDescription() const { return message; } inline NProgressStateNumeric::NProgressStateNumeric(long newCompleted, long newOutOf) : completed(newCompleted), outOf(newOutOf) { } inline NProgressNumber::NProgressNumber(long newCompleted, long newOutOf) : NProgress(), completed(newCompleted), outOf(newOutOf) { } inline long NProgressNumber::getCompleted() const { MutexLock(this); changed = false; return completed; } inline long NProgressNumber::getOutOf() const { MutexLock(this); changed = false; return outOf; } inline NProgressStateNumeric NProgressNumber::getNumericState() const { MutexLock(this); changed = false; return NProgressStateNumeric(completed, outOf); } inline void NProgressNumber::setCompleted(long newCompleted) { MutexLock(this); completed = newCompleted; changed = true; } inline void NProgressNumber::incCompleted(unsigned long extraCompleted) { MutexLock(this); completed += extraCompleted; changed = true; } inline void NProgressNumber::setOutOf(long newOutOf) { MutexLock(this); outOf = newOutOf; changed = true; } inline bool NProgressNumber::isPercent() const { MutexLock(this); return (outOf >= 0); } inline double NProgressNumber::internalGetPercent() const { return (outOf > 0 ? double(completed) * 100 / double(outOf) : double(0)); } } // namespace regina #endif regina-4.95/engine/README.txt000644 000765 000024 00000000270 12234011536 015546 0ustar00babstaff000000 000000 Regina Engine Directory ----------------------- This directory tree contains the C++ sources for the main calculation engine. This is where you will find all the "real mathematics". regina-4.95/engine/regina-config.h.in000644 000765 000024 00000004120 12235500316 017334 0ustar00babstaff000000 000000 /* Define if both signed and unsigned long long types are available. */ #cmakedefine LONG_LONG_FOUND /* Define if both int128_t and uint128_t types are available. */ #cmakedefine INT128_T_FOUND /* Define if both __int128_t and __uint128_t types are available. */ #cmakedefine __INT128_T_FOUND /* Define if native 128-bit arithmetic is available through either of the types defined above. */ #cmakedefine INT128_AVAILABLE /* Define if 64-bit integer literals are available with no suffix */ #cmakedefine NUMERIC_64_FOUND /* Define if 64-bit integer literals are available using the LL suffix */ #cmakedefine NUMERIC_64_LL_FOUND /* Define if Boost.Python is available. */ #cmakedefine BOOST_PYTHON_FOUND /* Define if langinfo.h and nl_langinfo() are available. */ #cmakedefine LANGINFO_FOUND /* Define if we are excluding Normaliz from the build. */ #cmakedefine EXCLUDE_NORMALIZ /* Define if we are excluding SnapPea / SnapPy from the build. */ #cmakedefine EXCLUDE_SNAPPEA /* Define as const if the declaration of iconv() needs const, or empty if not. */ #define ICONV_CONST @ICONV_CONST@ /* Define to Regina's primary home directory on the system. This can always be changed at runtime vi NGlobalDirs::setDirs(). */ #define REGINA_DATADIR "@PKGDATADIR@" /* Define to the directory on the system in which Regina's python module is installed, or the empty string if the module is installed in a standard python location (i.e., it can be found automatically on python's sys.path). This can always be changed at runtime vi NGlobalDirs::setDirs(). */ #define REGINA_PYLIBDIR "@PYLIBDIR@" /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" /* Define to the full name and version of this package. */ #define PACKAGE_STRING "@PACKAGE_STRING@" /* Define to the version of this package. */ #define PACKAGE_VERSION "@PACKAGE_VERSION@" /* Major version number of the package. */ #define PACKAGE_VERSION_MAJOR @PACKAGE_VERSION_MAJOR@ /* Minor version number of the package. */ #define PACKAGE_VERSION_MINOR @PACKAGE_VERSION_MINOR@ regina-4.95/engine/regina-core.h000644 000765 000024 00000011107 12234011536 016415 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file regina-core.h * \brief Core definitions that must be included in every Regina header file. */ #ifndef __REGINA_CORE_H #ifndef __DOXYGEN #define __REGINA_CORE_H #endif /** * \weakgroup engine * @{ */ #ifdef __DOXYGEN // Fake definitions just for doxygen. /** * All non-templated, non-static functions, classes and global variables * that are part of Regina's public interface \b must be declared with * REGINA_API. * In addition, global variables must also be declared with \c extern * as per normal. * Otherwise things may (and in some environments will) break when external * applications try to use Regina with optimisations such as gcc's * -fvisibility=hidden. * * Note: When building the Regina calculation engine shared library, * REGINA_DLL_EXPORTS must be defined (this ensures that API symbols are * marked for export). When importing (using) this library, * REGINA_DLL_EXPORTS must \e not be defined (this ensures that API symbols * are marked for import instead). */ #define REGINA_API /** * Classes and functions that are local to the current compilation unit * and should not be publicly exported may be declared with REGINA_LOCAL. * Use of this macro is optional. */ #define REGINA_LOCAL #else // The real definitions go here. // The macros below are modified from the instructions at // http://gcc.gnu.org/wiki/Visibility (retrieved on 22 May 2011). #if defined _WIN32 || defined __CYGWIN__ #define REGINA_HELPER_DLL_IMPORT __declspec(dllimport) #define REGINA_HELPER_DLL_EXPORT __declspec(dllexport) #define REGINA_HELPER_DLL_LOCAL #else #if __GNUC__ >= 4 #define REGINA_HELPER_DLL_IMPORT __attribute__ ((visibility("default"))) #define REGINA_HELPER_DLL_EXPORT __attribute__ ((visibility("default"))) #define REGINA_HELPER_DLL_LOCAL __attribute__ ((visibility("hidden"))) #else #define REGINA_HELPER_DLL_IMPORT #define REGINA_HELPER_DLL_EXPORT #define REGINA_HELPER_DLL_LOCAL #endif #endif // Assume that the library is always built as a shared library (not static). #ifdef REGINA_DLL_EXPORTS #define REGINA_API REGINA_HELPER_DLL_EXPORT #else #define REGINA_API REGINA_HELPER_DLL_IMPORT #endif #define REGINA_LOCAL REGINA_HELPER_DLL_LOCAL #endif // doxygen /*@}*/ #endif regina-4.95/engine/regina-engine-config.1000644 000765 000024 00000005753 12234011536 020120 0ustar00babstaff000000 000000 .TH REGINA-ENGINE-CONFIG 1 "September 8, 2011" .SH NAME regina-engine-config - get information about the installed version of Regina .SH SYNOPSIS .B regina-engine-config [\fB\-\-prefix\fP[=\fIDIR\fP]] [\fB\-\-exec\-prefix\fP[=\fIDIR\fP]] [\fB\-\-version\fP] [\fB\-\-libs\fP] [\fB\-\-cflags\fP] .SH DESCRIPTION .PP \fIregina-engine-config\fP tells you which compiler and linker flags should be used to build the mathematical engine of Regina into your own programs. Regina's mathematical engine is in the library \fIlibregina-engine.so\fP. .PP Regina is a software package for studying 3-manifold triangulations and normal surfaces. Other key features include angle structures, census enumeration, combinatorial recognition of triangulations, and high-level tasks such as 3-sphere recognition and connected sum decomposition. .SH OPTIONS \fIregina-engine-config\fP accepts the following options. .TP \fB\-\-prefix\fP=\fIDIR\fP Use DIR instead of the installation prefix that Regina was built with when computing the output for the \-\-cflags and \-\-libs options. This option is also used for the exec prefix if \-\-exec\-prefix is not specified. This option must be specified before any \-\-libs or \-\-cflags options. If no DIR is specified then the current installation prefix will be written to standard output. Most people will not need this option. .TP \fB\-\-exec\-prefix\fP=\fIDIR\fP Use DIR instead of the installation exec prefix that Regina was built with when computing the output for the \-\-cflags and \-\-libs options. This option must be specified before any \-\-libs or \-\-cflags options. If no DIR is specified then the current installation exec prefix will be written to standard output. Most people will not need this option. .TP .B \-\-version Print the currently installed version of Regina. .TP .B \-\-libs Print the linker flags that are required to link a program against the Regina calculation engine. .TP .B \-\-cflags Print the compiler flags that are required to compile a program that uses the Regina calculation engine. .SH SEE ALSO Regina includes a detailed users' handbook, which may be accessed through Regina's help menu or read online at \fIhttp://regina.sourceforge.net/\fP . .SH AUTHOR This utility was written by Ben Burton , and is licensed under the GNU General Public License. Many people have been involved in the development of Regina; see the users' handbook for a full list of acknowledgements. .PP This script regina-engine-config and its manual page were modified from \fIgtk-config\fP, which contains the following notice: .PP .RS Copyright \(co 1998 Owen Taylor .PP Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. .RE .PP Note that the permission statement above applies only to regina-engine-config, and not to Regina as a whole. regina-4.95/engine/regina-engine-config.in000644 000765 000024 00000004166 12234011536 020363 0ustar00babstaff000000 000000 #!/bin/sh # # This script is based on gtk-config, whose documentation contains the # following notice: # # Copyright (C) 1998 Owen Taylor # # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appear in all copies and that # both that copyright notice and this permission notice appear in # supporting documentation. # # Changes to this script were made by Ben Burton and are # released under the same license as above. prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix_set=no usage() { cat <&2 fi while test $# -gt 0; do case "$1" in -*=*) optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'` ;; *) optarg= ;; esac case $1 in --prefix=*) prefix=$optarg if test $exec_prefix_set = no ; then exec_prefix=$optarg fi ;; --prefix) echo_prefix=yes ;; --exec-prefix=*) exec_prefix=$optarg exec_prefix_set=yes ;; --exec-prefix) echo_exec_prefix=yes ;; --version) echo @PACKAGE_VERSION@ ;; --cflags) echo_cflags=yes ;; --libs) echo_libs=yes ;; *) usage 1 1>&2 ;; esac shift done if test "$echo_prefix" = "yes"; then echo $prefix fi if test "$echo_exec_prefix" = "yes"; then echo $exec_prefix fi regina_engine_libs="-lregina-engine @RECONFIG_LINK_FLAGS@" if test "$echo_cflags" = "yes"; then includes="@RECONFIG_INCLUDE_FLAGS@" includes="-I@INCLUDEDIR@ $includes" echo $includes fi if test "$echo_libs" = "yes"; then libdirs=-L@LIBDIR@ my_regina_engine_libs= for i in $regina_engine_libs ; do if test "x$i" != "x-L@LIBDIR@" ; then if test -z "$my_regina_engine_libs" ; then my_regina_engine_libs="$i" else my_regina_engine_libs="$my_regina_engine_libs $i" fi fi done echo $libdirs $my_regina_engine_libs fi regina-4.95/engine/shareableobject.cpp000644 000765 000024 00000005113 12234011536 017672 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "shareableobject.h" namespace regina { std::string ShareableObject::str() const { std::ostringstream out; writeTextShort(out); return out.str(); } std::string ShareableObject::detail() const { std::ostringstream out; writeTextLong(out); return out.str(); } } // namespace regina regina-4.95/engine/shareableobject.h000644 000765 000024 00000014626 12234011536 017350 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file shareableobject.h * \brief Deals with objects that can be shared from the * calculation engine with the outside world. */ #ifndef __SHAREABLEOBJECT_H #ifndef __DOXYGEN #define __SHAREABLEOBJECT_H #endif #include #include #include "regina-core.h" #include "utilities/boostutils.h" namespace regina { /** * \weakgroup engine * @{ */ /** * Facilitates mirroring objects in the underlying C++ calculation * engine using the various wrapper classes provided in the various * external interfaces (such as the Python interface). * * In the underlying C++ engine, a ShareableObject is an object that can * be shared with the outside world. In the external * interfaces, a ShareableObject is a vacuous wrapper that allows access * to the data and methods of the corresponding object in the underlying * engine. * * See the various interface notes pages for more details regarding * using classes derived from ShareableObject. */ class REGINA_API ShareableObject : public regina::boost::noncopyable { public: /** * Default constructor that does nothing. * * \ifacespython Not present. */ ShareableObject(); /** * Default destructor that does nothing. */ virtual ~ShareableObject(); /** * Writes this object in short text format to the given output stream. * The output should be human-readable, should fit on a single line, * and should not end with a newline. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. */ virtual void writeTextShort(std::ostream& out) const = 0; /** * Writes this object in long text format to the given output stream. * The output should provide the user with all the information they * could want. The output should be human-readable, should not * contain extremely long lines (so users can read the output * in a terminal), and should end with a final newline. * * The default implementation of this routine merely calls * writeTextShort() and adds a newline. * * \ifacespython The parameter \a out does not exist; * standard output will be used. * * @param out the output stream to which to write. */ virtual void writeTextLong(std::ostream& out) const; /** * Returns the output from writeTextShort() as a string. * * \ifacespython This implements the __str__() function. * * @return a short text representation of this object. */ std::string str() const; /** * A deprecated alias for str(), which returns the output from * writeTextShort() as a string. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type str() instead. * * @return a short text representation of this object. */ std::string toString() const; /** * Returns the output from writeTextLong() as a string. * * @return a long text representation of this object. */ std::string detail() const; /** * A deprecated alias for detail(), which returns the output * from writeTextLong() as a string. * * \deprecated This routine has (at long last) been deprecated; * use the simpler-to-type detail() instead. * * @return a long text representation of this object. */ std::string toStringLong() const; }; /*@}*/ // Inline functions for ShareableObject inline ShareableObject::ShareableObject() { } inline ShareableObject::~ShareableObject() { } inline void ShareableObject::writeTextLong(std::ostream& out) const { writeTextShort(out); out << '\n'; } inline std::string ShareableObject::toString() const { return str(); } inline std::string ShareableObject::toStringLong() const { return detail(); } } // namespace regina #endif regina-4.95/engine/snappea/CMakeLists.txt000644 000765 000024 00000000776 12234011536 020252 0ustar00babstaff000000 000000 # snappea # Files to compile SET ( FILES nsnappeatriangulation uimessages) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} snappea/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) ADD_SUBDIRECTORY(kernel) ADD_SUBDIRECTORY(snappy) # Set the variable in the parent directory SET(SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nsnappeatriangulation.h DESTINATION ${INCLUDEDIR}/snappea COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/snappea/kernel/canonize.c000644 000765 000024 00000002522 12235724562 020746 0ustar00babstaff000000 000000 /* * canonize.c * * This file provides the function * * FuncResult canonize(Triangulation *manifold); * * canonize() is a shell, which calls the functions * * proto_canonize() [found in canonize_part_1.c] * canonical_retriangulation() [found in canonize_part_2.c] * * The purpose of these functions is explained in the code below. * For the mathematical details, please see canonize_part_1.c * and canonize_part_2.c. * * canonize() does not preserve the original Triangulation; * if you need to keep it, make a copy with copy_triangulation() * before calling canonize(). */ #include "kernel.h" FuncResult canonize( Triangulation *manifold) { /* * Apply the tilt theorem to compute a Triangulation * which is a subdivision of the canonical cell decomposition. * Please see canonize_part_1.c for details. */ if (proto_canonize(manifold) == func_failed) return func_failed; /* * Replace the given subdivision of the canonical cell * decomposition with the canonical retriangulation. * This operation introduces finite vertices whenever * the canonical cell decomposition is not a triangulation * to begin with. Please see canonize_part_2.c for details. */ canonical_retriangulation(manifold); return func_OK; } regina-4.95/engine/snappea/kernel/canonize.h000644 000765 000024 00000002144 12235724571 020753 0ustar00babstaff000000 000000 /* * canonize.h * * In canonize_part_1.c, canonize_part_2.c, and canonize_result.c, the sum * of the tilts of two Tetrahedra relative to their common face is * considered to be zero iff it lies between -CONCAVITY_EPSILON and * +CONCAVITY_EPSILON. * * 95/10/20 cusp_neighborhoods.c now includes this file as well, * so it can supress the drawing of faces which serve only to subdivide * the canonical cell decomposition into tetrahedra. * * 97/3/1 The processing of the data for 16-crossing knots led * to several examples which sometimes were reported to have * canonical decompositions with cells other than tetrahedra, * and sometimes not. This led me to suspect that CONCAVITY_EPSILON * was too big, so I changed it from 1e-6 to 1e-7. (This problem * hadn't arisen with snappea 1.3.x because the latter normalized * cusp volumes to 1.0, whereas SnapPea 2.x normalizes them to * (3/16)sqrt(3).) Eventually I may need to adopt a more sophisticated * approach to using CONCAVITY_EPSILON, somehow taking into account * the volume of the cusp. */ #define CONCAVITY_EPSILON 1e-7 regina-4.95/engine/snappea/kernel/canonize_part_1.c000644 000765 000024 00000043721 12236247215 022216 0ustar00babstaff000000 000000 /* * canonize_part_1.c * * This file provides the function * * FuncResult proto_canonize(Triangulation *manifold); * * which replaces a Triangulation by the canonical triangulation * of the same manifold (if the canonical cell decomposition is a * triangulation) or by an arbitrary subdivision of the canonical * cell decomposition into Tetrahedra (if the canonical cell * decomposition contains cells other than tetrahedra). The * primary purpose of proto_canonize() is to be called by the * function canonize() (in canonize.c), although it can be called * for other reasons, too, such as checking whether a manifold * is a 2-bridge knot or link complement. * * proto_canonize() does not preserve the original Triangulation * (if you want to keep it, copy it with copy_triangulation() before * calling proto_canonize()). The Triangulation must admit a hyperbolic * structure. If one is not already present, proto_canonize() will call * find_complete_hyperbolic_structure() to compute it. * * proto_canonize() returns * * func_OK if the hyperbolic structure is of type * geometric_solution or nongeometric_solution * (in which case it will have found a subdivision * of the canonical cell decomposition) * * func_failed if the hyperbolic structure is of type * flat_solution, degenerate_solution, other_solution, * or no_solution (in which case it will not have * attempted the canonization) * * When proto_canonize() returns func_OK, a subdivision of the * canonical cell decomposition will be present, but because of the * possible need for random retriangulation (see below) I cannot * prove that proto_canonize() will terminate in all cases (but in * practice it always does). * * proto_canonize() uses the algorithm of * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * The Tilt Theorem (contained in the above paper) is generalized * and given a nicer proof in * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * Although the algorithm of "Convex hulls..." works fine in practice, * it has the aesthetic flaw that it does occasionally get stuck on * negatively oriented Tetrahedra, in which case it randomly retriangulates * the manifold and starts over. I've tried to find a new algorithm * which avoids this problem (while retaining the present algorithm's * virtues, namely that it's super fast, and provides an iron-clad * guarantee that the topology of the manifold does not change), but * so far without success. Maybe someday. * * Technical note: The canonization algorithm uses only the complete * hyperbolic structure, but the low-level retriangulation operations * (e.g. two_to_three()) will object if the filled structure is degenerate. * We take a strong precaution: we overwrite the filled structure with * the complete structure and let polish_hyperbolic_structures() recompute * the correct filled structure at the end. This really isn't as * inefficient as it sounds. Either the filled structure will be fairly * close to the complete structure, in which case it will be computed * quickly, or it will be far from the complete structure, in which * case the need to provide valid shape histories will force * polish_hyperbolic_structures() to recompute the filled structure * from scratch anyhow. * * Programming note: The old version of canonize.c shuffled the * tetrahedra about on various queues. The present code does not * do this. Instead, every time it makes some progress (cancellation * or a 2-3 or 3-2 move) it starts the loop over. This causes some * unnecessary scanning of the EdgeClass list, but I feel the wasted * time is small, and is more than compensated for by the reduced * complexity of the code. */ #include "kernel.h" #include "canonize.h" #define MAX_ATTEMPTS 64 #define MAX_RETRIANGULATIONS 64 #define MAX_MOVES 1000 #define ANGLE_EPSILON 1e-6 static FuncResult validate_hyperbolic_structure(Triangulation *manifold); static Boolean attempt_cancellation(Triangulation *manifold); static Boolean attempt_three_to_two(Triangulation *manifold); static Boolean concave_edge(EdgeClass *edge); static Boolean attempt_two_to_three(Triangulation *manifold); static Boolean concave_face(Tetrahedron *tet, FaceIndex f); static double sum_of_tilts(Tetrahedron *tet0, FaceIndex f0); static Boolean would_create_negatively_oriented_tetrahedra(Tetrahedron *tet0, FaceIndex f0); static Boolean validate_canonical_triangulation(Triangulation *manifold); FuncResult proto_canonize( Triangulation *manifold) { /* * 95/10/12 JRW * I added the needs_polishing flag so that the solution will be * polished iff it needs to be. In the past this was no big deal, * but now we want the cusp neighborhoods module to be able to * maintain a canonical triangulation in real time. If no changes * need to be made and all cusps are complete (so we don't have to * worry about restoring the filled solution), we want to get out * of here as quickly as possible. */ Boolean all_done, needs_polishing; int num_attempts, i; num_attempts = 0; needs_polishing = FALSE; do { /* * First make sure that a hyperbolic structure is present, and * all Tetrahedra are positively oriented. Overwrite the filled * structure with the complete one. */ if (manifold->solution_type[complete] == geometric_solution && all_cusps_are_complete(manifold) == TRUE) { /* * This is the express route. * * No polishing will be required if the triangulation is * already canonical, because the hyperbolic structure won't * change and there is no need to restore the filled solution. */ } else { /* * This is the generic algorithm. * * (validate_hyperbolic_structure() overwrites the filled * solution with the complete solution.) */ if (validate_hyperbolic_structure(manifold) == func_failed) { compute_CS_fudge_from_value(manifold); return func_failed; } needs_polishing = TRUE; } /* * Choose cusp cross sections bounding equal volumes, * and use the Tilt Theorem to compute the tilts. * (See "Convex hulls..." for details.) */ allocate_cross_sections(manifold); compute_cross_sections(manifold); compute_tilts(manifold); /* * Keep going through the following loop as long as we * continue to keep improving the triangulation. * Do not perform any operation (i.e. any two_to_three() * move) that would create negatively oriented Tetrahedra. * * It is possible to get into an infinite loop here, doing an * endless series of 2->3 and 3->2 moves, presumably * do to numerical instability. Thus the total number of * moves is capped. */ for(i = 0; i < MAX_MOVES*manifold->num_tetrahedra; i++) { /* * Cancel pairs of Tetrahedra sharing an EdgeClass * of order two. (Since the Triangulation contains * no negatively oriented Tetrahedra, Tetrahedra sharing * an EdgeClass of order two will be within epsilon of * being flat.) */ if (attempt_cancellation(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * Perform 3-2 moves whereever necessary. */ if (attempt_three_to_two(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * Perform 2-3 moves whereever necessary. */ if (attempt_two_to_three(manifold) == TRUE) { needs_polishing = TRUE; continue; } /* * We can't make any more progress. * Break out of the loop, and then check whether we've * really found a subdivision of the canonical cell * decomposition, or whether we've had the misfortune to * get stuck on (potential) negatively oriented Tetrahedra. */ break; } /* * We don't need the VertexCrossSections any more, so * we might as well get rid of them before (possibly) * randomizing the manifold. */ free_cross_sections(manifold); /* * Did we really find a subdivision of the canonical * cell decomposition? * Or did we just get stuck on (potential) negatively * oriented Tetrahedra? */ all_done = validate_canonical_triangulation(manifold); /* * If we got stuck on (potential) negatively oriented * Tetrahedra, randomize the Triangulation and try * again. */ if (all_done == FALSE) randomize_triangulation(manifold); /* * The documentation says that if a hyperbolic structure * with all positively oriented tetrahedra is present, then * proto_canonize() will never fail. And indeed with enough * random retriangulations it should always be able to find * a subdivision of the canonical cell decomposition. But * if a bug shows up somewhere we don't want this loop to run * forever, so if num_attempts exceeds MAX_ATTEMPTS we should * declare a fatal error and quit. */ if (++num_attempts > MAX_ATTEMPTS) uFatalError("proto_canonize", "canonize_part_1"); } while (all_done == FALSE); /* * Clean up. */ if (needs_polishing == TRUE) { /* * polish_hyperbolic_structures() takes responsibility for * the shape_histories. */ tidy_peripheral_curves(manifold); polish_hyperbolic_structures(manifold); /* * If the Chern-Simons invariant is present, update the fudge factor. */ compute_CS_fudge_from_value(manifold); } return func_OK; } static FuncResult validate_hyperbolic_structure( Triangulation *manifold) { int i; /* * First make sure some sort of solution is in place. */ if (manifold->solution_type[complete] == not_attempted) find_complete_hyperbolic_structure(manifold); /* * If the solution is something other than geometric_solution * or nongeometric_solution, we're out of luck. */ if (manifold->solution_type[complete] != geometric_solution && manifold->solution_type[complete] != nongeometric_solution) return func_failed; /* * Overwrite the filled structure with the complete structure * to keep the low-level retriangulation routines happy. * (See the technical note at the top of this file for a * more complete explanation.) */ copy_solution(manifold, complete, filled); /* * If all Tetrahedra are positively oriented, we're golden. */ if (manifold->solution_type[complete] == geometric_solution) return func_OK; /* * Try to find a geometric_solution by randomizing the Triangulation. * If we can't find one within MAX_RETRIANGULATIONS randomizations, * give up and return func_failed. */ for (i = 0; i < MAX_RETRIANGULATIONS; i++) { randomize_triangulation(manifold); if (manifold->solution_type[complete] == geometric_solution) return func_OK; } /* * Before we go, we'd better restore the filled solution. */ polish_hyperbolic_structures(manifold); return func_failed; } static Boolean attempt_cancellation( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 2) if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; return FALSE; } static Boolean attempt_three_to_two( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; /* * Note: It's easy to prove that if the three original Tetrahedra * are positively oriented, then the two new Tetrahedra must be * positively oriented as well. So we needn't worry about negatively * oriented Tetrahedra here. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 3) if (concave_edge(edge) == TRUE) { if (three_to_two(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * The only reason three_to_two() can fail is that the * three Tetrahedra surrounding the EdgeClass are not * distinct. But by Corolloary 3 of "Convex hulls..." * this cannot happen where the hull is concave. */ uFatalError("attempt_three_to_two", "canonize_part_1"); } return FALSE; } static Boolean concave_edge( EdgeClass *edge) { Tetrahedron *tet; FaceIndex f; /* * The hull in Minkowski space will be concave at an EdgeClass * of order 3 iff it is concave at each of the ideal 2-simplices * incident to the EdgeClass. */ tet = edge->incident_tet; f = one_face_at_edge[edge->incident_edge_index]; /* * According to Section 5 of "Convex hulls..." or Proposition 1.2 * of "Canonical cell decompositions...", a dihedral angle on the * hull will be concave iff the sum of the tilts is positive. * * We want to create a triangulation with as few Tetrahedra as possible, * so when the sum of the tilts is zero, we should return TRUE so the * three_to_two() move will be performed. Thus we return TRUE iff * the sum of the tilts is greater than some small negative epsilon. */ return ( sum_of_tilts(tet, f) > - CONCAVITY_EPSILON ); } static Boolean attempt_two_to_three( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (concave_face(tet, f) == TRUE && would_create_negatively_oriented_tetrahedra(tet, f) == FALSE) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * We should never get to this point. */ uFatalError("attempt_two_to_three", "canonize_part_1.c"); } return FALSE; } static Boolean concave_face( Tetrahedron *tet, FaceIndex f) { /* * According to Section 5 of "Convex hulls..." or Proposition 1.2 * of "Canonical cell decompositions...", a dihedral angle on the * hull will be concave iff the sum of the tilts is positive. * * If the sum of the tilts is zero we want to return FALSE, * to avoid an unnecessary two_to_three() move. * So we check whether the sum is greater than some small * positive epsilon. */ return ( sum_of_tilts(tet, f) > CONCAVITY_EPSILON ); } static double sum_of_tilts( Tetrahedron *tet0, FaceIndex f0) { Tetrahedron *tet1; FaceIndex f1; tet1 = tet0->neighbor[f0]; f1 = EVALUATE(tet0->gluing[f0], f0); return ( tet0->tilt[f0] + tet1->tilt[f1] ); } static Boolean would_create_negatively_oriented_tetrahedra( Tetrahedron *tet0, FaceIndex f0) { Permutation gluing; Tetrahedron *tet1; FaceIndex f1, side0, side1; gluing = tet0->gluing[f0]; tet1 = tet0->neighbor[f0]; f1 = EVALUATE(gluing, f0); /* * tet0 and tet1 meet at a common 2-simplex. For each edge * of that 2-simplex, add the incident dihedral angles of * tet0 and tet1. If any such sum is greater than pi, then * the two_to_three() move would create a negatively oriented * Tetrahedron on that side, and we return TRUE. Otherwise * no negatively oriented Tetrahedra will be created, and we * return FALSE. * * Choose ANGLE_EPSILON to allow the creation of Tetrahedra which * are just barely negatively oriented, but essentially flat. */ for (side0 = 0; side0 < 4; side0++) { if (side0 == f0) continue; side1 = EVALUATE(gluing, side0); if (tet0->shape[complete]->cwl[ultimate][edge3_between_faces[f0][side0]].log.imag + tet1->shape[complete]->cwl[ultimate][edge3_between_faces[f1][side1]].log.imag > PI + ANGLE_EPSILON) return TRUE; } return FALSE; } static Boolean validate_canonical_triangulation( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; /* * Check whether the sum of the tilts is nonnegative at each 2-simplex. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (concave_face(tet, f) == TRUE) return FALSE; return TRUE; } regina-4.95/engine/snappea/kernel/canonize_part_2.c000644 000765 000024 00000052174 12235724562 022225 0ustar00babstaff000000 000000 /* * canonize_part_2.c * * This file provides the function * * void canonical_retriangulation(Triangulation *manifold); * * which accepts an arbitrary subdivision of a canonical cell decomposition * into Tetrahedra, and replaces it with a canonical retriangulation * (defined below). If the canonical cell decomposition is itself a * triangulation (as is typically the case) then the canonical retriangulation * is just the canonical cell decomposition itself. If the canonical cell * decomposition is not a triangulation, the canonical retriangulation will * introduce a finite vertex at the center of each 3-cell. * canonical_retriangulation() is intended to follow a call to proto_canonize() * (cf. the function canonize() in canonize.c); it assumes the tet->tilt[] * fields are correct. * * If *manifold has a hyperbolic structure and/or VertexCrossSections, * they are discarded. * * * Definition of the canonical retriangulation. * * The canonical cell decomposition of a cusped hyperbolic 3-manifold * is defined in * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * and * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * If the canonical cell decomposition is a triangulation (as is * typically the case), then the canonical retriangulation is just * the canonical cell decomposition itself. Otherwise, the canonical * retriangulation is defined by the following two step procedure. * * Step #1. Subdivide each 3-cell by coning its boundary to a point * in its interior. * * Step #2. Each 2-cell in the canonical cell decomposition (just * the original 2-cells, not the 2-cells introduced in Step #1) * is the boundary between two 3-cells created at Step #1. * The union of the two 3-cells is the suspension of the * 2-cell between two of the vertices introduced in Step #1. * Triangulate this suspension in the "obvious" symmetrical way, * as n tetrahedra surrounding a common edge, where n is the * number of sides of the 2-cell, and the common edge runs * from one finite vertex to the other. * * This two-step procedure defines a canonical retriangulation of * the canonical cell decomposition. Note that it's not a subdivision. * * * Computing the canonical retriangulation. * * The following is a mathematical explanation of the algorithm. * The details of the implementation are documented in the code itself. * * We begin with an arbitrary subdivision of the canonical cell * decomposition into Tetrahedra. * * Definition. A 2-cell in the Triangulation is called "opaque" if * it lies in the 2-skeleton of the canonical cell decomposition, * and "transparent" if lies in the interior of a 3-cell of the * canonical decomposition. * * Step #1. * To cone a 3-cell to a point in its interior, first do a one-to-four * move to cone a single Tetrahedron to a point in its interior. * If that was the only Tetrahedron in the 3-cell, we're done. * Otherwise, perform a two-to-three move across a transparent face of * the coned tetrahedron. This yields a coned hexahedron. Continue * in this fashion until all the Tetrahedra in the 3-cell have been * absorbed into the coned polyhedron. This coned polyhedron may have * some pair of faces on its boundary identified to yield the original * 3-cell. Where this occurs, call cancel_tetrahedra() to simplify the * coned polyhedron. (This operation is documented more thoroughly * in the code itself.) * * Step #2. * Do a two-to-three move across each opaque face, then cancel * all pairs of Tetrahedra surrounding EdgeClasses of order 2. * You may take it as an exercise for the reader to prove that this * has the desired effect, or you may read the explanation provided * in the function step_two() below. * * * Programming note: I have coded this algorithm for simplicity * rather than speed. But even though the code is "less efficient" * because it does, e.g., some unnecessary rescanning of lists, I don't * think this will make a measurable difference in practice, and * in any case I think it's a small price to pay to keep the logic * of the code clean and simple. */ #include "kernel.h" #include "canonize.h" static void remove_vertex_cross_sections(Triangulation *manifold); static void attach_canonize_info(Triangulation *manifold); static void free_canonize_info(Triangulation *manifold); static void label_opaque_faces(Triangulation *manifold); static void step_one(Triangulation *manifold); static void initialize_tet_status(Triangulation *manifold); static Boolean cone_3_cell(Triangulation *manifold, int *num_finite_vertices); static Boolean find_unconed_tet(Triangulation *manifold, Tetrahedron **tet0); static Boolean expand_coned_region(Triangulation *manifold); static Boolean attempt_cancellation(Triangulation *manifold); static Boolean verify_coned_region(Triangulation *manifold); static void step_two(Triangulation *manifold); static Boolean eliminate_opaque_face(Triangulation *manifold); void canonical_retriangulation( Triangulation *manifold) { /* * Remove the hyperbolic structures and VertexCrossSections, if any. */ remove_hyperbolic_structures(manifold); remove_vertex_cross_sections(manifold); /* * If the canonical cell decomposition is a triangulation, we're done. * (Note: Comment out this line if you want to invoke the more * elaborate canonical retriangulation scheme for all manifolds, not * just those whose canonical cell decompositions contain cells other * than tetrahedra.) */ if (is_canonical_triangulation(manifold) == TRUE) return; /* * Add a CanonizeInfo field to each Tetrahedron to hold local variables. * These variables must be accessible to the low-level retriangulation * routines in simplify_triangulation.c, which is why we use the * special purpose pointer canonize_info in the Tetrahedron data * structure, rather than using the general purpose "extra" pointer. */ attach_canonize_info(manifold); /* * Note which 2-cells are opaque and which are transparent. */ label_opaque_faces(manifold); /* * Carry out the two step retriangulation algorithm described above. */ step_one(manifold); step_two(manifold); /* * Free the CanonizeInfo fields. */ free_canonize_info(manifold); /* * We can't possibly compute the Chern-Simons invariant * for a manifold with finite vertices, so we set * CS_fudge_is_known to FALSE. However, we can leave * CS_value_is_known as TRUE if it is already TRUE, since * the manifold is still the same. */ manifold->CS_fudge_is_known = FALSE; } static void remove_vertex_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->cross_section != NULL) { my_free(tet->cross_section); tet->cross_section = NULL; } } static void attach_canonize_info( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just to be safe . . . */ if (tet->canonize_info != NULL) uFatalError("attach_canonize_info", "canonize_part_2"); /* * Attach the CanonizeInfo. */ tet->canonize_info = NEW_STRUCT(CanonizeInfo); } } static void free_canonize_info( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the CanonizeInfo structure. */ my_free(tet->canonize_info); /* * Set the canonize_info pointer to NULL just to be safe. */ tet->canonize_info = NULL; } } static void label_opaque_faces( Triangulation *manifold) { Tetrahedron *tet, *nbr_tet; FaceIndex f, nbr_f; double sum_of_tilts; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); sum_of_tilts = tet->tilt[f] + nbr_tet->tilt[nbr_f]; tet->canonize_info->face_status[f] = sum_of_tilts < - CONCAVITY_EPSILON ? opaque_face : transparent_face; } } static void step_one( Triangulation *manifold) { int num_finite_vertices; /* * Initialize each part_of_coned_cell flag to FALSE. */ initialize_tet_status(manifold); /* * Keep track of the number of finite vertices that * have been introduced, so they can be given consecutive * negative integers as indices. */ num_finite_vertices = 0; /* * Cone each 3-cell of the canonical cell decomposition to a point * in its interior. Each call to cone_3_cell() cones a single 3-cell, * so we must keep calling cone_3_cell() until it returns FALSE. */ while (cone_3_cell(manifold, &num_finite_vertices) == TRUE) ; } static void initialize_tet_status( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->canonize_info->part_of_coned_cell = FALSE; } static Boolean cone_3_cell( Triangulation *manifold, int *num_finite_vertices) { Tetrahedron *tet0; /* * Is there a Tetrahedron which is not yet part of a coned cell? * If so, proceed. * If not, return FALSE. */ if (find_unconed_tet(manifold, &tet0) == FALSE) return FALSE; /* * Cone tet0 to its center. This will introduce a finite vertex. * Each of the four new Tetrahedra will have part_of_coned_cell * set to TRUE. The face_status for the "exterior" faces will * be preserved, and the face_status for the "interior" faces * will be set to inside_cone_face. */ one_to_four(tet0, &manifold->num_tetrahedra, -++*num_finite_vertices); /* * Expand the coned region. Whenever a Tetrahedron with * part_of_coned_cell == TRUE borders a Tetrahedron with * part_of_coned_cell == FALSE across a transparent face, * do a two-to-three move across the face to include the * (space occupied by the) latter Tetrahedron in the (growing) * coned region. Keep doing this as long as progress is * being made. The two-to-three move will set * part_of_coned_cell = TRUE for the three new Tetrahedra * it creates. It will preserve the face_status of the * "exterior" faces of the new Tetrahedra, and set the face_status * of the "interior" faces to inside_cone_face. * * (Yes, I know this isn't the optimally efficient way to do this, * but I don't think that's important. Please see the programming * note at the end of the documentation at the top of this file.) */ while (expand_coned_region(manifold) == TRUE) ; /* * If the initial, arbitrary subdivision of the 3-cell contained * edges in its interior (as would be the case with an octahedron, * for example), then the coned 3-cell we just produced will have * some identifications on its boundary (for example, in the case * of the octahedron we'd have a coned decahedron with two adjacent * boundary faces identified to yield the octahedron). Assuming at * least one pair of identified faces are adjacent, we can call * cancel_tetrahedra() to simplify the structure of the coned region * from a coned n-hedron with k pairs of faces identified to a coned * (n-2)-hedron with (k-1) pairs of faces identified. As long as some * pair of identified faces are adjacent (and identified in the obvious * way) we can keep repeating the simplification to arrive at a coned * (n-2k)-hedron with no faces identified, which is exactly what * we want in Step #1 of this algorithm. * * It's theoretically possible to have faces of a coned polyehdron * identified with no pair of identified faces adjacent to each other. * For example, consider the complement of the house-with-two-rooms * in the 3-sphere. Fortunately such examples are so rare and so * complicated that I doubt any will ever show up as triangulations * of 3-cells in canonical cell decompositions. If one did show up, * verify_coned_region() would detect the condition and call uFatalError(). * * Proposition. Identifying two adjacent faces (in the "obvious" way) * creates an EdgeClass of order 2, and this is the only way * EdgeClasses of order 2 arise. * * Proof. It's obvious that such an identification creates an * EdgeClass of order 2. We must prove that this is the only way * they may arise. Consider separately EdgeClasses from the * original Triangulation (i.e. from the original arbitrary subdivision * of the canonical cell decomposition), which connect one ideal * vertex to another, and EdgeClasses which are added during the * coning process, which connect an ideal vertex to a finite vertex. * * Case 1. Original EdgeClasses, which connect one ideal vertex * to another. There are no EdgeClasses of order 2 in the initial * Triangulation, because it is geometric. In a coned polyhedron * (ignoring boundary identifications for the moment) each * EdgeClass connecting one ideal vertex to another is incident * to precisely two of the coned polyhedron's Tetrahedra. If the * EdgeClass lies on the boundary of the 3-cell (of the canonical * cell decomposition) then its true order will be greater than two. * If it lies in the interior of the 3-cell, then the only way for * the order to be exactly two is to have the two adjacent faces * identified. * * Case 2. EdgeClasses added during the coning process, which * connect an ideal vertex to a finite vertex. We assume such * an EdgeClass has order 2, and will deduce a contradiction. * Consider the coned polyhedron, ignoring boundary identifications * for the moment. Consider the two faces on the boundary of * incident to the ideal endpoint of the given EdgeClass. These * faces are part of the original (geometric!) subdivision of the * canonical cell decomposition. They have all three ideal vertices * in common, therefore they must coincide. But this implies that * the boundary component at the ideal vertex of the given EdgeClass * is topologically a sphere. * * Q.E.D. */ while (attempt_cancellation(manifold) == TRUE) ; /* * As explained above, once we've cancelled all Tetrahedra incident * to EdgeClasses of order 2, we will almost surely have the required * coning of the 3-cell's boundary to a point in its interior, unless * we encounter a situation like the complement of the * house-with-two-rooms. verify_coned_region() checks that we * do in fact have a coning of the original 3-cell; that is, * no Tetrahedron with part_of_coned_cell == TRUE has a face with * face_status transparent_face. */ if (verify_coned_region(manifold) == FALSE) uFatalError("cone_3_cell", "canonize_part_2"); return TRUE; } static Boolean find_unconed_tet( Triangulation *manifold, Tetrahedron **tet0) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == FALSE) { *tet0 = tet; return TRUE; } return FALSE; } static Boolean expand_coned_region( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == TRUE) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == transparent_face) if (tet->neighbor[f]->canonize_info->part_of_coned_cell == FALSE) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* this should never occur */ uFatalError("expand_coned_region", "canonize_part_2"); } return FALSE; } static Boolean attempt_cancellation( Triangulation *manifold) { EdgeClass *edge, *where_to_resume; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->order == 2) { if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* * I don't think failure is possible, but if it is * I want to know about it. */ uFatalError("attempt_cancellation", "canonize_part_2"); } return FALSE; } static Boolean verify_coned_region( Triangulation *manifold) { /* * Because one_to_four(), two_to_three() and cancel_tetrahedra() * all maintain the coned polyhedron as some sort of coned * polyhedron, all we need to check is that it has no remaining * identifications on its boundary. That is, we need to check * that the faces of each Tetrahedron with part_of_coned_cell == TRUE * have face_status opaque_face or inside_cone_face, but never * transparent_face. */ Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->canonize_info->part_of_coned_cell == TRUE) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == transparent_face) return FALSE; return TRUE; } static void step_two( Triangulation *manifold) { /* * Step #1 of the algorithm has already been carried out, so * we know that every 3-cell in the canonical cell decomposition * has been subdivided by coning the boundary to a point in * the interior. There are three types of EdgeClasses: * * (A) Those which lie in the 1-skeleton of the canonical * cell decomposition. They have order at least 6. * * (B) Those which lie in the 2-skeleton of the canonical * cell decomposition, but not in the 1-skeleton. They * serve to (artificially) subdivide the faces of the 3-cells * into triangles. Each has order precisely 4. * * (C) Those in the interior of the 3-cells. They connect ideal * vertices to finite vertices, and have order at least 3. * * Step #2 of the algorithm consists of two substeps: * * Substep A. Do a two-to-three move across every opaque face. * * Substep B. Perform all possible cancellations of Tetrahedra * surrounding EdgeClasses of order 2. * * After Substep A is complete, we know that * * EdgeClasses of type A will have order at least 3. * EdgeClasses of type B will have order precisely 2. * EdgeClasses of type C will have order at least 6. * The new EdgeClasses introduced in Substep A will have * order precisely 3. * * Therefore Substep B will eliminate precisely the EdgeClasses * of type B. It's easy to see that removing these EdgeClasses * creates the Triangulation specified in Step #2 of the definition * of the canonical retriangulation at the top of this file. */ while (eliminate_opaque_face(manifold) == TRUE) ; while (attempt_cancellation(manifold) == TRUE) ; } static Boolean eliminate_opaque_face( Triangulation *manifold) { Tetrahedron *tet; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) if (tet->canonize_info->face_status[f] == opaque_face) { if (two_to_three(tet, f, &manifold->num_tetrahedra) == func_OK) return TRUE; else /* this should never occur */ uFatalError("expand_coned_region", "canonize_part_2"); } return FALSE; } regina-4.95/engine/snappea/kernel/canonize_result.c000644 000765 000024 00000004101 12235724562 022337 0ustar00babstaff000000 000000 /* * canonize_result.c * * The function * * Boolean is_canonical_triangulation(Triangulation *manifold); * * accepts a Triangulation which it assumes to be a subdivision of a canonical * cell decomposition (as produced by proto_canonize(), for example), and * says whether it is indeed the canonical cell decomposition itself. * In other words, is_canonical_triangulation() will return TRUE * when the canonical cell decomposition is a triangulation, and FALSE when * it is not. * * is_canonical_triangulation() assumes all Tetrahedra in the Triangulation * have valid "tilt" fields, as will be the case following a call to * proto_canonize(). */ #include "kernel.h" #include "canonize.h" Boolean is_canonical_triangulation( Triangulation *manifold) { Tetrahedron *tet, *nbr_tet; FaceIndex f, nbr_f; double sum_of_tilts; Boolean result; /* * We'll assume the canonical cell decomposition is a triangulation * unless we discover otherwise. */ result = TRUE; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); sum_of_tilts = tet->tilt[f] + nbr_tet->tilt[nbr_f]; /* * The sum of the tilts should never be positive, because * this would mean that we didn't have a subdivision of the * canonical cell decomposition after all. */ if (sum_of_tilts > CONCAVITY_EPSILON) uFatalError("is_canonical_triangulation", "canonize_result"); /* * If the sum of the tilts is zero, then the canonical cell * decomposition contains cell other than tetrahedra. */ if (sum_of_tilts > -CONCAVITY_EPSILON) result = FALSE; /* * Otherwise we're OK. */ } return result; } regina-4.95/engine/snappea/kernel/change_peripheral_curves.c000644 000765 000024 00000020441 12235724562 024167 0ustar00babstaff000000 000000 /* * change_peripheral_curves.c * * This file provides the function * * FuncResult change_peripheral_curves( * Triangulation *manifold, * CONST MatrixInt22 change_matrices[]); * * If all the change_matrices (there should be one for each Cusp) have * determinant +1, then on each Cusp change_peripheral_curves() replaces * * the meridian with change_matrices[i][0][0] meridians plus * change_matrices[i][0][1] longitudes, and * the longitude with change_matrices[i][1][0] meridians plus * change_matrices[i][1][1] longitudes, * * where i is the index of the Cusp. It returns func_OK. * * If some change_matrix has determinant != +1, change_peripheral_curves() * returns func_bad_input. */ #include "kernel.h" FuncResult change_peripheral_curves( Triangulation *manifold, CONST MatrixInt22 change_matrices[]) { int i, v, f, old_m, old_l; double old_m_coef, /* changed from int to double, JRW 2000/01/18 */ old_l_coef; Tetrahedron *tet; Cusp *cusp; Complex old_Hm, old_Hl; /* * First make sure all the change_matrices have determinant +1. */ for (i = 0; i < manifold->num_cusps; i++) if (DET2(change_matrices[i]) != +1) return func_bad_input; /* * The change_matrices for Klein bottle cusps must have zeros in the * off-diagonal entries. (Nothing else makes sense topologically.) */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->topology == Klein_cusp) for (i = 0; i < 2; i++) if (change_matrices[cusp->index][i][!i] != 0) uFatalError("change_peripheral_curves", "change_peripheral_curves"); /* * Change the peripheral curves according to the change_matrices. * As stated at the top of this file, the transformation rule is * * | new m | | | | old m | * | | = | change_matrices[i] | | | * | new l | | | | old l | */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) /* which orientation */ for (v = 0; v < 4; v++) /* which vertex */ for (f = 0; f < 4; f++) /* which side */ { old_m = tet->curve[M][i][v][f]; old_l = tet->curve[L][i][v][f]; tet->curve[M][i][v][f] = change_matrices[tet->cusp[v]->index][0][0] * old_m + change_matrices[tet->cusp[v]->index][0][1] * old_l; tet->curve[L][i][v][f] = change_matrices[tet->cusp[v]->index][1][0] * old_m + change_matrices[tet->cusp[v]->index][1][1] * old_l; } /* * Change the Dehn filling coefficients to reflect the new * peripheral curves. That is, we want the Dehn filling curves * to be topologically the same as before, even though the * coefficients will be different because we changed the basis. * * To keep our thinking straight, let's imagine all peripheral * curves -- old and new -- in terms of some arbitrary but * fixed basis for pi_1(T^2) = Z + Z. We never actually compute * such a basis, but it helps keep our thinking straight. * Relative to this fixed basis, we have * * old m = (old m [0], old m [1]) * old l = (old l [0], old l [1]) * new m = (new m [0], new m [1]) * new l = (new l [0], new l [1]) * * Note that these m's and l's are curves, not coefficients! * They are elements of pi_1(T^2) = Z + Z. * * We can then rewrite the above transformation rule, with * each peripheral curve (old m, old l, new m and new l) * appearing as a row in a 2 x 2 matrix: * * | <--new m--> | | | | <--old m--> | * | | = | change_matrices[i] | | | * | <--new l--> | | | | <--old l--> | * * We can invert the change_matrix to solve for the old curves * in terms of the new ones: * -1 * | <--old m--> | | | | <--new m--> | * | | = | change_matrices[i] | | | * | <--old l--> | | | | <--new l--> | * * The Dehn filling curve is * * old_m_coef * old_m + old_l_coef * old_l * * = old_m_coef * [ change_matrices[i][1][1] * new_m * - change_matrices[i][0][1] * new_l] * + old_l_coef * [- change_matrices[i][1][0] * new_m * + change_matrices[i][0][0] * new_l] * * = new_m * [ old_m_coef * change_matrices[i][1][1] * - old_l_coef * change_matrices[i][1][0] ] * + new_l * [- old_m_coef * change_matrices[i][0][1] * + old_l_coef * change_matrices[i][0][0] ] * * Therefore * * new_m_coef = old_m_coef * change_matrices[i][1][1] * - old_l_coef * change_matrices[i][1][0] * new_l_coef = - old_m_coef * change_matrices[i][0][1] * + old_l_coef * change_matrices[i][0][0] */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == FALSE) { old_m_coef = cusp->m; old_l_coef = cusp->l; cusp->m = old_m_coef * change_matrices[cusp->index][1][1] - old_l_coef * change_matrices[cusp->index][1][0]; cusp->l = - old_m_coef * change_matrices[cusp->index][0][1] + old_l_coef * change_matrices[cusp->index][0][0]; } /* * Update the holonomies according to the rule * * | new H(m) | | | | old H(m) | * | | = | change_matrices[i] | | | * | new H(l) | | | | old H(l) | * * (These are actually logs of holonomies, so it's correct to * add -- not multiply -- them.) * * For complete Cusps the holonomies should be zero, but that's OK. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_Hm = cusp->holonomy[i][M]; old_Hl = cusp->holonomy[i][L]; cusp->holonomy[i][M] = complex_plus( complex_real_mult( change_matrices[cusp->index][0][0], old_Hm ), complex_real_mult( change_matrices[cusp->index][0][1], old_Hl ) ); cusp->holonomy[i][L] = complex_plus( complex_real_mult( change_matrices[cusp->index][1][0], old_Hm ), complex_real_mult( change_matrices[cusp->index][1][1], old_Hl ) ); } /* * Update the cusp_shapes. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[initial] = transformed_cusp_shape( cusp->cusp_shape[initial], change_matrices[cusp->index]); if (cusp->is_complete == TRUE) cusp->cusp_shape[current] = transformed_cusp_shape( cusp->cusp_shape[current], change_matrices[cusp->index]); /* * else cusp->cusp_shape[current] == Zero, and needn't be changed. */ } return func_OK; } regina-4.95/engine/snappea/kernel/chern_simons.c000644 000765 000024 00000075701 12235724562 021640 0ustar00babstaff000000 000000 /* * chern_simons.c * * The computation of the Chern-Simons invariant is a little * delicate because the formula depends on a constant which * must initially be supplied by the user. * * For the UI, this file provides the functions * * void set_CS_value( Triangulation *manifold, * double a_value); * void get_CS_value( Triangulation *manifold, * Boolean *value_is_known, * double *the_value, * int *the_precision, * Boolean *requires_initialization); * * The UI calls set_CS_value() to pass to the kernel a user-supplied * value of the Chern-Simons invariant for the current manifold. * * The UI calls get_CS_value() to request the current value. If the * current value is known (or can be computed), get_CS_value() sets * *value_is_known to TRUE and writes the current value and its precision * (the number of significant digits to the right of the decimal point) * to *the_value and *the_precision, respectively. If the current value * is not known and cannot be computed, it sets *value_is_known to FALSE, * and then sets *requires_initialization to TRUE if the_value * is unknown because no fudge factor is available, or * to FALSE if the_value is unknown because the solution contains * negatively oriented Tetrahedra. The UI might want to convey * these situations to the user in different ways. * * get_CS_value() normalizes *the_value to the range (-1/4,+1/4]. * This is the ONLY point in code where such an adjustment is made; * all internal computations are done mod 0. * * * The kernel manages the Chern-Simons computation by keeping track of * both the current value and the arbitrary constant ("fudge factor") * which appears in the formula. It uses the following fields of * the Triangulation data structure: * * Boolean CS_value_is_known, * CS_fudge_is_known; * double CS_value[2], * CS_fudge[2]; * * The Boolean flags indicate whether the corresponding double is * presently known or unknown. To provide an estimate of precision, * CS_value[ultimate] and CS_value[penultimate] store the value of the * Chern-Simons invariant computed relative to the hyperbolic structure * at the ultimate and penultimate iterations of Newton's method, and * similarly for the fudge factor CS_fudge[]. * * For the kernel, this file provides the functions * * void compute_CS_value_from_fudge(Triangulation *manifold); * void compute_CS_fudge_from_value(Triangulation *manifold); * * compute_CS_value_from_fudge() computes the CS_value in terms of * CS_fudge, if CS_fudge_is_known is TRUE. (If CS_fudge_is_known is FALSE, * it sets CS_value_is_known to FALSE as well.) The kernel calls this * function when doing Dehn fillings on a fixed Triangulation, where * the CS_fudge will be known (and constant) but the CS_value will be * changing. * * compute_CS_fudge_from_value() computes the CS_fudge in terms of * CS_value, if CS_value_is_known is TRUE. (If CS_value_is_known is FALSE, * it sets CS_fudge_is_known to FALSE as well.) The kernel calls this * function when it changes a Triangulation without changing the manifold * it represents. * * * Bob Meyerhoff, Craig Hodgson and Walter Neumann have found at least * two different algorithms for computing the Chern-Simons invariant. * The following code allows easy substitution of algorithms, in the * function compute_CS(). * * 96/4/16 David Eppstein pointed out that when he does (1,0) Dehn filling * on m074(1,0), SnapPea quits with the message "The argument in the * dilogarithm function is too large to guarantee accuracy". I've modified * the code so that it displays the message "The argument in the dilogarithm * function is too large to guarantee an accurate value for the Chern-Simons * invariant" but does not quit. Instead it sets * * manifold->CS_value_is_known = FALSE; * or * manifold->CS_fudge_is_known = FALSE; * * (as appropriate) and continues normally. [By the way, I rejected the * idea of providing more coefficients for the series. The set of manifolds * for which the existing coefficients do not suffice is very, very small: * no problems arise for any of the manifolds in the cusped or closed censuses. * (Eppstein's example of m074(1,0) is a 3-sphere, but other descriptions * of the 3-sphere seem to work fine.) So I don't want to slow down the * computation of the Chern-Simons invariant in the generic case for the * sake of an almost vanishingly small set of exceptions.] */ #include "kernel.h" #define CS_EPSILON 1e-8 #define LOG_TWO_PI 1.83787706640934548356 static FuncResult compute_CS(Triangulation *manifold, double value[2]); static FuncResult algorithm_one(Triangulation *manifold, double value[2]); static Complex alg1_compute_Fu(Triangulation *manifold, int which_approximation, Boolean *Li2_error_flag); static Complex Li2(Complex w, ShapeInversion *z_history, Boolean *Li2_error_flag); static Complex log_w_minus_k_with_history(Complex w, int k, double regular_arg, ShapeInversion *z_history); static int get_history_length(ShapeInversion *z_history); static int get_wide_angle(ShapeInversion *z_history, int requested_index); void set_CS_value( Triangulation *manifold, double a_value) { manifold->CS_value_is_known = TRUE; manifold->CS_value[ultimate] = a_value; manifold->CS_value[penultimate] = a_value; compute_CS_fudge_from_value(manifold); } void get_CS_value( Triangulation *manifold, Boolean *value_is_known, double *the_value, int *the_precision, Boolean *requires_initialization) { if (manifold->CS_value_is_known) { *value_is_known = TRUE; *the_value = manifold->CS_value[ultimate]; *the_precision = decimal_places_of_accuracy( manifold->CS_value[ultimate], manifold->CS_value[penultimate]); *requires_initialization = FALSE; /* * Normalize reported value to the range (-1/4, 1/4]. */ while (*the_value < -0.25 + CS_EPSILON) *the_value += 0.5; while (*the_value > 0.25 + CS_EPSILON) *the_value -= 0.5; } else { *value_is_known = FALSE; *the_value = 0.0; *the_precision = 0; *requires_initialization = (manifold->CS_fudge_is_known == FALSE); } } void compute_CS_value_from_fudge( Triangulation *manifold) { double computed_value[2]; if (manifold->CS_fudge_is_known == TRUE && compute_CS(manifold, computed_value) == func_OK) { manifold->CS_value_is_known = TRUE; manifold->CS_value[ultimate] = computed_value[ultimate] + manifold->CS_fudge[ultimate]; manifold->CS_value[penultimate] = computed_value[penultimate] + manifold->CS_fudge[penultimate]; } else { manifold->CS_value_is_known = FALSE; manifold->CS_value[ultimate] = 0.0; manifold->CS_value[penultimate] = 0.0; } } void compute_CS_fudge_from_value( Triangulation *manifold) { double computed_value[2]; if (manifold->CS_value_is_known == TRUE && compute_CS(manifold, computed_value) == func_OK) { manifold->CS_fudge_is_known = TRUE; manifold->CS_fudge[ultimate] = manifold->CS_value[ultimate] - computed_value[ultimate]; manifold->CS_fudge[penultimate] = manifold->CS_value[penultimate] - computed_value[penultimate]; } else { manifold->CS_fudge_is_known = FALSE; manifold->CS_fudge[ultimate] = 0.0; manifold->CS_fudge[penultimate] = 0.0; } } static FuncResult compute_CS( Triangulation *manifold, double value[2]) { Cusp *cusp; /* * We can handle only orientable manifolds. */ if (manifold->orientability != oriented_manifold) return func_failed; /* * Cusps must either be complete, or have Dehn filling * coefficients which are relatively prime integers. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_relatively_prime_integers(cusp) == FALSE) return func_failed; /* * Here we plug in the algorithm of our choice. */ return algorithm_one(manifold, value); } static FuncResult algorithm_one( Triangulation *manifold, double value[2]) { Boolean Li2_error_flag; int i; Complex Fu[2], core_length_sum[2], complex_volume[2], length[2]; int singularity_index; Cusp *cusp; /* * This algorithm is taken directly from Craig Hodgson's * preprint "Computation of the Chern-Simons invariants". * It extends previous implementations in that it uses * the shape_histories of the Tetrahedra to compute * the dilogarithms, which allows solutions with negatively * oriented Tetrahedra. */ /* * To use the Chern-Simons formula, both the complete and filled * solutions must be geometric, nongeometric or flat. */ for (i = 0; i < 2; i++) /* i = complete, filled */ if (manifold->solution_type[i] != geometric_solution && manifold->solution_type[i] != nongeometric_solution && manifold->solution_type[i] != flat_solution) return func_failed; /* * Initialize the Li2_error_flag to FALSE. * If the coefficients in Li2() don't suffice to compute the dilogaritm * to full precision, Li2() will set Li2_error_flag to TRUE. */ Li2_error_flag = FALSE; /* * Compute F(u) relative to the ultimate and penultimate * hyperbolic structures, to allow an estimatation of precision. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ Fu[i] = alg1_compute_Fu(manifold, i, &Li2_error_flag); /* * If Li2() failed, return func_failed; */ if (Li2_error_flag == TRUE) { uAcknowledge("An argument in the dilogarithm function is too large to guarantee an accurate value for the Chern-Simons invariant."); return func_failed; } /* * F(u) is * * (complex volume) + pi/2 (sum of complex core lengths) * * So we subtract off the complex lengths of the core geodesics * to be obtain the complex volume. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ core_length_sum[i] = Zero; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { compute_core_geodesic(cusp, &singularity_index, length); switch (singularity_index) { case 0: /* * The cusp is complete. Do nothing. */ break; case 1: /* * Add this core length to the sum. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ core_length_sum[i] = complex_plus( core_length_sum[i], length[i]); break; default: /* * We should never arrive here. */ uFatalError("algorithm_one", "chern_simons"); } } /* * (complex volume) = F(u) - (pi/2)(sum of core lengths) */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { complex_volume[i] = complex_minus( Fu[i], complex_real_mult( PI_OVER_2, core_length_sum[i] ) ); value[i] = complex_volume[i].imag / (2.0 * PI * PI); } return func_OK; } static Complex alg1_compute_Fu( Triangulation *manifold, int which_approximation, /* ultimate or penultimate */ Boolean *Li2_error_flag) { Complex Fu; Tetrahedron *tet; static const Complex minus_i = {0.0, -1.0}; /* * We compute the function F(u), which Yoshida has proved holomorphic. * (See Craig's preprint mentioned above.) */ /* * Initialize F(u) to Zero. */ Fu = Zero; /* * Add up the log terms. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { Fu = complex_minus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][0].log, tet->shape[ filled ]->cwl[which_approximation][1].log ) ); Fu = complex_plus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][0].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][1].log) ) ); Fu = complex_minus( Fu, complex_mult( tet->shape[ filled ]->cwl[which_approximation][1].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][0].log) ) ); Fu = complex_minus( Fu, complex_mult( tet->shape[complete]->cwl[which_approximation][0].log, complex_conjugate( tet->shape[complete]->cwl[which_approximation][1].log) ) ); } /* * Multiply through by one half. */ Fu = complex_real_mult(0.5, Fu); /* * Add in the dilogarithms. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * To compute the dilogarithm of z, Li2() wants to be * passed w = log(z) / 2 pi i and the shape_history of z. */ Fu = complex_plus ( Fu, Li2 ( complex_div ( tet->shape[filled]->cwl[which_approximation][0].log, TwoPiI ), tet->shape_history[filled], Li2_error_flag ) ); } /* * Multiply by -i. */ Fu = complex_mult(minus_i, Fu); return Fu; } static Complex Li2( Complex w, ShapeInversion *z_history, Boolean *Li2_error_flag) { /* * Compute the dilogarithm of z = exp(2 pi i w) as explained * in Craig's preprint mentioned above. Note that we use * the variable w instead of the z which appears in Craig's * preprint, to avoid confusion with the z which appears * in the formula for F(u). * * The term Craig calls "S" we compute in two parts * * s0 = sum from i = 1 to infinity . . . * s1 = sum from k = 1 to N . . . + Nw * * The remaining part of the formula we call * * t = pi^2/6 + 2 pi i w - 2 pi i w log(-2 pi i w) + (pi w)^2 */ Complex s0, s1, s, t, w_squared, two_pi_i_w, kk, k_plus_w, k_minus_w, result; int i, k; static const Complex pi_squared_over_6 = {PI*PI/6.0, 0.0}, four_pi_i = {0.0, 4.0*PI}, minus_pi_i = {0.0, -PI}, log_minus_two_pi_i = {LOG_TWO_PI, -PI_OVER_2}; /* * The array a[] contains the coefficients for the infinite series * in s0. The constant num_terms tells how many we need to use to * insure accuracy (see Analysis of Convergence below). * * The following Mathematica code computed these coefficients * for N = 2. (I use "n" where Craig used "N" to conform both * to proramming conventions regarding capital letters, and also * to Mathematica's conventions.) * * a[i_, n_] := * N[(Zeta[2i] - Sum[k^(-2i), {k,1,n}]) / (2i(2i + 1)), 60] * a2[i_] := a[i, 2] * Array[a2, 30] * * Note that to get 20 significant digits in a2[30] = 6.4e-33, we * must request at least 53 decimal places of accuracy from * Mathematica, and probably a little more since the accuracy we * request is the accuracy to which the intermediate calculations * are truncated -- the final accuracy could be a little worse. * By the way, we really do need a lot of that accuracy even in * the tiny coefficients, because they will be multiplied by high * powers of w, and |w| may be greater than one. */ static const int num_terms = 30; static const int n = 2; static const double a[] ={ 0.0, 6.58223444747044060787e-2, 9.91161685556909575800e-4, 4.09062377249795170123e-5, 2.37647497144915803729e-6, 1.63751161982593974054e-7, 1.24738994105660169102e-8, 1.01418480335632980259e-9, 8.62880373230578403363e-11, 7.59064144690016509252e-12, 6.85041587014555123901e-13, 6.30901702974110744035e-14, 5.90712644809102073367e-15, 5.60732930747841393884e-16, 5.38501558411235458177e-17, 5.22344536523359867175e-18, 5.11092595568460128406e-19, 5.03912265560217431595e-20, 5.00200835767964640183e-21, 4.99518851712940000071e-22, 5.01545492014257760830e-23, 5.06048349504093155712e-24, 5.12862546072263579933e-25, 5.21876054821516289501e-26, 5.33019249317297967524e-27, 5.46257395282628942810e-28, 5.61585233364316675625e-29, 5.79023077981676178469e-30, 5.98614037451033538648e-31, 6.20422080422041301360e-32, 6.44530754870034591211e-33}; /* * Analysis of convergence. * * The i-th coefficient in the (partial) zeta function is * * (N+1)^(-2i) + (N+2)^(-2i) + (N+3)^(-2i) + ... * * Lemma. For large i, this series may be approximated by its first * term (N+1)^(-2i). * * Proof. [Probably not worth reading, but I figured I ought to * include it.] Get an upper bound on the sum of the neglected * terms by comparing them to an integral: * * (N+2)^(-2i) + (N+3)^(-2i) + ... * < (N+2)^(-2i) + integral from x = N+2 to infinity of x^(-2i) dx * = (N+2)^(-2i) + (N+2)^(-2i+1)/(2i-1) * < (N+2)^(-2i) + (N+2)^(-2i) * = 2 (N+2)^(-2i) * * Therefore the ratio (error)/(first term) is less than * * [2 (N+2)^(-2i)] / [(N+1)^(-2i)] * = 2 ((N+1)/(N+2))^(2i) * * Thus, for example, if N = 2 and i >= 10, the ratio * (error)/(first term) will be less than 2 (3/4)^10 = 1%. * When N = 4 we need i >= 15 to obtain 1% accuracy. * Q.E.D. * * * The preceding lemma implies that the infinite series * for S has the same convergence behavior as the series * * S' = (w/(N+1))^2i / i^2 * * so we analyze S' instead of S. The error introduced * by truncating the series after some i = i0 is bounded * by the corresponding error in the geometric series * * S" = |w/(N+1)|^2i / i0^2 * * The latter error is * * (first neglected term) / (1 - ratio) * * = (|w/(N+1)|^2i0 / i0^2) / (1 - |w/(N+1)|^2) * * = |w/(N+1)|^2i0 / (i0^2 (1 - |w/(N+1)|^2)) * * A quick calculcation in Mathematica shows that if * we are willing to calculate 30 terms in the series, * then |w/(N+1)| < 0.5 implies the error will be * less than 1e-20. In other words, the series can * be used successfully for |w| < (N+1)/2. What values * of z (i.e. what actual simplex shapes) does this * correspond to? * * Letting w = x + iy, we get * * z = exp(2 pi i (x + i y)) * = exp(-2 pi y + 2 pi i x) * = exp(-2 pi y) * (cos(2 pi x) + i sin(2 pi x)) * * In other words, at an argument of 2 pi x, the acceptable * parameters z are those with moduli between * exp(-2 pi sqrt(((N+1)/2)^2 - x^2)) and * exp(+2 pi sqrt(((N+1)/2)^2 - x^2)). * * For N = 2: * When x = 0 we get values of z along the positive real axis * from 0.00008 to 12000. * When x = 1/2 we get values of z along the negative real axis * from -0.0001 to -7000. * When x = 1 we get values of z along the positive real axis * from 0.0008 to 1000. * * This is good news: it means that the 30-term series for S will * be accurate to 1e-20 for all (reasonable) nondegenerate values * of z. I don't foresee the need for a greater radius of * convergence, but if one is ever needed, just switch to N = 4. */ /* * According to the preceding Analysis of Convergence, our * computations will be accurate to 1e-20 whenever * |w| < (N+1)/2 = 3/2. */ if (complex_modulus(w) > 1.5) { *Li2_error_flag = TRUE; return Zero; } /* * Note the values of w^2 and 2 pi i w. */ w_squared = complex_mult(w, w); two_pi_i_w = complex_mult(TwoPiI, w); /* * Compute t. * * In the third term, - 2 pi i w will lie in the strip * 0 < Im(- 2 pi i w) < - pi i, so we choose the argument * in its log to be in the range (0, - pi). */ t = pi_squared_over_6; t = complex_plus( t, two_pi_i_w ); t = complex_minus( t, complex_mult( two_pi_i_w, complex_plus( log_minus_two_pi_i, log_w_minus_k_with_history(w, 0, 0.0, z_history) ) ) ); t = complex_plus( t, complex_real_mult(PI * PI, w_squared) ); /* * Compute s0. * * Start with the high order terms and work backwards. * It's a little faster, because fewer multiplications * are required, and might also be a little more accurate. */ s0 = Zero; for (i = num_terms; i > 0; --i) { s0.real += a[i]; s0 = complex_mult(s0, w_squared); } s0 = complex_mult(s0, w); /* * Compute s1. */ s1 = Zero; for (k = 1; k <= n; k++) { kk = complex_real_mult(k, One); k_plus_w = complex_plus (kk, w); k_minus_w = complex_minus(kk, w); s1 = complex_plus( s1, complex_real_mult(log(k), w) ); s1 = complex_minus( s1, complex_real_mult( 0.5, complex_mult( k_plus_w, log_w_minus_k_with_history(w, -k, 0.0, z_history) ) ) ); s1 = complex_plus( s1, complex_real_mult( 0.5, complex_mult( k_minus_w, /* * We write Craig's log(k - w), which had an * argument of 0 for the regular case, as * log(-1) + log(w - k), and choose arg(log(-1)) = -pi * and arg(log(w - k)) = +pi for the regular case. */ complex_plus( minus_pi_i, log_w_minus_k_with_history(w, k, PI, z_history) ) ) ) ); } s1 = complex_plus( s1, complex_real_mult(n, w) ); /* * Add t + (4 pi i)(s0 + s1) to get the final answer. */ s = complex_plus(s0, s1); result = complex_plus( t, complex_mult(four_pi_i, s) ); return result; } static Complex log_w_minus_k_with_history( Complex w, int k, double regular_arg, ShapeInversion *z_history) { int which_strip; double estimated_argument; int i; /* * This function computes log(w - k), taking into account the "history" * of the shape z from which w is derived (z = exp(2 pi i w), as * explained above). That is, it takes into account z's precise * path through the parameter space, up to isotopy. * * regular_arg supplies the correct argument for the case of a * regular ideal tetrahedron, with z = (1/2) + (sqrt(3)/2)i, * w = 1/6, and a trivial "history". Typically regular_arg * will be 0 for k <= 0, and +pi for k > 0. * * To understand what's going on here, it will be helpful to make * yourself pictures of the z- and w-planes, as follows: * * z-plane. Draw axes for the complex plane representing z. * Draw small circles at 0 and 1 to show where * z is singular. * Color the real axis blue from -infinity to 0, and label * it '0' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 0. * Color the real axis red from 1 to +infinity, and label * it '1' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 1. * Color the real axis green from 0 to 1, and label * it '2' to indicate that z crosses this segment when * there is a ShapeInversion with wide_angle == 2. * * w-plane. Draw the preimage of the z-plane picture under the * map z = exp(2 pi i w). * The singularities occur at the integer points on * the real axis. * Red half-lines labeled 1 extend from each singularity * downward to infinity. * Green half-lines labeled 2 extend from each singularity * upward to infinity. * Blue lines labelled 0 pass vertically through each * half-integer point on the real axis. * * We will use the z_history to trace the path of w through the * w-plane picture, keeping track of the argument of log(w - k) * as we go. We begin with the shape of a regular ideal tetrahedron, * namely z = (1/2) + (sqrt(3)/2) i, w = 1/6 + 0 i. * * It suffices to keep track of the approximate argument to the * nearest multiple of pi, since the true argument will be within * pi/2 of that estimate. * * The vertical strips in the w-plane (which are preimages of * the halfplane z.imag > 0 and z.imag < 0 in the z-plane) * are indexed by integers. Strip n is the strip extending * from w.real = n/2 to w.real = (n+1)/2. */ /* * We begin at w = 1/6, and set the estimated_argument to * regular_arg (this will typically be 0 if k <= 0, or pi if k > 0, * corresponding to Walter and Craig's choices for the case of * positively oriented Tetrahedra). */ which_strip = 0; estimated_argument = regular_arg; /* * Now we read off the z_history, adjusting which_strip * and estimated_argument accordingly. * * Typically the z_history will be NULL, so nothing happens here. * * Technical note: this isn't the most efficient way to read * a linked list backwards, but clarity is more important than * efficiency here, but the z_histories are likely to be so short. */ for (i = 0; i < get_history_length(z_history); i++) switch (get_wide_angle(z_history, i)) { case 0: /* * If we're in an even numbered strip, move to the right. * If we're in an odd numbered strip, move to the left. * The estimated_argument does not change. */ if (which_strip % 2 == 0) which_strip++; else which_strip--; break; case 1: /* * If we're in an even numbered strip, move to the left, * and if we pass under the singularity at k, * subtract pi from the estimated_argument. * If we're in an odd numbered strip, move to the right, * and if we pass under the singularity at k, * add pi to the estimated_argument. */ if (which_strip % 2 == 0) { which_strip--; if (which_strip == 2*k - 1) estimated_argument -= PI; } else { which_strip++; if (which_strip == 2*k) estimated_argument += PI; } break; case 2: /* * If we're in an even numbered strip, move to the left, * and if we pass over the singularity at k, * add pi to the estimated_argument. * If we're in an odd numbered strip, move to the right, * and if we pass over the singularity at k, * subtract pi from the estimated_argument. */ if (which_strip % 2 == 0) { which_strip--; if (which_strip == 2*k - 1) estimated_argument += PI; } else { which_strip++; if (which_strip == 2*k) estimated_argument -= PI; } break; default: uFatalError("log_w_minus_k_with_history", "chern_simons"); } /* * Compute log(w - k) using the estimated_argument. */ return ( complex_log( complex_minus( w, complex_real_mult((double)k, One) ), estimated_argument ) ); } static int get_history_length( ShapeInversion *z_history) { int length; length = 0; while (z_history != NULL) { length++; z_history = z_history->next; } return length; } static int get_wide_angle( ShapeInversion *z_history, int requested_index) { while (--requested_index >= 0) z_history = z_history->next; return z_history->wide_angle; } regina-4.95/engine/snappea/kernel/choose_generators.c000644 000765 000024 00000112432 12235724562 022653 0ustar00babstaff000000 000000 /* * choose_generators.c * * This file contains the function * * void choose_generators( Triangulation *manifold, * Boolean compute_corners, * Boolean centroid_at_origin) * * which chooses a set of generators for the fundamental group * of the Triangulation *manifold. (The Dehn filling coefficients * are irrelevant.) * * A function which needs to use the generating set must first call * choose_generators(). [Note that this differs from the previous * SnapPea 2.0- convention, under which all functions which changed the * triangulation were responsible for calling choose_generators(). * The old convention was more efficient at runtime, but the new one * makes programming easier.] * * The algorithm begins with an arbitrary Tetrahedron, and recursively * attaches neighboring Tetrahedra to create a fundamental domain for * the manifold which is topologically a ball. Whenever a face of a * Tetrahedron lies in the interior of this fundamental domain, * tet->generator_status[face] is set to not_a_generator. Faces on the * exterior of the fundmental domain correspond to active generators, * and will have status outbound_generator or inbound_generator, depending * on how a particular generator is oriented (one face of a matching pair * will have status outbound_generator, and the other inbound_generator). * * The algorithm simplifies the generating set in two ways: * * (1) When it finds an EdgeClass with only one incident 2-cell which * is dual to an active generator, it does a "handle cancellation" * to eliminate that generator, and also sets the EdgeClass's * active_relation field to FALSE. The algorithm continues doing * this type of simplification until it can make no further progress. * * (2) At this point the boundary of the fundamental domain is likely * to contain groups of faces which are essentially n-gons (n > 3) * arbitrarily divided into triangles. The generators for such * triangular faces are all equivalent, and get merged. The * active_relation fields of the interior EdgeClasses are set to * FALSE. * * active_relation fields which are not set to FALSE in (1) or (2) * are set to TRUE. Each EdgeClass's num_incident_generators field * says, not surprisingly, how many active generators it is incident to. * Note that num_incident_generators becomes 0 when a handle cancellation * occurs in (1) above, but num_incident_generators remains 2 when the * EdgeClass's active_relation field is set to FALSE in (2) (the rationale * is that there are still two active incident generators, even though * they happen to be the same (yeah, it sounds suspicious to me too, but * that's how it is)). * * Each generator is a 1-cell in the dual to the Triangulation. * The generator dual to a given face of a given Tetrahedron is * described by three variables: * * tet->generator_status[face] takes the value * * outbound_generator if the generator is directed from * tet towards its neighbor, * inbound_generator if the generator is directed from * the neighbor towards tet, * not_a_generator if no generator corresponds to this * face (more on this in a minute), and * unassigned_generator if the algorithm hasn't gotten around * to considering this face yet. * * tet->generator_index[face] tells the index of the generator. * The numbering runs from 0 to (number-of-generators - 1). * tet->generator_index[face] is defined iff tet->generator_status[face] * is outbound_generator or inbound_generator. * * tet->generator_parity[face] tells whether the generator is * orientation_preserving or orientation_reversing. * * The field tet->generator_path lets you reconstruct the complete path of * a generator: it says by which face the given Tetrahedron was added to the * fundamental domain (cf. the recursive algorithm described above). The * central Tetrahedron used to begin the recursion has tet->generator_path = -1. * * If compute_corners is TRUE, * choose_generators() also computes the location on the sphere at infinity * of each ideal vertex of each Tetrahedron in the fundamental domain, and * stores it in the field tet->corner[vertex]. That is, tet->corner[vertex] * contains the complex number representing the location of the vertex in * the boundary of the upper half space model. The (relative) locations of * the corners are computed using the hyperbolic structure of the Dehn filled * manifold. If centroid_at_origin is TRUE, the initial tetrahedron is * positioned with its centroid at the origin; otherwise the initial tetrahedron * is positioned with its vertices at {0, 1/sqrt(z), sqrt(z), infinity}. */ #include "kernel.h" static void initialize_flags(Triangulation *manifold); static void visit_tetrahedra(Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin); static void initial_tetrahedron(Triangulation *manifold, Tetrahedron **tet, Boolean compute_corners, Boolean centroid_at_origin); static void count_incident_generators(Triangulation *manifold); static void eliminate_trivial_generators(Triangulation *manifold); static void kill_the_incident_generator(Triangulation *manifold, EdgeClass *edge); static void merge_equivalent_generators(Triangulation *manifold); static void merge_incident_generators(Triangulation *manifold, EdgeClass *edge); static void eliminate_empty_relations(Triangulation *manifold); void choose_generators( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin) { /* * To compute the corners we need some sort of geometric structure. */ if (compute_corners == TRUE && manifold->solution_type[filled] == not_attempted) uFatalError("choose_generators", "choose_generators.c"); /* * For each Tetrahedron tet, set tet->flag to unknown_orientation * to indicate that the Tetrahedron has not yet been visited, and * set each tet->generator_status[i] to unassigned_generator to * indicate that no generator has yet been assigned to any face. */ initialize_flags(manifold); /* * Start a recursion which visits each tetrahedron, assigns * generators to its faces, and recursively visits any unvisited * neighbors. */ visit_tetrahedra(manifold, compute_corners, centroid_at_origin); /* * The number_of_generators should be one plus the number of tetrahedra. */ if (manifold->num_generators != manifold->num_tetrahedra + 1) uFatalError("choose_generators", "choose_generators.c"); /* * At this point we have a valid set of generators, but it's * not as simple as it might be. We'll perform two types of * simplifications. First we need to count how many of the * faces incident to each EdgeClass correspond to active generators. * Initialize all the active_relation flags to TRUE while we're at it. */ count_incident_generators(manifold); /* * Now look for EdgeClasses in the Triangulation (2-cells in the * dual complex) which show that a single generator is homotopically * trivial, and eliminate the trivial generator. Topologically, this * corresponds to folding together two adjacent triangular faces * on the boundary of the fundamental domain (the close-the-book * move). Geometrically, this corresponds to realizing that two * faces of the (geometric) fundamental domain are in fact already * superimposed on each other. In Heegaard terms, it's a handle * cancellation. */ eliminate_trivial_generators(manifold); /* * At this point the boundary of the fundamental domain is likely * to contain groups of faces which are essentially n-gons (n > 3) * arbitrarily divided into triangles. The generators for such * triangular faces are all equivalent, and can be merged. They * can be recognized by looking for EdgeClasses with exactly two * incident (and distinct) generators. */ merge_equivalent_generators(manifold); /* * 2008/6/12 JRW * * Eliminate relations with zero generators. * * How can such relations arise? * * Under normal operation, eliminate_trivial_generators() finds an active generator * whose dual 2-cell is incident to an EdgeClass whose other incident 2-cells are * all dual to inactive generators (i.e. they lie in the interior * of the fundamental domain). The EdgeClass's relation (of length 1) cancels * the generator and all is well. One may visualize this operation as taking * two adjacent triangles on the boundary of the fundamental domain, which share * a common edge, and gluing them together via a "close-the-book move". * * Now consider two adjacent triangles (still on the boundary of the fundamental * domain) that share two common edges -- in effect a sort of triangular "pita pocket" * with two closed edges and one open edge. When eliminate_trivial_generators() * cancels the generator (dual to the triangular face) against one of the * incident EdgeClasses, the other EdgeClass is left with zero generators, * and may be eliminated. * * Note: Thinking in term of truncated tetrahedra, the above description seems * to imply that the boundary component at the tip of the pita pocket * between the two "closed edges" becomes a spherical boundary component * of the manifold. This suggests that this case would arise for finite * triangulations (as opposed to ideal triangulations), or for highly degenerate * ideal triangulations, but I confess that I haven't thought this through carefully. */ eliminate_empty_relations(manifold); } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; FaceIndex face; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { tet->flag = unknown_orientation; for (face = 0; face < 4; face++) { tet->generator_status[face] = unassigned_generator; tet->generator_index[face] = -2; /* garbage value */ } } } static void visit_tetrahedra( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin) { Tetrahedron **queue, *tet; int queue_first, queue_last; Tetrahedron *nbr_tet; Permutation gluing; FaceIndex face, nbr_face; int i; VertexIndex nbr_i; /* * choose_generators() has already called initialize_flags(). */ /* * Initialize num_generators to zero. */ manifold->num_generators = 0; /* * Allocate space for a queue of pointers to the Tetrahedra. * Each Tetrahedron will appear on the queue exactly once, * so an array of length manifold->num_tetrahedra will be just right. */ queue = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); /* * Initialize the queue. */ queue_first = 0; queue_last = 0; /* * Choose the initial Tetrahedron according to some criterion. * If compute_corners is TRUE, position its corners. * 2000/4/2 The choice of initial tetrahedron is independent * of compute_corners. */ initial_tetrahedron(manifold, &queue[0], compute_corners, centroid_at_origin); /* * Mark the initial Tetrahedron as visited. */ queue[0]->generator_path = -1; queue[0]->flag = right_handed; /* * Start processing the queue. */ do { /* * Pull a Tetrahedron off the front of the queue. */ tet = queue[queue_first++]; /* * Look at the four neighboring Tetrahedra. */ for (face = 0; face < 4; face++) { /* * Note who the neighbor is, and which of * its faces we're glued to. */ nbr_tet = tet->neighbor[face]; gluing = tet->gluing[face]; nbr_face = EVALUATE(gluing, face); /* * If nbr_tet hasn't been visited, set the appropriate * generator_statuses to not_a_generator, and then put * nbr_tet on the back of the queue. */ if (nbr_tet->flag == unknown_orientation) { tet ->generator_status[face] = not_a_generator; nbr_tet->generator_status[nbr_face] = not_a_generator; tet ->generator_index[face] = -1; /* garbage value */ nbr_tet->generator_index[nbr_face] = -1; nbr_tet->generator_path = nbr_face; nbr_tet->flag = (parity[gluing] == orientation_preserving) ? tet->flag : ! tet->flag; if (compute_corners) { for (i = 0; i < 4; i++) { if (i == face) continue; nbr_i = EVALUATE(gluing, i); nbr_tet->corner[nbr_i] = tet->corner[i]; } compute_fourth_corner( nbr_tet->corner, /* array of corner coordinates */ nbr_face, /* the corner to be computed */ nbr_tet->flag, /* nbr_tet's current orientation */ nbr_tet->shape[filled]->cwl[ultimate]); /* shapes */ } queue[++queue_last] = nbr_tet; } /* * If nbr_tet has been visited, check whether a generator * has been assigned to common face, and if not, assign one. */ else if (tet->generator_status[face] == unassigned_generator) { tet ->generator_status[face] = outbound_generator; nbr_tet->generator_status[nbr_face] = inbound_generator; tet ->generator_index[face] = manifold->num_generators; nbr_tet->generator_index[nbr_face] = manifold->num_generators; tet ->generator_parity[face] = nbr_tet->generator_parity[nbr_face] = ((parity[gluing] == orientation_preserving) == (tet->flag == nbr_tet->flag)) ? orientation_preserving : orientation_reversing; manifold->num_generators++; } } } while (queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); /* * An "unnecessary" (but quick) error check. */ if ( queue_first != manifold->num_tetrahedra || queue_last != manifold->num_tetrahedra - 1) uFatalError("visit_tetrahedra", "choose_generators.c"); } static void initial_tetrahedron( Triangulation *manifold, Tetrahedron **initial_tet, Boolean compute_corners, Boolean centroid_at_origin) { VertexIndex v[4]; Complex z, sqrt_z, w[4]; Tetrahedron *tet; EdgeIndex best_edge, edge; /* * Set a default choice of tetrahedron and edge. */ *initial_tet = manifold->tet_list_begin.next; best_edge = 0; /* * 2000/02/11 JRW Can we choose the initial tetrahedron in such * a way that if we happen to have the canonical triangulation * of a 2-bridge knot or link complement, the basepoint falls * at a center of D2 symmetry? That is, can we find a Tetrahedron * that looks like the "top of the tower" in the canonical * triangulation of a 2-bridge knot or link complement? */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (edge = 0; edge < 6; edge++) if (tet->neighbor[one_face_at_edge [edge]] == tet->neighbor[other_face_at_edge[edge]]) { *initial_tet = tet; best_edge = edge; } if (compute_corners) { if (centroid_at_origin == TRUE) { /* * Proposition. For any value of w, positioning the corners at * * corner[0] = w * corner[1] = w^-1 * corner[2] = -w^-1 * corner[3] = -w * * defines a tetrahedron with its centroid at the "origin" and * the common perpendiculars between pairs of opposite edges * coincident with the "coordinate axes". [In the Klein model, * the tetrahedron is inscribed in a rectangular box whose faces * are parallel to the coordinate axes.] * * Proof: Use the observation that the line from a0 to a1 will * intersect the line from b0 to b1 iff the cross ratio * * (b0 - a0) (b1 - a1) * ------------------- * (b1 - a0) (b0 - a1) * * of the tetrahedron they span is real, and they will be * orthogonal iff the cross ratio is -1. * * [-w, w] is orthogonal to [0, infinity] because * * (0 - -w) (infinity - w) * ----------------------- = -1 * (infinity - -w) (0 - w) * * and similarly for [-w^-1, w^-1] and [0, infinity]. * * [w^-1, w] is orthogonal to [-1, 1] because * * (-1 - w^-1) (1 - w) * ------------------- = -1 * (1 - w^-1) (-1 - w) * * and similarly for [-w^-1, -w] and [-1, 1]. * * [-w^-1, w] is orthogonal to [-i, i] because * * (-i - -w^-1) (i - w) * -------------------- = -1 * (i - -w^-1) (-i - w) * * and similarly for [w^-1, -w] and [-i, i]. * * Q.E.D. * * * The tetrahedron will have the correct cross ratio z iff * * (w - -w^-1) (w^-1 - -w ) (w + w^-1)^2 * z = -------------------------- = -------------- * (w - -w ) (w^-1 - -w^-1) 4 * * Solving for w in terms of z gives the four possibilities * * w = +- (sqrt(z) +- sqrt(z - 1)) * * Note that sqrt(z) + sqrt(z - 1) and sqrt(z) - sqrt(z - 1) are * inverses of one another. We can choose any of the four solutions * to be "w", and the other three will automatically become w^-1, * -w, and -w^-1. * * Comment: This position for the initial corners brings out * nice numerical properties in the O(3,1) matrices for manifolds * composed of regular ideal tetrahedra (cf. the proofs in the * directory "Tilings of H^3", which aren't part of SnapPea, but * I could give you a copy). */ z = (*initial_tet)->shape[filled]->cwl[ultimate][0].rect; w[0] = complex_plus( complex_sqrt(z), complex_sqrt(complex_minus(z, One)) ); w[1] = complex_div(One, w[0]); w[2] = complex_negate(w[1]); w[3] = complex_negate(w[0]); (*initial_tet)->corner[0] = w[0]; (*initial_tet)->corner[1] = w[1]; (*initial_tet)->corner[2] = w[2]; (*initial_tet)->corner[3] = w[3]; } else { /* * Originally this code positioned the Tetrahedron's vertices * at {0, 1, z, infinity}. As of 2000/02/04 I modified it * to put the vertices at {0, 1/sqrt(z), sqrt(z), infinity} instead, * so that the basepoint (0,0,1) falls at the midpoint * of the edge extending from 0 to infinity, and the * tetrahedron's symmetry axis lies parallel to the x-axis. * To convince yourself that the tetrahedron's axis of * symmetry does indeed pass through that point, note * that a half turn around the axis of symmetry factors * as a reflection in the plane |z| = 1 followed by * a reflection in the vertical plane sitting over x-axis. */ /* * Order the vertices so that the tetrahedron is positively * oriented, and the selected edge is between vertices * v[0] and v[1]. */ v[0] = one_vertex_at_edge[best_edge]; v[1] = other_vertex_at_edge[best_edge]; v[2] = remaining_face[v[1]][v[0]]; v[3] = remaining_face[v[0]][v[1]]; /* * Set the coordinates of the corners. */ z = (*initial_tet)->shape[filled]->cwl[ultimate][edge3[best_edge]].rect; sqrt_z = complex_sqrt(z); (*initial_tet)->corner[v[0]] = Infinity; (*initial_tet)->corner[v[1]] = Zero; (*initial_tet)->corner[v[2]] = complex_div(One, sqrt_z); (*initial_tet)->corner[v[3]] = sqrt_z; } } } void compute_fourth_corner( Complex corner[4], VertexIndex missing_corner, Orientation orientation, ComplexWithLog cwl[3]) { int i; VertexIndex v[4]; Complex z[4], cross_ratio, diff20, diff21, numerator, denominator; /* * Given the locations on the sphere at infinity in * the upper half space model of three of a Tetrahedron's * four ideal vertices, compute_fourth_corner() computes * the location of the remaining corner. * * corner[4] is the array which contains the three known * corners, and into which the fourth will be * written. * * missing_corner is the index of the unknown corner. * * orientation is the Orientation with which the Tetrahedron * is currently being viewed. * * cwl[3] describes the shape of the Tetrahedron. */ /* * Set up an indexing scheme v[] for the vertices. * * If some vertex (!= missing_corner) is positioned at infinity, let its * index be v0. Otherwise choose v0 arbitrarily. Then choose * v2 and v3 so that the Tetrahedron looks right_handed relative * to the v[]. */ v[3] = missing_corner; v[0] = ! missing_corner; for (i = 0; i < 4; i++) if (i != missing_corner && complex_infinite(corner[i])) v[0] = i; if (orientation == right_handed) { v[1] = remaining_face[v[3]][v[0]]; v[2] = remaining_face[v[0]][v[3]]; } else { v[1] = remaining_face[v[0]][v[3]]; v[2] = remaining_face[v[3]][v[0]]; } /* * Let z[i] be the location of v[i]. * The z[i] are known for i < 3, unknown for i == 3. */ for (i = 0; i < 3; i++) z[i] = corner[v[i]]; /* * Note the cross_ratio at the edge connecting v0 to v1. */ cross_ratio = cwl[edge3_between_faces[v[0]][v[1]]].rect; if (orientation == left_handed) cross_ratio = complex_conjugate(complex_div(One, cross_ratio)); /* * The cross ratio is defined as * * (z3 - z1) (z2 - z0) * cross_ratio = ----------------------- * (z2 - z1) (z3 - z0) * * Solve for z3. * * z1*(z2 - z0) - cross_ratio*z0*(z2 - z1) * z3 = ----------------------------------------- * (z2 - z0) - cross_ratio*(z2 - z1) * * If z0 is infinite, this reduces to * * z3 = z1 + cross_ratio * (z2 - z1) * * which makes sense geometrically. */ if (complex_infinite(z[0]) == TRUE) z[3] = complex_plus( z[1], complex_mult( cross_ratio, complex_minus(z[2], z[1]) ) ); else { diff20 = complex_minus(z[2], z[0]); diff21 = complex_minus(z[2], z[1]); numerator = complex_minus( complex_mult(z[1], diff20), complex_mult( cross_ratio, complex_mult(z[0], diff21) ) ); denominator = complex_minus( diff20, complex_mult(cross_ratio, diff21) ); z[3] = complex_div(numerator, denominator); /* will handle division by Zero correctly */ } corner[missing_corner] = z[3]; } static void count_incident_generators( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; FaceIndex face, face1; /* * For each EdgeClass, initialize num_incident_generators to zero. * Initialize all the active_relation flags to TRUE while we're at it. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->num_incident_generators = 0; edge->active_relation = TRUE; } /* * For each face of a Tetrahedron dual to an outbound_generator, * increment the num_incident_generators count of the three * adjacent EdgeClasses. Ignore inbound_generators, to avoid * counting each generator twice. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) if (tet->generator_status[face] == outbound_generator) for (face1 = 0; face1 < 4; face1++) if (face1 != face) tet->edge_class[edge_between_faces[face][face1]]->num_incident_generators++; } static void eliminate_trivial_generators( Triangulation *manifold) { Boolean progress; EdgeClass *edge; do { progress = FALSE; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->num_incident_generators == 1) { kill_the_incident_generator(manifold, edge); progress = TRUE; } } while (progress == TRUE); } static void kill_the_incident_generator( Triangulation *manifold, EdgeClass *edge) { PositionedTet ptet, ptet0; int dead_index; Tetrahedron *tet, *nbr_tet; Permutation gluing; FaceIndex face, nbr_face; /* * The EdgeClass edge is incident to a unique generator. * Find it. */ set_left_edge(edge, &ptet0); ptet = ptet0; while (TRUE) { /* * If we've found the active generator, * break out of the while loop. Otherwise . . . */ if (ptet.tet->generator_status[ptet.near_face] != not_a_generator) break; /* * . . . move on to the next Tetrahedron incident to the EdgeClass. */ veer_left(&ptet); /* * If we've come all the way around the EdgeClass without * finding a generator, something has gone terribly wrong. */ if (same_positioned_tet(&ptet, &ptet0)) uFatalError("kill_the_incident_generator", "choose_generators.c"); } /* * Note the index of the about to be killed generator . . . */ dead_index = ptet.tet->generator_index[ptet.near_face]; /* * . . . then kill it. */ nbr_tet = ptet.tet->neighbor[ptet.near_face]; gluing = ptet.tet->gluing[ptet.near_face]; nbr_face = EVALUATE(gluing, ptet.near_face); ptet.tet->generator_status[ptet.near_face] = not_a_generator; nbr_tet ->generator_status[nbr_face] = not_a_generator; ptet.tet->generator_index[ptet.near_face] = -1; /* garbage value */ nbr_tet ->generator_index[nbr_face] = -1; /* * The EdgeClass no longer represents an active relation. */ edge->active_relation = FALSE; /* * Decrement the num_incident_generators count at each of * the incident EdgeClasses. */ ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.left_face] ]->num_incident_generators--; ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face] ]->num_incident_generators--; ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->num_incident_generators--; /* * Decrement *number_of_generators. */ manifold->num_generators--; /* * If dead_index was not the highest numbered generator, then removing * it will have left a gap in the numbering scheme. Renumber the highest * numbered generator to keep the numbering contiguous. */ if (dead_index != manifold->num_generators) { for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) if (tet->generator_index[face] == manifold->num_generators) { if (tet->generator_status[face] == not_a_generator) uFatalError("kill_the_incident_generator", "choose_generators.c"); nbr_tet = tet->neighbor[face]; gluing = tet->gluing[face]; nbr_face = EVALUATE(gluing, face); tet ->generator_index[face] = dead_index; nbr_tet->generator_index[nbr_face] = dead_index; /* * Rather than worrying about breaking out of a * double loop, let's just return from here. */ return; } /* * The program should return from within the above double loop. */ uFatalError("kill_the_incident_generator", "choose_generators.c"); } else /* dead_index == manifold->num_generators, so nothing else to do */ return; } static void merge_equivalent_generators( Triangulation *manifold) { EdgeClass *edge; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->num_incident_generators == 2) merge_incident_generators(manifold, edge); } static void merge_incident_generators( Triangulation *manifold, EdgeClass *edge) { PositionedTet ptet, ptet0; Tetrahedron *tetA = NULL, *tetB = NULL, *tet; FaceIndex faceA = 0, faceB = 0, face; int indexA, indexB; Boolean generator_A_has_been_found, directions_agree; /* * Find the two incident generators by letting ptet * rotate around the EdgeClass. The first time we * encounter a nontrivial generator, call it * faceA of tetA; the second time, faceB of tetB. */ set_left_edge(edge, &ptet0); ptet = ptet0; generator_A_has_been_found = FALSE; while (TRUE) { /* * If we've found an active generator, record it. * If this is the second one we've found, break out of the loop. */ if (ptet.tet->generator_status[ptet.near_face] != not_a_generator) { if (generator_A_has_been_found == FALSE) { tetA = ptet.tet; faceA = ptet.near_face; generator_A_has_been_found = TRUE; } else { tetB = ptet.tet; faceB = ptet.near_face; break; } } /* * Move on to the next Tetrahedron incident to the EdgeClass. */ veer_left(&ptet); /* * If we've come all the way around the EdgeClass without * finding both generators, something has gone terribly wrong. */ if (same_positioned_tet(&ptet, &ptet0)) uFatalError("kill_the_incident_generator", "choose_generators.c"); } /* * If the two generators are the same, then either their product is * aA (in which case there is no further work to be done) or aa (in * which case they cannot be merged). Either way, we simply return. * [JRW 95/1/19. Actually, I don't think the first case (aA) is * likely to occur. The n-gons which are subdivided into triangles * have no interior vertices, so under normal circumstances the * generators we're merging should be distinct. If they're not, * it means we have a "face" which is topologically a cylinder, * or something weird like that. At any rate, we should return * without taking any action.] */ indexA = tetA->generator_index[faceA]; indexB = tetB->generator_index[faceB]; if (indexA == indexB) return; /* * Do the directions of the generators agree or disagree? * Note that the generator will point in the same direction * relative to the boundary of the fundamental domain iff * one is an outbound_generator and the other is an inbound_generator * relative to the preceding cyclic traversal around the EdgeClass. */ directions_agree = (tetA->generator_status[faceA] != tetB->generator_status[faceB]); /* * If directions_agree is FALSE, reverse the direction of generator A. * Then let generator A inherit the index of generator B. * * Let the highest numbered generator inherit the former index * of generator A, and decrement the number_of_generators count. * * Even in the special cases where indexA or indexB is the highest * index, generators A and B get merged, and the previously highest * index will no longer occur. This keeps the indices contiguous. */ manifold->num_generators--; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (face = 0; face < 4; face++) { if (tet->generator_index[face] == indexA) { if (directions_agree == FALSE) { if (tet->generator_status[face] == outbound_generator) tet->generator_status[face] = inbound_generator; else if (tet->generator_status[face] == inbound_generator) tet->generator_status[face] = outbound_generator; else uFatalError("merge_incident_generators", "choose_generators.c"); } tet->generator_index[face] = indexB; } if (tet->generator_index[face] == manifold->num_generators) tet->generator_index[face] = indexA; } /* * The EdgeClass no longer represents an active relation. */ edge->active_relation = FALSE; } static void eliminate_empty_relations(Triangulation *manifold) { EdgeClass *edge; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if (edge->num_incident_generators == 0) edge->active_relation = FALSE; } regina-4.95/engine/snappea/kernel/close_cusps.c000644 000765 000024 00000151370 12235724562 021470 0ustar00babstaff000000 000000 /* * close_cusps.c * * This file contains the function * * void close_cusps(Triangulation *manifold, Boolean fill_cusp[]); * * which is used by the function fill_cusps() to permanently * close the indicated cusps (those for which fill_cusp[cusp->index] * is TRUE). The Triangulation *manifold must be a manifold with * finite vertices, prepared as in subdivide(). Specifically, we * assume that the Tetrahedra incident to the cusps form disjoint * regular neighborhoods of the cusps in the manifold. close_cusps() * removes the regular neighborhood of each cusp, and fills in the * hole in such a way that the given Dehn filling curve (as specified * by cusp->m and cusp->l) becomes a trivial curve in the new manifold. * I.e. it does the Dehn filling. We assume the Dehn filling coefficients * are relatively prime integers (fill_cusps() checks this). * * * The remainder of this comment briefly explains the algorithm used * to fill the cusps. It is not a polished exposition, but I hope it * is mathematically clear and complete. * * We consider the 2-dimensional triangulation of a boundary component * of the manifold obtained when the regular neighborhood of a cusp * (as mentioned above) is stripped off. The basic idea is to fold * together adjacent triangles (which are really exposed faces of * tetrahedra) using a "close-the-book" move. Proposition 1 below * gives a sufficient condition that the folding does not change the * topology of either the manifold or its boundary. Proposition 2 * proves that if we keep folding as long as possible (i.e. as long * as we can find a pair of triangles satisfying the hypothesis of * Proposition 1), we will end up with no more than six triangles in * the (2-dimensional) triangulation. Further fussing around will * reduce the number of triangles to two. * * Before stating Propositions 1 and 2, we need a definition. * * Consider a triangle, along with one of its neighbors, which may or * may not be distinct from the first triangle. The neighbor won't be * distinct iff the first triangle is glued to itself along the given edge. * Strictly speaking, the illustration below lies in the universal cover. * * o * /|\ * / | \ * / | \ * A o | o B * \ | / * \ | / * \|/ * o * * Definition. When discussing two adjacent (but not necessarily * distinct) triangles sharing a common edge, the vertices further * from the common edge are called the "opposite vertices". In the * above illustration, the opposite vertices are labelled A and B. * * Proposition A. The close-the-book move is valid if the * opposite vertices are distinct. * * Proposition B. If we apply the close-the-book move * until there are no more adjacent triangles with * distinct opposite vertices, then we will have reached * a triangulation with at most 6 triangles. Attaching * one or two tetrahedra to the boundary lets us further * simplify the triangulation to have exactly two triangles. * * First a few preparatory lemmas. * * Lemma 1. If the opposite vertices are distinct, * then the triangles are distinct too. * * Proof. There are two ways a triangle may be glued * to itself (orientation preserving and orientation * reversing). It's trivial to check each case, and see * that the vertices opposite the identifed edge are * themselves identified. [In the figure below, I * attempted to draw arrows showing the edge identifications.] * * B A * o o * / \ / \ * _/ \_ _/ \ * /| |\ /| _\| * / \ / \ * A o---------o A A o---------o A * * * Lemma 2. The number of vertices in a triangulation of a * torus or Klein bottle is half the number of triangles. * * Proof. This is an easy Euler characteristic argument. * * chi = 0 = v - e + f = v - (3/2)f + f * * => v = f/2. * * Comment: We will think of the close-the-book move in * purely two-dimensional terms. We think of it as a two-step * process. First we collapse a line segment connecting the * opposite vertices, then we collapse the two resulting bigons. * * draw the line * connecting collapse collapse * opposite the the * vertices line bigons * o o o o * /|\ / \ / \ | * / | \ / \ | | | * / | \ / \ \ / | * A o | o B A o-------o B o A = B o * \ | / \ / / \ | * \ | / \ / | | | * \|/ \ / \ / | * o o o o * * Proposition A addresses the question of when these operations * are valid. * * Proof of Proposition A. * By Lemma 1 the triangles are distinct, so the * segment connecting A to B is an embedded arc with distinct * endpoints, and may therefore by collapsed to a point as * shown in the above illustration. * The two edges of the upper bigon (see the third frame * of the above illustration) are distinct, since otherwise * the top two edges in the second frame of the illustration * would be identified, and Lemma 1 would imply A = B. * Similarly, the two edges of the lower bigon are distinct. * It's possible that one edge of the upper bigon is identified * with an edge of the lower bigon, but both edges of the upper * bigon cannot be identified to edges of the lower, since that * would imply that the original triangulation contained only the * two triangles shown, and Lemma 2 would then imply that there * is only one vertex. It follows that the bigons may be collapsed. * Q.E.D. * * Comment. The converse to the Proposition A is almost true. If the * opposite vertices are not distinct (A = B in the above * illustration) then the line connecting them is a circle. * If this circle is homotopically nontrivial, then it certainly * cannot be collapsed to a point. However, if it's homotopically * trivial, then a modification of the close-the-book move is still * possible (but is not used in SnapPea's algorithm). * * Proof of Proposition B. * If opposite vertices are equivalent for each pair of adjacent * triangles, then all triangles will have the same set of vertices * (the torus or Klein bottle is connected). * Therefore the triangulation contains at most three vertices, * and, by Lemma 2, at most six triangles. * We now address the question of how to reduce the triangulation * to only two vertices. The basic idea is to find an edge connecting * two inequivalent vertices, and attach a (solid!) tetrahedron * so as to implement a "two-to-two move" in the triangulation of the * boundary. * * attach a * tetrahedron * to alter the * triangulation * as shown * o o * / \ /|\ * / \ / | \ * / \ / | \ * A o-------o B A o | o B * \ / \ | / * \ / \ | / * \ / \|/ * o o * * Clearly an edge connecting inequivalent vertices must exist * (by the connectivity of the torus or Klein bottle). The only * question is whether the two incident triangles are distinct. * If they weren't distinct, then the edge identifications would * have to have the pattern shown on the left side of the * illustration accompanying the proof of Lemma 1. (They couldn't * have the pattern shown on the right side of that illustration, * because then there wouldn't be two inequivalent vertices.) * Vertex B in the left side of the illustration accompanying * the proof of Lemma 1 would be isolated from the rest of the * boundary manifold. Therefore it would be opposite a distinct * vertex, and the close-the-book move would still be possible. * Q.E.D. * * Technical note: close_cusps() always performs the two-to-two * move in such a way that the subsequent close-the-book move does * not fold together two faces of the same tetrahedron. This * avoids needlessly creating an EdgeClass of order one in the * manifold. Maybe such an EdgeClass would do no harm, but who knows? */ #include "kernel.h" struct extra { VertexIndex ideal_vertex_index; int Dehn_filling_curve[4]; }; static void transfer_to_short_list(Triangulation *manifold, Boolean fill_cusp[], Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean incident_to_filled_cusp(Tetrahedron *tet, Boolean fill_cusp[]); static void simplify_cusps(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void fold_boundary(Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean cancel_triangles(Tetrahedron *tet, FaceIndex f0); static Boolean further_simplification(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static Boolean two_to_two(Triangulation *manifold, Tetrahedron *tet, FaceIndex f0, Boolean require_distinct_edges); static void transfer_curves(Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void standard_form(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void standard_torus_form(Triangulation *manifold, Tetrahedron *tet); static int max_abs_intersection_number(Tetrahedron *tet); static void apply_two_to_two_to_eliminate(Triangulation *manifold, Tetrahedron *tet, int target); static void standard_Klein_bottle_form(Triangulation *manifold, Tetrahedron *tet); static void fold_cusps(Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end); static void fold_one_cusp(Triangulation *manifold, Tetrahedron *tet0); static void replace_fake_cusps(Triangulation *manifold); static void renumber_real_cusps(Triangulation *manifold); void close_cusps( Triangulation *manifold, Boolean fill_cusp[]) { Tetrahedron short_list_begin, short_list_end; /* * Move the Tetrahedra incident to cusps-to-be-filled onto a * separate short list, so we don't have to be constantly sifting * through vast numbers of irrelevant Tetrahedra. Attach and * initialize an Extra structure on each Tetrahedron on the * short list. */ transfer_to_short_list(manifold, fill_cusp, &short_list_begin, &short_list_end); /* * Simplify the cusps to be filled until each is triangulated * by at most two triangles. Ignore (1) fake "Cusps" at finite * vertices and (2) EdgeClasses not incident to a real Cusp; * we'll fix them up at the end. We maintain the tet->edge_class() * fields for edges incident to real Cusps so that we can tell * whether two EdgeClasses are distinct. The fields within an * EdgeClass are not maintained. */ simplify_cusps(manifold, &short_list_begin, &short_list_end); /* * Transfer the Dehn filling curves to tet->extra->Dehn_filling_curve[]. */ transfer_curves(&short_list_begin, &short_list_end); /* * Put each boundary triangulation at each cusp to be filled into * the standard form: * * torus Klein bottle * ------>>----- ------>------ * | @ /| | @ /| * | @ / | | @ / | * | @ | | @ | * | / @ | | / @ | * | / @| | / @| * ^@ / ^ ^@ / ^ * | @ / | | @ / ^ * | @/ | | @/ | * | /@ | | /@ | * | / @ | | / @ | * |/ @ | |/ @ | * ------>>----- ------>>----- * * * where the line of @'s is the Dehn filling curve. */ standard_form(manifold, &short_list_begin, &short_list_end); /* * Collapse each cusp by folding along the diagonal in * the above illustrations. */ fold_cusps(manifold, &short_list_begin, &short_list_end); /* * Get rid of the old EdgeClasses and install new ones. */ replace_edge_classes(manifold); /* * Get rid of the old fake Cusps and install new ones. */ replace_fake_cusps(manifold); /* * Renumber the remaining real Cusps, so the indices * are contiguous. */ renumber_real_cusps(manifold); /* * 96/9/28 I haven't actually observed any incorrect behavior * (and I don't think there is any) but I was looking through * the kernel code to see how orient() was being used, and I * got to wondering whether close_cusps() is guaranteed to preserve * the orientation. Just to make sure it does, call orient() now, * to transfer the orientation from one of the untouched tetrahedra * (i.e. an original tetrahedron not incident to one of the * cusps-to-be-filled) to all remaining tetrahedra, including the * new ones. * * 96/9/30 After adding the call to orient() I rechecked all * Chern-Simons value for the cusped census, and they are all correct. */ orient(manifold); } static void transfer_to_short_list( Triangulation *manifold, Boolean fill_cusp[], Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet, *this_tet; /* * Initialize the short list. */ short_list_begin->prev = NULL; short_list_begin->next = short_list_end; short_list_end ->prev = short_list_begin; short_list_end ->next = NULL; /* * Transfer Tetrahedra incident to cusps-to-be-filled * to the short list. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (incident_to_filled_cusp(tet, fill_cusp) == TRUE) { this_tet = tet; tet = tet->prev; /* so the loop proceeds correctly */ REMOVE_NODE(this_tet); INSERT_BEFORE(this_tet, short_list_end); manifold->num_tetrahedra--; } } static Boolean incident_to_filled_cusp( Tetrahedron *tet, Boolean fill_cusp[]) { int i; for (i = 0; i < 4; i++) if (tet->cusp[i]->is_finite == FALSE && fill_cusp[tet->cusp[i]->index] == TRUE) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("incident_to_filled_cusp", "close_cusps"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); /* * Record for posterity the index of the ideal vertex. */ tet->extra->ideal_vertex_index = i; return TRUE; } return FALSE; } static void simplify_cusps( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { /* * simplify_cusps() simplifies a triangulation until each * boundary component is triangulated by exactly two triangles. * * It calls two other functions: * * fold_boundary() folds together adjacent boundary * triangles whereever possible, as explained in the proofs * at the top of this file. It is guaranteed to reduce the * triangulation of each boundary component to at most six * triangles. * * further_simplification() look for an edge (in the 2-dimensional * triangulation of the boundary) with distinct endpoints. * When it finds one it does a two-to-two move, as shown in * the illustration below, so that fold_boundary() can * make further progress. Because the number of vertices is * exactly half the number of triangles, further_simplification() * is guaranteed to make progress as long as the number of * triangles in a given boundary component exceeds two. * * before after * /\ c /|\ c * / \ / | \ * / \ / | \ * / \ / | \ * a /________\ b a / | \ b * \ / \ | / * \ / \ | / * \ / \ | / * \ / \ | / * \/ c \|/ c * * Note that this algorithm risks the creation of edges of order one * in the (3-dimenisional) triangulation of the manifold. But we * bravely press on, confident that if we get into trouble further * down the road, we can call a general purpose simplification * routine to remove the offending edges. */ do { fold_boundary(short_list_begin, short_list_end); } while (further_simplification(manifold, short_list_begin, short_list_end) == TRUE); } static void fold_boundary( Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; FaceIndex f; /* * Scan down the list of boundary Tetrahedra, looking for * one which can be cancelled with one of its neighbors. * (One expects almost any pair of adjacent boundary Tetraheda * to be cancellable.) When one is found, do the cancellation * and resume the search from the start of the list. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) for (f = 0; f < 4; f++) { if (f == tet->extra->ideal_vertex_index) continue; if (cancel_triangles(tet, f) == TRUE) { tet = short_list_begin; break; } } } static Boolean cancel_triangles( Tetrahedron *tet, FaceIndex f0) { Tetrahedron *nbr_tet, *t, *nbr_t; FaceIndex f[4], nbr_f[4], v[4], nbr_v[4]; EdgeIndex edge, nbr_edge; EdgeClass *e_class, *nbr_class; int i, ii, j; int b[2], c[2], delta[2][2]; PositionedTet ptet, ptet0; /* * f0 will be part of an array. */ f[0] = f0; /* * Find the neighbor adjacent to face f[0]. */ nbr_tet = tet->neighbor[f[0]]; nbr_f[0] = EVALUATE(tet->gluing[f[0]], f[0]); /* * Note the base of each Tetrahedron. */ f[1] = tet->extra->ideal_vertex_index; nbr_f[1] = nbr_tet->extra->ideal_vertex_index; /* * Do a quick error check. */ if (nbr_f[1] != EVALUATE(tet->gluing[f[0]], f[1])) uFatalError("cancel_triangles", "close_cusps"); /* * Note the EdgeIndices of the "vertical" edges farthest from * the common face. According to the propositions at the top * of this file, the Tetrahedra may be cancelled iff these * two edges belong to different EdgeClasses. */ edge = edge_between_vertices[f[0]][f[1]]; nbr_edge = edge_between_vertices[nbr_f[0]][nbr_f[1]]; e_class = tet->edge_class[edge]; nbr_class = nbr_tet->edge_class[nbr_edge]; if (e_class == nbr_class) return FALSE; /* * According to the propositions at the top of this file, * the fact that the EdgeClasses are distinct implies that * the Tetrahedra are distinct as well. Let's check, just * to be sure. */ if (tet == nbr_tet) uFatalError("cancel_triangles", "close_cusps"); /* * The following line isn't really necessary, but it serves to avoid * creating EdgeClasses of order one. If further_simplification() * has just laid down a Tetrahedron to implement a two-to-two * move, we want to avoid folding that Tetrahedron onto itself. * By checking cases (cf. the documentation and illustrations * in two_to_two() below) it's easy to see that some other * call to cancel_triangles() (not involving gluing a Tetrahedron * to itself) must succeed. */ if (tet->neighbor[f[1]] == nbr_tet->neighbor[nbr_f[1]]) return FALSE; /* * Adjust the peripheral curves so that when we collapse * the Tetrahedra, the curves match up correctly. * * /\ * / \ * a / \ b * / \ * /________\ * \ / * \ / * d \ / c * \ / * \/ E * * We want to insure that b = -c, which automatically imples a = -d. * Geometrically, all strands at c should pass to b, and all strands * at d should pass to a. Nothing should cut across the middle * from c to a, or from d to b. To accomplish this, we subtract * (b + c) all the way around the (vertical) EdgeClass E. * * As long as we're travelling around the EdgeClass, set the * tet->edge_class pointers to the address of the EdgeClass * this one is merging into. */ f[2] = remaining_face[f[1]][f[0]]; f[3] = remaining_face[f[0]][f[1]]; nbr_f[2] = EVALUATE(tet->gluing[f[0]], f[2]); nbr_f[3] = EVALUATE(tet->gluing[f[0]], f[3]); for (i = 0; i < 2; i++) /* which sheet */ { ii = (parity[tet->gluing[f[0]]] == orientation_preserving) ? i : !i; for (j = 0; j < 2; j++) /* which curve */ { b[j] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[2]]; c[j] = tet->curve[j][i ][ f[1]][ f[2]]; delta[j][i] = b[j] + c[j]; } } ptet0.tet = tet; ptet0.near_face = f[2]; ptet0.left_face = f[3]; ptet0.right_face = f[0]; ptet0.bottom_face = f[1]; ptet0.orientation = right_handed; ptet = ptet0; do { for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; for (j = 0; j < 2; j++) /* which curve */ { ptet.tet->curve[j][i][ptet.bottom_face][ptet.left_face] += delta[j][ii]; ptet.tet->curve[j][i][ptet.bottom_face][ptet.near_face] -= delta[j][ii]; } } ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.left_face]] = nbr_class; veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); /* * Imagine removing tet and nbr_tet from the manifold. * Glues together the three exposed pairs of faces. * * Miraculously, this code is correct even in degenerate * cases (corresponding to when the bigons collapse in * the proofs at the top of this file). */ for (i = 1; i < 4; i++) { t = tet->neighbor[f[i]]; nbr_t = nbr_tet->neighbor[nbr_f[i]]; for (j = 0; j < 4; j++) { v[j] = EVALUATE(tet->gluing[f[i]], f[j]); nbr_v[j] = EVALUATE(nbr_tet->gluing[nbr_f[i]], nbr_f[j]); } t->neighbor[v[i]] = nbr_t; nbr_t->neighbor[nbr_v[i]] = t; t->gluing[v[i]] = CREATE_PERMUTATION(v[0], nbr_v[0], v[1], nbr_v[1], v[2], nbr_v[2], v[3], nbr_v[3]); nbr_t->gluing[nbr_v[i]] = CREATE_PERMUTATION(nbr_v[0], v[0], nbr_v[1], v[1], nbr_v[2], v[2], nbr_v[3], v[3]); } /* * Free tet and nbr_tet. */ REMOVE_NODE(tet); REMOVE_NODE(nbr_tet); free_tetrahedron(tet); free_tetrahedron(nbr_tet); return TRUE; } static Boolean further_simplification( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; FaceIndex f; /* * Scan down the list of boundary Tetrahedra, looking for * an edge (in the 2-dimensional triangulation of the boundary) * with distinct endpoints. If one is found, do the necessary * retriangulation (as illustrated in simplify_cusps() above) * and return TRUE. If none are found, return FALSE. * * Note that at most one retriangulation will be performed in * a single call to further_simplification(). */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) for (f = 0; f < 4; f++) { if (f == tet->extra->ideal_vertex_index) continue; if (two_to_two(manifold, tet, f, TRUE) == TRUE) return TRUE; } return FALSE; } /* * two_to_two() is called from two different parts of close_cusps(). * * (1) further_simplification() calls it to alter a triangulation * so that the number of triangles in the triangulation of * a boundary component can be reduced from 4 or 6 to 2. * In this case, two_to_two() should do nothing and return * FALSE if a certain pair of EdgeClasses are not distinct. * * (2) standard_form() calls it (indirectly) to put 2-triangle * triangulations into the standard form. Here there is no * need for any EdgeClasses to be distinct (in fact, they never * will be). * * The argument require_distinct_edges says whether distinct EdgeClasses * should be required. */ static Boolean two_to_two( Triangulation *manifold, Tetrahedron *tet, FaceIndex f0, Boolean require_distinct_edges) { int i, ii, j; FaceIndex f[4], nbr_f[4]; Tetrahedron *nbr_tet, *new_tet, *tetA, *tetB; EdgeClass *e_class[4]; /* * Get set up as in cancel_triangles() above. */ f[0] = f0; nbr_tet = tet->neighbor[f[0]]; nbr_f[0] = EVALUATE(tet->gluing[f[0]], f[0]); f[1] = tet->extra->ideal_vertex_index; nbr_f[1] = nbr_tet->extra->ideal_vertex_index; if (nbr_f[1] != EVALUATE(tet->gluing[f[0]], f[1])) uFatalError("two_to_two", "close_cusps"); f[2] = remaining_face[f[1]][f[0]]; f[3] = remaining_face[f[0]][f[1]]; nbr_f[2] = EVALUATE(tet->gluing[f[0]], f[2]); nbr_f[3] = EVALUATE(tet->gluing[f[0]], f[3]); /* * Note the EdgeClasses. */ e_class[0] = tet->edge_class[ edge_between_faces[ f[2]][ f[3]] ]; e_class[1] = nbr_tet->edge_class[ edge_between_faces[nbr_f[2]][nbr_f[3]] ]; e_class[2] = tet->edge_class[ edge_between_faces[ f[0]][ f[2]] ]; e_class[3] = tet->edge_class[ edge_between_faces[ f[0]][ f[3]] ]; /* * If require_distinct_edges is TRUE, check the EdgeClasses. * (See comment preceeding this function.) * further_simplification() can make progress iff e_class[2] != e_class[3]. */ if (require_distinct_edges == TRUE && e_class[2] == e_class[3]) return FALSE; /* * In further_simplification() . . . * * We now know that no triangle in the * 2-dimensional triangulation of the boundary is * glued to itself, because if it were glued to itself with * * an orientation preserving gluing, then there would * be an isolated vertex, and further progress would * have been possible in fold_boundary() * * an orientation reversing gluing, then there would be * only one vertex in the triangulation of the boundary, * and class2 would have equalled class3. (Recall * that when fold_boundary() can make no more * progress, all boundary triangles have the same set * of vertices, including multiplicity.) * * In standard_form() . . . * * We know that no triangle can be glued to itself, because * otherwise the boundary would be a Klein bottle already in * standard form. */ if (tet == nbr_tet) uFatalError("two_to_two", "close_cusps"); /* * [This comment applies on if we were called from * further_simplification().] * * Continuing with the idea that all boundary triangles * have the same vertex set, it follows that each triangle * must be of the form * * A A * o o * / \ / \ * / \ OR / \ * / \ / \ * / \ / \ * B o---------o B B o---------o C * * In the first case, the boundary component is formed * by identifying sides of the square * * B o-------------o B * |\ /| * | \ / | * | \ / | * | \ / | * | \ / | * | \ / | * | A o | * | / \ | * | / \ | * | / \ | * | / \ | * | / \ | * |/ \| * B o-------------o B * * and in the second case by identifying sides of the * hexagon * * B ____________ C * /\ /\ * / \ / \ * / \ / \ * / \ / \ * / \ / \ * C /_________A\/__________\ B * \ /\ / * \ / \ / * \ / \ / * \ / \ / * \ / \ / * \/__________\/ * B C * * It's easy to figure out that there are three possible * gluing patterns for the square (xyXY, xyXy and xxyy) * and two for the hexagon (xyzXYZ and xyzXzy). In all * but one case (the xxyy gluing of the square) we may * conclude that tet and nbr_tet's neighbors are distinct * from tet and nbr_tet (except for obvious place they meet). * Even in the exceptional case, there are other places * where we could do the two-to-two move where the * neighbors are distinct from tet and nbr_tet. So * if the neighbors aren't distinct from tet and nbr_tet, * we simply return FALSE and wait for one of those * more convenient places to show up. */ /* * First label everything in sight. * Use macros rather than writing quantities into * arrays, so that in cases where tet and nbr_tet are glued * nontrivially to each other, the results will be correct. * That is, we want the quantities to be reevaluated every * time they are used. */ #define TET_T(i) tet->neighbor[f[i]] #define NBR_T(i) nbr_tet->neighbor[nbr_f[i]] #define TET_V(i,j) EVALUATE(tet->gluing[f[i]], f[j]) #define NBR_V(i,j) EVALUATE(nbr_tet->gluing[nbr_f[i]], nbr_f[j]) /* * In the event we were called from further_simplification, * check that the TET_T(i) and NBR_T(i) are distinct from * tet and nbr_tet. This avoids unnecessarily creating an * EdgeClass of order one (maybe it doesn't matter, but * why risk it?). */ if (require_distinct_edges == TRUE) for (i = 2; i < 4; i++) if (TET_T(i) == tet || TET_T(i) == nbr_tet || NBR_T(i) == tet || NBR_T(i) == nbr_tet) return FALSE; /* * We introduce a new Tetrahedron which realizes the * two-to-two move on the boundary triangulation, and * adjust tet and nbr_tet to sit correctly above it. * * We could just reuse the tet and nbr_tet structures, but * then there'd be problems if either is glued to itself or * the other (aside from the obvious place they're glued). * So we replace tet and nbr_tet with tetA and tetB * respectively. * * You might want to draw a picture to keep track of what's * going on. * * Vertex 0 of the new_tet sits at vertex (not face) f[0] of tet. * Vertex 1 of the new_tet sits at vertex (not face) nbr_f[0] of nbr_tet. * Vertex 2 of the new_tet sits at vertex (not face) f[3] of tet. * Vertex 3 of the new_tet sits at vertex (not face) f[2] of tet. * * Vertex 0 of tetA will be over vertex 0 of new_tet. * Vertex 1 of tetA will be at the cusp. * Vertex 2 of tetA will be over vertex 1 of new_tet. * Vertex 3 of tetA will be over vertex 2 of new_tet. * * Vertex 0 of tetB will be over vertex 1 of new_tet. * Vertex 1 of tetB will be at the cusp. * Vertex 2 of tetB will be over vertex 3 of new_tet. * Vertex 3 of tetB will be over vertex 0 of new_tet. * * Take a deep breath and set all the necessary fields . . . */ new_tet = NEW_STRUCT(Tetrahedron); tetA = NEW_STRUCT(Tetrahedron); tetB = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); initialize_tetrahedron(tetA); initialize_tetrahedron(tetB); new_tet->cusp[0] = tet->cusp[ f[0]]; new_tet->cusp[1] = nbr_tet->cusp[nbr_f[0]]; new_tet->cusp[2] = tet->cusp[ f[3]]; new_tet->cusp[3] = tet->cusp[ f[2]]; new_tet->neighbor[0] = NBR_T(1); new_tet->neighbor[1] = TET_T(1); new_tet->gluing[0] = CREATE_PERMUTATION( 0, NBR_V(1, 1), 1, NBR_V(1, 0), 2, NBR_V(1, 3), 3, NBR_V(1, 2)); new_tet->gluing[1] = CREATE_PERMUTATION( 0, TET_V(1, 0), 1, TET_V(1, 1), 2, TET_V(1, 3), 3, TET_V(1, 2)); NBR_T(1)->neighbor[NBR_V(1, 1)] = new_tet; TET_T(1)->neighbor[TET_V(1, 1)] = new_tet; NBR_T(1)->gluing[NBR_V(1, 1)] = inverse_permutation[new_tet->gluing[0]]; TET_T(1)->gluing[TET_V(1, 1)] = inverse_permutation[new_tet->gluing[1]]; new_tet->neighbor[2] = tetB; new_tet->neighbor[3] = tetA; new_tet->gluing[2] = CREATE_PERMUTATION(0, 3, 1, 0, 2, 1, 3, 2); new_tet->gluing[3] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 3, 3, 1); tetA->neighbor[1] = new_tet; tetB->neighbor[1] = new_tet; tetA->gluing[1] = inverse_permutation[new_tet->gluing[3]]; tetB->gluing[1] = inverse_permutation[new_tet->gluing[2]]; tetA->neighbor[2] = TET_T(2); tetA->gluing[2] = CREATE_PERMUTATION(0, TET_V(2, 0), 1, TET_V(2, 1), 2, TET_V(2, 2), 3, TET_V(2, 3)); TET_T(2)->neighbor[TET_V(2, 2)] = tetA; TET_T(2)->gluing[TET_V(2, 2)] = inverse_permutation[tetA->gluing[2]]; tetA->neighbor[0] = NBR_T(2); tetA->gluing[0] = CREATE_PERMUTATION(0, NBR_V(2, 2), 1, NBR_V(2, 1), 2, NBR_V(2, 0), 3, NBR_V(2, 3)); NBR_T(2)->neighbor[NBR_V(2, 2)] = tetA; NBR_T(2)->gluing[NBR_V(2, 2)] = inverse_permutation[tetA->gluing[0]]; tetB->neighbor[0] = TET_T(3); tetB->gluing[0] = CREATE_PERMUTATION(0, TET_V(3, 3), 1, TET_V(3, 1), 2, TET_V(3, 2), 3, TET_V(3, 0)); TET_T(3)->neighbor[TET_V(3, 3)] = tetB; TET_T(3)->gluing[TET_V(3, 3)] = inverse_permutation[tetB->gluing[0]]; tetB->neighbor[3] = NBR_T(3); tetB->gluing[3] = CREATE_PERMUTATION(0, NBR_V(3, 0), 1, NBR_V(3, 1), 2, NBR_V(3, 2), 3, NBR_V(3, 3)); NBR_T(3)->neighbor[NBR_V(3, 3)] = tetB; NBR_T(3)->gluing[NBR_V(3, 3)] = inverse_permutation[tetB->gluing[3]]; tetA->neighbor[3] = tetB; tetB->neighbor[2] = tetA; tetA->gluing[3] = CREATE_PERMUTATION(0, 3, 1, 1, 2, 0, 3, 2); tetB->gluing[2] = inverse_permutation[tetA->gluing[3]]; for (j = 0; j < 2; j++) /* which curve */ for (i = 0; i < 2; i++) /* which sheet */ { ii = (parity[CREATE_PERMUTATION(0, f[0], 1, f[1], 2, f[2], 3, f[3])] == 0) ? i : !i; tetA->curve[j][i][1][2] = tet->curve[j][ii][f[1]][f[2]]; ii = (parity[CREATE_PERMUTATION(0, nbr_f[2], 1, nbr_f[1], 2, nbr_f[0], 3, nbr_f[3])] == 0) ? i : !i; tetA->curve[j][i][1][0] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[2]]; tetA->curve[j][i][1][3] = - (tetA->curve[j][i][1][2] + tetA->curve[j][i][1][0]); ii = (parity[CREATE_PERMUTATION(0, f[3], 1, f[1], 2, f[2], 3, f[0])] == 0) ? i : !i; tetB->curve[j][i][1][0] = tet->curve[j][ii][f[1]][f[3]]; ii = (parity[CREATE_PERMUTATION(0, nbr_f[0], 1, nbr_f[1], 2, nbr_f[2], 3, nbr_f[3])] == 0) ? i : !i; tetB->curve[j][i][1][3] = nbr_tet->curve[j][ii][nbr_f[1]][nbr_f[3]]; tetB->curve[j][i][1][2] = - (tetB->curve[j][i][1][0] + tetB->curve[j][i][1][3]); } tetA->edge_class[edge_between_faces[2][3]] = e_class[0]; tetA->edge_class[edge_between_faces[3][0]] = e_class[1]; tetA->edge_class[edge_between_faces[0][2]] = e_class[2]; tetB->edge_class[edge_between_faces[0][2]] = e_class[0]; tetB->edge_class[edge_between_faces[2][3]] = e_class[1]; tetB->edge_class[edge_between_faces[3][0]] = e_class[3]; tetA->cusp[1] = tet->cusp[f[1]]; tetB->cusp[1] = tet->cusp[f[1]]; tetA->cusp[0] = new_tet->cusp[0]; tetA->cusp[2] = new_tet->cusp[1]; tetA->cusp[3] = new_tet->cusp[2]; tetB->cusp[0] = new_tet->cusp[1]; tetB->cusp[2] = new_tet->cusp[3]; tetB->cusp[3] = new_tet->cusp[0]; tetA->extra = NEW_STRUCT(Extra); tetB->extra = NEW_STRUCT(Extra); tetA->extra->ideal_vertex_index = 1; tetB->extra->ideal_vertex_index = 1; if (require_distinct_edges == FALSE) { tetA->extra->Dehn_filling_curve[2] = tet->extra->Dehn_filling_curve[ f[2]]; tetB->extra->Dehn_filling_curve[0] = tet->extra->Dehn_filling_curve[ f[3]]; tetA->extra->Dehn_filling_curve[0] = nbr_tet->extra->Dehn_filling_curve[nbr_f[2]]; tetB->extra->Dehn_filling_curve[3] = nbr_tet->extra->Dehn_filling_curve[nbr_f[3]]; tetA->extra->Dehn_filling_curve[3] = - (tetA->extra->Dehn_filling_curve[2] + tetA->extra->Dehn_filling_curve[0]); tetB->extra->Dehn_filling_curve[2] = - (tetB->extra->Dehn_filling_curve[0] + tetB->extra->Dehn_filling_curve[3]); } INSERT_BEFORE(new_tet, &manifold->tet_list_end); /* * To avoid screwing up linked lists of Tetrahedra * (i.e. the short list, which standard_form() traverses * in a for(;;) loop), we copy tetA and tetB onto the * storage formerly used by tet and nbr_tet. * In spirit they are new Tetrahedra, but we want * them to occupy the same physical memory as the old * Tetrahedra, so as not to screw up a higher level * function which holds a pointer to one of the old * Tetrahedra. */ tetA->prev = tet->prev; tetB->prev = nbr_tet->prev; tetA->next = tet->next; tetB->next = nbr_tet->next; my_free( tet->extra); my_free(nbr_tet->extra); *tet = *tetA; *nbr_tet = *tetB; /* * If they are glued to themselves, correct the * neighbor fields. */ for (i = 0; i < 4; i++) { if (tet->neighbor[i] == tetA) tet->neighbor[i] = tet; if (tet->neighbor[i] == tetB) tet->neighbor[i] = nbr_tet; if (nbr_tet->neighbor[i] == tetA) nbr_tet->neighbor[i] = tet; if (nbr_tet->neighbor[i] == tetB) nbr_tet->neighbor[i] = nbr_tet; } /* * Correct the neighbor fields for remaining neighbors. */ for (i = 0; i < 4; i++) { tet->neighbor[i]->neighbor[EVALUATE( tet->gluing[i],i)] = tet; nbr_tet->neighbor[i]->neighbor[EVALUATE(nbr_tet->gluing[i],i)] = nbr_tet; } my_free(tetA); my_free(tetB); manifold->num_tetrahedra++; return TRUE; } static void transfer_curves( Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; VertexIndex v; Cusp *cusp; int i, j; /* * Transfer the Dehn filling curves to tet->extra->Dehn_filling_curve[]. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) { v = tet->extra->ideal_vertex_index; cusp = tet->cusp[v]; for (i = 0; i < 4; i++) { if (i == v) continue; tet->extra->Dehn_filling_curve[i] = 0; for (j = 0; j < 2; j++) tet->extra->Dehn_filling_curve[i] += (int)cusp->m * tet->curve[M][j][v][i] + (int)cusp->l * tet->curve[L][j][v][i]; } } } static void standard_form( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { Tetrahedron *tet; /* * See the documentation in close_cusps() for an illustration of * the standard forms. */ for (tet = short_list_begin->next; tet != short_list_end; tet = tet->next) if (tet->cusp[tet->extra->ideal_vertex_index]->topology == torus_cusp) standard_torus_form(manifold, tet); else standard_Klein_bottle_form(manifold, tet); } static void standard_torus_form( Triangulation *manifold, Tetrahedron *tet) { int max; /* * The idea here is to modify the triangulation of the * boundary torus so that the Dehn filling curve * looks simpler. Geometrically, we are going to do Dehn * twists which realize the Euclidean algorithm, but you * needn't think in terms of Dehn twists as you read the * following code. * * The Dehn filling curve will intersect the sides of a * boundary triangle with intersection numbers a, b and c, * where a + b + c = 0. If, say, c has the greatest absolute * value, then a and b will have the same sign, and c = -(a + b). * The intersection numbers on the other triangle in the triangulation * are of course the negatives of these. * * If we do a two-to-two move across the edge with intersection * number c, then the new intersection numbers will be a, -b and * (b - a). Each time we do this we reduce the absolute value of * the largest intersection number, until we reach a state where * one of the intersection numbers is zero and the other two are * negatives of each other. The latter two must be +1 and -1, * because the Dehn filling curve is a simple closed curve. * Thus, we eventually reach a state where the intersection * numbers are {0, +1, -1}. * * The state just before this (if any) must have been {1, 1, 2}. * {1, 1, 2} is the standard form. * * So . . . the algorithm is * * if (state = {0, +1, -1}) * back up to {1, 1, 2} * else * while (state is not {1, 1, 2}) * apply a two-to-two move to reduce the absolute value * of the largest intersection number * * Technical comment: in the case where we have to back up * to {1, 1, 2}, when we seal the cusp we'll be creating an * EdgeClass of order 1 in the 3-manifold. */ max = max_abs_intersection_number(tet); if (max == 1) apply_two_to_two_to_eliminate(manifold, tet, 0); else while (max > 2) { apply_two_to_two_to_eliminate(manifold, tet, max); max = max_abs_intersection_number(tet); } } static int max_abs_intersection_number( Tetrahedron *tet) { VertexIndex v; int max; int i; v = tet->extra->ideal_vertex_index; max = 0; for (i = 0; i < 4; i++) { if (i == v) continue; if (ABS(tet->extra->Dehn_filling_curve[i]) > max) max = ABS(tet->extra->Dehn_filling_curve[i]); } return max; } static void apply_two_to_two_to_eliminate( Triangulation *manifold, Tetrahedron *tet, int target) { VertexIndex v; FaceIndex f; /* * apply_two_to_two_to_eliminate() applies a two-to-two move * to alter the boundary triangulation in such a way as to * eliminate the edge whose intersection number with the * Dehn filling curve has absolute value target. */ v = tet->extra->ideal_vertex_index; /* * Find the FaceIndex f of the face of tet corresponding to the * 2-d edge we want to eliminate. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (ABS(tet->extra->Dehn_filling_curve[f]) == target) break; } if (f == 4) /* didn't find the right f */ uFatalError("apply_two_to_two_to_eliminate", "close_cusps"); (void) two_to_two(manifold, tet, f, FALSE); } static void standard_Klein_bottle_form( Triangulation *manifold, Tetrahedron *tet) { VertexIndex v; FaceIndex f; v = tet->extra->ideal_vertex_index; /* * Consider the triangle corresponding to tet in the * (2-dimensional) triangulation of the boundary Klein bottle. * The triangulation of the Klein bottle is in the standard * form iff this triangle has two sides glued to each other. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (tet->neighbor[f] == tet) return; } /* * The boundary triangulation must have one of the following * two forms, where the line of @'s is the meridian. * (In fact the two forms represent the same triangulation. * I've drawn them separately to convince the reader--and * myself--that this is the only triangulation other than * than the standard one.) * * ------>>----- -----<<------ * | /| | @ /| * | / | | @ / | * | / | | @ / | * | / | | @ / | * | / | | @ / | * ^@@@@@/@@@@@^ ^@ / @^ * | / | | / @ ^ * | / | | / @ | * | / | | / @ | * | / | | / @ | * |/ | |/ @ | * ------<<----- ------<------ * * A two-to-two move across the edge disjoint from the meridian * will put the Klein bottle into standard form. */ for (f = 0; f < 4; f++) { if (f == v) continue; if (tet->extra->Dehn_filling_curve[f] == 0) { (void) two_to_two(manifold, tet, f, FALSE); break; } } } static void fold_cusps( Triangulation *manifold, Tetrahedron *short_list_begin, Tetrahedron *short_list_end) { while (short_list_begin->next != short_list_end) fold_one_cusp(manifold, short_list_begin->next); } static void fold_one_cusp( Triangulation *manifold, Tetrahedron *tet0) { Tetrahedron *tet[2], *nbr[2]; FaceIndex f[2][4], nf[2][4]; int i, j; int abs_int_num; Cusp *dead_cusp; /* * The illustrations in close_cusps() show the standard forms * for torus and Klein bottle cusps. We are going to fold * along the diagonal. Note that the absolute value of the * intersection number of the Dehn filling curve with the edge * we're folding along is 2 for a torus and 0 for a Klein bottle. * For all other edges it's 1. */ /* * f[0][0] will be the FaceIndex of the bottom face of tet[0] (the * one furthest from the ideal vertex). f[0][1] will be the face * incident to the edge we're folding along. f[0][2] and f[0][3] * will be the remaining faces. f[1][0-3] will be the corresponding * faces of tet[1], the other Tetrahedron at this cusp. */ tet[0] = tet0; f[0][0] = tet[0]->extra->ideal_vertex_index; for (f[0][1] = 0; f[0][1] < 4; f[0][1]++) { if (f[0][1] == f[0][0]) continue; abs_int_num = ABS(tet[0]->extra->Dehn_filling_curve[f[0][1]]); if (abs_int_num == 2 || abs_int_num == 0) break; } if (f[0][1] == 4) uFatalError("fold_one_cusp", "close_cusps"); f[0][2] = remaining_face[f[0][0]][f[0][1]]; f[0][3] = remaining_face[f[0][1]][f[0][0]]; tet[1] = tet[0]->neighbor[f[0][1]]; for (i = 0; i < 4; i++) f[1][i] = EVALUATE(tet[0]->gluing[f[0][1]], f[0][i]); /* * nbr[0] (resp. nbr[1]) is the Tetrahedron (with all finite vertices) * which sits underneath tet[0] (resp. tet[1]). Their FaceIndices * are nf[0][] and nf[1][], and are indexed in the natural way * relative to tet[0] and tet[1]. */ for (i = 0; i < 2; i++) { nbr[i] = tet[i]->neighbor[f[i][0]]; for (j = 0; j < 4; j++) nf[i][j] = EVALUATE(tet[i]->gluing[f[i][0]], f[i][j]); } /* * To fold the cusp, we simply identify similarly indexed * vertices of nbr[0] and nbr[1]. */ for (i = 0; i < 2; i++) { nbr[i]->neighbor[nf[i][0]] = nbr[!i]; nbr[i]->gluing[nf[i][0]] = CREATE_PERMUTATION( nf[i][0], nf[!i][0], nf[i][1], nf[!i][1], nf[i][2], nf[!i][2], nf[i][3], nf[!i][3]); } /* * Discard tet[0] and tet[1]. */ dead_cusp = tet[0]->cusp[f[0][0]]; if (dead_cusp->topology == torus_cusp) manifold->num_or_cusps--; else manifold->num_nonor_cusps--; manifold->num_cusps--; REMOVE_NODE(dead_cusp); my_free(dead_cusp); for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } } static void replace_fake_cusps( Triangulation *manifold) { Tetrahedron *tet; int i; Cusp *cusp, *dead_cusp; /* * Set to NULL all tet->cusp pointers which point to fake Cusps. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) if (tet->cusp[i]->is_finite == TRUE) tet->cusp[i] = NULL; /* * Free the fake Cusps. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == TRUE) { dead_cusp = cusp; cusp = cusp->prev; /* so the loop will proceed correctly */ REMOVE_NODE(dead_cusp); my_free(dead_cusp); } /* * Assign new fake Cusps. */ create_fake_cusps(manifold); } static void renumber_real_cusps( Triangulation *manifold) { Cusp *cusp; int cusp_count; cusp_count = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE) cusp->index = cusp_count++; } regina-4.95/engine/snappea/kernel/CMakeLists.txt000644 000765 000024 00000002070 12234011536 021517 0ustar00babstaff000000 000000 # snappea kernel # Files to compile SET ( FILES Dehn_coefficients canonize canonize_part_1 canonize_part_2 canonize_result change_peripheral_curves chern_simons choose_generators close_cusps complex core_geodesics current_curve_basis cusp_cross_sections cusp_neighborhoods cusp_shapes cusps edge_classes filling find_cusp finite_vertices gcd gluing_equations holonomy hyperbolic_structure identify_solution_type interface intersection_numbers my_malloc o31_matrices orient peripheral_curves positioned_tet precision shortest_cusp_basis simplify_triangulation solve_equations subdivide tables tet_shapes tidy_peripheral_curves transcendentals triangulations unix_file_io update_shapes volume ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} snappea/kernel/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET(SOURCES ${SOURCES} PARENT_SCOPE) # SnapPea headers should not be shipped: these are for internal use only. regina-4.95/engine/snappea/kernel/complex.c000644 000765 000024 00000007762 12235724562 020622 0ustar00babstaff000000 000000 /* * complex.c * * This file provides the standard complex arithmetic functions: * * Complex complex_minus (Complex z0, Complex z1), * complex_plus (Complex z0, Complex z1), * complex_mult (Complex z0, Complex z1), * complex_div (Complex z0, Complex z1), * complex_sqrt (Complex z), * complex_conjugate (Complex z), * complex_negate (Complex z), * complex_real_mult (double r, Complex z), * complex_exp (Complex z), * complex_log (Complex z, double approx_arg); * double complex_modulus (Complex z); * double complex_modulus_squared (Complex z); * Boolean complex_nonzero (Complex z); * Boolean complex_infinite (Complex z); */ #include "kernel.h" Complex Zero = { 0.0, 0.0}; Complex One = { 1.0, 0.0}; Complex Two = { 2.0, 0.0}; Complex Four = { 4.0, 0.0}; Complex MinusOne = {-1.0, 0.0}; Complex I = { 0.0, 1.0}; Complex TwoPiI = { 0.0, TWO_PI}; Complex Infinity = {1e34, 0.0}; Complex complex_plus( Complex z0, Complex z1) { Complex sum; sum.real = z0.real + z1.real; sum.imag = z0.imag + z1.imag; return sum; } Complex complex_minus( Complex z0, Complex z1) { Complex diff; diff.real = z0.real - z1.real; diff.imag = z0.imag - z1.imag; return diff; } Complex complex_div( Complex z0, Complex z1) { double mod_sq; Complex quotient; mod_sq = z1.real * z1.real + z1.imag * z1.imag; if (mod_sq == 0.0) { if (z0.real != 0.0 || z0.imag != 0.0) return Infinity; else uFatalError("complex_div", "complex"); } quotient.real = (z0.real * z1.real + z0.imag * z1.imag)/mod_sq; quotient.imag = (z0.imag * z1.real - z0.real * z1.imag)/mod_sq; return quotient; } Complex complex_mult( Complex z0, Complex z1) { Complex product; product.real = z0.real * z1.real - z0.imag * z1.imag; product.imag = z0.real * z1.imag + z0.imag * z1.real; return product; } Complex complex_sqrt( Complex z) { double mod, arg; Complex root; mod = sqrt(complex_modulus(z)); /* no need for safe_sqrt() */ if (mod == 0.0) return Zero; arg = 0.5 * atan2(z.imag, z.real); root.real = mod * cos(arg); root.imag = mod * sin(arg); return root; } Complex complex_conjugate( Complex z) { z.imag = - z.imag; return z; } Complex complex_negate( Complex z) { z.real = - z.real; z.imag = - z.imag; return z; } Complex complex_real_mult( double r, Complex z) { Complex multiple; multiple.real = r * z.real; multiple.imag = r * z.imag; return multiple; } Complex complex_exp( Complex z) { double modulus; Complex result; modulus = exp(z.real); result.real = modulus * cos(z.imag); result.imag = modulus * sin(z.imag); return result; } Complex complex_log( Complex z, double approx_arg) { Complex result; if (z.real == 0.0 && z.imag == 0.0) { uAcknowledge("log(0 + 0i) encountered"); result.real = - DBL_MAX; result.imag = approx_arg; return result; } result.real = 0.5 * log(z.real * z.real + z.imag * z.imag); result.imag = atan2(z.imag, z.real); while (result.imag - approx_arg > PI) result.imag -= TWO_PI; while (approx_arg - result.imag > PI) result.imag += TWO_PI; return result; } double complex_modulus( Complex z) { return sqrt(z.real * z.real + z.imag * z.imag); /* no need for safe_sqrt() */ } double complex_modulus_squared( Complex z) { return (z.real * z.real + z.imag * z.imag); } Boolean complex_nonzero( Complex z) { return (z.real || z.imag); } Boolean complex_infinite( Complex z) { return (z.real == Infinity.real && z.imag == Infinity.imag); } regina-4.95/engine/snappea/kernel/core_geodesics.c000644 000765 000024 00000017217 12235724562 022124 0ustar00babstaff000000 000000 /* * core_geodesics.c * * This file provides the function * * void core_geodesic( Triangulation *manifold, * int cusp_index, * int *singularity_index, * Complex *core_length, * int *precision); * * which computes the core_length and singularity_index for the * Cusp of index cusp_index in Triangulation *manifold. The * singularity_index describes the nature of the cusp: * * If the Cusp is unfilled or the Dehn filling coefficients are * not integers, then * * *singularity_index is set to zero, * *core_length is undefined. * * If the Cusp is filled and the Dehn filling coefficients are * relatively prime integers (so we have a manifold locally), then * * *singularity_index is set to one, * *core_length is set to the complex length * of the central geodesic, * * If the Cusp is filled and the Dehn filling coefficients are * non relatively prime integers (so we have an orbifold locally), then * * *singularity_index is set to the index of the singular locus, * *core_length is set to the complex length of the central * geodesic in the smallest manifold cover of * a neighborhood of the singular set. * * If the precision pointer is not NULL, *precision is set to the * number of decimal places of accuracy in the computed value of * core_length. * * Klein bottle cusps are OK. Their torsion will always be zero. * * This file also provides the function * * void compute_core_geodesic( Cusp *cusp, * int *singularity_index, * Complex length[2]); * * for use within the kernel. It is similar to core_geodesic(), * only it takes a Cusp pointer as input, and outputs the complex * lengths relative to the ultimate and penultimate hyperbolic * structures, rather than reporting a precision. * * * The Algorithm. * * Say we're doing (p, q) Dehn filling on some Cusp. The (closed) core * geodesic lifts to a set of (infinite) geodesics in the universal cover. * Let L be one such (infinite) geodesic in the univeral cover, and * consider the group G of covering transformations fixing L (setwise). * G is generated by the holonomies of the meridian and longitude, * which we denote H(m) and H(l), subject to the single relation * p H(m) + q H(l) = 0. We would like to find new generators g and h * for the group such that the relation takes the form n g + 0 h = 0. * The generator g will be the purely rotational part of the group G * (n will be the order of the singular locus), and h will generate * the translational part of G. * * One approach to finding g and h would be to proceed in the general * context of finitely generated abelian groups, and apply the Euclidean * algorithm to the relation p H(m) + q H(l) = 0, changing bases until * the relation simplifies down to something of the form n g + 0 h = 0. * But rather than messing with the bookkeeping of this approach, we'll * use a more pedestrian method. * * Let (a, b; c, d) be the matrix expressing (g, h) in terms * of (H(m), H(l)): * * | g | | a b | | H(m) | * | | = | | | | * | h | | c d | | H(l) | * * Because 0 = n g = n (a H(m) + b H(l)) = na H(m) + nb H(l) is * the identity, and p H(m) + q H(l) is the only relation in the * presentation of the group, it follows that (a, b) must be * proportional to (p, q). Furthermore, since the matrix (a, b; c, d) * has determinant one, a and b must be relatively prime, so therefore * (a, b) = (p, q)/gcd(p,q). It now follows that c and d are integers * satisfying * 1 = a d - b c = d p/gcd(p,q) - c q/gcd(p,q) * <=> * d p - c q = gcd(p,q) * * We can find such integers using SnapPea's standard * euclidean_algorithm() function. */ #include "kernel.h" #define TORSION_EPSILON 1e-5 void core_geodesic( Triangulation *manifold, int cusp_index, int *singularity_index, Complex *core_length, int *precision) { Cusp *cusp; Complex length[2]; cusp = find_cusp(manifold, cusp_index); /* * Compute the complex length relative to the ultimate * and penultimate hyperbolic structures. */ compute_core_geodesic(cusp, singularity_index, length); /* * Package up the results. */ if (*singularity_index != 0) { *core_length = length[ultimate]; if (precision != NULL) *precision = complex_decimal_places_of_accuracy( length[ultimate], length[penultimate]); } else { *core_length = Zero; if (precision != NULL) *precision = 0; } } void compute_core_geodesic( Cusp *cusp, int *singularity_index, Complex length[2]) { int i; long int positive_d, negative_c; double pi_over_n; /* * If the Cusp is unfilled or the Dehn filling coefficients aren't * integers, then just write in some zeros (as explained at the top * of this file) and return. */ if (cusp->is_complete == TRUE || Dehn_coefficients_are_integers(cusp) == FALSE) { *singularity_index = 0; length[ultimate] = Zero; length[penultimate] = Zero; return; } /* * The euclidean_algorithm() will give the singularity index * directly (as the g.c.d.), and the coefficients lead to the * complex length (cf. the explanation at the top of this file). */ *singularity_index = euclidean_algorithm( (long int) cusp->m, (long int) cusp->l, &positive_d, &negative_c); for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { /* * length[i] = c H(m) + d H(l) * * (The holonomies are already in logarithmic form.) */ length[i] = complex_plus( complex_real_mult( (double) (- negative_c), cusp->holonomy[i][M] ), complex_real_mult( (double) positive_d, cusp->holonomy[i][L] ) ); /* * Make sure the length is positive. */ if (length[i].real < 0.0) length[i] = complex_negate(length[i]); /* * We want to normalize the torsion to the range * [-pi/n + epsilon, pi/n + epsilon], where n is * the order of the singular locus. */ pi_over_n = PI / *singularity_index; while (length[i].imag < - pi_over_n + TORSION_EPSILON) length[i].imag += 2 * pi_over_n; while (length[i].imag > pi_over_n + TORSION_EPSILON) length[i].imag -= 2 * pi_over_n; /* * In the case of a Klein bottle cusp, H(m) will be purely * rotational and H(l) will be purely translational * (cf. the documentation at the top of holonomy.c). * But the longitude used in practice is actually the * double cover of the true longitude, so we have to * divide the core_length by two to compensate. */ if (cusp->topology == Klein_cusp) length[i].real /= 2.0; } } regina-4.95/engine/snappea/kernel/covers.h000644 000765 000024 00000011063 12235724571 020446 0ustar00babstaff000000 000000 /* * covers.h * * SnapPea constructs an n-sheeted cover of a given manifold as follows. * First it creates n copies of a fundamental domain. For convenience it * uses the fundamental domain defined in choose_generators.c, which is * a union of the triangulation's tetrahedra. It assigns to each generator * (defined in choose_generators.c) a permutation of the n sheets, and * glues the sheets accordingly. The product of the permutations * surrounding an edge class must, of course, be the identity. * Algebraically this is equivalent to finding transitive representations * of the fundamental group into S(n), the symmetric group on n letters. * ("Transitive" means that the corresponding covering space is connected.) * * The algorithm for computing all connected n-sheeted covers of a * given manifold consists of two parts: * * (1) Find all transitive representations of the manifold's fundamental * group into S(n). [cf. representations.c] * * (2) For each representation, construct the corresponding cover. * [cover.c] * * This naive algorithm can, of course, be simplified. For example, * representations which are conjugate by an element of S(n) yield * equivalent covering spaces. The file representations.c describes * these optimizations. */ /* * This file (covers.h) is intended solely for inclusion in SnapPea.h. */ /* * A covering is "regular" iff for any two lifts of a point in the base * manifold, there is a covering transformation taking one to the other. * (An alternative definition is that the cover's fundamental group * projects down to a normal subgroup of the base manifold's fundamental * group. For a proof that the two definitions are equivalent, please * see page 362 of Rolfsen's Knots and Links.) * * All cyclic coverings are regular. */ typedef int CoveringType; enum { unknown_cover, irregular_cover, regular_cover, cyclic_cover }; typedef struct RepresentationIntoSn RepresentationIntoSn; typedef struct { /* * How many face pairs does the fundamental domain * (defined in choose_generators.c) have? */ int num_generators; /* * How many sheets does the covering have? */ int num_sheets; /* * How many cusps (filled or unfilled) does the manifold have? * (For use with primitive_Dehn_image below.) */ int num_cusps; /* * The representations themselves are kept on a NULL-terminated * singly linked list. */ RepresentationIntoSn *list; } RepresentationList; struct RepresentationIntoSn { /* * The permutation corresponding to generator i takes sheet j * of the cover to sheet image[i][j]. * * Note that the size of the image array depends on both the * num_generators and the num_sheets defined in the RepresentationList. */ int **image; /* * The algorithm in construct_cover() in cover.c would like to know * the permutation assigned to each "primitive" Dehn filling curve. * If the Dehn filling coefficients are (a,b), the primitive Dehn * filling curve is defined to be (a/c, b/c), where c = gcd(a,b). * (When the Dehn filling coefficients are relative prime -- as is * always the case for a manifold -- the primitive Dehn filling curve * is just the Dehn filling curve itself, and the assigned permutation * is perforce the identity. The concept of a primitive Dehn filling * curve is useful only for orbifolds.) * * The permutation corresponding to the primitive Dehn filling curve * on cusp i takes sheet j of the cover to sheet Dehn_image[i][j]. * (For unfilled cusps, the identity permutation is given instead.) */ int **primitive_Dehn_image; /* * Is the cover defined by this representation irregular, * regular or cyclic? */ CoveringType covering_type; /* * The RepresentationList keeps RepresentationIntoSn's on * a NULL-terminated singly linked list. */ RepresentationIntoSn *next; }; /* * find_representations() takes a PermutationSubgroup parameter * specifying the subgroup of the symmetric group S(n) into which * the representations are to be found. */ typedef int PermutationSubgroup; enum { permutation_subgroup_Zn, /* finds cyclic covers only */ permutation_subgroup_Sn /* finds all n-fold covers */ /* eventually an option for dihedral covers could be added */ }; regina-4.95/engine/snappea/kernel/current_curve_basis.c000644 000765 000024 00000012725 12235724562 023215 0ustar00babstaff000000 000000 /* * current_curve_basis.c * * This file provides the functions * * void current_curve_basis( Triangulation *manifold, * int cusp_index, * MatrixInt22 basis_change); * * void install_current_curve_bases( Triangulation *manifold); * * * current_curve_basis() accepts a Triangulation and a cusp index, * and computes a 2 x 2 integer matrix basis_change with the property that * * if the Cusp of index cusp_index is filled, and has * integer Dehn filling coefficients, * * the first row of basis_change is set to the current * Dehn filling coefficients (divided by their gcd), and * the second row of basis_change is set to the shortest * curve which completes a basis. * * else * * basis_change is set to the identity * * Note that for nonorientable cusps, the only possible Dehn * filling coefficients are +/- (m,0), and +/- the longitude will be the * shortest curve which completes the basis. * * install_current_curve_bases() installs the current curve basis * on each Cusp of manifold. * * 96/9/28 Modified to accept non relatively prime integer coefficients. * For example, (15, 20) is treated the same as (3,4). Thus in * the new basis the surgery coefficients will be of the form (m,0), * where m is the gcd of the original coefficients. * * 99/11/05 Added install_current_curve_bases(). */ #include "kernel.h" #define EPSILON 1e-5 #define BIG_MODULUS 1e+5 static void current_curve_basis_on_cusp(Cusp *cusp, MatrixInt22 basis_change); void current_curve_basis( Triangulation *manifold, int cusp_index, MatrixInt22 basis_change) { current_curve_basis_on_cusp( find_cusp(manifold, cusp_index), basis_change); } static void current_curve_basis_on_cusp( Cusp *cusp, MatrixInt22 basis_change) { int m_int, l_int, the_gcd; long a, b; Complex new_shape; int multiple; int i, j; m_int = (int) cusp->m; l_int = (int) cusp->l; if (cusp->is_complete == FALSE /* cusp is filled and */ && m_int == cusp->m /* coefficients are integers */ && l_int == cusp->l) { /* * Find a and b such that am + bl = gcd(m, l). */ the_gcd = euclidean_algorithm(m_int, l_int, &a, &b); /* * Divide through by the g.c.d. */ m_int /= the_gcd; l_int /= the_gcd; /* * Set basis_change to * * m l * -b a */ basis_change[0][0] = m_int; basis_change[0][1] = l_int; basis_change[1][0] = -b; basis_change[1][1] = a; /* * Make sure the new longitude is as short as possible. * The ratio (new longitude)/(new meridian) should have a * real part in the interval (-1/2, +1/2]. */ /* * Compute the new_shape, using the tentative longitude. */ new_shape = transformed_cusp_shape( cusp->cusp_shape[initial], basis_change); /* * 96/10/1 There is a danger that for nonhyperbolic solutions * the cusp shape will be ill-defined (either very large or NaN). * However for some nonhyperbolic solutions (flat solutions * for example) it may make good sense. So we attempt to * make the longitude short iff the new_shape is defined and * not outrageously large; otherwise we're content with an * arbitrary longitude. */ if (complex_modulus(new_shape) < BIG_MODULUS) { /* * Figure out how many meridians we need to subtract * from the longitude. */ multiple = (int) floor(new_shape.real - (-0.5 + EPSILON)); /* * longitude -= multiple * meridian */ for (j = 0; j < 2; j++) basis_change[1][j] -= multiple * basis_change[0][j]; } } else { /* * Set basis_change to the identity. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = (i == j); } } void install_current_curve_bases( Triangulation *manifold) { Cusp *cusp; MatrixInt22 *change_matrices; /* * Allocate an array to store the change of basis matrices. */ change_matrices = NEW_ARRAY(manifold->num_cusps, MatrixInt22); /* * Compute the change of basis matrices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->index < 0 || cusp->index >= manifold->num_cusps) uFatalError("install_current_curve_bases", "current_curve_basis"); current_curve_basis_on_cusp(cusp, change_matrices[cusp->index]); } /* * Install the change of basis matrices. */ if (change_peripheral_curves(manifold, change_matrices) != func_OK) uFatalError("install_current_curve_bases", "current_curve_basis"); /* * Free the array used to store the change of basis matrices. */ my_free(change_matrices); } regina-4.95/engine/snappea/kernel/cusp_cross_sections.c000644 000765 000024 00000043316 12235724562 023240 0ustar00babstaff000000 000000 /* * cusp_cross_sections.c * * This file provides the high-level functions * * void allocate_cross_sections(Triangulation *manifold); * void free_cross_sections(Triangulation *manifold); * void compute_cross_sections(Triangulation *manifold); * void compute_tilts(Triangulation *manifold); * * for use within the kernel, in particular by canonize(). * * It also provides the low-level functions * * void compute_three_edge_lengths(Tetrahedron *tet, VertexIndex v, * FaceIndex f, double known_length); * void compute_tilts_for_one_tet(Tetrahedron *tet); * * for its own use, and for the use of two_to_three() and * three_to_two() in simplify_triangulation.c (so they can * maintain cusp cross sections and tilts correctly). * Further documentation of compute_three_edge_lengths() * and compute_tilts_for_one_tet() appears in the code itself. * * The cusp cross section functions, as well as canonize(), use the * concepts and terminology of * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * The Tilt Theorem (contained in the above paper) is generalized * and given a nicer proof in * * M. Sakuma and J. Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * compute_cross_sections() and compute_tilts() set the cross_section * and tilt fields, respectively, of the Tetrahedron data structure. * * The vertex cross section at vertex v of Tetrahedron tet is a * triangle. The length of its edge incident to face f of tet is * stored as tet->cross_section->edge_length[v][f]. (The edge_length * is undefined when v == f.) * * tet->tilt[f] stores the tilt of the Tetrahedron tet relative to face f. * * By convention, * * when no cusp cross sections are in place, the cross_section field * of each Tetrahedron is set to NULL, and * * when cusp cross sections are created, the routine that creates * them must allocate the VertexCrossSections structures. * * Thus, routines which modify a triangulation (e.g. the two_to_three() * and three_to_two() moves) know that they must keep track of cusp cross * sections if and only if the cross_section fields of the Tetrahedra are * not NULL. * * allocate_cross_sections() and free_cross_sections() allocate and * free the VertexCrossSections. * * compute_cross_sections() sets the (already allocated) VertexCrossSections * to correspond to cusp cross sections of area (3/8)sqrt(3). As explained * in cusp_neighborhoods.c, such cusp cross sections will always have * nonoverlapping interiors. * * compute_tilts() applies the Tilt Theorem (see "Convex hulls...") * to compute the tilts from the VertexCrossSections. * * The standard way to use these functions is * * allocate_cross_sections(manifold); * compute_cross_sections(manifold); * compute_tilts(manifold); * *** Do stuff with the tilts, possibly including calls to *** * *** two_to_three() and three_to_two(), which update the *** * *** cross_sections and tilts correctly whenever the *** * *** cross_section pointers are not NULL. *** * free_cross_sections(manifold); */ #include "kernel.h" #define CIRCUMRADIUS_EPSILON 1e-10 typedef struct ideal_vertex { Tetrahedron *tet; VertexIndex v; struct ideal_vertex *next; } IdealVertex; static void initialize_flags(Triangulation *manifold); static void cross_section(Triangulation *manifold, Cusp *cusp); static void find_starting_point(Triangulation *manifold, Cusp *cusp, Tetrahedron **tet0, VertexIndex *v0); static double vertex_area(IdealVertex *ideal_vertex); static void normalize_cusp(Triangulation *manifold, Cusp *cusp, double cusp_area); void allocate_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure no VertexCrossSections * are already allocated. */ if (tet->cross_section != NULL) uFatalError("allocate_cross_sections", "cusp_cross_sections"); /* * Allocate a VertexCrossSections structure. */ tet->cross_section = NEW_STRUCT(VertexCrossSections); } } void free_cross_sections( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure the VertexCrossSections * really are there. */ if (tet->cross_section == NULL) uFatalError("free_cross_sections", "cusp_cross_sections"); /* * Free the VertexCrossSections structure, and set the pointer * to NULL. */ my_free(tet->cross_section); tet->cross_section = NULL; } } void compute_cross_sections( Triangulation *manifold) { Cusp *cusp; /* * Initialize cross_section->has_been_set flags to FALSE. */ initialize_flags(manifold); /* * Compute a cross section of area (3/8)sqrt(3) for each cusp. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cross_section(manifold, cusp); } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cross_section->has_been_set[v] = FALSE; } static void cross_section( Triangulation *manifold, Cusp *cusp) { double cusp_area; Tetrahedron *tet0, *nbr_tet; VertexIndex v0, nbr_v; FaceIndex f, nbr_f; IdealVertex *vertex_stack, *initial_vertex, *this_vertex, *nbr_vertex; Permutation gluing; /* * The plan is to compute an arbitrary cross section of the * cusp, and then normalize it to have area (3/8)sqrt(3). */ /* * The variable cusp_area will keep track of the area of the * cusp cross section. Initialize it to zero. */ cusp_area = 0.0; /* * Find an ideal vertex belonging to this cusp. */ find_starting_point(manifold, cusp, &tet0, &v0); /* * Set the edge_length of some edge of the initial vertex cross section * to some arbitrary value, say 1.0, and compute the other two * edge_lengths at the initial vertex in terms of it. * Set the has_been_set flag to TRUE. */ compute_three_edge_lengths(tet0, v0, !v0, 1.0); /* * At this point the simplest thing would be to write a * recursive function to set the edge_lengths of the remaining * vertices. However, recursive functions can cause trouble * (e.g. stack/heap collisions) if the recursion is exceptionally * deep, so I'll create my own stack explicitly. The stack will * contain vertices whose edge_lengths are set, but whose neighbors * have not yet been checked. Each ideal vertex experiences the * following operations in the following order: * * (1) edge_lengths are computed * (2) has_been_set flag is set to TRUE * (3) IdealVertex is put on stack * (4) IdealVertex comes off stack * (5) area of vertex cross section is added to cusp_area * (6) neighboring ideal vertices are checked, and * added to stack as necessary * (7) IdealVertex data structure is destroyed * * Proposition. Each ideal vertex goes onto the stack exactly once. * Proof. No ideal vertex can go onto the stack more than once, * because once its has_been_set flag is TRUE it is excluded from * further consideration. When a vertex comes off the * stack its neighbors are considered for addition to the stack, * therefore because the cusp is connected all its ideal vertices * will eventually go onto the stack. */ initial_vertex = NEW_STRUCT(IdealVertex); initial_vertex->tet = tet0; initial_vertex->v = v0; initial_vertex->next = NULL; vertex_stack = initial_vertex; while (vertex_stack != NULL) { /* * Pull an IdealVertex off the vertex_stack. */ this_vertex = vertex_stack; vertex_stack = vertex_stack->next; /* * Add the area of the vertex cross section to cusp_area. */ cusp_area += vertex_area(this_vertex); /* * Check the three neighbors of this IdealVertex. */ for (f = 0; f < 4; f++) { if (f == this_vertex->v) continue; /* * Locate this_vertex's neighbor by face f. */ gluing = this_vertex->tet->gluing[f]; nbr_tet = this_vertex->tet->neighbor[f]; nbr_v = EVALUATE(gluing, this_vertex->v); /* * If the neighbor's edge_lengths have not yet been computed, * compute them and add the neighbor to the stack. */ if (nbr_tet->cross_section->has_been_set[nbr_v] == FALSE) { /* * Find the face of nbr_tet which glues to * face f of this_vertex->tet. */ nbr_f = EVALUATE(gluing, f); /* * Set the edge_lengths of vertex nbr_v of Tetrahedron * nbr_tet, and set its has_been_set flag to TRUE. */ compute_three_edge_lengths( nbr_tet, nbr_v, nbr_f, this_vertex->tet->cross_section->edge_length[this_vertex->v][f]); /* * Add the neighbor to the stack. */ nbr_vertex = NEW_STRUCT(IdealVertex); nbr_vertex->tet = nbr_tet; nbr_vertex->v = nbr_v; nbr_vertex->next = vertex_stack; vertex_stack = nbr_vertex; } } /* * Free this IdealVertex. */ my_free(this_vertex); } /* * We have constructed a cusp cross section of area cusp_area. * To normalize it to have area (3/8)sqrt(3), we must multiply all * edge_lengths by sqrt( (3/8)sqrt(3) / cusp_area ). */ normalize_cusp(manifold, cusp, cusp_area); } static void find_starting_point( Triangulation *manifold, Cusp *cusp, Tetrahedron **tet0, VertexIndex *v0) { for (*tet0 = manifold->tet_list_begin.next; *tet0 != &manifold->tet_list_end; *tet0 = (*tet0)->next) for (*v0 = 0; *v0 < 4; (*v0)++) if ((*tet0)->cusp[*v0] == cusp) return; /* * We should never get to this point. */ uFatalError("find_starting_point", "cusp_cross_sections"); } /* * compute_three_edge_lengths() sets tet->cross_section->edge_length[v][f] * to known_length, computes the remaining two edge_lengths at vertex v * in terms of it, and sets the has_been_set flag to TRUE. */ void compute_three_edge_lengths( Tetrahedron *tet, VertexIndex v, FaceIndex f, double known_length) { double *this_triangle; FaceIndex left_face, right_face; /* * For convenience, note which triangle we're working with. */ this_triangle = tet->cross_section->edge_length[v]; /* * Set the given edge_length. */ this_triangle[f] = known_length; /* * Find the left and right edges of the triangle, corresponding * to the left_face and right_face of the Tetrahedron, in the * imagery of positioned_tet.h. Work relative to the right_handed * Orientation of the Tetrahedron, since that's how the TetShapes * are defined. */ left_face = remaining_face[v][f]; right_face = remaining_face[f][v]; /* * The real part of the logarithmic form of the angle between the * near and left faces gives us the log of the ratio of the lengths * of the near and left sides of this_triangle, and similarly for * the right side. */ this_triangle[left_face] = known_length * exp(tet->shape[complete]->cwl[ultimate][edge3_between_faces[f][left_face ]].log.real); this_triangle[right_face] = known_length / exp(tet->shape[complete]->cwl[ultimate][edge3_between_faces[f][right_face]].log.real); /* * Set the has_been_set flag to TRUE. */ tet->cross_section->has_been_set[v] = TRUE; } static double vertex_area( IdealVertex *ideal_vertex) { /* * We compute the area of a triangular vertex cross section * using Heron's formula * * area = sqrt( s * (s - a) * (s - b) * (s - c) ) * * where a, b and c are the length of the triangle's sides, * and s is the semiperimeter (a + b + c)/2. */ double *this_triangle, a, b, c, s, area; VertexIndex v; FaceIndex face_a, face_b, face_c; v = ideal_vertex->v; face_a = ! v; face_b = remaining_face[v][face_a]; face_c = remaining_face[face_a][v]; this_triangle = ideal_vertex->tet->cross_section->edge_length[v]; a = this_triangle[face_a]; b = this_triangle[face_b]; c = this_triangle[face_c]; s = 0.5 * (a + b + c); area = safe_sqrt( s * (s - a) * (s - b) * (s - c) ); return area; } static void normalize_cusp( Triangulation *manifold, Cusp *cusp, double cusp_area) { double factor; Tetrahedron *tet; VertexIndex v; FaceIndex f; /* * The given cusp has area cusp_area. * Multiply all the edge_lengths by sqrt( (3/8)sqrt(3) / cusp_area ) * to normalize the area to (3/8)sqrt(3). */ factor = safe_sqrt(0.375 * ROOT_3 / cusp_area); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) for (f = 0; f < 4; f++) if (f != v) tet->cross_section->edge_length[v][f] *= factor; } void compute_tilts( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) compute_tilts_for_one_tet(tet); } void compute_tilts_for_one_tet( Tetrahedron *tet) { double factor, R[4]; int i, j; /* * Theorem 2 of "Convex hulls..." gives the tilts in terms * of the circumradii. A generalization of the theorem and * a cleaner proof appear in "Canonical cell decompositions...". * * We may compute the circumradius of a triangle in terms * of the length of any side c and its opposite angle C, * according to the formula * * R = c / (2 sin(C)) * * We must be careful in the case of flat (or almost flat) * ideal Tetrahedra. As sin(C) goes to zero, the circumradii * and the tilts go to infinity. We must take care that the * numerical values computed for the circumradii are in * proportion to the linear dimensions of the four vertex * cross sections. That way even though the numerical values * of the tilts will be very large numbers, they will have * the correct signs, and the canonization algorithm will proceed * correctly. To insure that the circumradii are computed * correctly, we use a fixed value for sin(C) (rather than reading * the sines of different angles at different vertex cross sections), * and we make sure its value exceeds some small epsilon (in * particular, we don't want it to be zero). */ /* * Compute the circumradii. */ /* * Let factor = 2 sin(C), where C is the angle at edge 0. * Make sure factor is at least CIRCUMRADIUS_EPSILON. */ factor = 2 * sin(tet->shape[complete]->cwl[ultimate][0].log.imag); if (factor < CIRCUMRADIUS_EPSILON) factor = CIRCUMRADIUS_EPSILON; /* * Use the relationship R = c / factor (cf. above) to compute * the circumradii. */ R[0] = tet->cross_section->edge_length[0][1] / factor; R[1] = tet->cross_section->edge_length[1][0] / factor; R[2] = tet->cross_section->edge_length[2][3] / factor; R[3] = tet->cross_section->edge_length[3][2] / factor; /* * 95/9/19 JRW * Scale the circumradii according to the cusps' displacements. * As explained in cusp_neighborhoods.c, a cusp's linear * dimensions vary as the exponential of the displacement. */ for (i = 0; i < 4; i++) R[i] *= tet->cusp[i]->displacement_exp; /* * Apply the Tilt Theorem to compute the tilts in terms * of the circumradii. */ for (i = 0; i < 4; i++) { tet->tilt[i] = 0.0; for (j = 0; j < 4; j++) if (j == i) tet->tilt[i] += R[j]; else tet->tilt[i] -= R[j] * cos(tet->shape[complete]->cwl[ultimate][edge3_between_vertices[i][j]].log.imag); } } regina-4.95/engine/snappea/kernel/cusp_neighborhoods.c000644 000765 000024 00000450050 12236247215 023023 0ustar00babstaff000000 000000 /* * cusp_neighborhoods.c * * This file provides the following functions for creating and manipulating * horospherical cross sections of a manifold's cusps, and computing the * triangulation dual to the corresponding Ford complex. These functions * communicate with the UI by passing pointers to CuspNeighborhoods * data structures; but even though the UI may keep pointers to * CuspNeighborhoods, the structure's internal details are private * to this file. * * CuspNeighborhoods *initialize_cusp_neighborhoods( * Triangulation *manifold); * * void free_cusp_neighborhoods( * CuspNeighborhoods *cusp_neighborhoods); * * [Many other externally available functions are provided -- please see * SnapPea.h for details.] * * When the canonical cell decomposition dual to the Ford complex is not * a triangulation, it is arbitrarily subdivided into tetrahedra. * * Note: This file uses the fields "displacement" and "displacement_exp" * in the Cusp data structure to keep track of where the cusp cross * sections are (details appear below). Manifolds not under the care * of a CuspNeighborhoods structure should keep the "displacement" set * to 0 and the "displacement_exp" set to 1 at all times, so that * canonical cell decompositions will be computed relative to cusps * cross sections of equal volume. */ /* * Proposition 1. The area of a horospherical cusp cross section is * exactly twice the volume it contains. * * Proof. Do an integral in the upper half space model of hyperbolic * 3-space. Consider a unit square in the horosphere z == 1, and calculate * the volume lying above it as the integral of 1/z^3 dz from z = 1 to * z = infinity. QED * * Comment. This proposition relies on the hyperbolic manifold having * curvature -1. If the curvature had some other value, the proportionality * constant would be something other than 2. * * Comment. The proportionality constant has units of 1 / distance. * Normally, though, one doesn't have to think about units in hyperbolic * geometry, because one uses the canonical ones. I just wanted to make * sure that nobody is bothered by the fact that we're specifying an area * as twice a volume. * * Comment. We can measure the size of a cusp cross section by area or * by volume. The two measures are the same modulo a factor of two. * * * Proposition 2. If we choose a manifold's cusp cross sections to each * have area (3/8)sqrt(3), then their interiors cannot overlap themselves * or each other. * * Proof. By Lemma A below, we can choose a set of cusp cross sections * with nonoverlapping interiors. Advance each cusp cross section into * the fat part of the manifold, until it bumps into itself or another * cross section. Look at the horoball packing as seen from a given cusp. * Because the cusp is tangent to itself or some other cusp, there'll be * a maximally large horoball. If we draw the given cusp as the plane * z == 1 in the upper half space model, the maximal horoball (and each of * its translates) will appear as a sphere of diameter 1. The view as * seen from the given cusp therefore includes a packing of disjoint circles * of diameter 1/2. If it's a hexagonal packing the area of the cusp will * equal the area of a hexagon of outradius 1/2, which works out to be * (3/8)sqrt(3). If it's not a hexagonal packing, the cusp's area will be * even greater. In the latter case, we retract the cusp cross section * until its area is exactly (3/8)sqrt(3). QED * * Lemma A. We can choose a set of cusp cross sections with nonoverlapping * interiors. * * Comment. One expects the proof of this lemma to be completely trivial, * but I don't think it is. * * Proof #1. Lemma A follows directly from the Margulis Lemma. The * required cusp cross sections are portions of the boundary of the thin * part of the manifold. QED * * Proof #2. Start with any decomposition of the manifold into positively * oriented 3-cells. For example, we could start with the canonical cell * decomposition constructed in * * J. Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149. * * Choose arbitrary cusp cross sections. Retract each cusp as necessary * so that its intersection with each 3-cell is "standard". (I don't want * to spend a lot of time fussing over the wording of this -- the idea is * that the cross section shouldn't be so far forward that it has * unnecessary intersections with other faces of the 3-cell.) Then further * retract each cusp cross section so that it doesn't intersect the other * cross sections incident to the same 3-call. This gives the nonoverlapping * cross sections, as required. QED * * * Definition. The "home position" of a cusp cross section is the one * at which its area is (3/8)sqrt(3) and its enclosed volume is (3/16)sqrt(3). * * By Proposition 2 above, when all the cusps are at their home positions, * their interiors are disjoint. * * The displacement field in the Cusp data structure measures how far * a cusp cross section is from its home position. The displacement is * measured towards the fat part of the manifold, so a positive displacement * means the cusp cross section is larger, and a negative displacement * means it is smaller. * * If we visualize a cusp's home position as a plane at height z == 1 in * the upper half space model, then after a displacement d > 0 it will be * at some height h < 1. Set d equal to the integral of dz/z from z = h * to z = 1 to obtain d = - log h, or h = exp(-d). It follows that a cusp's * linear dimensions vary as exp(d), while its area (and therefore its * enclosed volume) vary as exp(2d). The Cusp data structure stores the * quantity exp(d) in its displacement_exp field, to avoid excessive * recomputation. * * * Definition. The "reach" of a cusp is the distance from the cross * section's home position to the position at which it first bumps into * itself. * * Note that the reach is half the distance from the cusp to itself, * measured along the shortest homotopically nontrivial path. * Proposition 2 implies that the reach of each cusp will be nonnegative. * * Definition. As a given cusp cross section moves forward into the * fat part of the manifold, the first cusp cross section it bumps into * is called its "stopper". The displacement (measured from the home * position) at which the given cusp meets its stopper is called the * "stopping displacement". * * Comment. Unlike the reach, the stopper and the stopping displacement * depend on the current displacements of all the cusps in the triangulation. * They vary dynamically as the user moves the cusp cross sections. * * * Sometimes the user may wish to change two or more cusp displacements * in unison. The Cusp's is_tied field supports this. The displacements * of "tied" cusps always stay the same -- when one changes they all do. * The tie_group_reach keeps track of the reach of the tied cusps: * it tells the displacement at which some cusp in the group first * bumps into itself or some other cusp in the group. Note that the * tie_group_reach might be less than the stopping displacement of any * of its constituent cusps; this is because when a cusp moves forward * its (ordinary) stopper stays still, but members of its tie group * move towards it. */ #include "kernel.h" #include "canonize.h" #include /* needed for qsort() and rand() */ /* * Report all horoballs higher than the requested cutoff_height * minus CUTOFF_HEIGHT_EPSILON. For example, if the user wants to see * all horoballs of height at least 0.25, we should report a horoball * of height 0.249999999963843. */ #define CUTOFF_HEIGHT_EPSILON 1e-6 /* * A horoball is considered to be "maximal" iff it's distance from a fixed * cusp is within INTERCUSP_EPSILON of being minimal. (The idea is that * if there are several different maximal cusps, whose distances from the * fixed cusp differ only by roundoff error, we want to consider all them * to be maximal.) */ #define INTERCUSP_EPSILON 1e-6 /* * If a given cusp does not have a maximal horoball, all other cusp cross * sections are retracted in increments of DELTA_DISPLACEMENT until it does. * The value of DELTA_DISPLACEMENT should be large enough that the algorithm * has a fair shot at the getting a maximal horoball on the first try, * but not so large that the canonization algorithm has to do a lot of * thrasing around (in particular, we don't want it to have to randomize * very often). */ #define DELTA_DISPLACEMENT 0.5 /* * If the longitudinal translation has length zero, * something has gone very, very wrong. */ #define LONGITUDE_EPSILON 1e-2 /* * contains_north_pole() uses NORTH_POLE_EPSILON to decide when a face * of a tetrahedron stands vertically over a vertex. */ #define NORTH_POLE_EPSILON 1e-6 /* * A complex number of modulus greater than KEY_INFINITY is considered * to be infinite, at least for the purpose of computing key values. */ #define KEY_INFINITY 1e+6 /* * tiling_tet_on_tree() will compare two TilingTets iff their key values * are within KEY_EPSILON of each other. KEY_EPSILON can be fairly large; * other than a loss of speed there is no harm in having the program make * some occasional unnecessary comparisons. */ #define KEY_EPSILON 1e-4 /* * Two TilingTets are considered equivalent under the Z + Z action of * the cusp translations iff their corresponding (transformed) corners * lie within CORNER_EPSILON of each other. */ #define CORNER_EPSILON 1e-6 /* * cull_duplicate_horoballs() checks whether two horoballs are equivalent * iff their radii differ by less than DUPLICATE_RADIUS_EPSILON. * We should make DUPLICATE_RADIUS_EPSILON fairly large, to be sure we * don't miss any horoballs even when their precision is low. */ #define DUPLICATE_RADIUS_EPSILON 1e-3 typedef int MinDistanceType; enum { dist_self_to_self, dist_self_to_any, dist_group_to_group, dist_group_to_any }; typedef struct { Tetrahedron *tet; Orientation h; VertexIndex v; } CuspTriangle; typedef struct TilingHoroball { CuspNbhdHoroball data; struct TilingHoroball *next; } TilingHoroball; typedef struct TilingTet { /* * Which Tetrahedron in the original manifold lifts to this TilingTet? */ Tetrahedron *underlying_tet; /* * Does it appear with the left_handed or right_handed orientation? */ Orientation orientation; /* * Where are its four corners on the boundary of upper half space? */ Complex corner[4]; /* * What is the Euclidean diameter of the horoball at each corner? */ double horoball_height[4]; /* * If the neighboring TilingTet incident to face f has already been * found, neighbor_found[f] is set to TRUE so we won't waste time * finding it again. More importantly, we won't have to worry about * the special case of "finding" the initial TilingTets incident to * the "horoball of infinite Euclidean radius". */ Boolean neighbor_found[4]; /* * Pointer for the NULL-terminated queue. */ struct TilingTet *next; /* * Pointers for the tree. */ /* * The left child and right child pointers implement the binary tree. */ struct TilingTet *left, *right; /* * The sort key is a continuous function of the TilingTet's corners, * and is well defined under the Z + Z action of the group of * covering transformations of the cusp. */ double key; /* * We don't want our tree handling functions to be recursive, * for fear of stack/heap collisions. So we implement them using * our own private stack, which is a NULL-terminated linked list * using the next_subtree pointer. Unlike the "left" and "right" * fields (which are maintained throughout the algorithm) the * "next_subtree" field is used only locally within a given tree * handling function. */ struct TilingTet *next_subtree; } TilingTet; typedef struct { TilingTet *begin, *end; } TilingQueue; static void initialize_cusp_displacements(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_reaches(CuspNeighborhoods *cusp_neighborhoods); static void compute_one_reach(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp); static void compute_tie_group_reach(CuspNeighborhoods *cusp_neighborhoods); static Cusp *some_tied_cusp(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_stoppers(CuspNeighborhoods *cusp_neighborhoods); static void compute_intercusp_distances(Triangulation *manifold); static void compute_one_intercusp_distance(EdgeClass *edge); static double compute_min_dist(Triangulation *manifold, Cusp *cusp, MinDistanceType min_distance_type); static void initialize_cusp_ties(CuspNeighborhoods *cusp_neighborhoods); static void initialize_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static void allocate_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static void compute_cusp_nbhd_positions(CuspNeighborhoods *cusp_neighborhoods); static Boolean contains_meridian(Tetrahedron *tet, Orientation h, VertexIndex v); static void set_one_component(Tetrahedron *tet, Orientation h, VertexIndex v, int max_triangles); static CuspNbhdHoroballList *get_quick_horoball_list(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp); static void get_quick_edge_horoballs(Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball); static void get_quick_face_horoballs(Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball); static CuspNbhdHoroballList *get_full_horoball_list(CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp, double cutoff_height); static void compute_exp_min_d(Triangulation *manifold); static void compute_parallelogram_to_square(Complex meridian, Complex longitude, double parallelogram_to_square[2][2]); static void read_initial_tetrahedra(Triangulation *manifold, Cusp *cusp, TilingQueue *tiling_queue, TilingTet **tiling_tree_root, TilingHoroball **horoball_linked_list, double cutoff_height); static TilingTet *get_tiling_tet_from_queue(TilingQueue *tiling_queue); static void add_tiling_tet_to_queue(TilingTet *tiling_tet, TilingQueue *tiling_queue); static void add_tiling_horoball_to_list(TilingTet *tiling_tet, VertexIndex v, TilingHoroball **horoball_linked_list); static Boolean face_contains_useful_edge(TilingTet *tiling_tet, FaceIndex f, double cutoff_height); static TilingTet *make_neighbor_tiling_tet(TilingTet *tiling_tet, FaceIndex f); static void prepare_sort_key(TilingTet *tiling_tet, double parallelogram_to_square[2][2]); static Boolean tiling_tet_on_tree(TilingTet *tiling_tet, TilingTet *tiling_tree_root, Complex meridian, Complex longitude); static Boolean same_corners(TilingTet *tiling_tet1, TilingTet *tiling_tet2, Complex meridian, Complex longitude); static void add_tiling_tet_to_tree(TilingTet *tiling_tet, TilingTet **tiling_tree_root); static void add_horoball_if_necessary(TilingTet *tiling_tet, TilingHoroball **horoball_linked_list, double cutoff_height); static Boolean contains_north_pole(TilingTet *tiling_tet, VertexIndex v); static void free_tiling_tet_tree(TilingTet *tiling_tree_root); static CuspNbhdHoroballList *transfer_horoballs(TilingHoroball **horoball_linked_list); static int CDECL compare_horoballs(const void *horoball0, const void *horoball1); static void cull_duplicate_horoballs(Cusp *cusp, CuspNbhdHoroballList *aHoroballList); /* * Conceptually, the CuspNeighborhoods structure stores cross sections * of a manifold's cusps, and also keeps a Triangulation dual to the * corresponding Ford complex. In the present implementation, the * information about the cross sections is stored entriely within the * copy of the triangulation (specifically, in the Cusp's displacment, * displacement_exp and reach fields, the EdgeClass's intercusp_distance * field, and the Triangulation's max_reach field). * * SnapPea.h (the only header file common to the user interface and the * computational kernel) contains the opaque typedef * * typedef struct CuspNeighborhoods CuspNeighborhoods; * * This opaque typedef allows the user interface to declare and pass * a pointer to a CuspNeighborhoods structure, without being able to * access a CuspNeighborhoods structure's fields directly. Here is * the actual definition, which is private to this file. */ struct CuspNeighborhoods { /* * We'll keep our own private copy of the Triangulation, to avoid * messing up the original one. */ Triangulation *its_triangulation; }; /* * Technical musings. * * There are different approaches to maintaining a canonical * triangulation as the cusp displacements change. * * Low-level approach. * Handle the 2-3 and 3-2 moves explicitly. Calculate which * move will be required next as the given cusp moves towards * the requested displacement. * * High-level approach. * Set the requested cusp displacement directly, and call the * standard proto_canonize() function to compute the corresponding * canonical triangulation. * * The low-level approach would be much more efficient at run time. * The overhead of setting up the cusp cross sections at the beginning, * and polishing the hyperbolic structure at the end, would be done * only once. It would also be efficient in that it tracks the convex * hull (i.e. the canonical triangulation) precisely as the cusp moves * toward the requested displacement. (At each step it finds the next * 2-3 or 3-2 move which would be required as the cusp cross section * moves continuously towards the requested displacement.) * * The drawback of the low-level approach is that it would require * a lot of low-level programming, which is time consuming, tends to * make a mess, and can be error prone. The high-level approach keeps * the code cleaner, even though it's less efficient at run time. * * For now I have implemented the high-level approach. If it turns * out that it is too slow, I can consider replacing it with the * low-level approach. An even better approach might be to make * some simple changes to speed up the high-level approach. For example, * I was concerned that for large manifolds proto_canonize()'s bottleneck * might be polishing the hyperbolic structure at the end. I modified * proto_canonize() to polish the hyperbolic structure iff the * triangulation has been changed. */ CuspNeighborhoods *initialize_cusp_neighborhoods( Triangulation *manifold) { Triangulation *simplified_manifold; CuspNeighborhoods *cusp_neighborhoods; /* * If the space isn't a manifold, return NULL. */ if (all_Dehn_coefficients_are_relatively_prime_integers(manifold) == FALSE) return NULL; /* * Get rid of "unnecessary" cusps. * If we encounter topological obstructions, return NULL. */ simplified_manifold = fill_reasonable_cusps(manifold); if (simplified_manifold == NULL) return NULL; /* * If the manifold is closed, free it and return NULL. */ if (all_cusps_are_filled(simplified_manifold) == TRUE) { free_triangulation(simplified_manifold); return NULL; } /* * Attempt to canonize the manifold. */ if (proto_canonize(simplified_manifold) == func_failed) { free_triangulation(simplified_manifold); return NULL; } /* * Our manifold has passed all its tests, * so set up a CuspNeighborhoods structure. */ cusp_neighborhoods = NEW_STRUCT(CuspNeighborhoods); /* * Install our private copy of the triangulation. */ cusp_neighborhoods->its_triangulation = simplified_manifold; simplified_manifold = NULL; /* * Most likely the displacements will be zero already, * but we set them anyhow, just to be safe. */ initialize_cusp_displacements(cusp_neighborhoods); /* * Compute all cusp reaches. */ compute_cusp_reaches(cusp_neighborhoods); /* * Find the stoppers. */ compute_cusp_stoppers(cusp_neighborhoods); /* * Initially no cusps are tied. */ initialize_cusp_ties(cusp_neighborhoods); /* * Set up an implicit coordinate system on each cusp cross section * so that we can report the position of horoballs etc. consistently, * even as the canonical triangulation changes. */ initialize_cusp_nbhd_positions(cusp_neighborhoods); /* * Record the volume so we don't have to recompute it * over and over in real time. */ cusp_neighborhoods->its_triangulation->volume = volume(cusp_neighborhoods->its_triangulation, NULL); /* * Done. */ return cusp_neighborhoods; } void free_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods) { if (cusp_neighborhoods != NULL) { free_triangulation(cusp_neighborhoods->its_triangulation); my_free(cusp_neighborhoods); } } static void initialize_cusp_displacements( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->displacement = 0.0; cusp->displacement_exp = 1.0; } } static void compute_cusp_reaches( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; cusp_neighborhoods->its_triangulation->max_reach = 0.0; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { compute_one_reach(cusp_neighborhoods, cusp); if (cusp->reach > cusp_neighborhoods->its_triangulation->max_reach) cusp_neighborhoods->its_triangulation->max_reach = cusp->reach; } } static void compute_one_reach( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp) { /* * The key observation is the following. Think of a horoball * packing corresponding to the cusp cross sections in their home * positions, with the given cusp lifting to the plane z == 1 in * the upper half space model. The vertical line passing through * the top of a maximally (Eucliean-)large round horoball is * guaranteed to be an edge in the canonical triangulation. * (Proof: As the horoballs expand equivariantly, the largest round * horoball(s) is(are) the first one(s) to touch the z == 1 horoball.) * So by measuring the distance between cusp cross sections along the * edges of the canonical triangulation, we can deduce the distance * from the given cusp to the largest round horoball(s). If a largest * round horoball corresponds to the given cusp, then we know the * cusp's reach and we're done. If the largest horoballs all belong * to other cusps, then we retract the other cusps a bit (i.e. give * them a negative displacement) and try again. Eventually a horoball * corresponding to the given cusp will be maximal. */ Triangulation *triangulation_copy; Cusp *cusp_copy, *other_cusp; double dist_any, dist_self; /* * Make a copy of the triangulation, so we don't disturb the original. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &triangulation_copy); cusp_copy = find_cusp(triangulation_copy, cusp->index); /* * Carry out the algorithm described above. */ while (TRUE) { /* * Compute the distances between cusp cross sections along each * edge of the (already canonical) triangulation, and store the * results in the EdgeClass's intercusp_distance field. * * Technical note: There is a small inefficiency here in that * proto_canonize() creates and discards the cusp cross sections, * and here we create and discard them again. If this turns out * to be a problem we could have proto_canonize() compute the * intercusp distances when it does the canonization, but for * now I'll put up with the inefficiency to keep the code clean. */ compute_intercusp_distances(triangulation_copy); /* * Does a maximally large round horoball belong to the given cusp? * If so, we know the reach and we're done. */ dist_self = compute_min_dist(triangulation_copy, cusp_copy, dist_self_to_self); dist_any = compute_min_dist(triangulation_copy, cusp_copy, dist_self_to_any); if (dist_self < dist_any + INTERCUSP_EPSILON) { cusp->reach = 0.5 * dist_self; break; } /* * Otherwise, retract all cross sections except the given one, * recanonize, and continue with the loop. * * Note: initialize_cusp_neighborhoods() has already checked * that the manifold is hyperbolic, so proto_canonize() should * not fail. */ for (other_cusp = triangulation_copy->cusp_list_begin.next; other_cusp != &triangulation_copy->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp != cusp_copy) { other_cusp->displacement -= DELTA_DISPLACEMENT; other_cusp->displacement_exp = exp(other_cusp->displacement); } if (proto_canonize(triangulation_copy) != func_OK) uFatalError("compute_one_reach", "cusp_neighborhoods.c"); } /* * Free the copy of the triangulation. */ free_triangulation(triangulation_copy); } static void compute_tie_group_reach( CuspNeighborhoods *cusp_neighborhoods) { /* * This function is similar to compute_one_reach(), but instead of * computing the reach of a single cusp, it computes the reach of * a group of tied cusps (that is a group of cusp neighborhoods which * move forward and backward in unison). Please see compute_one_reach() * above for detailed documentation. */ Triangulation *triangulation_copy; double dist_any, dist_self; Cusp *cusp; /* * If no cusps are tied, there is nothing to be done. */ if (some_tied_cusp(cusp_neighborhoods) == NULL) { cusp_neighborhoods->its_triangulation->tie_group_reach = 0.0; return; } /* * Make a copy of the triangulation, so we don't disturb the original. * copy_triangulation() copies the is_tied field, even though it is * in some sense private to this file. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &triangulation_copy); /* * Carry out the algorithm described in compute_one_reach(). */ while (TRUE) { compute_intercusp_distances(triangulation_copy); dist_self = compute_min_dist(triangulation_copy, NULL, dist_group_to_group); dist_any = compute_min_dist(triangulation_copy, NULL, dist_group_to_any); if (dist_self < dist_any + INTERCUSP_EPSILON) { cusp_neighborhoods->its_triangulation->tie_group_reach = some_tied_cusp(cusp_neighborhoods)->displacement + 0.5 * dist_self; break; } for (cusp = triangulation_copy->cusp_list_begin.next; cusp != &triangulation_copy->cusp_list_end; cusp = cusp->next) if (cusp->is_tied == FALSE) { cusp->displacement -= DELTA_DISPLACEMENT; cusp->displacement_exp = exp(cusp->displacement); } if (proto_canonize(triangulation_copy) != func_OK) uFatalError("compute_tie_group_reach", "cusp_neighborhoods.c"); } free_triangulation(triangulation_copy); } static Cusp *some_tied_cusp( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) if (cusp->is_tied) return cusp; return NULL; } static void compute_cusp_stoppers( CuspNeighborhoods *cusp_neighborhoods) { /* * Think of a horoball packing corresponding to the cusp cross sections * in their current positions, with a given cusp lifting to the plane * z == 1 in the upper half space model. The vertical line passing * through the top of a maximally (Eucliean-)large round horoball is * guaranteed to be an edge in the canonical cell decomposition. * (Proof: As the horoballs expand equivariantly, the largest * round horoballs will be the first to touch the z == 1 horoball.) * * Case 1. The maximal horoball belongs to the given cusp. * * In this case, the given cusp is its own stopper, and the * stopping displacement is its reach. * * Case 2. The maximal horoball belongs to some other cusp. * * The displacement at which the given cusp meets the other cusp * may or may not be less than the given cusp's reach. * (A less-than-maximal horoball belonging to the given cusp may * overtake a formerly maximal cusp, because horoballs belonging * to the given cusp grow as the given cusp moves forward, while * other horoballs do not.) If the stopping displacement is * less than the given cusp's reach, then we've found a stopper * cusp and stopping displacement (the stopping displacement is * unique, even though the stopper cusp may not be). If the * stopping is greater than or equal to the given cusp's reach, * then the cusp is its own stopper, as in case 1. */ Cusp *cusp, *c[2]; EdgeClass *edge; int i; double possible_stopping_displacement; /* * Initialize each stopper to be the cusp itself, and the stopping * displacement to be its reach. */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->stopper_cusp = cusp; cusp->stopping_displacement = cusp->reach; } /* * Now look at each edge of the canonical triangulation, to see * whether some other cusp cross section is closer. * * cusp_neighborhoods->its_triangulation is always the canonical * triangulation (or an arbitrary subdivision of the canonical * cell decomposition). */ compute_intercusp_distances(cusp_neighborhoods->its_triangulation); for (edge = cusp_neighborhoods->its_triangulation->edge_list_begin.next; edge != &cusp_neighborhoods->its_triangulation->edge_list_end; edge = edge->next) { c[0] = edge->incident_tet->cusp[ one_vertex_at_edge[edge->incident_edge_index]]; c[1] = edge->incident_tet->cusp[other_vertex_at_edge[edge->incident_edge_index]]; for (i = 0; i < 2; i++) { possible_stopping_displacement = c[i]->displacement + edge->intercusp_distance; if (possible_stopping_displacement < c[i]->stopping_displacement) { c[i]->stopping_displacement = possible_stopping_displacement; c[i]->stopper_cusp = c[!i]; } } } } static void compute_intercusp_distances( Triangulation *manifold) { /* * In the present context we may assume the triangulation is * canonical (although all we really need to know is that it * has a geometric_solution). */ EdgeClass *edge; /* * Set up the cusp cross sections. */ allocate_cross_sections(manifold); compute_cross_sections(manifold); /* * Compute the intercusp_distances. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) compute_one_intercusp_distance(edge); /* * Release the cusp cross sections. */ free_cross_sections(manifold); } static void compute_one_intercusp_distance( EdgeClass *edge) { int i, j; Tetrahedron *tet; EdgeIndex e; VertexIndex v[2]; FaceIndex f[2]; double length[2][2], product; /* * Find an arbitrary Tetrahedron incident to the given EdgeClass. */ tet = edge->incident_tet; e = edge->incident_edge_index; /* * Note which vertices and faces are incident to the EdgeClass. */ v[0] = one_vertex_at_edge[e]; v[1] = other_vertex_at_edge[e]; f[0] = one_face_at_edge[e]; f[1] = other_face_at_edge[e]; /* * The vertex cross section at each vertex v[i] is a triangle. * Note the lengths of the triangle's edges incident to the EdgeClass. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) length[i][j] = tet->cusp[v[i]]->displacement_exp * tet->cross_section->edge_length[v[i]][f[j]]; /* * Our task is to compute the distance between the vertex cross sections * as a function of the length[][]'s. Fortunately this is easier than * you might except. I recommend you make sketches for yourself as * you read through the following. (It's much simpler in pictures * than it is in words.) * * Proposition. There is a unique common perpendicular to a pair of * opposite edges of an ideal tetrahedron. * * Proof. Consider the line segment which minimizes the distance * between the two opposite edges. If it weren't perpendicular to * each edge, then a shorter line segement could be found. QED * * Definition. The "midpoint" of an edge of an ideal tetrahedron is * the point where the edge intersects the unique common perpendicular * to the opposite edge. * * Proposition. A half turn about the aforementioned unique common * perpendicular is a symmetry of the ideal tetrahedron. * * Proof. It preserves (setwise) a pair of opposite edges. Therefore * it preserves (setwise) the tetrahedron's four ideal vertices, and * therefore the whole tetrahedron. QED * * Proposition. Consider a vertex cross section which passes through * the midpoint of an edge. The two sides of the vertex cross section * which are incident to the given edge of the tetrahedron have lengths * which are reciprocals of one another. * * Proof. Position the tetrahedron in the upper half space model so * that the given edge is vertical and its midpoint is at height one. * * Let P1 be the unique plane which contains the aforementioned unique * common perpendicular and also contains the edge itself. * * Let P2 be the unique plane which contains the aforementioned unique * common perpendicular and is orthogonal to the edge itself. * * Let S be the symmetry defined by a reflection in P1 followed by a * reflection in P2. * * S is equivalent to a half turn about the unique common perpendicular * (proof: P1 and P2 are orthogonal to each other, and both contain * the unique common perpendicular). Therefore S is a symmetry of the * ideal tetrahedron, by the preceding proposition. * * Let L1 and L2 be the lengths of the two sides of the vertex cross * section which are incident to the given edge. Because the vertex * cross section is at height one in the upper half space model, * L1 and L2 also represent the Euclidean lengths of two sides of the * triangle obtained by projecting the ideal tetrahedron onto the * plane z == 0 in the upper half space model. Reflection in the * plane P1 does not change the lengths of those two sides of the * triangle, while reflection in the plane P2 (which, in Euclidean * terms, is inversion in a hemisphere of radius one) sends each * length to its inverse. Since the composition S of the two * reflections preserves the triangle, it follows that L1 and L2 * must be inverses of one another. QED * * If a vertex cross section passes through the midpoint of an edge, * then the product of the lengths L1 and L2 (using the notation of * the preceding proof) is L1 L2 = 1. Now consider a vertex cross * section which is a distance d away from the midpoint (towards * the fat part of the manifold if d is positive, towards the cusp * if d is negative). According to the documentation at the top of * this file, a cusp cross section's linear dimensions vary as exp(d), * so the lengths of the corresponding sides of the new vertex cross * section will be exp(d)L1 and exp(d)L2. Their product is * exp(d)L1 exp(d)L2 = exp(2d) L1 L2 = exp(2d). * * If the lengths of the sides of the vertex cross section at the * other end of the given edge are exp(d')L1 and exp(d')L2, then * their product is exp(2d'). The product of all four lengths is * * exp(d)L1 exp(d)L2 exp(d')L1 exp(d')L2 = exp(2(d + d')). * * This is exactly what we need to know: d + d' is the negative * of the intercusp distance. (Note that the midpoint has dropped * out of the picture!) */ product = 1.0; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) product *= length[i][j]; edge->intercusp_distance = -0.5 * log(product); } static double compute_min_dist( Triangulation *manifold, Cusp *cusp, /* ignored for tie group distances */ MinDistanceType min_distance_type) { /* * This function assumes the intercusp_distances * have already been computed. */ double min_dist; EdgeClass *edge; Cusp *cusp1, *cusp2; min_dist = DBL_MAX; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { cusp1 = edge->incident_tet->cusp[ one_vertex_at_edge[edge->incident_edge_index]]; cusp2 = edge->incident_tet->cusp[other_vertex_at_edge[edge->incident_edge_index]]; if (edge->intercusp_distance < min_dist) switch (min_distance_type) { case dist_self_to_self: if (cusp == cusp1 && cusp == cusp2) min_dist = edge->intercusp_distance; break; case dist_self_to_any: if (cusp == cusp1 || cusp == cusp2) min_dist = edge->intercusp_distance; break; case dist_group_to_group: if (cusp1->is_tied && cusp2->is_tied) min_dist = edge->intercusp_distance; break; case dist_group_to_any: if (cusp1->is_tied || cusp2->is_tied) min_dist = edge->intercusp_distance; break; } } return min_dist; } int get_num_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods) { if (cusp_neighborhoods == NULL) return 0; else return get_num_cusps(cusp_neighborhoods->its_triangulation); } CuspTopology get_cusp_neighborhood_topology( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->topology; } double get_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->displacement; } Boolean get_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->is_tied; } double get_cusp_neighborhood_cusp_volume( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { /* * As explained in the documentation at the top of this file, * the volume will be the volume enclosed by the cusp in its * home position, multiplied by exp(2 * displacement). */ return 0.1875 * ROOT_3 * exp(2 * find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->displacement); } double get_cusp_neighborhood_manifold_volume( CuspNeighborhoods *cusp_neighborhoods) { return cusp_neighborhoods->its_triangulation->volume; } Triangulation *get_cusp_neighborhood_manifold( CuspNeighborhoods *cusp_neighborhoods) { Triangulation *manifold_copy; Cusp *cusp; /* * Make a copy of its_triangulation. */ copy_triangulation(cusp_neighborhoods->its_triangulation, &manifold_copy); /* * Reset the cusp displacements to zero, so if a canonical triangulation * is needed later it will be computed relative to cusp cross sections * of equal volume. */ for (cusp = manifold_copy->cusp_list_begin.next; cusp != &manifold_copy->cusp_list_end; cusp = cusp->next) { cusp->displacement = 0.0; cusp->displacement_exp = 1.0; } return manifold_copy; } double get_cusp_neighborhood_reach( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->reach; } double get_cusp_neighborhood_max_reach( CuspNeighborhoods *cusp_neighborhoods) { return cusp_neighborhoods->its_triangulation->max_reach; } double get_cusp_neighborhood_stopping_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->stopping_displacement; } int get_cusp_neighborhood_stopper_cusp_index( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { return find_cusp(cusp_neighborhoods->its_triangulation, cusp_index)->stopper_cusp->index; } void set_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, double new_displacement) { Cusp *cusp, *other_cusp; /* * Get a pointer to the cusp whose displacement is being changed. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Clip the displacement to the feasible range. */ /* MC 2013-06-17 if (new_displacement < 0.0) new_displacement = 0.0; */ if (cusp->is_tied == FALSE) { /* * The stopping_displacement has already been set to be less than or * equal to the reach, so by clipping to the stopping_displacement * we know the cusp neighborhood won't overlap itself or any * other cusp neighborhood. */ if (new_displacement > cusp->stopping_displacement) new_displacement = cusp->stopping_displacement; } else /* cusp->is_tied == TRUE */ { /* * Make sure the new_displacement doesn't exceed the tie_group_reach. * Other cusps in the tie group will be coming at us as we move * toward them, so collisions might not be detected by the * stopping_displacement alone. (The latter assumes the other * cusp is stationary.) */ if (new_displacement > cusp_neighborhoods->its_triangulation->tie_group_reach) new_displacement = cusp_neighborhoods->its_triangulation->tie_group_reach; /* * Don't overlap untied stoppers either. */ for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied && new_displacement > other_cusp->stopping_displacement) new_displacement = other_cusp->stopping_displacement; } /* * Set the new displacement. */ if (cusp->is_tied == FALSE) { cusp->displacement = new_displacement; cusp->displacement_exp = exp(new_displacement); } else /* cusp->is_tied == TRUE */ { for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied) { other_cusp->displacement = new_displacement; other_cusp->displacement_exp = exp(new_displacement); } } /* * Compute the canonical cell decomposition * relative to the new displacement. */ if (proto_canonize(cusp_neighborhoods->its_triangulation) != func_OK) uFatalError("set_cusp_neighborhood_displacement", "cusp_neighborhoods"); /* * The cusp reaches won't have changed, but the stoppers might have. */ compute_cusp_stoppers(cusp_neighborhoods); } void set_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean new_tie) { Cusp *cusp, *other_cusp; double min_displacement; /* * Get a pointer to the cusp which is being tied or untied. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Tie or untie the cusp. */ cusp->is_tied = new_tie; /* * If the cusp is being tied, bring it and its mates into line. */ if (cusp->is_tied == TRUE) { /* * Find the minimum displacement for a tied cusp . . . */ min_displacement = DBL_MAX; for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied && other_cusp->displacement < min_displacement) min_displacement = other_cusp->displacement; /* * . . . and set all tied cusps to that minimum value. */ for (other_cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; other_cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; other_cusp = other_cusp->next) if (other_cusp->is_tied) { other_cusp->displacement = min_displacement; other_cusp->displacement_exp = exp(min_displacement); } /* * Compute the canonical cell decomposition * relative to the minimum displacement. */ if (proto_canonize(cusp_neighborhoods->its_triangulation) != func_OK) uFatalError("set_cusp_neighborhood_tie", "cusp_neighborhoods"); /* * The cusp reaches won't have changed, * but the stoppers might have. */ compute_cusp_stoppers(cusp_neighborhoods); } /* * How far can the group of tied cusps go before bumping into itself? */ compute_tie_group_reach(cusp_neighborhoods); } static void initialize_cusp_ties( CuspNeighborhoods *cusp_neighborhoods) { Cusp *cusp; /* * Initially no cusps are tied . . . */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) cusp->is_tied = FALSE; /* * . . . and the tie_group_reach is undefined. */ cusp_neighborhoods->its_triangulation->tie_group_reach = 0.0; } static void initialize_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { /* * Install VertexCrossSections so that we know the size of each * vertex cross section in the cusp's home position. */ allocate_cross_sections(cusp_neighborhoods->its_triangulation); compute_cross_sections(cusp_neighborhoods->its_triangulation); /* * Allocate storage for the CuspNbhdPositions . . . */ allocate_cusp_nbhd_positions(cusp_neighborhoods); /* * . . . and then compute them. */ compute_cusp_nbhd_positions(cusp_neighborhoods); /* * Free the VertexCrossSections now that we're done with them. * (proto_canonize() will of course need them again, but it likes * to allocate them for itself -- this keeps its interaction with * the rest of the kernel cleaner.) */ free_cross_sections(cusp_neighborhoods->its_triangulation); } static void allocate_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { Tetrahedron *tet; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { /* * Just for good measure, make sure no CuspNbhdPositions * are already allocated. */ if (tet->cusp_nbhd_position != NULL) uFatalError("allocate_cusp_nbhd_positions", "cusp_neighborhoods"); /* * Allocate a CuspNbhdPosition structure. */ tet->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); } } static void compute_cusp_nbhd_positions( CuspNeighborhoods *cusp_neighborhoods) { Tetrahedron *tet; Orientation h; VertexIndex v; int max_triangles; Cusp *cusp; PeripheralCurve c; Complex (*x)[4][4], *translation; Boolean (*in_use)[4]; FaceIndex f, f0, f1, f2; int strands1, strands2, flow; double length; Complex factor; /* * Initialize all the tet->in_use[][] fields to FALSE, * and all tet->x[][][] to Zero. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ for (v = 0; v < 4; v++) { for (f = 0; f < 4; f++) tet->cusp_nbhd_position->x[h][v][f] = Zero; tet->cusp_nbhd_position->in_use[h][v] = FALSE; } /* * For each vertex cross section which has not yet been set, set the * positions of its three vertices, and then recursively set the * positions of neighboring vertex cross sections. The positions * are relative to each cusp cross section's home position. * (Recall that initialize_cusp_nbhd_positions() has already called * compute_cross_sections() for us.) For torus cusps, do only the * sheet of the double cover which contains the peripheral curves * (this will be the right_handed sheet if the manifold is orientable). */ max_triangles = 2 * 4 * cusp_neighborhoods->its_triangulation->num_tetrahedra; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp_nbhd_position->in_use[right_handed][v] == FALSE && tet->cusp_nbhd_position->in_use[ left_handed][v] == FALSE) { /* * Use the sheet which contains the peripheral curves. * If neither does, do nothing for now. They'll show * up eventually. */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ if (contains_meridian(tet, h, v) == TRUE) { set_one_component(tet, h, v, max_triangles); break; } } /* * Compute the meridional and longitudinal translation on each * cusp cross section. For Klein bottle cusps, the longitude * will actually be that of the double cover. The translations * are stored in the Cusp data structure as translation[M] and * translation[L]. */ /* * The Algorithm * * The calls to set_one_component() have assigned coordinates to all * the triangles in the induced triangulation of the cusp cross section. * The problem is that these coordinates are well defined only up * to translations in the covering transformation group (or the * orientation preserving subgroup, in the case of a Klein bottle cusp). * So we want an algorithm which uses only the local coordinates within * each triangle, without requiring global consistency. * * Imagine following a peripheral curve around the cusp cross section, * and look at the sides of the triangles it passes through. As we * go along, we can keep track of the coordinates of the left and * right hand edges. When we "veer left" the left hand endpoint stays * constant, while the right hand endpoint moves forward, and vice * versa when we "veer right". By adding up all the displacements to * each endpoint, by the time we get back to our starting point we will * have computed the total translation along the curve. Actually, * it suffices to compute the total displacement for only one endpoint * (left or right) since both will give the same answer. * * Finally, note that it doesn't matter in what order we sum the * displacements. We can just iterate through all tetrahedra in the * triangulation without explicitly tracing curves. */ /* * Initialize all translations to (0.0, 0.0), and then . . . */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) for (c = 0; c < 2; c++) cusp->translation[c] = Zero; /* * . . . add in the contribution of each piece of each curve. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { cusp = tet->cusp[v]; for (c = 0; c < 2; c++) { translation = &cusp->translation[c]; for (f0 = 0; f0 < 4; f0++) { if (f0 == v) continue; /* * Relative to the right_handed Orientation, the faces * f0, f1 and f2 are arranged around the ideal vertex v * like this * * /\ * f1 / \ f0 * /____\ * f2 * * The triangles corners inherit the indices of the * opposite sides. */ f1 = remaining_face[f0][v]; f2 = remaining_face[v][f0]; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; strands1 = tet->curve[c][h][v][f1]; strands2 = tet->curve[c][h][v][f2]; flow = FLOW(strands2, strands1); /* * We're interested only in displacements of the * left hand endpoint (cf. above), which occur when * the flow is negative (if h == right_handed) or * the flow is positive (if h == left_handed). */ if ((h == right_handed) ? (flow < 0) : (flow > 0)) *translation = complex_plus( *translation, complex_real_mult( flow, complex_minus(x[h][v][f2], x[h][v][f1]))); } } } } } /* * Rotate the coordinates so that the longitudes point in the * direction of the positive x-axis. */ /* * Find the rotation needed for each cusp, * and use it to rotate the meridian and longitude. */ for (cusp = cusp_neighborhoods->its_triangulation->cusp_list_begin.next; cusp != &cusp_neighborhoods->its_triangulation->cusp_list_end; cusp = cusp->next) { cusp->scratch = cusp->translation[L]; length = complex_modulus(cusp->scratch); if (length < LONGITUDE_EPSILON) uFatalError("compute_cusp_nbhd_positions", "cusp_neighborhoods"); cusp->scratch = complex_real_mult(1.0/length, cusp->scratch); cusp->scratch = complex_div(One, cusp->scratch); cusp->translation[M] = complex_mult(cusp->scratch, cusp->translation[M]); cusp->translation[L] = complex_mult(cusp->scratch, cusp->translation[L]); cusp->translation[L].imag = 0.0; /* kill the roundoff error */ } /* * Use the same rotation (stored in cusp->scratch) to rotate * the coordinates in the triangulation of the cusp. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ for (v = 0; v < 4; v++) { if (in_use[h][v] == FALSE) continue; factor = tet->cusp[v]->scratch; for (f = 0; f < 4; f++) { if (f == v) continue; x[h][v][f] = complex_mult(factor, x[h][v][f]); } } } } static Boolean contains_meridian( Tetrahedron *tet, Orientation h, VertexIndex v) { /* * It suffices to check any two sides, because the meridian * can't possibly intersect only one side of a triangle. * (These are signed intersection numbers.) */ VertexIndex w0, w1; w0 = ! v; w1 = remaining_face[v][w0]; return (tet->curve[M][h][v][w0] != 0 || tet->curve[M][h][v][w1] != 0); } static void set_one_component( Tetrahedron *tet, Orientation h, VertexIndex v, int max_triangles) { /* * FaceIndices are the natural way to index the corners * of a vertex cross section. * * The VertexIndex v tells which vertex cross section we're at. * The vertex cross section is (a triangular component of) the * intersection of a cusp cross section with the ideal tetrahedron. * Each side of the triangle is the intersection of the cusp cross * section with some face of the ideal tetrahedron, so FaceIndices * may naturally be used to index them. Each corner of the triangle * then inherits the FaceIndex of the opposite side. */ FaceIndex f[3], ff, nbr_f[3]; int i; CuspTriangle *queue, tri, nbr; int queue_begin, queue_end; Permutation gluing; CuspNbhdPosition *our_data, *nbr_data; /* * Find the three FaceIndices for the corners of the triangle. * (f == v is excluded.) */ for ( i = 0, ff = 0; i < 3; i++, ff++) { if (ff == v) ff++; f[i] = ff; } /* * Let the corner f[0] be at the origin. */ tet->cusp_nbhd_position->x[h][v][f[0]] = Zero; /* * Let the corner f[1] be on the positive x-axis. */ tet->cusp_nbhd_position->x[h][v][f[1]].real = tet->cross_section->edge_length[v][f[2]]; tet->cusp_nbhd_position->x[h][v][f[1]].imag = 0.0; /* * Use the TetShape to find the position of corner f[2]. */ cn_find_third_corner(tet, h, v, f[0], f[1], f[2]); /* * Mark this triangle as being in_use. */ tet->cusp_nbhd_position->in_use[h][v] = TRUE; /* * We'll now "recursively" set the remaining triangles of this * cusp cross section. We'll keep a queue of the triangles whose * positions have been set, but whose neighbors have not yet * been examined. */ queue = NEW_ARRAY(max_triangles, CuspTriangle); queue[0].tet = tet; queue[0].h = h; queue[0].v = v; queue_begin = 0; queue_end = 0; while (queue_begin <= queue_end) { /* * Pull a CuspTriangle off the queue. */ tri = queue[queue_begin++]; /* * Consider each of its three neighbors. */ for (ff = 0; ff < 4; ff++) { if (ff == tri.v) continue; gluing = tri.tet->gluing[ff]; nbr.tet = tri.tet->neighbor[ff]; nbr.h = (parity[gluing] == orientation_preserving) ? tri.h : ! tri.h; nbr.v = EVALUATE(gluing, tri.v); our_data = tri.tet->cusp_nbhd_position; nbr_data = nbr.tet->cusp_nbhd_position; /* * If the neighbor hasn't been set . . . */ if (nbr_data->in_use[nbr.h][nbr.v] == FALSE) { /* * . . . set it . . . */ f[0] = remaining_face[tri.v][ff]; f[1] = remaining_face[ff][tri.v]; f[2] = ff; for (i = 0; i < 3; i++) nbr_f[i] = EVALUATE(gluing, f[i]); for (i = 0; i < 2; i++) nbr_data->x[nbr.h][nbr.v][nbr_f[i]] = our_data->x[tri.h][tri.v][f[i]]; cn_find_third_corner(nbr.tet, nbr.h, nbr.v, nbr_f[0], nbr_f[1], nbr_f[2]); nbr_data->in_use[nbr.h][nbr.v] = TRUE; /* * . . . and put it on the queue. */ queue[++queue_end] = nbr; } } } /* * An "unnecessary" error check. */ if (queue_begin > max_triangles) uFatalError("set_one_component", "cusp_neighborhoods"); /* * Free the queue. */ my_free(queue); } void cn_find_third_corner( Tetrahedron *tet, /* which tetrahedron */ Orientation h, /* right_handed or left_handed sheet */ VertexIndex v, /* which ideal vertex */ FaceIndex f0, /* known corner */ FaceIndex f1, /* known corner */ FaceIndex f2) /* corner to be computed */ { /* * We want to position the Tetrahedron so that the following * two conditions hold. * * (1) The corners f0, f1 and f2 are arranged counterclockwise * around the triangle's perimeter. * * f2 * / \ * / \ * f0------f1 * * (2) The cusp cross section is seen with its preferred orientation. * (Cf. the discussion in the second paragraph of section (2) in * the documentation at the top of the file peripheral_curves.c.) * If this is the right handed sheet (h == right_handed), * the Tetrahedron should appear right handed. * (Cf. the definition of Orientation in kernel_typedefs.h.) * If this is the left handed sheet (h == left_handed), the * Tetrahedron should appear left handed (the left_handed sheet has * the opposite orientation of the Tetrahedron, so if this is the * left handed sheet and the Tetrahedron is viewed in a left handed * position, the sheet will be appear right handed -- got that?). * * Of course these two conditions may not be compatible. * If we position the corners as in (1) and then find that (2) doesn't * hold (or vice versa), then we must swap the indices f0 and f1. * * Note: We could force the conditions to hold by making our * recursive calls carefully and consistently, but fixing the * ordering of f0 and f1 as needed is simpler and more robust. */ Orientation tet_orientation; FaceIndex temp; Complex s, t, z; /* * Position the tetrahedron as in Condition (1) above. * If the tetrahedron appears in its right_handed Orientation, * then remaining_face[f0][f1] == f2, according to the definition of * remaining_face[][] in tables.c. If the tetrahedron appears in * its left_handed Orientation, then remaining_face[f0][f1] == v. */ tet_orientation = (remaining_face[f0][f1] == f2) ? right_handed : left_handed; /* * Does the vertex cross section appear with its preferred orientation, * as discussed in Condition (2) above? If not, fix it. */ if (h != tet_orientation) { temp = f0; f0 = f1; f1 = temp; tet_orientation = ! tet_orientation; } /* * Let s be the vector from f0 to f1, * t be the vector from f0 to f2, * z be the complex edge angle v/u. */ s = complex_minus( tet->cusp_nbhd_position->x[h][v][f1], tet->cusp_nbhd_position->x[h][v][f0]); /* * TetShapes are always stored relative to the right_handed Orientation. * If we're viewing the tetrahedron relative to the left_handed * Orientation, we need to use the conjugate-inverse instead. */ z = tet->shape[complete]->cwl[ultimate][edge3_between_vertices[v][f0]].rect; if (tet_orientation == left_handed) z = complex_conjugate(complex_div(One, z)); t = complex_mult(z, s); tet->cusp_nbhd_position->x[h][v][f2] = complex_plus(tet->cusp_nbhd_position->x[h][v][f0], t); } void get_cusp_neighborhood_translations( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Complex *meridian, Complex *longitude) { Cusp *cusp; cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); *meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); *longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); } CuspNbhdSegmentList *get_cusp_neighborhood_triangulation( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { Cusp *cusp; CuspNbhdSegmentList *theSegmentList; CuspNbhdSegment *next_segment; Tetrahedron *tet, *nbr_tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v; Orientation h; FaceIndex f, nbr_f; /* * Make sure the EdgeClasses are numbered. */ number_the_edge_classes(cusp_neighborhoods->its_triangulation); /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Allocate the wrapper for the array. */ theSegmentList = NEW_STRUCT(CuspNbhdSegmentList); /* * We don't know ahead of time exactly how many CuspNbhdSegments * we'll need. Torus cusps report each segment once, but Klein * bottle cusps report each segment twice, once for each sheet. * * To get an upper bound on the number of segments, * assume all cusps are Klein bottle cusps. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * 3 sides/triangle * / 2 sides/visible side (no need to draw each edge twice) * * = 12n visible sides */ theSegmentList->segment = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdSegment); /* * Keep a pointer to the first empty CuspNbhdSegment. */ next_segment = theSegmentList->segment; for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; for (f = 0; f < 4; f++) { if (f == v) continue; nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(tet->gluing[f], f); /* * We want to report each segment only once, so we * make the (arbitrary) convention that we report * a segment only from the Tetrahedron whose address * in memory is less. In the case of a Tetrahedron * glued to itself, we report it from the lower * FaceIndex. */ if (tet > nbr_tet || (tet == nbr_tet && f > nbr_f)) continue; /* * Don't report edges which are part of the arbitrary * subdivision of the canonical cell decomposition * into tetrahdra. We rely on the fact that * proto_canonize() has computed the tilts and left * them in place. The sum of the tilts will never be * positive for a subdivision of the canonical cell * decomposition. If it's close to zero, ignore that * face. */ if (tet->tilt[f] + nbr_tet->tilt[nbr_f] > -CONCAVITY_EPSILON) continue; /* * This edge has passed all its tests, so record it. */ next_segment->endpoint[0] = complex_real_mult(cusp->displacement_exp, x[h][v][remaining_face[f][v]]); next_segment->endpoint[1] = complex_real_mult(cusp->displacement_exp, x[h][v][remaining_face[v][f]]); next_segment->start_index = tet->edge_class[edge_between_vertices[v][remaining_face[f][v]]]->index; next_segment->middle_index = tet->edge_class[edge_between_faces[v][f]]->index; next_segment->end_index = tet->edge_class[edge_between_vertices[v][remaining_face[v][f]]]->index; /* * Move on. */ next_segment++; } } } } /* * How many segments did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdSegment).) */ theSegmentList->num_segments = next_segment - theSegmentList->segment; /* * Did we find more segments than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theSegmentList->num_segments > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_triangulation", "cusp_neighborhoods"); return theSegmentList; } void free_cusp_neighborhood_segment_list( CuspNbhdSegmentList *segment_list) { if (segment_list != NULL) { if (segment_list->segment != NULL) my_free(segment_list->segment); my_free(segment_list); } } CuspNbhdHoroballList *get_cusp_neighborhood_horoballs( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean full_list, double cutoff_height) { Cusp *cusp; CuspNbhdHoroballList *theHoroballList; /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Provide a small margin to allow for roundoff error. */ cutoff_height -= CUTOFF_HEIGHT_EPSILON; /* * Use the appropriate algorithm for finding * the quick or full list of horoballs. */ if (full_list == FALSE) theHoroballList = get_quick_horoball_list(cusp_neighborhoods, cusp); else theHoroballList = get_full_horoball_list(cusp_neighborhoods, cusp, cutoff_height); /* * Sort the horoballs in order of increasing size. */ qsort( theHoroballList->horoball, theHoroballList->num_horoballs, sizeof(CuspNbhdHoroball), &compare_horoballs); /* * There's a chance that get_full_horoball_list() may produce duplicate * horoballs (when a 2-cell passes through a horoball's north pole) or * that get_quick_horoball_list() may produce duplicatate horoballs * (when face horoballs coincide). Remove any such duplications. */ cull_duplicate_horoballs(cusp, theHoroballList); return theHoroballList; } static CuspNbhdHoroballList *get_quick_horoball_list( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp) { CuspNbhdHoroballList *theHoroballList; CuspNbhdHoroball *next_horoball; /* * Allocate the wrapper for the array. */ theHoroballList = NEW_STRUCT(CuspNbhdHoroballList); /* * We don't know ahead of time exactly how many CuspNbhdHoroballs * we'll need. Torus cusps report each horoball once, but Klein * bottle cusps report each horoball twice, once for each sheet. * To get an upper bound on the number of horoballs, assume all * cusps are Klein bottle cusps. We report two types of horoballs. * * Edge Horoballs * * Edge horoballs are horoballs which the given cusp sees along an * edge of the canonical triangulation (i.e. along a vertical edge * in the usual upper half space picture). The total number of * edges in the canonical triangulation is the same as the number * of tetrahedra (by an Euler characteristic argument), so the * following gives an upper bound on the number of edge horoballs. * * n edges * * 2 endpoints/edge * * 2 sheets/endpoint (left_handed and right_handed) * * = 4n edge horoballs * * Face Horoballs * * Face horoballs are horoballs which the given cusp sees across * a face of the canonical triangulation. The number of triangles * in the cusp triangulation provides an upper bound on the number * of face horoballs. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * = 8n visible sides * * Therefore the total number of horoballs we will report will be * at most 4n + 8n = 12n. (The maximum will be realized in the case * of a manifold like the Gieseking with one nonorientable cusp.) */ theHoroballList->horoball = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdHoroball); /* * Keep a pointer to the first empty CuspNbhdHoroball. */ next_horoball = theHoroballList->horoball; /* * Find the edge horoballs. */ get_quick_edge_horoballs( cusp_neighborhoods->its_triangulation, cusp, &next_horoball); /* * Find the face horoballs. */ get_quick_face_horoballs( cusp_neighborhoods->its_triangulation, cusp, &next_horoball); /* * How many horoballs did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdHoroball).) */ theHoroballList->num_horoballs = next_horoball - theHoroballList->horoball; /* * Did we find more horoballs than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theHoroballList->num_horoballs > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_triangulation", "cusp_neighborhoods"); return theHoroballList; } static void get_quick_edge_horoballs( Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball) { EdgeClass *edge; double radius; Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v[2]; int i; int other_index; Orientation h; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * Consider a horosphere of Euclidean height h in the upper half * space model. Integrate along a vertical edge connecting the * horosphere to the horosphere at infinity to compute the distance * between the two as * * d = integral of dz/z from z=h to z=1 * = log 1 - log h * = - log h * or * h = exp(-d) * * set_cusp_neighborhood_displacement() calls compute_cusp_stoppers(), * which in turn calls compute_intercusp_distances(), so we may use * the edge->intercusp_distance fields for d. */ radius = 0.5 * exp( - edge->intercusp_distance); /* * Dereference tet, x and in_use for clarity. */ tet = edge->incident_tet; x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; /* * Consider each of the edge's endpoints. */ v[0] = one_vertex_at_edge[edge->incident_edge_index]; v[1] = other_vertex_at_edge[edge->incident_edge_index]; for (i = 0; i < 2; i++) { /* * Are we at the right cusp? */ if (tet->cusp[v[i]] != cusp) continue; /* * What is the index of the other cusp? */ other_index = tet->cusp[v[!i]]->index; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v[i]] == FALSE) continue; (*next_horoball)->center = complex_real_mult(cusp->displacement_exp, x[h][v[i]][v[!i]]); (*next_horoball)->radius = radius; (*next_horoball)->cusp_index = other_index; (*next_horoball)++; } } } } static void get_quick_face_horoballs( Triangulation *manifold, Cusp *cusp, CuspNbhdHoroball **next_horoball) { /* * There are several ways we might find the location and size of * the face horoballs. * * (1) Use the TetShape to locate the center, and then use the * lemma below to find the size. * * This method is fairly efficient computationally, and lets * us use the existing function compute_fourth_corner() from * choose_generators.c. * * (2) Ignore the TetShape, and rely entirely on the intercusp_distances * to find both the location and size. * * This method is conceptually straightforward. Using the lemma * below, one obtains three equations involving the location (x,y) * and the height h of the face horoball. The equations are * quadratic in x and y, but they are monic, so subtracting * equations gives linear dependencies between x, y and h. * One can solve for x and y in terms of h, and obtain a quadratic * equation to solve for h. It's easy to prove that the lesser * value of h will be the desired solution. Confession: I haven't * actually worked out the equation for h. It seems like it would * be messy. * * (3) Work in the Minkowski space model, and use linear algebra * to compute the horoball as a vector on the light cone. * * For background ideas, see * * Weeks, Convex hulls and isometries of cusped hyperbolic * 3-manifolds, Topology Appl. 52 (1993) 127-149 * and * Sakuma and Weeks, The generalized tilt formula, * Geometriae Dedicata 55 (1995) 115-123. * * The method might prove to be more-or-less equivalent to (2). * By Lemma 4.2(c) of Weeks, the equation = constant gives * all the horospheres v a fixed distance from a horosphere u. * So to find a horosphere a given distance from three given * horospheres, one ends up intersecting three hyperplanes in * E^(3,1) to get a line, and then intersecting the line with the * upper light cone. As in approach (2), the calculations are * initially linear, but become quadratic at the end. Again, I * haven't worked through the details. * * (4) Find a matrix in PSL(2,C) which takes an ideal tetrahedron * in standard position to the desired ideal tetrahedron. * * This is the approach used in snappea 1.3. The formulas are * simpler than you might expect. The main disadvantage is that * the 1.3 treatment applies only to orientable manifolds. It * might be possible to fix it up using MoebiusTransformations. * * We use method (1), because it seems simplest. * * Lemma. Consider two horospheres of Euclidean height h1 and h2 (resp.) * in the upper half space model of hyperbolic 3-space. If the * Euclidean distances between their centers (on the sphere at infinity) * is c, then the hyperbolic distance d between the horospheres is * * d = log( c^2 / h1*h2 ) * * Proof. Draw yourself a picture of the horospheres (or horocycles -- * a 2D cross sectional picture will serve just as well). Label the * distances h1, h2, c and d. Now sketch a Euclidean hemisphere of * radius c centered at the base of the first horosphere; this is * a plane in hyperbolic space. Reflect the whole picture in this * plane (in Euclidean terms, the reflection is an inversion in the * hemisphere). One of the horospheres gets taken to a horizontal * Euclidean plane at height c^2/h1. The other horosphere remains * (setwise) invariant. It is now obvious that the shortest distance * from one horosphere to the other is along the vertical arc connecting * them. The distance is the integral of dz/z from h=h2 to h=c^2/h1, * which works out to be log( c^2 / h1*h2 ). QED * * Comment. We don't need it for the present code, but I can't * resist pointing out that the above lemma has a nice intrinsic * formulation, which doesn't rely on the upper half space model. * Let H be the horosphere which appears as a horizontal plane z == 1 * in the upper half space model, and draw in the vertical geodesics * connecting it to each of the two horospheres mentioned in the lemma. * Let a = -log(h1) and b = -log(h2) be the respective distances from * H to each of the old horospheres. Interpret c as the distance along * H from one of those segments to the other. Now redraw the picture * in, say, the Poincare ball model. It'll be more symmetric now, * since there's no longer a preferred "horosphere at infinity". * You'll have an ideal triangle, with a horosphere at each vertex. * The quantities a, b and d are the length of the shortest geodesics * between horospheres, while c is the distance along a horosphere * between two such geodesics. The above lemma becomes * * Lemma. 2 log c = d - a - b. * * With better notation, namely a, b and c are the distances between * cusp cross sections, and A, B and C are the distances along the * cusps, the lemma becomes * * 2 log A = a - b - c * 2 log B = b - c - a * 2 log C = c - a - b * * Add two of those equations (say the first two) to get * * log AB = -c * * As a special case, when c == 0, AB = 1. */ Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex u, v, w, missing_corner; Permutation gluing; Complex corner[4]; Orientation h; double height_u, exp_d, c_squared; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * Are we at the right cusp? */ if (tet->cusp[v] != cusp) continue; gluing = tet->gluing[v]; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; /* * Prepare for a call to compute_fourth_corner(). */ for (w = 0; w < 4; w++) if (w != v) corner[EVALUATE(gluing, w)] = complex_real_mult(cusp->displacement_exp, x[h][v][w]); missing_corner = EVALUATE(gluing, v); /* * Call compute_fourth_corner() to compute * corner[missing_corner]. */ compute_fourth_corner( corner, missing_corner, (parity[gluing] == orientation_preserving) ? h : !h, tet->neighbor[v]->shape[complete]->cwl[ultimate]); /* * The missing_corner gives us the horoball's center. */ (*next_horoball)->center = corner[missing_corner]; /* * Prepare to use the above lemma to compute the radius. */ /* * Let u be any vertex of the original Tetrahedron except v. */ u = !v; /* * According to the explanation in get_quick_edge_horoballs(), * the height of the edge horoball at vertex u is * exp( - intercusp_distance). */ height_u = exp( - tet->edge_class[edge_between_vertices[u][v]]->intercusp_distance); /* * A different intercusp_distance gives the distance d * in the lemma. */ exp_d = exp(tet->neighbor[v]->edge_class[edge_between_vertices[EVALUATE(gluing,u)][missing_corner]]->intercusp_distance); /* * Compute the squared distance between the edge horoball * at vertex u and the face horoball we are interested in. */ c_squared = complex_modulus_squared(complex_minus( (*next_horoball)->center, complex_real_mult(cusp->displacement_exp, x[h][v][u]))); /* * Apply the lemma. * * exp(d) = c^2 / h1*h2 * => * h1 = c^2 / exp(d)*h2 */ (*next_horoball)->radius = 0.5 * c_squared / (exp_d * height_u); /* * Note the cusp index of the new horoball. */ (*next_horoball)->cusp_index = tet->neighbor[v]->cusp[missing_corner]->index; /* * Move on. */ (*next_horoball)++; } } } } static CuspNbhdHoroballList *get_full_horoball_list( CuspNeighborhoods *cusp_neighborhoods, Cusp *cusp, double cutoff_height) { /* * We want to find all horoballs of Euclidean height at least * cutoff_height, up to the Z + Z action of the group of covering * transformations of the cusp. (We work with the double cover * of Klein bottle cusps, so in effect all cusps are torus cusps.) * * Let M' be H^3 / (Z + Z), where the Z + Z is the group of covering * transformations of the cusp. Visualize M' as a chimney in the * upper half space model; when its sides are glued together its * parallelogram cross section becomes the torus cross section * of the cusp. * * Our plan is to lift ideal tetrahedra from the original manifold M * to the chimney manifold M'. We begin with the tetrahedra incident * to the chimney's cusp (i.e. its top end), and then gradually tile * our way downward. Whenever a new tetrahedron introduces a new * ideal vertex, we consider the horoball centered at that vertex. * If its Euclidean height is greater than cutoff_height, we add it * to a list. Our challenge is to find an algorithm which does as * little tiling as possible, yet still finds all horoballs higher * than the cutoff_height. * * The Naive Algorithm * * The naive algorithm is to consider the neighbors of each tetrahedron * already in the tiling. If adding a neighbor would introduce no * new vertices, add it. If adding a neighbor would introduce a new * vertex, add it iff the horoball at the new vertex is higher than * the cutoff_height. * * Unfortunately the naive algorithm fails. The Whitehead link * provides a counterexample. Visualize the Whitehead link as an * octahedron with faces identified. The ideal vertices at the * "north and south poles" form one cusp ("the red cusp") while the * "equatorial ideal vertices" form the other cusp ("the blue cusp"). * Push the blue cusp cross section forward until it meets itself, * but retract the red cusp cross section until it's tiny. The * canonical cell decomposition is a subdivision of the octahedron * into two square pyramids (a "northern" and a "southern" one). * SnapPea will, of course, arbitrarily subdivide each pyramid into * two tetrahedra. Now consider what happens when we apply the naive * algorithm to this example, with the red cusp at infinity. Each * of the initial tetrahedra has a red vertex at infinity, and three * blue vertices on the horizontal plane. Its three neighbors to the * sides are other tetrahedra of the same type (red at infinity and * blue on the horizontal plane). Its underneath neighbor shares the * same three blue vetices, and introduce a new red vertex on the * horizontal plane. But because the red horoball is tiny, the naive * algorithm will say not to add this tetrahedron. So no new tetrahedra * will be added, and the algorithm will terminate. The naive * algorithm has therefore failed, because it's missed blue horoballs * of varying sizes. (Assuming we've chosen the size of the tiny * red cusp cross section to be small enough that the largest red * horoballs are smaller than the medium sized blue one.) * * The naive algorithm's failure was the bad news. The good news * is that if we take into account the varying sizes of the horoballs, * the algorithm can be patched up and made to work. First a few * background lemmas. * * Lemma 1. For each horoball H, there is (a lift of) an edge * of the canonical cell decompostion which connects H to some * larger horoball H'. * * Proof. The horoball H is surrounded by (lifts of) 2-cells * of the Ford complex. Consider a 2-cell F which lies above some * point of H (in the upper half space model). F is dual to an edge * of the canonical cell decomposition which connects H to some other * horoball H'. F lies above H, so by Lemma 2 below, H' is larger * than H. QED * * Lemma 2. Consider two horoballs H and H'. If H' has a larger * Euclidean height than H when viewed in some fixed way in the upper * half space model of hyperbolic 3-space, then the plane P lying * midway between them appears as a Euclidean hemisphere enclosing * H and excluding H'. In particular, every point of H is directly * below some point of P, while no point of H' is. * * Proof. Draw the horoballs and construct P. QED * * Definition. Two horoballs are "edge-connected" if (a lift of) an * edge of the canonical cell decomposition connects one to the other. * * Lemma 3. Let H' be a horoball which is edge-connected to a smaller * horoball H. Then the Euclidean distance c between their centers * (on the boundary plane of the upper half space model) is * * c = sqrt( a * b * exp(d) ) * * where * a = Euclidean height of H' * b = Euclidean height of H * d = hyperbolic distance from H' to H. * * Proof. The lemma in get_quick_face_horoballs() says that * d = log(c^2 / a*b). Solve for c = sqrt( a * b * exp(d) ). QED * * Lemma 4. Let H' be a horoball which is edge-connected to a smaller * horoball H. If the Euclidean height of H is at least cutoff_height, * then the Euclidean distance c between the centers of H and H' is * at least * c >= sqrt( a * cutoff_height * exp(min_d) ) * * where a is the height of H' and min_d is the least distance from * the horoball H' to any other horoball. * * Proof. Follows immediately from Lemma 3. * * Comment. The exp(min_d) factor makes H' act like a bigger horoball * than it really is. If you were to increase the cusp displacement * by min_d, the height of H' would increase to a*exp(min_d). * * Definition. (A lift of) an edge of the canonical triangulation * is "potentially useful" if one endpoint lies at the center of * a horoball H' of height at least cutoff_height, and the distance * between its two endpoints is at least c (as defined in Lemma 4). * (As a special case, vertical edges (in the upper half space) are * always "potentially useful". The informal justification for this * is that the horosphere at infinity is infinity large and its center * is infinitely far away.) * * Definition. (A lift of) an ideal tetrahedron is "potentially useful" * iff it contains at least one potentially useful edge. * * The Corrected Algorithm * * As before, begin with the tetrahedra incident to the chimney's cusp * and gradually tile downward. For each tetrahedron already in the * tiling, consider its four neighbors and add those which are * potentially useful. * * Lemma 5. Let H' be a horoball higher than the cutoff_height. * If the Corrected Algorithm adds one potentially useful tetrahedron * incident to H', then it adds them all. * * Proof. Look at the surface of the horoball H', which intrinsically * is a Euclidean plane E. An edge of the triangulation intersects * the plane E in point P. The edge is potentially useful iff P lies * within a disk D (of intrinsic radius a/c in the Euclidean geometry * of the horosphere E, but we don't need that fact). A tetrahedron * incident to H' is potentially useful iff it intersects the disk D. * The set of all such tetrahedra forms a connected set (this follows * from the path connectedness of the disk D). Therefore if the * algorithm adds one such tetrahedron, it will add them all. QED * * Proposition 6. The Corrected Algorithm finds all horoballs higher * than the cutoff_height. * * Proof. Let H be a horoball of maximal height (greater than the * cutoff_height) which the algorithm missed. By Lemma 1, there * is a higher horoball H', and an edge connecting H' to H. The * edge is potentially useful, by Lemma 4 and the definition of a * potentially useful edge. By the assumed maximal height of H * (among all horoballs which the Corrected Algorithm should have * found but didn't), we know that the algorithm did find H', i.e. * it added some potentially useful tetrahedron incident to the center * of H'. By Lemma 5, it must have added all potentially useful * tetrahedra incident to H', and therefore must have found H. QED * * Corollary 7. We can refine the Corrected Algorithm as follows. * For each tetrahedron T already added, we consider only those * neighbors T' incident to a face of T which contains at least * one potentially useful edge. * * Proof. The proof of Proposition 6 still works. QED */ TilingHoroball *horoball_linked_list; TilingQueue tiling_queue; TilingTet *tiling_tree_root, *tiling_tet, *tiling_nbr; Complex meridian, longitude; double parallelogram_to_square[2][2]; FaceIndex f; CuspNbhdHoroballList *theHoroballList; /* * We don't know a priori how many horoballs we'll find. * So we temporarily keep them on a NULL-terminated linked list, * and transfer them to an array when we're done. * * To avoid recording multiple copies of each horoball, we make the * convention that each horoball is recorded only by the TilingTet * which contains its north pole. If the north pole lies on the * boundary of two TilingTets, they both record it. * get_cusp_neighborhood_horoballs() will remove the duplications. * If three or more TilingTets meet at the north pole, then a vertical * edge connects the horoball to infinity in the upper half space model; * read_initial_tetrahedra() records such horoballs without duplication. * (Other strategies are possible, like preferring the Tetrahedron * with the lower address in memory, but the present approach is * least vulnerable to roundoff error.) */ horoball_linked_list = NULL; /* * We'll need to store the potentially useful tetrahedra in two ways. * * Queue * The Tetrahedra which have been added, but whose neighbors have * not been examined, go on a queue, so we know which one * to process next. When we remove a tetrahedron from the queue * we examine its neighbors. We use a queue rather than a stack * so that we tile generally downwards (rather than snaking around) * in hopes of obtaining the best numerical precision. * * Tree * All tetrahedra which have been added are kept on a tree, so that * we can tell whether new tetrahedra are duplications of old ones * or not. (Note: Checking whether a tetrahedron is "the same as" * an old one means checking whether they are equivalent under * the Z + Z action of the covering transformations. * * The TilingTet structure supports both the queue and the tree, * simultaneously and independently. */ /* * Initialize the data structures. */ tiling_queue.begin = NULL; tiling_queue.end = NULL; tiling_tree_root = NULL; /* * For each cusp, compute the quantity exp(min_d) needed in Lemma 4. */ compute_exp_min_d(cusp_neighborhoods->its_triangulation); /* * Compute the current meridional and longitudinal translations. */ meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); /* * prepare_sort_key() will need a linear transformation which * maps a fundamental parallelogram for the cusp (or the double * cover, in the case of a Klein bottle cusp) to the unit square. */ compute_parallelogram_to_square(meridian, longitude, parallelogram_to_square); /* * Read in the tetrahedra incident to the vertex at infinity, * and record the incident horoballs. * * Note: We check the horoballs when we put TilingTets onto the * tiling_queue (rather than when we pull it off) so we can handle * the special case of the initial tetrahedra more efficiently. */ read_initial_tetrahedra( cusp_neighborhoods->its_triangulation, cusp, &tiling_queue, &tiling_tree_root, &horoball_linked_list, cutoff_height); /* * Carry out the Corrected Algorithm, refined as in Lemma 7. */ while (tiling_queue.begin != NULL) { tiling_tet = get_tiling_tet_from_queue(&tiling_queue); for (f = 0; f < 4; f++) if (tiling_tet->neighbor_found[f] == FALSE && face_contains_useful_edge(tiling_tet, f, cutoff_height) == TRUE) { tiling_nbr = make_neighbor_tiling_tet(tiling_tet, f); prepare_sort_key(tiling_nbr, parallelogram_to_square); if (tiling_tet_on_tree(tiling_nbr, tiling_tree_root, meridian, longitude) == FALSE) { add_horoball_if_necessary(tiling_nbr, &horoball_linked_list, cutoff_height); add_tiling_tet_to_tree(tiling_nbr, &tiling_tree_root); add_tiling_tet_to_queue(tiling_nbr, &tiling_queue); } else my_free(tiling_nbr); } } /* * Free the TilingTets. */ free_tiling_tet_tree(tiling_tree_root); /* * Transfer the horoballs from the linked list * to a CuspNbhdHoroballList, and free the linked list. */ theHoroballList = transfer_horoballs(&horoball_linked_list); return theHoroballList; } static void compute_exp_min_d( Triangulation *manifold) { /* * Compute the quantity exp(min_d) needed * in Lemma 4 of get_full_horoball_list(). */ Cusp *cusp; EdgeClass *edge; double exp_d; VertexIndex v[2]; int i; /* * Initialize all exp_min_d's to infinity. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->exp_min_d = DBL_MAX; /* * The closest horoball to a given cusp will lie along an edge * of the canonical cell decomposition, so look at all edges * to find the true exp_min_d's. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * set_cusp_neighborhood_displacement() calls compute_cusp_stoppers(), * which in turn calls compute_intercusp_distances(), so we may use * the edge->intercusp_distance fields for exp_d. */ exp_d = exp(edge->intercusp_distance); v[0] = one_vertex_at_edge[edge->incident_edge_index]; v[1] = other_vertex_at_edge[edge->incident_edge_index]; for (i = 0; i < 2; i++) { cusp = edge->incident_tet->cusp[v[i]]; if (cusp->exp_min_d > exp_d) cusp->exp_min_d = exp_d; } } } static void compute_parallelogram_to_square( Complex meridian, Complex longitude, double parallelogram_to_square[2][2]) { /* * prepare_sort_key() needs a linear transformation which takes * a meridian to (1,0) and a longitude to (0,1), so TilingTets which * are equivalent under the Z + Z action of the group of covering * translations of the cusp be assigned corner coordinates which * differ by integers. The required linear transformation is * the inverse of * * ( meridian.real longitude.real ) * ( meridian.imag longitude.imag ) */ double det; det = meridian.real * longitude.imag - meridian.imag * longitude.real; parallelogram_to_square[0][0] = longitude.imag / det; parallelogram_to_square[0][1] = - longitude.real / det; parallelogram_to_square[1][0] = - meridian.imag / det; parallelogram_to_square[1][1] = meridian.real / det; } static void read_initial_tetrahedra( Triangulation *manifold, Cusp *cusp, TilingQueue *tiling_queue, TilingTet **tiling_tree_root, TilingHoroball **horoball_linked_list, double cutoff_height) { Tetrahedron *tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v, w; Orientation h; TilingTet *tiling_tet; EdgeIndex edge_index; EdgeClass *edge; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; tiling_tet = NEW_STRUCT(TilingTet); tiling_tet->underlying_tet = tet; tiling_tet->orientation = h; for (w = 0; w < 4; w++) if (w != v) { /* * Please see get_quick_edge_horoballs() for * an explanation of the horoball height. */ edge_index = edge_between_vertices[v][w]; edge = tet->edge_class[edge_index]; tiling_tet->corner[w] = complex_real_mult(cusp->displacement_exp, x[h][v][w]); tiling_tet->horoball_height[w] = exp( - edge->intercusp_distance); tiling_tet->neighbor_found[w] = TRUE; /* * To avoid duplications, record the TilingHoroball * iff this is the preferred tet and edge_index * to see it from. */ if (edge->incident_tet == tet && edge->incident_edge_index == edge_index && tiling_tet->horoball_height[w] >= cutoff_height) add_tiling_horoball_to_list(tiling_tet, w, horoball_linked_list); } else { tiling_tet->corner[w] = Infinity; tiling_tet->horoball_height[w] = DBL_MAX; tiling_tet->neighbor_found[w] = FALSE; } /* * Give each tiling_tet a random value of the sort key, * to keep the tree broad. */ tiling_tet->key = 0.5 * ((double) rand() / (double) RAND_MAX); add_tiling_tet_to_queue(tiling_tet, tiling_queue); add_tiling_tet_to_tree(tiling_tet, tiling_tree_root); } } } } static TilingTet *get_tiling_tet_from_queue( TilingQueue *tiling_queue) { TilingTet *tiling_tet; tiling_tet = tiling_queue->begin; if (tiling_queue->begin != NULL) tiling_queue->begin = tiling_queue->begin->next; if (tiling_queue->begin == NULL) tiling_queue->end = NULL; return tiling_tet; } static void add_tiling_tet_to_queue( TilingTet *tiling_tet, TilingQueue *tiling_queue) { tiling_tet->next = NULL; if (tiling_queue->end != NULL) { tiling_queue->end->next = tiling_tet; tiling_queue->end = tiling_tet; } else { tiling_queue->begin = tiling_tet; tiling_queue->end = tiling_tet; } } static void add_tiling_horoball_to_list( TilingTet *tiling_tet, VertexIndex v, TilingHoroball **horoball_linked_list) { TilingHoroball *tiling_horoball; tiling_horoball = NEW_STRUCT(TilingHoroball); tiling_horoball->data.center = tiling_tet->corner[v]; tiling_horoball->data.radius = 0.5 * tiling_tet->horoball_height[v]; tiling_horoball->data.cusp_index = tiling_tet->underlying_tet->cusp[v]->index; tiling_horoball->next = *horoball_linked_list; *horoball_linked_list = tiling_horoball; } static Boolean face_contains_useful_edge( TilingTet *tiling_tet, FaceIndex f, double cutoff_height) { /* * Note: We may assume that the face f has no vertices at the point * at infinity in upper half space. The reason is that the intial * tetrahedra have neighbor_found[] == TRUE for their side faces, and * get_full_horoball_list() calls us only if neighbor_found[f] is FALSE. */ /* * How many vertices incident to face f have horoballs * higher than cutoff_height? */ int num_big_horoballs; VertexIndex v, big_vertex; double min_separation_sq; num_big_horoballs = 0; for (v = 0; v < 4; v++) { if (v == f) continue; if (tiling_tet->horoball_height[v] > cutoff_height) { num_big_horoballs++; big_vertex = v; } } /* * If there are no big horoballs, * the face cannot contain a useful edge. */ if (num_big_horoballs == 0) return FALSE; /* * If there are two or more big horoballs, * the face must contain a useful edge. */ if (num_big_horoballs >= 2) return TRUE; /* * At this point we know that the unique large horoball lies * at the vertex big_vertex. There will be a useful edge iff * the distance from big_vertex to some other vertex of face f * is at least sqrt( height_of_big_vertex * cutoff_height * exp(min_d) ). * For a detailed explanation, please see Lemma 4 and the definition * of "useful edge" in get_full_horoball_list(). */ min_separation_sq = tiling_tet->horoball_height[big_vertex] * cutoff_height * tiling_tet->underlying_tet->cusp[big_vertex]->exp_min_d; for (v = 0; v < 4; v++) { if (v == f || v == big_vertex) continue; if (complex_modulus_squared( complex_minus( tiling_tet->corner[big_vertex], tiling_tet->corner[v] ) ) > min_separation_sq) return TRUE; } return FALSE; } static TilingTet *make_neighbor_tiling_tet( TilingTet *tiling_tet, FaceIndex f) { Tetrahedron *tet, *nbr; Permutation gluing; TilingTet *tiling_nbr; VertexIndex v, w, ff, some_vertex; double exp_d, c_squared; /* * Find the underlying tetrahedra and the gluing between them. */ tet = tiling_tet->underlying_tet; nbr = tet->neighbor[f]; gluing = tet->gluing[f]; /* * Set up the new TilingTet. */ tiling_nbr = NEW_STRUCT(TilingTet); tiling_nbr->underlying_tet = nbr; tiling_nbr->orientation = (parity[gluing] == orientation_preserving) ? tiling_tet->orientation : ! tiling_tet->orientation; for (v = 0; v < 4; v++) { if (v == f) continue; w = EVALUATE(gluing, v); tiling_nbr->corner[w] = tiling_tet->corner[v]; tiling_nbr->horoball_height[w] = tiling_tet->horoball_height[v]; tiling_nbr->neighbor_found[w] = FALSE; } /* * Deal with the remaining corner. */ ff = EVALUATE(gluing, f); /* * Call compute_fourth_corner() to locate the remaining ideal vertex. */ compute_fourth_corner( tiling_nbr->corner, ff, tiling_nbr->orientation, nbr->shape[complete]->cwl[ultimate]); /* * Use the lemma from get_quick_face_horoballs() to compute * the height of the remaining horoball. */ some_vertex = ! ff; exp_d = exp(nbr->edge_class[edge_between_vertices[ff][some_vertex]]->intercusp_distance); c_squared = complex_modulus_squared(complex_minus( tiling_nbr->corner[ff], tiling_nbr->corner[some_vertex])); tiling_nbr->horoball_height[ff] = c_squared / (exp_d * tiling_nbr->horoball_height[some_vertex]); /* * Don't backtrack to the TilingTet we just came from. */ tiling_nbr->neighbor_found[ff] = TRUE; /* * get_full_horoball_list() will decide whether to add tiling_nbr * to the linked list and tree, and whether to add the new horoball * to the horoball list. */ tiling_nbr->next = NULL; tiling_nbr->left = NULL; tiling_nbr->right = NULL; tiling_nbr->key = 0.0; return tiling_nbr; } static void prepare_sort_key( TilingTet *tiling_tet, double parallelogram_to_square[2][2]) { VertexIndex v; Complex transformed_corner[4]; static const double coefficient[4][2] = {{37.0, 25.0}, {43.0, 13.0}, {2.0, 29.0}, {11.0, 7.0}}; /* * Special case: To avoid questions of numerical accuracy, assign * the "illegal" key value of -1 to TilingTets incident to infinity * in upper half space. read_initial_tetrahedra() puts all such * TilingTets on the tree, so none need be added again. */ for (v = 0; v < 4; v++) if (complex_modulus(tiling_tet->corner[v]) > KEY_INFINITY) { tiling_tet->key = -1.0; return; } /* * Recall that we are tiling H^3 / (Z + Z), where the Z + Z is * the group of covering transformations of the cusp. In other words, * two TilingTets are equivalent iff corresponding corners differ * by some combination of meridional and/or longitudinal translations. * The linear transformation parallelogram_to_square maps a meridian * to (1,0) and a longitude to (0,1). We apply it to the TilingTets' * corners, so corresponding corners will differ by integers. */ for (v = 0; v < 4; v++) { transformed_corner[v].real = parallelogram_to_square[0][0] * tiling_tet->corner[v].real + parallelogram_to_square[0][1] * tiling_tet->corner[v].imag; transformed_corner[v].imag = parallelogram_to_square[1][0] * tiling_tet->corner[v].real + parallelogram_to_square[1][1] * tiling_tet->corner[v].imag; } /* * To implement a binary tree, we need a search key which is well * defined under the action of the meridional and longitudinal * translations. In terms of the transformed_corners, it should be * well defined under integer translations. Any integer linear * combination of the real and imaginary parts of the transformed * corners will do. We choose a random looking one, to reduce the * chances that distinct points will be assigned the same value of * the search key. (Of course the algorithm works correctly in any * case -- it's just faster if all the search key values are distinct.) * The linear combination provides a continuous map from the transformed * corners modulo integers to the reals modulo integers, i.e. to the * circle. We then map the circle to the interval [0, 1/2] in a * continuous way. (It's a two-to-one map, but that's unavoidable.) */ /* * Form a random looking integer combination of the corner coordinates. */ tiling_tet->key = 0.0; for (v = 0; v < 4; v++) { tiling_tet->key += coefficient[v][0] * transformed_corner[v].real; tiling_tet->key += coefficient[v][1] * transformed_corner[v].imag; } /* * Take the fractional part. */ tiling_tet->key -= floor(tiling_tet->key); /* * Fold the unit interval [0,1] onto the half interval [0, 1/2] * in ensure continuity. */ if (tiling_tet->key > 0.5) tiling_tet->key = 1.0 - tiling_tet->key; } static Boolean tiling_tet_on_tree( TilingTet *tiling_tet, TilingTet *tiling_tree_root, Complex meridian, Complex longitude) { TilingTet *subtree_stack, *subtree; double delta; Boolean left_flag, right_flag; FaceIndex f; /* * As a special case, TilingTets incident to infinity in upper half * space are already all on the tree. prepare_sort_key() marks * duplicates of such TilingTets with a key value of -1. (Computing * and comparing the usual key value is awkward when some of the * numbers are infinite.) */ if (tiling_tet->key == -1.0) return TRUE; /* * Reliability is our first priority. Speed is second. So if * tiling_tet->key and subtree->key are close, we want to search both * the left and right subtrees. Otherwise we search only one or the * other. We implement the recursion using our own stack, rather than * the system stack, to avoid the possibility of a stack/heap collision * during deep recursions. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = tiling_tree_root; if (tiling_tree_root != NULL) tiling_tree_root->next_subtree = NULL; /* * Process the subtrees on the stack, * adding additional subtrees as needed. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * Compare the key values of the tiling_tet and the subtree's root. */ delta = tiling_tet->key - subtree->key; /* * Which side(s) should we search? */ left_flag = (delta < +KEY_EPSILON); right_flag = (delta > -KEY_EPSILON); /* * Put the subtrees we need to search onto the stack. */ if (left_flag && subtree->left) { subtree->left->next_subtree = subtree_stack; subtree_stack = subtree->left; } if (right_flag && subtree->right) { subtree->right->next_subtree = subtree_stack; subtree_stack = subtree->right; } /* * Check this TilingTet if the key values match. */ if (left_flag && right_flag) /* * Are the TilingTets translations of one another? */ if (same_corners(tiling_tet, subtree, meridian, longitude)) { /* * *subtree is a TilingTet which may or may not have been * processed yet. If not, then when we do process it, we * know there's no need to recreate tiling_tet's "parent". */ for (f = 0; f < 4; f++) subtree->neighbor_found[f] |= tiling_tet->neighbor_found[f]; return TRUE; } } return FALSE; } static Boolean same_corners( TilingTet *tiling_tet1, TilingTet *tiling_tet2, Complex meridian, Complex longitude) { /* * Are tiling_tet1 and tiling_tet2 translations of the same tetrahedron * in H^3/(Z + Z) ? * * Note: This function does *not* take into account the size of the * TilingTets. Two TilingsTets which were very tiny and very close * could cause a false positive, and such TilingTets could be mistakenly * omitted from the tiling. But that's not likely to happen for any * computationally feasible value of cutoff_epsilon. */ Complex offset, fractional_part, diff; double num_meridians, num_longitudes, error; VertexIndex v; /* * Is the offset between a pair of corresponding vertices * an integer combination of meridians and longitudes? */ offset = complex_minus( tiling_tet2->corner[0], tiling_tet1->corner[0]); fractional_part = offset; num_meridians = floor(fractional_part.imag / meridian.imag + 0.5); fractional_part = complex_minus( fractional_part, complex_real_mult(num_meridians, meridian)); num_longitudes = floor(fractional_part.real / longitude.real + 0.5); fractional_part = complex_minus( fractional_part, complex_real_mult(num_longitudes, longitude)); if (complex_modulus(fractional_part) > CORNER_EPSILON) return FALSE; /* * Do all pairs of corresponding vertices differ by the same offset? */ for (v = 1; v < 4; v++) { diff = complex_minus( tiling_tet2->corner[v], tiling_tet1->corner[v]); error = complex_modulus(complex_minus(offset, diff)); if (error > CORNER_EPSILON) return FALSE; } return TRUE; } static void add_tiling_tet_to_tree( TilingTet *tiling_tet, TilingTet **tiling_tree_root) { /* * tiling_tet_on_tree() has already checked that tiling_tet is not * a translation of any TilingTet already on the tree. So here we * just add it in the appropriate spot, based on the key value. */ TilingTet **location; location = tiling_tree_root; while (*location != NULL) { if (tiling_tet->key <= (*location)->key) location = &(*location)->left; else location = &(*location)->right; } *location = tiling_tet; tiling_tet->left = NULL; tiling_tet->right = NULL; } static void add_horoball_if_necessary( TilingTet *tiling_tet, TilingHoroball **horoball_linked_list, double cutoff_height) { VertexIndex v; for (v = 0; v < 4; v++) { /* * Ignore horoballs which are too small. */ if (tiling_tet->horoball_height[v] < cutoff_height) continue; /* * Recall the convention made in get_full_horoball_list() that * each horoball is recorded only by the TilingTet * which contains its north pole. If the north pole lies on the * boundary of two TilingTets, they both record it. */ if (contains_north_pole(tiling_tet, v) == TRUE) add_tiling_horoball_to_list(tiling_tet, v, horoball_linked_list); } } static Boolean contains_north_pole( TilingTet *tiling_tet, VertexIndex v) { /* * Check whether vertex v lies within the triangle defined * by the remaining three vertices. */ int i; VertexIndex w[3]; Complex u[3]; double s[3], det; /* * Label the remaining three vertices w[0], w[1] and w[2] * as you go counterclockwise around the triangle they define * on the boundary of upper half space. * * w[2] * / \ * / \ * / \ * w[0]-------w[1] * * If v lies inside that triangle we'll return TRUE; * otherwise we'll return FALSE. */ w[0] = !v; if (tiling_tet->orientation == right_handed) { w[1] = remaining_face[v][w[0]]; w[2] = remaining_face[w[0]][v]; } else { w[1] = remaining_face[w[0]][v]; w[2] = remaining_face[v][w[0]]; } /* * The vector u[i] runs from v to w[i]. * * w[2] * / | \ * / v \ * / / \ \ * w[0]-------w[1] */ for (i = 0; i < 3; i++) u[i] = complex_minus(tiling_tet->corner[w[i]], tiling_tet->corner[v]); /* * s[i] is the squared length of the triangle's i-th side. */ for (i = 0; i < 3; i++) s[i] = complex_modulus_squared(complex_minus(tiling_tet->corner[w[(i+1)%3]], tiling_tet->corner[w[i]])); /* * If v lies in the triangle's interior, we of course return TRUE. * But if v lies (approximately) on one of the triangle's sides, we * also want to return TRUE, so that in ambiguous cases horoballs are * recorded twice, not zero times. * * We need a scale invariant measure of the signed distance from v * to each side of the triangle, so that we can apply our error epsilon * in a meaningful way. (We don't want to return TRUE for *all* tiny * triangles, simply because they are tiny!) The determinant * * | u[i].real u[i+1].real | * det = | | * | u[i].imag u[i+1].imag | * * gives twice the area of the triangle (v, w[i], w[i+1]). * Therefore det/dist(w[i], w[i+1]) gives the triangle's altitude, * and det/dist(w[i], w[i+1])^2 = det/s[i] gives the ratio of * the altitude to the length of the side. If that ratio is at least * -NORTH_POLE_EPSILON for all sides, we return TRUE. */ for (i = 0; i < 3; i++) { det = u[i].real * u[(i+1)%3].imag - u[i].imag * u[(i+1)%3].real; if (det / s[i] < -NORTH_POLE_EPSILON) return FALSE; } return TRUE; } static void free_tiling_tet_tree( TilingTet *tiling_tree_root) { TilingTet *subtree_stack, *subtree; /* * Implement the recursive freeing algorithm using our own stack * rather than the system stack, to avoid the possibility of a * stack/heap collision. */ /* * Initialize the stack to contain the whole tree. */ subtree_stack = tiling_tree_root; if (tiling_tree_root != NULL) tiling_tree_root->next_subtree = NULL; /* * Process the subtrees on the stack one at a time. */ while (subtree_stack != NULL) { /* * Pull a subtree off the stack. */ subtree = subtree_stack; subtree_stack = subtree_stack->next_subtree; subtree->next_subtree = NULL; /* * If the subtree's root has nonempty left and/or right subtrees, * add them to the stack. */ if (subtree->left != NULL) { subtree->left->next_subtree = subtree_stack; subtree_stack = subtree->left; } if (subtree->right != NULL) { subtree->right->next_subtree = subtree_stack; subtree_stack = subtree->right; } /* * Free the subtree's root node. */ my_free(subtree); } } static CuspNbhdHoroballList *transfer_horoballs( TilingHoroball **horoball_linked_list) { CuspNbhdHoroballList *theHoroballList; TilingHoroball *the_tiling_horoball, *the_dead_horoball; int i; /* * Allocate the wrapper. */ theHoroballList = NEW_STRUCT(CuspNbhdHoroballList); /* * Count the horoballs. */ for ( the_tiling_horoball = *horoball_linked_list, theHoroballList->num_horoballs = 0; the_tiling_horoball != NULL; the_tiling_horoball = the_tiling_horoball->next, theHoroballList->num_horoballs++) ; /* * If we found some horoballs, allocate an array * for the CuspNbhdHoroballs. */ if (theHoroballList->num_horoballs > 0) theHoroballList->horoball = NEW_ARRAY(theHoroballList->num_horoballs, CuspNbhdHoroball); else theHoroballList->horoball = NULL; /* * Copy the data from the linked list to the array. */ for ( the_tiling_horoball = *horoball_linked_list, i = 0; the_tiling_horoball != NULL; the_tiling_horoball = the_tiling_horoball->next, i++) theHoroballList->horoball[i] = the_tiling_horoball->data; /* * Free the linked list. */ while (*horoball_linked_list != NULL) { the_dead_horoball = *horoball_linked_list; *horoball_linked_list = the_dead_horoball->next; my_free(the_dead_horoball); } return theHoroballList; } void free_cusp_neighborhood_horoball_list( CuspNbhdHoroballList *horoball_list) { if (horoball_list != NULL) { if (horoball_list->horoball != NULL) my_free(horoball_list->horoball); my_free(horoball_list); } } static int CDECL compare_horoballs( const void *horoball0, const void *horoball1) { if (((CuspNbhdHoroball *)horoball0)->radius < ((CuspNbhdHoroball *)horoball1)->radius) return -1; else if (((CuspNbhdHoroball *)horoball0)->radius > ((CuspNbhdHoroball *)horoball1)->radius) return +1; else return 0; } static void cull_duplicate_horoballs( Cusp *cusp, CuspNbhdHoroballList *aHoroballList) { int original_num_horoballs, i, j, k; Complex meridian, longitude, delta; double cutoff_radius, mult; Boolean distinct; /* * Note the meridional and longitudinal translations. */ meridian = complex_real_mult(cusp->displacement_exp, cusp->translation[M]); longitude = complex_real_mult(cusp->displacement_exp, cusp->translation[L]); /* * Examine each horoball on the list. * If it's distinct from all previously examined horoballs, keep it. * Otherwise ignore it. * * We could implement this algorithm by copying the horoballs * we want to keep from the array aHoroballList->horoball onto * a new array. But it's simpler just to copy the array onto itself. * (This sounds distressing at first, but if you think it through * you'll realize that it's perfectly safe.) * * The index i keeps track of the horoball we're examining. * The index j keeps track of where we're writing it to. */ original_num_horoballs = aHoroballList->num_horoballs; for (i = 0, j = 0; j < original_num_horoballs; j++) { /* * If the j-th horoball is distinct from all previous ones, copy * it into the i-th position of the array. In practice, of course, * we compare it only to previous horoballs of the same radius. * We may assume that get_cusp_neighborhood_horoballs() has * already sorted the horoballs in order of increasing size. */ /* * Assume the j-th horoball is distinct from horoballs * 0 through i - 1, unless we discover otherwise. */ distinct = TRUE; /* * What is the smallest radius we should consider? */ cutoff_radius = aHoroballList->horoball[j].radius - DUPLICATE_RADIUS_EPSILON; /* * Start with horoball i - 1, and work downwards until either * we reach horoball 0, or the radii drop below the cutoff_radius. */ for (k = i; --k >= 0; ) { /* * If horoball k is too small, there is no need to examine * the remaining ones, which are even smaller. */ if (aHoroballList->horoball[k].radius < cutoff_radius) break; /* * Let delta be the difference between the center of j and * the center of k, modulo the Z + Z action of the group * of covering transformations of the cusp. */ delta = complex_minus(aHoroballList->horoball[j].center, aHoroballList->horoball[k].center); mult = floor(delta.imag / meridian.imag + 0.5); delta = complex_minus(delta, complex_real_mult(mult, meridian)); mult = floor(delta.real / longitude.real + 0.5); delta = complex_minus(delta, complex_real_mult(mult, longitude)); /* * If the distance between the centers of horoballs j and k is * less than the radius, then the horoballs must be equivalent. */ if (complex_modulus(delta) < cutoff_radius) { distinct = FALSE; break; } } if (distinct == TRUE) { aHoroballList->horoball[i] = aHoroballList->horoball[j]; i++; } else aHoroballList->num_horoballs--; } } CuspNbhdSegmentList *get_cusp_neighborhood_Ford_domain( CuspNeighborhoods *cusp_neighborhoods, int cusp_index) { Cusp *cusp; CuspNbhdSegmentList *theSegmentList; CuspNbhdSegment *next_segment; Tetrahedron *tet, *nbr_tet; Complex (*x)[4][4]; Boolean (*in_use)[4]; VertexIndex v, nbr_v, u, nbr_u, w[3]; Orientation h, nbr_h; FaceIndex f, nbr_f; Permutation gluing; int i; Complex corner[3], delta, inward_normal, offset, p; double length, tilt, a[2], b[2], c[2], det; /* * Find the requested Cusp. */ cusp = find_cusp(cusp_neighborhoods->its_triangulation, cusp_index); /* * Allocate the wrapper for the array. */ theSegmentList = NEW_STRUCT(CuspNbhdSegmentList); /* * We don't know ahead of time exactly how many CuspNbhdSegments * we'll need. Torus cusps report each segment once, but Klein * bottle cusps report each segment twice, once for each sheet. * * To get an upper bound on the number of segments, * assume all cusps are Klein bottle cusps. * * n tetrahedra * * 4 vertices/tetrahedron * * 2 triangles/vertex (left_handed and right_handed) * * 3 sides/triangle * / 2 Ford edges/side (no need to draw each edge twice) * * = 12n Ford edges */ theSegmentList->segment = NEW_ARRAY(12*cusp_neighborhoods->its_triangulation->num_tetrahedra, CuspNbhdSegment); /* * Keep a pointer to the first empty CuspNbhdSegment. */ next_segment = theSegmentList->segment; /* * Compute the Ford domain's vertices. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; /* * There are at least two ways to locate the Ford vertex. * * (1) Use Theorem 3.1 of * * M. Sakuma and J. Weeks, The generalized tilt * formula, Geometriae Dedicata 55 (115-123) 1995, * * which states that the Euclidean distance in the * cusp from (the projection of) the Ford vertex * to a side of the enclosing triangle equals the * tilt on that side. (Or better yet, see the * preprint version of the article, which has a lot * more pictures and fuller explanations.) * * (2) Write down the equations for the three planes * which lie halfway between the cusp at infinity * and the cusp at each of the three remaining ideal * vertices. Each such plane appears at a Euclidean * hemisphere. Subtracting the equations for two * such hemispheres gives a linear equation, and * two such linear equations may be solved * simultaneously to locate the Ford vertex. * * Either of the above approaches should work fine. * Here we choose approach (1) because it looks a tiny * bit simpler numerically. */ /* * Label the triangles corners w[0], w[1] and w[2], * going counterclockwise around the triangle. * * w[2] * / \ * / \ * / \ * w[0]-------w[1] */ w[0] = !v; if (h == right_handed) { w[1] = remaining_face[w[0]][v]; w[2] = remaining_face[v][w[0]]; } else { w[1] = remaining_face[v][w[0]]; w[2] = remaining_face[w[0]][v]; } /* * Record the triangle's corners. */ for (i = 0; i < 3; i++) corner[i] = complex_real_mult(cusp->displacement_exp, x[h][v][w[i]]); /* * w[2] * / \ * ------/---*--\------- * / \ * w[0]-------w[1] * * The Ford vertex lies on a line parallel to a side of * the triangle at a distance "tilt" away (by Theorem 3.1 * of Sakuma & Weeks). Find the equations of such lines * (the third is redundant -- it could perhaps be used * to enhance accuracy if desired). */ for (i = 0; i < 2; i++) { /* * Make yourself a sketch as you follow along. */ delta = complex_minus(corner[(i+1)%3], corner[i]); inward_normal.real = +delta.imag; inward_normal.imag = -delta.real; length = complex_modulus(inward_normal); tilt = tet->tilt[w[(i+2)%3]]; offset = complex_real_mult(tilt/length, inward_normal); p = complex_plus(corner[i], offset); /* * The equation of the desired line is * * y - p.imag delta.imag * ---------- = ---------- * x - p.real delta.real * * Cross multiply to get * * delta.imag * x - delta.real * y * = delta.imag * p.real - delta.real * p.imag * * This last equation also has a natural cross product * interpretation: delta X (x,y) = p X (x,y). * * Record the equation as ax + by = c. */ a[i] = delta.imag; b[i] = -delta.real; c[i] = delta.imag * p.real - delta.real * p.imag; } /* * Solve the matrix equation * * ( a[0] b[0] ) (x) = (c[0]) * ( a[1] b[1] ) (y) (c[1]) * => * (x) = _1_ ( b[1] -b[0] ) (c[0]) * (y) det (-a[1] a[0] ) (c[1]) */ det = a[0]*b[1] - a[1]*b[0]; FORD_VERTEX(x,h,v).real = (b[1]*c[0] - b[0]*c[1]) / det; FORD_VERTEX(x,h,v).imag = (a[0]*c[1] - a[1]*c[0]) / det; } } } /* * Record the Ford domain edges. */ for (tet = cusp_neighborhoods->its_triangulation->tet_list_begin.next; tet != &cusp_neighborhoods->its_triangulation->tet_list_end; tet = tet->next) { x = tet->cusp_nbhd_position->x; in_use = tet->cusp_nbhd_position->in_use; for (v = 0; v < 4; v++) { /* * If this isn't the cusp the user wants, ignore it. */ if (tet->cusp[v] != cusp) continue; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { if (in_use[h][v] == FALSE) continue; for (f = 0; f < 4; f++) { if (f == v) continue; gluing = tet->gluing[f]; nbr_tet = tet->neighbor[f]; nbr_f = EVALUATE(gluing, f); /* * We want to report each segment only once, so we * make the (arbitrary) convention that we report * a segment only from the Tetrahedron whose address * in memory is less. In the case of a Tetrahedron * glued to itself, we report it from the lower * FaceIndex. */ if (tet > nbr_tet || (tet == nbr_tet && f > nbr_f)) continue; /* * Don't report Ford edges dual to 2-cells which are * part of the arbitrary subdivision of the canonical * cell decomposition into tetrahdra. (They'd have * length zero anyway, but we want to be consistent * with how we report the triangulation. We rely on * the fact that proto_canonize() has computed the * tilts and left them in place. The sum of the tilts * will never be positive for a subdivision of the * canonical cell decomposition. If it's close to * zero, ignore the Ford edge dual to that face. */ if (tet->tilt[f] + nbr_tet->tilt[nbr_f] > -CONCAVITY_EPSILON) continue; /* * This edge has passed all its tests, so record it. * Keep in mind that the coordinate systems in * neighboring Tetrahedra may differing by translations. */ nbr_v = EVALUATE(gluing, v); nbr_h = (parity[gluing] == orientation_preserving) ? h : !h; next_segment->endpoint[0] = FORD_VERTEX( tet->cusp_nbhd_position->x, h, v); next_segment->endpoint[1] = FORD_VERTEX(nbr_tet->cusp_nbhd_position->x, nbr_h, nbr_v); /* * The segment indices are currently used only * for the triangulation, not the Ford domain. */ next_segment->start_index = -1; next_segment->middle_index = -1; next_segment->end_index = -1; /* * Compensate for the (possibly) translated * coordinate systems. Compare the position of * a vertex u as seen by tet and nbr_tet. */ u = remaining_face[v][f]; nbr_u = EVALUATE(gluing, u); next_segment->endpoint[1] = complex_plus ( next_segment->endpoint[1], complex_real_mult ( cusp->displacement_exp, complex_minus ( tet->cusp_nbhd_position->x[ h][ v][ u], nbr_tet->cusp_nbhd_position->x[nbr_h][nbr_v][nbr_u] ) ) ); /* * Move on. */ next_segment++; } } } } /* * How many segments did we find? * * (ANSI C will subtract the pointers correctly, automatically * dividing by sizeof(CuspNbhdSegment).) */ theSegmentList->num_segments = next_segment - theSegmentList->segment; /* * Did we find more segments than we had allocated space for? * This should be impossible, but it doesn't hurt to check. */ if (theSegmentList->num_segments > 12*cusp_neighborhoods->its_triangulation->num_tetrahedra) uFatalError("get_cusp_neighborhood_Ford_domain", "cusp_neighborhoods"); return theSegmentList; } regina-4.95/engine/snappea/kernel/cusp_shapes.c000644 000765 000024 00000042501 12235724562 021456 0ustar00babstaff000000 000000 /* * cusp_shapes.c * * This file provides the function * * void compute_cusp_shapes(Triangulation *manifold, * FillingStatus which_structure); * * which computes the shape of each unfilled cusp. The shape of an * orientable cusp is defined to be the ratio of the complex numbers * representing the longitudinal and meridional translations * (i.e. longitude/meridian). The shape of a nonorientable cusp is * defined to be the shape of the cusp's orientation double cover; * it is always pure imaginary. (Another reasonable definition for * the shape of a nonorientable cusp would be to divide the shape * of the orientation double cover by two, to correspond more closely * to the geometry of the nonorientable cusp's fundamental domain, * but I decided not to do this.) * * compute_cusp_shapes() computes the cusp shapes using the which_structure * (initial or current) hyperbolic structure, and stores each computed cusp * shape in the cusp_shape[which_structure] field of the Cusp data structure. * * To estimate the computed cusp shape's precision, compute_cusp_shapes() * computes the cusp shape using both the ultimate and penultimate values of * the Tetrahedron shapes. The number of digits to which the answers agree * is stored in the shape_precision[which_structure] field of the Cusp data * structure. * * do_Dehn_filling() calls compute_cusp_shapes() to maintain correct values * in the cusp_shape[current] field of each unfilled cusp whenever a * hyperbolic structure is present. find_complete_hyperbolic_structure() * takes responsibility for copying the original shape of each cusp into * the cusp_shape[initial] field. * * Cusp shapes are computed iff manifold->solution_type[which_structure] * is geometric_solution, nongeometric_solution, flat_solution, or * (tentatively) other_solution. For other solution types (degenerate_solution, * etc.) all cusp shapes are set to zero. * * Technical note: with the usual orientation conventions for the * longitude and meridian, we must view the cusp from the fat part * of the manifold looking out towards infinity in order to have the * imaginary part of the cusp shape be positive. The code * in compute_translation() views the cusp from infinity looking in * towards the fat part of the manifold (as usual), and then * compute_one_cusp_shape() takes the complex conjugate of the shape * at the very end. * * The function shortest_cusp_basis() in shortest_cusp_basis.c converts * the (meridian, longitude) basis to the (shortest, second shortest) * basis. cusp_modulus() uses the latter to compute the cusp modulus * as (second shortest translation)/(shortest translation). * * 96/9/27 Added the which_structure parameter to compute_cusp_shapes() * so it can compute the cusp shapes for either the initial (complete) * or the current (filled) hyperbolic structure. Previously it used only * the current structure. */ #include "kernel.h" static void compute_the_cusp_shapes(Triangulation *manifold, FillingStatus which_structure); static void set_all_shapes_to_zero(Triangulation *manifold, FillingStatus which_structure); static void compute_one_cusp_shape(Triangulation *manifold, Cusp *cusp, FillingStatus which_structure); static void compute_translation(PositionedTet *initial_ptet, PeripheralCurve which_curve, TraceDirection which_direction, Complex translation[2], FillingStatus which_structure); static PositionedTet find_start(Triangulation *manifold, Cusp *cusp); void compute_cusp_shapes( Triangulation *manifold, FillingStatus which_structure) { /* * If we have some reasonable (or semi-reasonable) hyperbolic * structure, then compute the cusp shapes. Otherwise, fill in * all shapes with zeros. */ switch (manifold->solution_type[which_structure]) { case geometric_solution: case nongeometric_solution: case flat_solution: case other_solution: /* we'll give the cusps of other_solution a try */ /* other_solution can be moved below if its */ /* cusp shapes can't be computed meaningfully */ compute_the_cusp_shapes(manifold, which_structure); break; case not_attempted: case degenerate_solution: case no_solution: set_all_shapes_to_zero(manifold, which_structure); break; } } static void compute_the_cusp_shapes( Triangulation *manifold, FillingStatus which_structure) { Cusp *cusp; /* * Call compute_one_cusp_shape() for each unfilled cusp. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if ( which_structure == initial || (which_structure == current && cusp->is_complete)) compute_one_cusp_shape(manifold, cusp, which_structure); else { cusp->cusp_shape[which_structure] = Zero; cusp->shape_precision[which_structure] = 0; } } static void set_all_shapes_to_zero( Triangulation *manifold, FillingStatus which_structure) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[which_structure] = Zero; cusp->shape_precision[which_structure] = 0; } } static void compute_one_cusp_shape( Triangulation *manifold, Cusp *cusp, FillingStatus which_structure) { PositionedTet initial_ptet; TraceDirection direction[2]; /* direction[M/L] */ Complex translation[2][2], /* translation[M/L][ultimate/penultimate] */ shape[2]; /* shape[ultimate/penultimate] */ int i; /* * Compute the longitudinal and meridional translations, and * divide them to get the cusp shape. * * Do parallel computations for the ultimate and penultimate shapes, * to estimate the accuracy of the final answer. */ /* * Find and position a tetrahedron so that the near edge of the top * vertex intersects both the meridian and the longitude. */ initial_ptet = find_start(manifold, cusp); for (i = 0; i < 2; i++) /* which curve */ { /* * Decide whether the meridian and longitude cross the near edge of the * top vertex in a forwards or backwards direction. */ direction[i] = (initial_ptet.tet->curve[i][initial_ptet.orientation] [initial_ptet.bottom_face] [initial_ptet.near_face] > 0) ? trace_forwards: trace_backwards; /* * Compute the translation. */ compute_translation(&initial_ptet, i, direction[i], translation[i], which_structure); } /* * Compute the cusp shape. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ shape[i] = complex_div(translation[L][i], translation[M][i]); /* will handle division by Zero correctly */ /* * Record the cusp shape and its accuracy. */ cusp->cusp_shape[which_structure] = shape[ultimate]; cusp->shape_precision[which_structure] = complex_decimal_places_of_accuracy(shape[ultimate], shape[penultimate]); /* * Adjust for the fact that the meridian and/or the longitude may have * been traced backwards. */ if (direction[M] != direction[L]) { cusp->cusp_shape[which_structure].real = - cusp->cusp_shape[which_structure].real; cusp->cusp_shape[which_structure].imag = - cusp->cusp_shape[which_structure].imag; } /* * As explained at the top of this file, the usual convention for the * cusp shape requires viewing the cusp from the fat part of * the manifold looking out, rather than from the cusp looking in, as * in done in the rest of SnapPea. For this reason, we must take the * complex conjugate of the final cusp shape. */ cusp->cusp_shape[which_structure].imag = - cusp->cusp_shape[which_structure].imag; } static void compute_translation( PositionedTet *initial_ptet, PeripheralCurve which_curve, TraceDirection which_direction, Complex translation[2], /* returns translations based on ultimate */ /* and penultimate shapes */ FillingStatus which_structure) { PositionedTet ptet; int i, initial_strand, strand, *this_vertex, near_strands, left_strands; Complex left_endpoint[2], /* left_endpoint[ultimate/penultimate] */ right_endpoint[2], /* right_endpoint[ultimate/penultimate] */ old_diff, new_diff, rotation; /* * Place the near edge of the top vertex of the initial_ptet in the * complex plane with its left endpoint at zero and its right endpoint at one. * Trace the curve which_curve in the direction which_direction, using the * shapes of the ideal tetrahedra to compute the position of endpoints of * each edge we cross. When we return to our starting point in the manifold, * the position of the left endpoint (or the position of the right endpoint * minus one) will tell us the translation. * * Note that we are working in the orientation double cover of the cusp. * * Here's how we keep track of where we are. At each step, we are always * at the near edge of the top vertex (i.e. the truncated vertex opposite * the bottom face) of the PositionedTet ptet. The curve (i.e. the * meridian or longitude) may cross that edge several times. The variable * "strand" keeps track of which intersection we are at; 0 means we're at * the strand on the far left, 1 means we're at the next strand, etc. */ ptet = *initial_ptet; initial_strand = 0; strand = initial_strand; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { left_endpoint[i] = Zero; right_endpoint[i] = One; } do { /* * Note the curve's intersection numbers with the near side and left side. */ this_vertex = ptet.tet->curve[which_curve][ptet.orientation][ptet.bottom_face]; near_strands = this_vertex[ptet.near_face]; left_strands = this_vertex[ptet.left_face]; /* * If we are tracing the curve backwards, negate the intersection numbers * so the rest of compute_translation() can enjoy the illusion that we * are tracing the curve forwards. */ if (which_direction == trace_backwards) { near_strands = - near_strands; left_strands = - left_strands; } /* * Does the current strand bend to the left or to the right? */ if (strand < FLOW(near_strands, left_strands)) { /* * The current strand bends to the left. */ /* * The left_endpoint remains fixed. * Update the right_endpoint. * * The plan is to compute the vector old_diff which runs * from left_endpoint to right_endpoint, multiply it by the * complex edge parameter to get the vector new_diff which * runs from left_endpoint to the new value of right_endpoint, * and then add new_diff to left_endpoint to get the new * value of right_endpoint itself. * * Note that the complex edge parameters are always expressed * relative to the right_handed Orientation, so if we are * viewing this Tetrahedron relative to the left_handed * Orientation, we must take the conjugate-inverse of the * edge parameter. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_diff = complex_minus(right_endpoint[i], left_endpoint[i]); rotation = ptet.tet->shape[which_structure]->cwl[i][edge3_between_faces[ptet.near_face][ptet.left_face]].rect; if (ptet.orientation == left_handed) { rotation = complex_div(One, rotation); /* invert . . . */ rotation.imag = - rotation.imag; /* . . . and conjugate */ } new_diff = complex_mult(old_diff, rotation); right_endpoint[i] = complex_plus(left_endpoint[i], new_diff); } /* * strand remains unchanged. */ /* * Move the PositionedTet onward, following the curve. */ veer_left(&ptet); } else { /* * The current strand bends to the right. * * Proceed as above, but note that * * (1) We now divide by the complex edge parameter * instead of multiplying by it. * * (2) We must adjust the variable "strand". Some of the strands * from the near edge may be peeling off to the left (in which * case left_strands is negative), or some strands from the left * edge may be joining those from the near edge in passing to * the right edge (in which case left_strands is positive). * Either way, the code "strand += left_strands" is correct. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { old_diff = complex_minus(left_endpoint[i], right_endpoint[i]); rotation = ptet.tet->shape[which_structure]->cwl[i][edge3_between_faces[ptet.near_face][ptet.right_face]].rect; if (ptet.orientation == left_handed) { rotation = complex_div(One, rotation); rotation.imag = - rotation.imag; } new_diff = complex_div(old_diff, rotation); left_endpoint[i] = complex_plus(right_endpoint[i], new_diff); } strand += left_strands; veer_right(&ptet); } } while ( ! same_positioned_tet(&ptet, initial_ptet) || strand != initial_strand); /* * Write the computed translations, and return. */ for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ translation[i] = left_endpoint[i]; } static PositionedTet find_start( Triangulation *manifold, Cusp *cusp) { Tetrahedron *tet; VertexIndex vertex; Orientation orientation; FaceIndex side; PositionedTet ptet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (vertex = 0; vertex < 4; vertex++) { if (tet->cusp[vertex] != cusp) continue; for (orientation = right_handed; orientation <= left_handed; orientation++) for (side = 0; side < 4; side++) if (side != vertex && tet->curve[M][orientation][vertex][side] != 0 && tet->curve[L][orientation][vertex][side] != 0) { /* * Record the current position of the Tetrahedron in * a PositionedTet structure . . . */ ptet.tet = tet; ptet.bottom_face = vertex; ptet.near_face = side; if (orientation == right_handed) { ptet.left_face = remaining_face[ptet.bottom_face][ptet.near_face]; ptet.right_face = remaining_face[ptet.near_face][ptet.bottom_face]; } else { ptet.left_face = remaining_face[ptet.near_face][ptet.bottom_face]; ptet.right_face = remaining_face[ptet.bottom_face][ptet.near_face]; } ptet.orientation = orientation; /* * . . . and return it. */ return ptet; } } /* * The program should never get to this point. */ uFatalError("find_start", "cusp_shapes"); /* * The compiler would like a return value, even though * we never return from the uFatalError() call. * The Metrowerks compiler would, in addition, like * the data to be initialized before use. */ ptet.tet = NULL; ptet.near_face = 0; ptet.left_face = 0; ptet.right_face = 0; ptet.bottom_face = 0; ptet.orientation = unknown_orientation; return ptet; } regina-4.95/engine/snappea/kernel/cusps.c000644 000765 000024 00000026072 12235724562 020303 0ustar00babstaff000000 000000 /* * cusps.c * * This file contains the functions * * void create_cusps(Triangulation *manifold); * void create_fake_cusps(Triangulation *manifold); * void count_cusps(Triangulation *manifold); * Boolean mark_fake_cusps(Triangulation *manifold); * * create_cusps() is used within the kernel to assign Cusp data * structures to Triangulations. It assumes the neighbor and gluing * fields have been set (all other fields are optional). It assigns * cusp indices, but does not install peripheral curves, determine * the CuspTopologies, or count the cusps. You should call * peripheral_curves() to install the peripheral curves and determine * the CuspTopologies, then call count_cusps() to set num_cusps, * num_or_cusps and num_nonor_cusps. * * create_fake_cusps() is used within the kernel to assign Cusp data * structures to the "fake cusps" corresponding to finite vertices. * It assumes fake cusps are indicated by tet->cusp[v] fields of NULL. * The fake cusps are numbered -1, -2, etc. As explained in the * documentation at the top of finite_vertices.c, finite vertices use * only the is_finite, index, prev and next fields of the Cusp data * structure. create_fake_cusps() does not disturb the real cusps or * the non-NULL tet->cusp[v] fields. * * count_cusps() counts the Cusps of each CuspTopology, and sets * manifold->num_cusps, manifold->num_or_cusps and manifold->num_nonor_cusps. * * mark_fake_cusps() distinguishes real cusps from fake cusps * ( = finite vertices) by computing the Euler characteristic. * Sets is_finite to TRUE for fake cusps, and renumbers all cusps so that * real cusps have consecutive nonnegative indices beginning at 0 and * fake cusps have consecutive negative indices beginning at -1. * Returns TRUE if fake cusps are present, FALSE otherwise. */ #include "kernel.h" typedef struct { Tetrahedron *tet; VertexIndex v; } IdealVertex; static void compute_cusp_Euler_characteristics(Triangulation *manifold); void create_cusps( Triangulation *manifold) { int count; Tetrahedron *tet; VertexIndex v; /* * Make sure no Cusps are present, and everything is neat and tidy. */ error_check_for_create_cusps(manifold); /* * The variable "count" will keep track of the next index * to be assigned. The first Cusp we create will have * index 0, the next will have 1, and so on. */ count = 0; /* * We look at each vertex of each Tetrahedron, and whenever we * encounter a vertex with no assigned Cusp, we create a Cusp * for it and recursively assign it to neighboring ideal vertices. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == NULL) create_one_cusp(manifold, tet, FALSE, v, count++); } void error_check_for_create_cusps( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; if (manifold->num_cusps != 0 || manifold->num_or_cusps != 0 || manifold->num_nonor_cusps != 0 || manifold->cusp_list_begin.next != &manifold->cusp_list_end) uFatalError("error_check_for_create_cusps", "cusps"); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] != NULL) uFatalError("error_check_for_create_cusps", "cusps"); } void create_fake_cusps( Triangulation *manifold) { int count; Tetrahedron *tet; VertexIndex v; /* * The variable "count" will keep track of the (negative) index * most recently assigned. The first finite vertex we create * will have index -1, the next will have -2, and so on. */ count = 0; /* * We look at each vertex of each Tetrahedron, and whenever we * encounter an ideal vertex with no assigned Cusp, we create a Cusp * for it and assign it recursively to neighboring ideal vertices. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == NULL) create_one_cusp(manifold, tet, TRUE, v, --count); } void create_one_cusp( Triangulation *manifold, Tetrahedron *tet, Boolean is_finite, VertexIndex v, int cusp_index) { Cusp *cusp; IdealVertex *queue; int queue_first, queue_last; Tetrahedron *tet1, *nbr; VertexIndex v1, nbr_v; FaceIndex f; /* * Create the cusp, add it to the list, and set * the is_finite and index fields. */ cusp = NEW_STRUCT(Cusp); initialize_cusp(cusp); INSERT_BEFORE(cusp, &manifold->cusp_list_end); cusp->is_finite = is_finite; cusp->index = cusp_index; /* * We don't set the topology, is_complete, m, l, holonomy, * cusp_shape or shape_precision fields. * * For "real" cusps the calling routine may * * (1) call peripheral_curves() to set the cusp->topology, * * (2) keep the default values of cusp->is_complete, * cusp->m and cusp->l as set by initialize_cusp(), and * * (3) let the holonomy and cusp_shape be computed automatically * when hyperbolic structure is computed. * * Alternatively, the calling routine may set these fields in other * ways, as it sees fit. * * If we were called by create_fake_cusps(), then the above fields * are all irrelevant and ignored. */ /* * Set the tet->cusp field at all vertices incident to the new cusp. */ /* * Allocate space for a queue of pointers to the IdealVertices. * Each IdealVertex will appear on the queue at most once, so an * array of length 4 * manifold->num_tetrahedra will suffice. */ queue = NEW_ARRAY(4 * manifold->num_tetrahedra, IdealVertex); /* * Set the cusp of the given IdealVertex... */ tet->cusp[v] = cusp; /* * ...and put it on the queue. */ queue_first = 0; queue_last = 0; queue[0].tet = tet; queue[0].v = v; /* * Start processing the queue. */ do { /* * Pull an IdealVertex off the front of the queue. */ tet1 = queue[queue_first].tet; v1 = queue[queue_first].v; queue_first++; /* * Look at the three neighboring IdealVertices. */ for (f = 0; f < 4; f++) { if (f == v1) continue; nbr = tet1->neighbor[f]; nbr_v = EVALUATE(tet1->gluing[f], v1); /* * If the neighbor's cusp hasn't been set... */ if (nbr->cusp[nbr_v] == NULL) { /* * ...set it... */ nbr->cusp[nbr_v] = cusp; /* * ...and add it to the end of the queue. */ ++queue_last; queue[queue_last].tet = nbr; queue[queue_last].v = nbr_v; } } } while (queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); } void count_cusps( Triangulation *manifold) { Cusp *cusp; manifold->num_cusps = 0; manifold->num_or_cusps = 0; manifold->num_nonor_cusps = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { manifold->num_cusps++; switch (cusp->topology) { case torus_cusp: manifold->num_or_cusps++; break; case Klein_cusp: manifold->num_nonor_cusps++; break; default: uFatalError("count_cusps", "cusps"); } } } Boolean mark_fake_cusps( Triangulation *manifold) { int real_cusp_count, fake_cusp_count; Cusp *cusp; compute_cusp_Euler_characteristics(manifold); real_cusp_count = 0; fake_cusp_count = 0; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) switch (cusp->euler_characteristic) { case 0: cusp->is_finite = FALSE; cusp->index = real_cusp_count++; break; case 2: cusp->is_finite = TRUE; cusp->index = --fake_cusp_count; break; default: uFatalError("mark_fake_cusps", "cusps"); } return (fake_cusp_count < 0); } static void compute_cusp_Euler_characteristics( Triangulation *manifold) { Cusp *cusp; EdgeClass *edge; Tetrahedron *tet; VertexIndex v, v0, v1; /* * Initialize all Euler characteristics to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->euler_characteristic = 0; /* * It'll be easier to count the edges twice (once from each side) * so compute twice the Euler characteristic and divide by two * at the end. */ /* * Count the vertices in the triangulation of the boundary, * which come from edges in the manifold itself. */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { tet = edge->incident_tet; v0 = one_vertex_at_edge[edge->incident_edge_index]; v1 = other_vertex_at_edge[edge->incident_edge_index]; tet->cusp[v0]->euler_characteristic += 2; tet->cusp[v1]->euler_characteristic += 2; } /* * Count the edges in the triangulation of the boundary, * which come from faces in the manifold itself. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cusp[v]->euler_characteristic -= 3; /* * Count the faces in the triangulation of the boundary, * which come from tetrahedra in the manifold itself. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->cusp[v]->euler_characteristic += 2; /* * Divide by two (cf. above). */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->euler_characteristic % 2 != 0) uFatalError("compute_cusp_Euler_characteristics", "cusps"); cusp->euler_characteristic /= 2; } } regina-4.95/engine/snappea/kernel/Dehn_coefficients.c000644 000765 000024 00000004634 12235724562 022545 0ustar00babstaff000000 000000 /* * This file contains the functions * * Boolean all_Dehn_coefficients_are_integers(Triangulation *manifold); * Boolean Dehn_coefficients_are_integers(Cusp *cusp); * * Boolean all_Dehn_coefficients_are_relatively_prime_integers(Triangulation *manifold); * Boolean Dehn_coefficients_are_relatively_prime_integers(Cusp *cusp); * * Boolean all_cusps_are_complete(Triangulation *manifold); * Boolean all_cusps_are_filled(Triangulation *manifold); * * which are used within the kernel to test whether Dehn filling coefficients * are (relatively prime) integers. */ #include "kernel.h" Boolean all_Dehn_coefficients_are_integers( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_integers(cusp) == FALSE) return FALSE; return TRUE; } Boolean all_Dehn_coefficients_are_relatively_prime_integers( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (Dehn_coefficients_are_relatively_prime_integers(cusp) == FALSE) return FALSE; return TRUE; } Boolean Dehn_coefficients_are_integers( Cusp *cusp) { return ( cusp->is_complete == TRUE || ( cusp->m == (double)(int)cusp->m && cusp->l == (double)(int)cusp->l ) ); } Boolean Dehn_coefficients_are_relatively_prime_integers( Cusp *cusp) { return ( cusp->is_complete == TRUE || ( cusp->m == (double)(int)cusp->m && cusp->l == (double)(int)cusp->l && gcd((long int)cusp->m, (long int)cusp->l) == 1 ) ); } Boolean all_cusps_are_complete( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == FALSE) return FALSE; return TRUE; } Boolean all_cusps_are_filled( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_complete == TRUE) return FALSE; return TRUE; } regina-4.95/engine/snappea/kernel/dual_one_skeleton_curve.h000644 000765 000024 00000007431 12235724571 024047 0ustar00babstaff000000 000000 /* * dual_one_skeleton_curve.h * * This file defines the data structure used to represent * a simple closed curve in the 1-skeleton of a Triangulation's * dual complex. * * The 0-skeleton of the Triangulation's dual complex consists * of one vertex in the interior of each Tetrahedron. * * The 1-skeleton of the Triangulation's dual complex (the * "dual 1-skeleton") consists of one edge transverse to each * face of each Tetrahedron (more precisely, to each pair * of identified faces in the Triangulation). * * To specify a curve in the dual 1-skeleton, we specify the * 2-cells which it intersects in the original Triangulation. * Specifically, for each Tetrahedron we keep four Boolean * flags which say whether the given dual curve intersects * each of the Tetrahedron's four faces. * * Note that a dual curve is described in terms of a specific * Triangulation. If the Triangulation is modified, the * description of the dual curve becomes meaningless. * * The file SnapPea.h contains the "opaque typedef" * * typedef struct DualOneSkeletonCurve DualOneSkeletonCurve; * * which lets the UI declare and pass pointers to DualOneSkeletonCurves * without actually knowing what they are. This file provides the kernel * with the actual definition. */ #ifndef _dual_one_skeleton_curve_ #define _dual_one_skeleton_curve_ #include "SnapPea.h" /* * An array of four Booleans represents the intersection * of a dual curve with the four faces of a given * Tetrahedron. The i-th Boolean is TRUE iff the dual * curve passes through the Tetrahedron's i-th face. */ typedef Boolean DualOneSkeletonCurvePiece[4]; /* * An array of DualOneSkeletonCurvePieces represents * a complete curve in the dual 1-skeleton. The i-th * element in the array describes the curve's intersection * with the Triangulation's i-th Tetrahedron. * * Note that this definition relies on the Tetrahedra * being consecutively indexed, e.g. by the kernel function * number_the_tetrahedra(). The numbering is that of * the indices rather than the position in the doubly- * linked list (normally the two will be the same, but * we don't assume this). */ struct DualOneSkeletonCurve { /* * tet_intersection will contain the address of * an array of n DualOneSkeletonCurvePieces, where n * is the number of Tetrahedra in the Triangulation. * tet_intersection[i][j] will be TRUE iff the dual * curve passes through face j of Tetrahedron i. */ DualOneSkeletonCurvePiece *tet_intersection; /* * Is this curve orientation_reversing or orientation_preserving? */ MatrixParity parity; /* * The length field will contain the complex length of * the geodesic in the homotopy class of the dual curve. * length[complete] and length[filled] give the length * relative to the complete and filled hyperbolic * structures, respectively. */ Complex length[2]; /* * The size field will contain the number * of segments in the curve. */ int size; /* * We'll be working with large numbers of DualOneSkeletonCurves, * many of which will be homotopic to each other, so for efficiency * in sorting them out we'll keep them on a binary tree, keyed by * complex length (i.e. keyed by length.real, with length.imag * considered in case of ties). The next_subtree field is used * locally for tree-handling operations to avoid doing recursions * on the system stack; the latter run the risk of stack/heap * collisions. */ DualOneSkeletonCurve *left_child, *right_child, *next_subtree; }; #endif regina-4.95/engine/snappea/kernel/edge_classes.c000644 000765 000024 00000016705 12235724562 021571 0ustar00babstaff000000 000000 /* * edge_classes.c * * This file provides the functions * * void create_edge_classes(Triangulation *manifold); * void replace_edge_classes(Triangulation *manifold); * void orient_edge_classes(Triangulation *manifold); * * which are used within the kernel. * * create_edge_classes() adds EdgeClasses to a partially * constructed manifold which does not yet have them. * It assumes the tet->neighbor and tet->gluing fields * contain correct values. * * replace_edge_classes() removes all EdgeClasses from a manifold * and adds fresh ones. replace_edge_classes() is typically called * by functions which would rather replace invalid EdgeClasses * at the end of an algorithm rather than try to maintain them * as they go along. * * orient_edge_classes() orients a neighborhood of each EdgeClass. * Relative to this orientation, each of the incident tetrahedra * will be seen as right_ or left_handed. * * The edges of a tetrahedron are indexed according to the following table: * * lies lies * edge between between * faces vertices * 0 0,1 2,3 * 1 0,2 1,3 * 2 0,3 1,2 * 3 1,2 0,3 * 4 1,3 0,2 * 5 2,3 0,1 * * orient_edge_classes() sets the field tet->edge_orientation[e] to be the * orientation of Tetrahedron tet as seen by EdgeClass tet->edge_class[e]. * In an oriented manifold, all edge_orientations will be right_handed. * * orient_edge_classes() should be called as soon as a Triangulation * is created, and functions which modify a Triangulation should * maintain the edge_orientation[] fields. * * As explained in the documentation at the top of orient.c, orient() * and orient_edge_classes() may be called in either order, but both * should be called. */ #include "kernel.h" static void initialize_tet_edge_classes(Triangulation *manifold); static void create_one_edge_class(Triangulation *manifold, Tetrahedron *tet, EdgeIndex e); void create_edge_classes( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; /* * First set tet->edge_class[] to NULL for all * edges of all Tetrahedra. */ initialize_tet_edge_classes(manifold); /* * Go down the list of Tetrahedra, and whenever * an edge is found with no EdgeClass, create one * for it. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) if (tet->edge_class[e] == NULL) create_one_edge_class(manifold, tet, e); } static void initialize_tet_edge_classes( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) tet->edge_class[e] = NULL; } static void create_one_edge_class( Triangulation *manifold, Tetrahedron *tet, EdgeIndex e) { EdgeClass *new_edge_class; FaceIndex front, back, temp; Permutation gluing; Tetrahedron *tet0; EdgeIndex e0; /* * Create the new EdgeClass and add it to the list. */ new_edge_class = NEW_STRUCT(EdgeClass); initialize_edge_class(new_edge_class); INSERT_BEFORE(new_edge_class, &manifold->edge_list_end); /* * Initialize the fields of the EdgeClass. */ new_edge_class->order = 0; new_edge_class->incident_tet = tet; new_edge_class->incident_edge_index = e; /* * Walk around the edge class, setting the tet->edge_class * field for each edge we encounter. */ front = one_face_at_edge[e]; back = other_face_at_edge[e]; tet0 = tet; e0 = e; do { /* * Set the edge_class pointer . . . */ tet->edge_class[e] = new_edge_class; /* * . . . increment new_edge_class->order . . . */ new_edge_class->order++; /* * . . . and move on to the next edge. */ gluing = tet->gluing[front]; tet = tet->neighbor[front]; temp = front; front = EVALUATE(gluing, back); back = EVALUATE(gluing, temp); e = edge_between_faces[front][back]; } while (tet != tet0 || e != e0); } void replace_edge_classes( Triangulation *manifold) { EdgeClass *dead_edge_class; /* * Remove all existing EdgeClasses . . . */ while (manifold->edge_list_begin.next != &manifold->edge_list_end) { dead_edge_class = manifold->edge_list_begin.next; REMOVE_NODE(dead_edge_class); my_free(dead_edge_class); } /* * . . . and add fresh ones. */ create_edge_classes(manifold); } void orient_edge_classes( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; FaceIndex front, back, temp; Orientation relative_orientation; Permutation gluing; int count; /* * For each EdgeClass in the Triangulation . . . */ for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * Find an incident edge. */ tet = edge->incident_tet; e = edge->incident_edge_index; front = one_face_at_edge[e]; back = other_face_at_edge[e]; /* * View the incident Tetrahedron relative to the * right_handed Orientation. */ relative_orientation = right_handed; /* * We'll walk around the EdgeClass, setting * the Orientation of each incident edge. */ for (count = edge->order; --count >= 0; ) { /* * Set the edge_orientation of the present edge . . . */ tet->edge_orientation[e] = relative_orientation; /* * . . . and move on to the next edge. */ gluing = tet->gluing[front]; tet = tet->neighbor[front]; temp = front; front = EVALUATE(gluing, back); back = EVALUATE(gluing, temp); e = edge_between_faces[front][back]; /* * Change the relative_orientation iff the new * new Tetrahedron is oriented differently than * the old one. */ if (parity[gluing] == orientation_reversing) relative_orientation = ! relative_orientation; } /* * When we return to the initial Tetrahedron, * the relative_orientation should again be right_handed. * If it isn't, the triangulation defines an orbifold * with a cone-on-a-projective-plane at the center of * the current edge class. This error should be rare -- * in fact is should be possible only for hand-coded * Triangulations. */ if (relative_orientation != right_handed) { uAcknowledge("The triangulation has a cone-on-a-projective-plane singularity at the midpoint of an edge class."); uFatalError("orient_edge_classes", "edge_classes"); } } } regina-4.95/engine/snappea/kernel/filling.c000644 000765 000024 00000021577 12235724562 020577 0ustar00babstaff000000 000000 /* * filling.c * * This file contains the functions * * Triangulation *fill_cusps(Triangulation *manifold, * Boolean fill_cusp[], * char *new_name, * Boolean fill_all_cusps); * * Triangulation *fill_reasonable_cusps(Triangulation *manifold); * * Boolean cusp_is_fillable(Cusp *cusp); * Boolean is_closed_manifold(Triangulation *manifold); * * which the kernel provides to the UI. * * fill_cusps() permanently fills k of the cusps of an n-cusp manifold. * It returns an ideal Triangulation of the resulting (n - k)-cusp manifold. * * 99/06/04 Previous versions of fill_cusps() insisted that at least * one cusp be left unfilled. The current version allows all cusps * to be filled, in which case it produces a finite triangulation. * Warning: Most SnapPea functions insist on an ideal triangulation -- * the finite triagulation is provided mainly for writing to disk. * * Arguments: * * manifold is the original manifold. The Dehn filling * coefficients cusp->m and cusp->l specify how * each cusp is to be filled. * * fill_cusp says which cusps are to be filled. The cusp * of index i will be filled iff fill_cusp[i] is TRUE. * If fill_all_cusps (see below) is TRUE, then * fill_cusp is ignored and may be NULL. * * new_name provides the name for the new Triangulation. * * fill_all_cusps says whether to fill all the cusps, producing * a triangulation with finite vertices only. * Usually fill_all_cusps is FALSE. * * The UI should decide how to present fill_cusps() to the user. * Should all currently Dehn filled cusps be filled at once? * Should the user be presented with a list of check boxes to * specify which cusps to fill? Should cusps be filled one at a time? * My hope is that fill_cusps() is sufficiently general to support * whatever approach the UI developer prefers. * * Having said that, let me now mention fill_reasonable_cusps(), which * makes a decision about which cusps to fill, and then makes a call * to fill_cusp(). fill_reasonable_cusps() will fill all cusps which * have relatively prime Dehn filling coefficients, unless this would * leave no unfilled cusps, in which case it leaves cusp 0 unfilled. * It copies the name from the manifold being filled. * * cusp_is_fillable() determines whether an individual cusp is fillable. * * The original manifold is always left unaltered. * * The files subdivide.c, close_cusps.c, and remove_finite_vertices.c * document the algorithm in detail. */ #include "kernel.h" static Boolean check_fill_cusp_array(Triangulation *manifold, Boolean fill_cusp[]); static Boolean cusp_is_fillable_x(Cusp *cusp); static Boolean no_cusps_to_be_filled(int num_cusps, Boolean fill_cusp[]); Triangulation *fill_cusps( Triangulation *manifold, Boolean fill_cusp[], char *new_name, Boolean fill_all_cusps) { Triangulation *new_triangulation; Boolean at_least_one_cusp_is_left; Boolean *all_true; int i; /* * 95/10/1 JRW * The following algorithm works correctly even if no cusps are * to be filled, but we can speed it up a bit by simply copying * the Triangulation. */ if (fill_all_cusps == FALSE && no_cusps_to_be_filled(manifold->num_cusps, fill_cusp) == TRUE) { copy_triangulation(manifold, &new_triangulation); return new_triangulation; } /* * First let's do a little error checking on the fill_cusp[] array. */ if (fill_all_cusps == FALSE) { /* * Check that Dehn filling coefficients are relatively prime integers, * and also that at least one cusp is left unfilled. */ at_least_one_cusp_is_left = check_fill_cusp_array(manifold, fill_cusp); if (at_least_one_cusp_is_left == FALSE) uFatalError("fill_cusps", "filling"); } else { /* * Check that Dehn filling coefficients are relatively prime integers. */ all_true = NEW_ARRAY(manifold->num_cusps, Boolean); for (i = 0; i < manifold->num_cusps; i++) all_true[i] = TRUE; (void) check_fill_cusp_array(manifold, all_true); /* * Do NOT free all_true just yet. */ } /* * Subdivide the triangulation, introducing finite vertices. * Note that the original triangulation is left unharmed. */ new_triangulation = subdivide(manifold, new_name); /* * Close the Cusps specified in the fill_cusp[] array. */ close_cusps(new_triangulation, fill_all_cusps ? all_true : fill_cusp); /* * We're done with the all_true array. */ if (fill_all_cusps == TRUE) my_free(all_true); /* * Retriangulate with no finite vertices. */ if (fill_all_cusps == FALSE) remove_finite_vertices(new_triangulation); /* includes basic_simplification() */ else basic_simplification(new_triangulation); /* * If the old manifold had a hyperbolic structure, * try to find one for the new_triangulation as well. */ if (fill_all_cusps == FALSE && manifold->solution_type[complete] != not_attempted) { find_complete_hyperbolic_structure(new_triangulation); do_Dehn_filling(new_triangulation); /* * If the old manifold had a known Chern-Simons invariant, * pass it to the new_triangulation. */ if (manifold->CS_value_is_known == TRUE) { new_triangulation->CS_value_is_known = manifold->CS_value_is_known; new_triangulation->CS_value[ultimate] = manifold->CS_value[ultimate]; new_triangulation->CS_value[penultimate] = manifold->CS_value[penultimate]; /* * The solution_type may or may not be good enough to compute * the fudge factor, but we'll let compute_CS_fudge_from_value() * worry about that. */ compute_CS_fudge_from_value(new_triangulation); } } return new_triangulation; } Triangulation *fill_reasonable_cusps( Triangulation *manifold) { Boolean *fill_cusp; Cusp *cusp; int i; Boolean all_cusps_are_fillable; Triangulation *new_triangulation; /* * Allocate the fill_cusp[] array. */ fill_cusp = NEW_ARRAY(manifold->num_cusps, Boolean); /* * See which cusps are fillable. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) fill_cusp[cusp->index] = cusp_is_fillable_x(cusp); /* * If all the cusps are fillable, leave cusp 0 unfilled. */ all_cusps_are_fillable = TRUE; for (i = 0; i < manifold->num_cusps; i++) if (fill_cusp[i] == FALSE) all_cusps_are_fillable = FALSE; if (all_cusps_are_fillable == TRUE) fill_cusp[0] = FALSE; /* * Call fill_cusps(). */ new_triangulation = fill_cusps(manifold, fill_cusp, manifold->name, FALSE); /* * Free the fill_cusp[] array. */ my_free(fill_cusp); /* * Done. */ return new_triangulation; } static Boolean check_fill_cusp_array( Triangulation *manifold, Boolean fill_cusp[]) { Boolean at_least_one_cusp_is_left; Cusp *cusp; at_least_one_cusp_is_left = FALSE; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (fill_cusp[cusp->index]) { if (cusp_is_fillable_x(cusp) == FALSE) uFatalError("check_fill_cusp_array", "filling"); } else at_least_one_cusp_is_left = TRUE; return at_least_one_cusp_is_left; } Boolean cusp_is_fillable( /* For external use */ Triangulation *manifold, int cusp_index) { return cusp_is_fillable_x(find_cusp(manifold, cusp_index)); } static Boolean cusp_is_fillable_x( /* For internal use */ Cusp *cusp) { return( cusp->is_complete == FALSE && Dehn_coefficients_are_relatively_prime_integers(cusp) == TRUE); } static Boolean no_cusps_to_be_filled( int num_cusps, Boolean fill_cusp[]) { int i; for (i = 0; i < num_cusps; i++) if (fill_cusp[i] == TRUE) return FALSE; return TRUE; } Boolean is_closed_manifold( Triangulation *manifold) { return (all_cusps_are_filled(manifold) && all_Dehn_coefficients_are_relatively_prime_integers(manifold)); } regina-4.95/engine/snappea/kernel/find_cusp.c000644 000765 000024 00000002130 12235724562 021105 0ustar00babstaff000000 000000 /* * find_cusp.c * * This function provides the following utility for use within the kernel. * * Cusp *find_cusp(Triangulation *manifold, int cusp_index); * * The UI refers to cusps by their indices, because it doesn't know about * the Cusp data structure. When a kernel function receives a cusp_index, * it may call the function find_cusp() to convert the index to an actual * pointer to the corresponding Cusp data structure. If find_cusp() cannot * find a Cusp with the given cusp_index, it calls uFatalError(). */ #include "kernel.h" Cusp *find_cusp( Triangulation *manifold, int cusp_index) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->index == cusp_index) return cusp; /* * No Cusp with the given index was found. */ uFatalError("find_cusp", "find_cusp"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return NULL; } regina-4.95/engine/snappea/kernel/finite_vertices.c000644 000765 000024 00000050043 12235724562 022323 0ustar00babstaff000000 000000 /* * finite_vertices.c * * Certain routines make temporary use of finite (as opposed to ideal) * vertices; e.g. they are used in triangulating a link complement, * in retriangulating a partially filled multicusp manifold, and in * splitting along a normal surface. SnapPea represents a finite vertex * as a cusp whose is_finite field is set to TRUE. Except for the * is_finite, index, prev and next fields, all other fields of the Cusp * data structure are ignored. The indices are negative integers. * Functions which do not use finite vertices may safely ignore the * is_finite field, and assume no finite vertices are present. * Finite vertices are never counted in the num_cusps, num_or_cusps, * or num_nonor_cusps fields of a Triangulation. * * This file contains the function * * void remove_finite_vertices(Triangulation *manifold); * * which is used within the kernel to retriangulate the manifold * to remove the finite vertices. If manifold has no real cusps, * a single real cusp is created (it may be either a torus or * Klein bottle cusp). * * Technical note: It's OK to pass a manifold with some or all * of the peripheral curves missing, the cusp topologies unknown, * and num_or_cusps and/or num_nonor_cusps not set. (For example, * normal_surface_splitting.c does exactly that.) Of course any * peripheral curves which are known will be preserved. */ /* * The Algorithm * * Overview * * A finite vertex is represented by a "cusp" whose cross section is * topologically a sphere (in contrast to real cusps, whose cross sections * are always tori or Klein bottles). Throughout this documentation, * please imagine all tetrahedra to have truncated vertices, so that * a finite vertex appears as a spherical boundary component, and a * real cusp appears as a torus or Klein bottle boundary component. * To "remove a finite vertex", we'll modify the triangulation so as * to drill out a tube connecting a spherical boundary component to * a nearby torus or Klein bottle boundary component. We'll repeat * the procedure until no spherical boundary components remain. * * Details * * The manifold is assumed to be connected, so as long as spherical * boundary components remain we may find an edge E (in the triangulation * of the manifold) connecting a spherical boundary component to a * torus or Klein bottle boundary component. Let T be any triangle * (in the triangulation of the manifold) incident to E. * * If we cut along the triangle T, insert a triangular pillow, and reglue, * the topology of the manifold doesn't change. But instead of inserting * an ordinary triangular pillow, we'll insert a triangular pillow from * which a "tunnel" has been drilled out, so as to connect one of its * truncated vertices to another. (The tunnel is unknotted, but it * really doesn't matter.) * * A triangular-pillow-with-tunnel may be constructed from only two * ideal tetrahedra, according to the following gluings (the notation * is as in the file TriangulationFileFormat). * * tetrahedron 0 * 1 free free 1 * 0213 ---- ---- 1023 * * tetrahedron 1 * 0 1 1 0 * 0213 0213 0213 1023 * * Note: In my drawing the two tetrahedra are glued together along * face 0 to form a hexahedron. Vertex 0 of tetrahedron 0 appears * at the "north pole", vertex 0 of tetrahedron 1 appears at the * "south pole", and the remain three vertices appear along the "equator". * * Closed Manifolds * * If the triangulation has no real cusps, then an arbitrary spherical * boundary component is selected, and all other spherical boundary * components are connected to it as above. This yields a manifold * with a single spherical boundary component. An additional * triangular-pillow-with-tunnel is added to convert the spherical * boundary component to a torus or Klein bottle boundary. * * Acknowledgements * * My path to this algorithm was indirect. I thank Sergei Matveev * for suggesting I think about manifolds in terms of spines, and * I thank Carlo Petronio for helpful discussions which led me in * the direction of this construction. */ #include "kernel.h" static void initialize_matching_cusps(Triangulation *manifold, Cusp **special_fake_cusp); static void merge_cusps(Triangulation *manifold); static void drill_tube(Triangulation *manifold, Tetrahedron *tet, EdgeIndex e, Boolean creating_new_cusp); static void set_real_cusps(Triangulation *manifold, Cusp *special_fake_cusp); void remove_finite_vertices( Triangulation *manifold) { Cusp *special_fake_cusp; /* * Simplify the triangulation before we begin. * basic_simplification() should work OK even with finite vertices. */ basic_simplification(manifold); /* * The matching_cusp field of each fake cusp records the real cusp * to which the fake cusp has been connected. It's initialized to * NULL to indicate that the fake cusp has not yet been connected * to anything. For real cusps, it's convenient to have the * matching_cusp field always point to the cusp itself. If the * manifold has no real cusps, then choose a "special fake cusp" * to which all other fake cusps will be connected, and set its * matching_cusp field to point to itself. */ initialize_matching_cusps(manifold, &special_fake_cusp); /* * Keep merging fake cusps with real cusps until no further progress * is possible. */ merge_cusps(manifold); /* * Ideal vertices which used to be incident to fake cusps are * now all incident to real cusps. Update the tet->cusp[] fields, * and free the fake cusps (except for the special_fake_cusp, if any). */ set_real_cusps(manifold, special_fake_cusp); /* * If the manifold is closed (no real cusps) it will, at this point, * have one spherical "cusp", namely the special_fake_cusp. * Drill out a tube connecting the special_fake_cusp to itself, * to convert it from a sphere to a torus or Klein bottle. */ if (special_fake_cusp != NULL) { /* * Simplify the triangulation before drilling, * to increases the chances that the drilled out tube will * follow a topologically nontrivial loop through the manifold. * (This is essential if we want to express the resulting * closed manifold as a hyperbolic Dehn filling.) */ basic_simplification(manifold); drill_tube(manifold, manifold->tet_list_begin.next, 0, TRUE); } /* * The triangulation is now correct, but it is horribly inefficient. * Simplify it. * * Note: basic_simplification() calls tidy_peripheral_curves() * and compute_CS_fudge_from_value(). */ basic_simplification(manifold); } static void initialize_matching_cusps( Triangulation *manifold, Cusp **special_fake_cusp) { Boolean has_real_cusp; Cusp *cusp; has_real_cusp = FALSE; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite) cusp->matching_cusp = NULL; else { cusp->matching_cusp = cusp; has_real_cusp = TRUE; } if (has_real_cusp == FALSE) { *special_fake_cusp = manifold->cusp_list_begin.next; (*special_fake_cusp)->matching_cusp = *special_fake_cusp; } else *special_fake_cusp = NULL; } static void merge_cusps( Triangulation *manifold) { Boolean progress; EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; Cusp *one_cusp, *other_cusp; do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { tet = edge->incident_tet; e = edge->incident_edge_index; one_cusp = tet->cusp[one_vertex_at_edge[e]]; other_cusp = tet->cusp[other_vertex_at_edge[e]]; if (one_cusp->matching_cusp == NULL && other_cusp->matching_cusp != NULL) { one_cusp->matching_cusp = other_cusp->matching_cusp; drill_tube(manifold, tet, e, FALSE); progress = TRUE; } if (other_cusp->matching_cusp == NULL && one_cusp->matching_cusp != NULL) { other_cusp->matching_cusp = one_cusp->matching_cusp; drill_tube(manifold, tet, e, FALSE); progress = TRUE; } } } while (progress == TRUE); } static void drill_tube( Triangulation *manifold, Tetrahedron *tet, EdgeIndex e, Boolean creating_new_cusp) { /* * Insert a triangular-pillow-with-tunnel (as described at the top * of this file) so as to connect the boundary component at one * end of the given edge to the boundary component at the other end. * The orientation on the triangular pillow will match the * orientation on tet, so that the orientation on the manifold * (if there is one) will be preserved. Edge orientations are also * respected. */ VertexIndex v0, v1, v2, vv0, vv1, vv2; FaceIndex f, ff; Tetrahedron *nbr_tet, *new_tet0, *new_tet1; Permutation gluing; EdgeClass *edge0, *edge1, *edge2, *new_edge; Orientation edge_orientation0, edge_orientation1, edge_orientation2; PeripheralCurve c; Orientation h; int num_strands, intersection_number[2], the_gcd; Cusp *unique_cusp; MatrixInt22 basis_change[1]; /* * Relative to the orientation of tet, the vertices v0, v1 and v2 * are arranged in counterclockwise order around the face f. */ v0 = one_vertex_at_edge[e]; v1 = other_vertex_at_edge[e]; v2 = remaining_face[v1][v0]; f = remaining_face[v0][v1]; /* * Note the matching face and its vertices. */ nbr_tet = tet->neighbor[f]; gluing = tet->gluing[f]; ff = EVALUATE(gluing, f); vv0 = EVALUATE(gluing, v0); vv1 = EVALUATE(gluing, v1); vv2 = EVALUATE(gluing, v2); /* * Note the incident EdgeClasses (which may or may not be distinct). */ edge0 = tet->edge_class[e]; edge1 = tet->edge_class[edge_between_vertices[v1][v2]]; edge2 = tet->edge_class[edge_between_vertices[v2][v0]]; /* * Construct the triangular-pillow-with-tunnel, as described * at the top of this file. */ new_tet0 = NEW_STRUCT(Tetrahedron); new_tet1 = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet0); initialize_tetrahedron(new_tet1); INSERT_BEFORE(new_tet0, &manifold->tet_list_end); INSERT_BEFORE(new_tet1, &manifold->tet_list_end); manifold->num_tetrahedra += 2; new_edge = NEW_STRUCT(EdgeClass); initialize_edge_class(new_edge); INSERT_BEFORE(new_edge, &manifold->edge_list_end); new_tet0->neighbor[0] = new_tet1; new_tet0->neighbor[1] = NULL; /* assigned below */ new_tet0->neighbor[2] = NULL; /* assigned below */ new_tet0->neighbor[3] = new_tet1; new_tet1->neighbor[0] = new_tet0; new_tet1->neighbor[1] = new_tet1; new_tet1->neighbor[2] = new_tet1; new_tet1->neighbor[3] = new_tet0; new_tet0->gluing[0] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet0->gluing[1] = 0x00; /* assigned below */ new_tet0->gluing[2] = 0x00; /* assigned below */ new_tet0->gluing[3] = CREATE_PERMUTATION(0, 1, 1, 0, 2, 2, 3, 3); new_tet1->gluing[0] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[1] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[2] = CREATE_PERMUTATION(0, 0, 1, 2, 2, 1, 3, 3); new_tet1->gluing[3] = CREATE_PERMUTATION(0, 1, 1, 0, 2, 2, 3, 3); new_tet0->edge_class[0] = edge1; new_tet0->edge_class[1] = edge1; new_tet0->edge_class[2] = edge0; new_tet0->edge_class[3] = edge2; new_tet0->edge_class[4] = edge0; new_tet0->edge_class[5] = edge0; new_tet1->edge_class[0] = edge1; new_tet1->edge_class[1] = edge1; new_tet1->edge_class[2] = edge0; new_tet1->edge_class[3] = new_edge; new_tet1->edge_class[4] = edge0; new_tet1->edge_class[5] = edge0; edge0->order += 6; edge1->order += 4; edge2->order += 1; new_edge->order = 1; new_edge->incident_tet = new_tet1; new_edge->incident_edge_index = 3; edge_orientation0 = tet->edge_orientation[e]; edge_orientation1 = tet->edge_orientation[edge_between_vertices[v1][v2]]; edge_orientation2 = tet->edge_orientation[edge_between_vertices[v2][v0]]; new_tet0->edge_orientation[0] = edge_orientation1; new_tet0->edge_orientation[1] = edge_orientation1; new_tet0->edge_orientation[2] = edge_orientation0; new_tet0->edge_orientation[3] = edge_orientation2; new_tet0->edge_orientation[4] = edge_orientation0; new_tet0->edge_orientation[5] = edge_orientation0; new_tet1->edge_orientation[0] = edge_orientation1; new_tet1->edge_orientation[1] = edge_orientation1; new_tet1->edge_orientation[2] = edge_orientation0; new_tet1->edge_orientation[3] = right_handed; new_tet1->edge_orientation[4] = edge_orientation0; new_tet1->edge_orientation[5] = edge_orientation0; new_tet0->cusp[0] = tet->cusp[v0]; new_tet0->cusp[1] = tet->cusp[v0]; new_tet0->cusp[2] = tet->cusp[v0]; new_tet0->cusp[3] = tet->cusp[v2]; new_tet1->cusp[0] = tet->cusp[v0]; new_tet1->cusp[1] = tet->cusp[v0]; new_tet1->cusp[2] = tet->cusp[v0]; new_tet1->cusp[3] = tet->cusp[v2]; /* * Install the triangular-pillow-with-tunnel. */ tet->neighbor[f] = new_tet0; tet->gluing[f] = CREATE_PERMUTATION(f, 2, v0, 0, v1, 1, v2, 3); new_tet0->neighbor[2] = tet; new_tet0->gluing[2] = inverse_permutation[tet->gluing[f]]; nbr_tet->neighbor[ff] = new_tet0; nbr_tet->gluing[ff] = CREATE_PERMUTATION(ff, 1, vv0, 0, vv1, 2, vv2, 3); new_tet0->neighbor[1] = nbr_tet; new_tet0->gluing[1] = inverse_permutation[nbr_tet->gluing[ff]]; /* * Typically creating_new_cusp is FALSE, meaning that we are * connecting a spherical boundary component to a torus or * Klein bottle boundary component, and we simply extend the * existing peripheral curves across the new tetrahedra. * * In the exceptional case that creating_new_cusp is TRUE, * meaning that the manifold has no real cusps and we are * connecting the "special fake cusp" to itself, we must * install a meridian and longitude, and set up the Dehn filling. */ if (creating_new_cusp == FALSE) { /* * Extend the peripheral curves across the boundary of the * triangular-pillow-with-tunnel. * * Note: The orientations of new_tet0 and new_tet1 match that * of tet, so the right_handed and left_handed sheets match up * in the obvious way. */ for (c = 0; c < 2; c++) /* c = M, L */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { num_strands = tet->curve[c][h][v0][f]; new_tet0->curve[c][h][0][2] = -num_strands; new_tet0->curve[c][h][0][1] = +num_strands; num_strands = tet->curve[c][h][v1][f]; new_tet0->curve[c][h][1][2] = -num_strands; new_tet0->curve[c][h][1][0] = +num_strands; new_tet1->curve[c][h][2][0] = -num_strands; new_tet1->curve[c][h][2][1] = +num_strands; new_tet1->curve[c][h][1][2] = -num_strands; new_tet1->curve[c][h][1][0] = +num_strands; new_tet0->curve[c][h][2][0] = -num_strands; new_tet0->curve[c][h][2][1] = +num_strands; num_strands = tet->curve[c][h][v2][f]; new_tet0->curve[c][h][3][2] = -num_strands; new_tet0->curve[c][h][3][1] = +num_strands; } } else /* creating_new_cusp == TRUE */ { /* * We have just installed a tube connecting the (unique) * spherical "cusp" to itself, to convert it to a torus or * Klein bottle. */ unique_cusp = tet->cusp[v0]->matching_cusp; unique_cusp->is_complete = TRUE; /* to be filled below */ unique_cusp->index = 0; unique_cusp->is_finite = FALSE; manifold->num_cusps = 1; /* * Install an arbitrary meridian and longitude. */ peripheral_curves(manifold); count_cusps(manifold); /* * Two sides of the (truncated) vertex 0 of new_tet0 * (namely the sides incident to faces 1 and 2 of new_tet0) * define the Dehn filling curve by which we can recover * the closed manifold. Count how many times the newly * installed meridian and longitude cross this Dehn filling curve. * To avoid messy questions about which sheet of the cusp's * double cover we're on, use two (parallel) copies of the * Dehn filling curve, one on each sheet of the cover. * Ultimately we're looking for a linear combination of the * meridian and longitude whose intersection number with * the Dehn filling curve is zero, so it won't matter if * we're off by a factor of two. */ for (c = 0; c < 2; c++) /* c = M, L */ { intersection_number[c] = 0; for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ { intersection_number[c] += new_tet0->curve[c][h][0][1]; intersection_number[c] += new_tet0->curve[c][h][0][2]; } } /* * Use the intersection numbers to deduce * the desired Dehn filling coefficients. */ the_gcd = gcd(intersection_number[M], intersection_number[L]); unique_cusp->is_complete = FALSE; unique_cusp->m = -intersection_number[L] / the_gcd; unique_cusp->l = +intersection_number[M] / the_gcd; /* * Switch to a basis in which the Dehn filling curve is a meridian. */ unique_cusp->cusp_shape[initial] = Zero; /* force current_curve_basis() to ignore the cusp shape */ current_curve_basis(manifold, 0, basis_change[0]); if (change_peripheral_curves(manifold, basis_change) != func_OK) uFatalError("drill_tube", "finite_vertices"); } } static void set_real_cusps( Triangulation *manifold, Cusp *special_fake_cusp) { Tetrahedron *tet; int i; Cusp *cusp, *dead_cusp; /* * Update the cusp fields. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 4; i++) tet->cusp[i] = tet->cusp[i]->matching_cusp; /* * Free the Cusp structures which had been used for finite vertices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == TRUE && cusp != special_fake_cusp) { dead_cusp = cusp; cusp = cusp->prev; /* so the loop will proceed correctly */ REMOVE_NODE(dead_cusp); my_free(dead_cusp); } } regina-4.95/engine/snappea/kernel/gcd.c000644 000765 000024 00000010504 12235724562 017674 0ustar00babstaff000000 000000 /* * gcd.c * * This file contains the kernel functions * * long int gcd(long int a, long int b); * long int euclidean_algorithm(long int m, long int n, long int *a, long int *b); * long int Zq_inverse(long int p, long int q); * * gcd() returns the greatest common divisor of two long integers, * at least one of which is nonzero. * * euclidean_algorithm() returns the greatest common divisor of two long * integers m and n, and also finds long integers a and b such that * am + bn = gcd(m,n). The integers m and n may be negative, but cannot * both be zero. * * Zq_inverse() returns the inverse of p in the ring Z/q. Assumes p and q * are relatively prime integers satisfying 0 < p < q. * * 97/3/31 gcd() modified to accept negative integers. */ #include "kernel.h" long int gcd( long int a, long int b) { a = ABS(a); b = ABS(b); if (a == 0) { if (b == 0) uFatalError("gcd", "gcd"); else return b; } while (TRUE) { if ((b = b%a) == 0) return a; if ((a = a%b) == 0) return b; } } long int euclidean_algorithm( long int m, long int n, long int *a, long int *b) { /* * Given two long integers m and n, use the Euclidean algorithm to * find integers a and b such that a*m + b*n = g.c.d.(m,n). * * Recall the Euclidean algorithm is to keep subtracting the * smaller of {m, n} from the larger until one of them reaches * zero. At that point the other will equal the g.c.d. * * As the algorithm progresses, we'll use the coefficients * mm, mn, nm, and nn to express the current values of m and n * in terms of the original values: * * current m = mm*(original m) + mn*(original n) * current n = nm*(original m) + nn*(original n) */ long int mm, mn, nm, nn, quotient; /* * Begin with a quick error check. */ if (m == 0 && n == 0) uFatalError("euclidean_algorithm", "gcd"); /* * Initially we have * * current m = 1 (original m) + 0 (original n) * current n = 0 (original m) + 1 (original n) */ mm = nn = 1; mn = nm = 0; /* * It will be convenient to work with nonnegative m and n. */ if (m < 0) { m = - m; mm = -1; } if (n < 0) { n = - n; nn = -1; } while (TRUE) { /* * If m is zero, then n is the g.c.d. and we're done. */ if (m == 0) { *a = nm; *b = nn; return n; } /* * Let n = n % m, and adjust the coefficients nm and nn accordingly. */ quotient = n / m; nm -= quotient * mm; nn -= quotient * mn; n -= quotient * m; /* * If n is zero, then m is the g.c.d. and we're done. */ if (n == 0) { *a = mm; *b = mn; return m; } /* * Let m = m % n, and adjust the coefficients mm and mn accordingly. */ quotient = m / n; mm -= quotient * nm; mn -= quotient * nn; m -= quotient * n; } /* * We never reach this point. */ } long int Zq_inverse( long int p, long int q) { long int a, b, g; /* * Make sure 0 < p < q. */ if (p <= 0 || p >= q) uFatalError("Zq_inverse", "gcd"); /* * Find a and b such that ap + bq = gcd(p,q) = 1. */ g = euclidean_algorithm(p, q, &a, &b); /* * Check that p and q are relatively prime. */ if (g != 1) uFatalError("Zq_inverse", "gcd"); /* * ap + bq = 1 * => ap = 1 (mod q) * => The inverse of p in Z/q is a. * * Normalize a to the range (0, q). * * [My guess is that a must always fall in the range -q < a < q, * in which case the follwing code would simplify to * * if (a < 0) * a += q; * * but I haven't worked out a proof.] */ while (a < 0) a += q; while (a > q) a -= q; return a; } regina-4.95/engine/snappea/kernel/gluing_equations.c000644 000765 000024 00000044737 12235724562 022533 0ustar00babstaff000000 000000 /* * gluing_equations.c * * This file provides the function * * void compute_gluing_equations(Triangulation *manifold); * * which the function do_Dehn_filling() in hyperbolic_structure.c calls * to compute the edge and cusp equations and their derivatives. * It computes complex gluing equations for oriented manifolds, and * real gluing equations for nonoriented manifolds. It assumes that * space for the equations has already been assigned to the cusps and * edges, and that a coordinate system has been chosen for each * tetrahedron (cf. allocate_equations() and choose_coordinate_system() * in hyperbolic_structures.c). */ #include "kernel.h" static void initialize_gluing_equations(Triangulation *manifold); static void compute_derivative(Triangulation *manifold); static void compute_rhs(Triangulation *manifold); void compute_gluing_equations( Triangulation *manifold) { compute_holonomies(manifold); compute_edge_angle_sums(manifold); initialize_gluing_equations(manifold); compute_derivative(manifold); compute_rhs(manifold); } static void initialize_gluing_equations( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; int i; /* * Initialize edge equations. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) for (i = 0; i < manifold->num_tetrahedra; i++) if (manifold->orientability == oriented_manifold) edge->complex_edge_equation[i] = Zero; else { edge->real_edge_equation_re[2*i] = 0.0; edge->real_edge_equation_re[2*i + 1] = 0.0; edge->real_edge_equation_im[2*i] = 0.0; edge->real_edge_equation_im[2*i + 1] = 0.0; } /* * Initialize cusp equations. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < manifold->num_tetrahedra; i++) if (manifold->orientability == oriented_manifold) cusp->complex_cusp_equation[i] = Zero; else { cusp->real_cusp_equation_re[2*i] = 0.0; cusp->real_cusp_equation_re[2*i + 1] = 0.0; cusp->real_cusp_equation_im[2*i] = 0.0; cusp->real_cusp_equation_im[2*i + 1] = 0.0; } } /* * The coordinate system for the parameter space of each * tetrahedron has already been chosen as explained in * the comment preceding the function choose_coordinate_system() * in the file hyperbolic_structure.c. The derivatives computed * in that comment may be expressed as * * d(log z0) d(log z0) d(log z0) -1 * --------- = 1 --------- = -z2 --------- = -- * d(log z0) d(log z1) d(log z2) z1 * * d(log z1) -1 d(log z1) d(log z1) * --------- = -- --------- = 1 --------- = -z0 * d(log z0) z2 d(log z1) d(log z2) * * d(log z2) d(log z2) -1 d(log z2) * --------- = -z1 --------- = -- --------- = 1 * d(log z0) d(log z1) z0 d(log z2) * * compute_derivative() uses these forms to compute the entries * of the derivative matrix. If the manifold is oriented, these complex * numbers are added directly to the appropriate entries in the matrix. * If the manifold is unoriented, each complex number (a + bi) is * converted to a 2 x 2 real matrix * * a -b * * b a * * This matrix mimics the action of the complex derivative. That is, * (a + bi)(dx + i dy) = (a dx - b dy) + i(b dx + a dy), and * * | a dx - b dy | | a -b | | dx | * | | = | | | | * | b dx + a dy | | b a | | dy | * * If the Tetrahedron is seen as right_handed by its EdgeClass, then * the above matrix is added directly to the appropriate 2 x 2 block * in the derivative matrix. If the Tetrahedron is seen as left_handed * by it EdgeClass, then we must account for the fact that the EdgeClass * sees the conjugate-inverse of the edge parameter. That is, the * imaginary part of the log (i.e. the angle) will be the same, but * the real part of the log (i.e. the compression/expansion factor) * will be negated. We therefore use the following matrix instead. * * -a b * * b a * */ static void compute_derivative( Triangulation *manifold) { Tetrahedron *tet; Complex z[3], d[3], *eqn_coef = NULL, dz[2]; EdgeIndex e; VertexIndex v; FaceIndex initial_side, terminal_side; int init[2][2], term[2][2]; double m, l, a, b, *eqn_coef_00 = NULL, *eqn_coef_01 = NULL, *eqn_coef_10 = NULL, *eqn_coef_11 = NULL; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Note the three edge parameters. */ for (i = 0; i < 3; i++) z[i] = tet->shape[filled]->cwl[ultimate][i].rect; /* * Set the derivatives of log(z0), log(z1) and log(z2) * with respect to the given coordinate system, as * indicated by the above table. */ switch (tet->coordinate_system) { case 0: d[0] = One; d[1] = complex_div(MinusOne, z[2]); d[2] = complex_minus(Zero, z[1]); break; case 1: d[0] = complex_minus(Zero, z[2]); d[1] = One; d[2] = complex_div(MinusOne, z[0]); break; case 2: d[0] = complex_div(MinusOne, z[1]); d[1] = complex_minus(Zero, z[0]); d[2] = One; break; } /* * Record this tetrahedron's contribution to the edge equations. */ for (e = 0; e < 6; e++) /* Look at each of the six edges. */ { /* * Find the matrix entry(ies) corresponding to the * derivative of the edge equation with respect to this * tetrahedron. If the manifold is oriented it will be * a single entry in the complex matrix. If the manifold * is unoriented it will be a 2 x 2 block in the real matrix. */ if (manifold->orientability == oriented_manifold) eqn_coef = &tet->edge_class[e]->complex_edge_equation[tet->index]; else { eqn_coef_00 = &tet->edge_class[e]->real_edge_equation_re[2 * tet->index]; eqn_coef_01 = &tet->edge_class[e]->real_edge_equation_re[2 * tet->index + 1]; eqn_coef_10 = &tet->edge_class[e]->real_edge_equation_im[2 * tet->index]; eqn_coef_11 = &tet->edge_class[e]->real_edge_equation_im[2 * tet->index + 1]; } /* * Add in the derivative of the log of the edge parameter * with respect to the chosen coordinate system. Please * see the comment preceding this function for details. */ if (manifold->orientability == oriented_manifold) *eqn_coef = complex_plus(*eqn_coef, d[edge3[e]]); else { /* * These are the same a and b as in the comment * preceding this function. */ a = d[edge3[e]].real; b = d[edge3[e]].imag; if (tet->edge_orientation[e] == right_handed) { *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 += b; *eqn_coef_11 += a; } else { *eqn_coef_00 -= a; *eqn_coef_01 += b; *eqn_coef_10 += b; *eqn_coef_11 += a; } } } /* * Record this tetrahedron's contribution to the cusp equations. */ for (v = 0; v < 4; v++) /* Look at each ideal vertex. */ { /* * Note the Dehn filling coefficients on this cusp. * If the cusp is complete, use m = 1.0 and l = 0.0. */ if (tet->cusp[v]->is_complete) { m = 1.0; l = 0.0; } else { m = tet->cusp[v]->m; l = tet->cusp[v]->l; } /* * Find the matrix entry(ies) corresponding to the * derivative of the cusp equation with respect to this * tetrahedron. If the manifold is oriented it will be * a single entry in the complex matrix. If the manifold * is unoriented it will be a 2 x 2 block in the real matrix. */ if (manifold->orientability == oriented_manifold) eqn_coef = &tet->cusp[v]->complex_cusp_equation[tet->index]; else { eqn_coef_00 = &tet->cusp[v]->real_cusp_equation_re[2 * tet->index]; eqn_coef_01 = &tet->cusp[v]->real_cusp_equation_re[2 * tet->index + 1]; eqn_coef_10 = &tet->cusp[v]->real_cusp_equation_im[2 * tet->index]; eqn_coef_11 = &tet->cusp[v]->real_cusp_equation_im[2 * tet->index + 1]; } /* * Each ideal vertex contains two triangular cross sections, * one right_handed and the other left_handed. We want to * compute the contribution of each angle of each triangle * to the holonomy. We begin by considering the right_handed * triangle, looking at each of its three angles. A directed * angle is specified by its initial and terminal sides. * We find the number of strands of the Dehn filling curve * passing from the initial side to the terminal side; * it is m * (number of strands of meridian) * + l * (number of strands of longitude), where (m,l) are * the Dehn filling coefficients (in practice, m and l need * not be integers, but it's simpler to imagine them to be * integers as you try to understand the following code). * The number of strands of the Dehn filling curves passing * from the initial to the terminal side is multiplied by * the derivative of the log of the complex angle, to yield * the contribution to the derivative matrix. If the manifold * is oriented, that complex number is added directly to * the relevant matrix entry. If the manifold is unoriented, * we convert the complex number to a 2 x 2 real matrix * (cf. the comments preceding this function) and add it to * the appropriate 2 x 2 block of the real derivative matrix. * The 2 x 2 matrix for the left_handed triangle is modified * to account for the fact that although the real part of the * derivative of the log (i.e. the compression/expansion * factor) is the same, the imaginary part (i.e. the rotation) * is negated. [Note that in computing the edge equations * the real part was negated, while for the cusp equations * the imaginary part is negated. I will leave an explanation * of the difference as an exercise for the reader.] * * Note that we cannot possibly handle curves on the * left_handed sheet of the orientation double cover of * a cusp of an oriented manifold. The reason is that the * log of the holonomy of the Dehn filling curve is not * a complex analytic function of the shape of the tetrahedron * (it's the complex conjugate of such a function). I.e. * it doesn't have a derivative in the complex sense. This * is why we make the convention that all peripheral curves * in oriented manifolds lie on the right_handed sheet of * the double cover. */ for (initial_side = 0; initial_side < 4; initial_side++) { if (initial_side == v) continue; terminal_side = remaining_face[v][initial_side]; /* * Note the intersection numbers of the meridian and * longitude with the initial and terminal sides. */ for (i = 0; i < 2; i++) { /* which curve */ for (j = 0; j < 2; j++) { /* which sheet */ init[i][j] = tet->curve[i][j][v][initial_side]; term[i][j] = tet->curve[i][j][v][terminal_side]; } } /* * For each triangle (right_handed and left_handed), * multiply the number of strands of the Dehn filling * curve running from initial_side to terminal_side * by the derivative of the log of the edge parameter. */ for (i = 0; i < 2; i++) /* which sheet */ dz[i] = complex_real_mult( m * FLOW(init[M][i],term[M][i]) + l * FLOW(init[L][i],term[L][i]), d[ edge3_between_faces[initial_side][terminal_side] ] ); /* * If the manifold is oriented, the Dehn filling curve * must lie of the right_handed sheet of the orientation * double cover (cf. above). Add its contributation to * the cusp equation. */ if (manifold->orientability == oriented_manifold) *eqn_coef = complex_plus(*eqn_coef, dz[right_handed]); /* "else" follows below */ /* * If the manifold is unoriented, treat the right_ and * left_handed sheets separately. Add in the contribution * of the right_handed sheet normally. For the left_handed * sheet, we must account for the fact that even though * the modulus of the derivative (i.e. the expansion/ * contraction factor) is correct, its argument (i.e. the * angle of rotation) is the negative of what it should be. */ else { a = dz[right_handed].real; b = dz[right_handed].imag; *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 += b; *eqn_coef_11 += a; a = dz[left_handed].real; b = dz[left_handed].imag; *eqn_coef_00 += a; *eqn_coef_01 -= b; *eqn_coef_10 -= b; *eqn_coef_11 -= a; } } } } } /* * compute_complex_rhs() assumes that compute_holonomies() and * compute_edge_angle_sums() have already been called. */ static void compute_rhs( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; Complex desired_holonomy, current_holonomy, rhs; /* * The right hand side of each equation will be the desired value * of the edge angle sum or the holonomy (depending on whether it's * an edge equation or a cusp equation) minus the current value. * Thus, when the equations are solved and the Shapes of the * Tetrahedra are updated, the edge angle sums and the holonomies * will take on their desired values, to the accuracy of the * linear approximation. */ /* * Set the right hand side (rhs) of each edge equation. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { /* * The desired value of the sum of the logs of the complex * edge parameters is 2 pi i. The current value is * edge->edge_angle_sum. */ rhs = complex_minus(edge->target_angle_sum, edge->edge_angle_sum); if (manifold->orientability == oriented_manifold) edge->complex_edge_equation[manifold->num_tetrahedra] = rhs; else { edge->real_edge_equation_re[2 * manifold->num_tetrahedra] = rhs.real; edge->real_edge_equation_im[2 * manifold->num_tetrahedra] = rhs.imag; } } /* * Set the right hand side (rhs) of each cusp equation. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { /* * For complete cusps we want the log of the holonomy of the * meridian to be zero. For Dehn filled cusps we want the * log of the holonomy of the Dehn filling curve to be 2 pi i. */ if (cusp->is_complete) { desired_holonomy = Zero; current_holonomy = cusp->holonomy[ultimate][M]; } else { desired_holonomy = cusp->target_holonomy; current_holonomy = complex_plus( complex_real_mult(cusp->m, cusp->holonomy[ultimate][M]), complex_real_mult(cusp->l, cusp->holonomy[ultimate][L]) ); } rhs = complex_minus(desired_holonomy, current_holonomy); if (manifold->orientability == oriented_manifold) cusp->complex_cusp_equation[manifold->num_tetrahedra] = rhs; else { cusp->real_cusp_equation_re[2 * manifold->num_tetrahedra] = rhs.real; cusp->real_cusp_equation_im[2 * manifold->num_tetrahedra] = rhs.imag; } } } regina-4.95/engine/snappea/kernel/holonomy.c000644 000765 000024 00000021206 12235724562 021004 0ustar00babstaff000000 000000 /* * holonomy.c * * This file provides the functions * * void compute_holonomies(Triangulation *manifold); * void compute_edge_angle_sums(Triangulation *manifold); * * which the functions compute_complex_equations() and * compute_real_equations() in gluing_equations.c call. They store * their results directly into Triangulation *manifold, so whenever * a hyperbolic structure has been found, you may also assume the * holonomies are present. (The edge angle sums will be present too, * but since they'll all be 2 pi i they won't be very interesting.) * * The most accurate holonomies are stored in holonomy[ultimate][M] * and holonomy[ultimate][L]. The fields holonomy[penultimate][M] * and holonomy[penultimate][L] store the holonomies from the * penultimate iteration of Newton's method, for use in estimating * the numerical error. * * * The holonomy of a Klein bottle cusp deserves special discussion. * * (1) The holonomy of the meridian may have a rotational part, but * never has a contraction part. I.e. it's log is pure imaginary. * Proof: the meridian is freely homotopic to it's own inverse. * * (2) The holonomy of the longitude doesn't even make sense. As * you tilt the angle of the curve at the basepoint, the angle * of the next lift down the line rotates in the opposite * direction. The rotational part of the holonomy is not an * isotopy invariant for an orientation-reversing curve. The * contraction is an isotopy invariant, but it's cleaner and * simpler to work with the preimage of the longitude in the * Klein bottle's orientation double cover, which is a torus. * The curve on the double cover must have zero rotational part, * because the orientation-reversing covering transformation * takes the curve to itself. * * These observations imply that a Dehn filling on a Klein bottle cusp * must be of the form (m,0). * * * 96/9/27 I split compute_holonomies() into separate functions * copy_holonomies_ultimate_to_penultimate() and compute_the_holonomies(), * so that other functions (e.g. cover.c) can call compute_the_holonomies() * to compute the penultimate holonomies directly. */ #include "kernel.h" static void copy_holonomies_ultimate_to_penultimate(Triangulation *manifold); void compute_holonomies( Triangulation *manifold) { copy_holonomies_ultimate_to_penultimate(manifold); compute_the_holonomies(manifold, ultimate); } static void copy_holonomies_ultimate_to_penultimate( Triangulation *manifold) { /* * Copy holonomy[ultimate][] into holonomy[penultimate][]. */ Cusp *cusp; int i; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ cusp->holonomy[penultimate][i] = cusp->holonomy[ultimate][i]; } void compute_the_holonomies( Triangulation *manifold, Ultimateness which_iteration) { Cusp *cusp; Tetrahedron *tet; Complex log_z[2]; VertexIndex v; FaceIndex initial_side, terminal_side; int init[2][2], term[2][2]; int i, j; /* * Initialize holonomy[which_iteration][] to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ cusp->holonomy[which_iteration][i] = Zero; /* * Now add the contribution of each tetrahedron. * * The cross section of the ideal vertex v is the union * of two triangles, one with the right_handed orientation * and one with the left_handed orientation (please see the * documentationvat the top of peripheral_curves.c for details). * * This loop is similar to the loop in compute_complex_derivative() * in gluing_equations.c. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) for (initial_side = 0; initial_side < 4; initial_side++) { if (initial_side == v) continue; terminal_side = remaining_face[v][initial_side]; /* * Note the log of the complex edge parameter, * for use with the right_handed triangle. */ log_z[right_handed] = tet->shape[filled]->cwl[which_iteration][ edge3_between_faces[initial_side][terminal_side] ].log; /* * The conjugate of log_z[right_handed] will apply to * the left_handed triangle. */ log_z[left_handed] = complex_conjugate(log_z[right_handed]); /* * Note the intersection numbers of the meridian and * longitude with the initial and terminal sides. */ for (i = 0; i < 2; i++) { /* which curve */ for (j = 0; j < 2; j++) { /* which sheet */ init[i][j] = tet->curve[i][j][v][initial_side]; term[i][j] = tet->curve[i][j][v][terminal_side]; } } /* * holonomy[which_iteration][i] += * FLOW(init[i][right_handed], term[i][right_handed]) * log_z[right_handed] * + FLOW(init[i][left_handed ], term[i][left_handed ]) * log_z[left_handed ]; */ for (i = 0; i < 2; i++) /* which curve */ #if 0 The stupid Symantec C compiler is choking on the following expression. So I am breaking it into two parts to make its work easier. original version: tet->cusp[v]->holonomy[which_iteration][i] = complex_plus( tet->cusp[v]->holonomy[which_iteration][i], complex_plus( complex_real_mult( FLOW(init[i][right_handed], term[i][right_handed]), log_z[right_handed] ), complex_real_mult( FLOW(init[i][left_handed], term[i][left_handed]), log_z[left_handed] ) ) ); modified version: #else { Complex temp; temp = complex_plus( tet->cusp[v]->holonomy[which_iteration][i], complex_plus( complex_real_mult( FLOW(init[i][right_handed], term[i][right_handed]), log_z[right_handed] ), complex_real_mult( FLOW(init[i][left_handed], term[i][left_handed]), log_z[left_handed] ) ) ); tet->cusp[v]->holonomy[which_iteration][i] = temp; } #endif } } void compute_edge_angle_sums( Triangulation *manifold) { EdgeClass *edge; Tetrahedron *tet; EdgeIndex e; /* * Initialize all edge_angle_sums to zero. */ for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) edge->edge_angle_sum = Zero; /* * Add in the contribution of each edge of each tetrahedron. * * If the EdgeClass sees the Tetrahedron as right_handed, * add in the log of the edge parameter directly. If it sees * it as left_handed, add in the log of the conjugate-inverse * (i.e. add the imaginary part of the log as usual, but subtract * the real part). */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) { tet->edge_class[e]->edge_angle_sum.imag += tet->shape[filled]->cwl[ultimate][edge3[e]].log.imag; if (tet->edge_orientation[e] == right_handed) tet->edge_class[e]->edge_angle_sum.real += tet->shape[filled]->cwl[ultimate][edge3[e]].log.real; else tet->edge_class[e]->edge_angle_sum.real -= tet->shape[filled]->cwl[ultimate][edge3[e]].log.real; } } regina-4.95/engine/snappea/kernel/homology.h000644 000765 000024 00000003715 12235724571 021007 0ustar00babstaff000000 000000 /* * homology.h * * This file defines a structure used in homology.c to hold a * relation matrix for a finitely generated abelian group. * MC 01/26/08 * */ /* * This file (homology.h) is intended solely for inclusion in SnapPea.h. */ /* * To minimize the possibility of overflows, we use long integers instead * of regular integers to do the matrix computations. Take ENTRY_MIN * to be -LONG_MAX instead of LONG_MIN, to minimize the (unlikely) * possibility that negation causes an overflow (i.e. -LONG_MIN * = -(0x80000000) = (0x80000000) = LONG_MIN). */ /* Note added by MC: in fact, overflow is extremely common in the * classical algorithm for computing Smith normal form. See the * paper "Asymptotically Fast Triangularization of Matrices over * Rings" by James L. Hafner and Kevin S. McCurley. They give an * example of a 20x20 matrix with entries between 0 and 10 such that * the classical integer triangularization algorithm produces a * matrix entry larger than 10^5011. A typical matrix of that type * leads to entries of size 10^500. The algorithm discussed in the * paper avoids the overflow by doing computations modulo a multiple * of the exponent of the torsion subgroup. This algorithm is * implemented in PARI, so need not be implemented in SnapPea. * However, to use an external library to compute the Smith form * we need access to the relation matrix. This is the reason that * I moved this declaration was here, from homology.c */ typedef long int MatrixEntry; #define ENTRY_MAX LONG_MAX #define ENTRY_MIN (-LONG_MAX) /* * The number of meaningful rows and columns in a RelationMatrix are * are given by num_rows and num_columns, respectively. max_rows * records the original number of rows allocated, so we know how many * rows to free at the end. */ typedef struct { int num_rows, num_columns, max_rows; MatrixEntry **relations; } RelationMatrix; regina-4.95/engine/snappea/kernel/hyperbolic_structure.c000644 000765 000024 00000140575 12235724562 023433 0ustar00babstaff000000 000000 /* * hyperbolic_structure.c * * This file contains the following functions which the kernel * provides for the UI: * * SolutionType find_complete_hyperbolic_structure(Triangulation *manifold); * SolutionType do_Dehn_filling(Triangulation *manifold); * SolutionType remove_Dehn_fillings(Triangulation *manifold); * * Their use is described in SnapPea.h. * * This file also provides the following functions for use * within the kernel * * void remove_hyperbolic_structures(Triangulation *manifold); * void polish_hyperbolic_structures(Triangulation *manifold); * * remove_hyperbolic_structures() frees the TetShapes (if any) pointed to * by each tet->shape[] and sets manifold->solution_type[complete] and * manifold->solution_type[filled] to not_attempted. * * polish_hyperbolic_structures() attempts to increase the accuracy of * both the complete and the Dehn filled hyperbolic structures already * present in *manifold. It's designed to be called following * retriangulation operations which diminish the accuracy of the TetShapes. * * * SnapPea uses Newton's method to solve the gluing equations (see * Thurston's notes for an explanation of the gluing equations). The * linear equations generated at each iteration of Newton's method are * solved using Gaussian elimination with partial pivoting. * * The number of gluing equations is (number of tetrahedra + number of cusps), * while the number of variables is just the number of tetrahedra. * Unlike previous versions of SnapPea, this version does not select out * a linearly independent subset of the gluing equations, but rather * solves the whole system. My hope is that in cases where the gluing * equations are degenerate (or nearly so) the pivoting will tend to * select a more robust subset of the equations. In any case, once the * equations have been solved, there will be some rows of zeros at the * bottom, one for each cusp. The constants on the right hand side of * these zero rows provide a measure of how accurately the equations were * solved. For example, in the case of the Whitehead link complement, * which has four tetrahedra and two cusps, the matrix will reduce to * * 1 0 0 0 a <- solution * 0 1 0 0 b * 0 0 1 0 c * 0 0 0 1 d * 0 0 0 0 e <- should be zero * 0 0 0 0 f * * where the constants a - d represent the solution to the equations, * and the constants e - f (which will be close to zero) measure the * solution's accuracy. * * The coordinate systems used to parameterize the shapes of the * tetrahedra are chosen dynamically so as to avoid singularities. * The comment preceding the function choose_coordinate_system() * (see below) explains the underlying mathematics. * * The gluing equations are written in terms of complex variables, * namely the edge parameters of the tetrahedra. If the manifold is * oriented, they are analytic functions of these variables, and * Newton's method is applied directly. If the manifold is unoriented, * they are almost analytic, but not quite: they are analytic functions * of the variables and their complex conjugates. (Reversing the * orientation of a tetrahedron replaces its edge parameter with the * inverse of its complex conjugate.) Newton's method is applied by * writing the n x m system of complex equations as a 2n x 2m system of * real equations. * * [One could of course use real equations for oriented manifolds as * well, but the speed suffers. The arithmetic involved in the row * operations (mulitplying an entry in one row by a constant and adding * it to the corresponding entry in another row) is four times faster * for real numbers than for complex numbers, but a 2n x 2m real system * requires eight times as many such steps as does an n x m complex system. * Hence the speed decreases by a factor of two. This is why SnapPea * handles oriented and unoriented manifolds differently. Other than * loss of speed, there is no harm in passing an unoriented (but * orientable) manifold, with manifold->orientability == * unknown_orientability).] * * do_Dehn_filling() computes the shape of each unfilled cusp and * stores it in the field cusp->cusp_shape[current]. * find_complete_hyperbolic_structure(), after calling do_Dehn_filling(), * copies cusp->cusp_shape[current] to cusp->cusp_shape[initial]. */ #include "kernel.h" const static ComplexWithLog regular_shape = { {0.5, ROOT_3_OVER_2}, {0.0, PI_OVER_3} }; /* * RIGHT_BALLPARK must be set fairly large to allow for degenerate * solutions, which cannot be computed to great accuracy. */ #define RIGHT_BALLPARK 1e-2 #define QUADRATIC_THRESHOLD 1e-4 /* * If the solution is degenerate and Newton's method has been * iterated at least DEGENERACY_ITERATIONS times, then * do_Dehn_filling() will keep going iff the distance to * the solution decreases by a factor of at least DEGENERACY_RATIO * each time. */ #define DEGENERACY_ITERATIONS 10 #define DEGENERACY_RATIO 0.9 /* * If we haven't converged and aren't making progress after * ITERATION_LIMIT iterations, we give up. */ #define ITERATION_LIMIT 101 /* * The CuspInfo and ChernSimonsInfo data structures are * used only in polish_hyperbolic_structures(). */ typedef struct { Boolean is_complete; double m, l; } CuspInfo; typedef struct { Boolean CS_value_is_known, CS_fudge_is_known; double CS_value[2], CS_fudge[2]; } ChernSimonsInfo; static void allocate_cusp_status_arrays(Triangulation *manifold, Boolean **is_complete_array, double **m_array, double **l_array); static void free_cusp_status_arrays(Boolean *is_complete_array, double *m_array, double *l_array); static void record_cusp_status(Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]); static void restore_cusp_status(Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]); static void copy_tet_shapes(Triangulation *manifold, FillingStatus source, FillingStatus dest); static void copy_cusp_shapes(Triangulation *manifold, FillingStatus source, FillingStatus dest); static void verify_coefficients(Triangulation *manifold); static void allocate_equations(Triangulation *manifold, Complex ***complex_equations, double ***real_equations, int *num_rows, int *num_columns); static void free_equations(Triangulation *manifold, Complex **complex_equations, double **real_equations, int num_rows); static void allocate_complex_equations(Triangulation *manifold, Complex ***complex_equations, int *num_rows, int *num_columns); static void allocate_real_equations(Triangulation *manifold, double ***real_equations, int *num_rows, int *num_columns); static void free_complex_equations(Complex **complex_equations, int num_rows); static void free_real_equations(double **real_equations, int num_rows); static void associate_complex_eqns_to_edges_and_cusps(Triangulation *manifold, Complex **complex_equations); static void associate_real_eqns_to_edges_and_cusps(Triangulation *manifold, double **real_equations); static void dissociate_eqns_from_edges_and_cusps(Triangulation *manifold); static void choose_coordinate_system(Triangulation *manifold); static Boolean check_convergence(Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, double *distance_to_solution, Boolean *convergence_is_quadratic, double *distance_ratio); static double compute_distance_complex(Complex **complex_equations, int num_rows, int num_columns); static double compute_distance_real(double **real_equations, int num_rows, int num_columns); static FuncResult solve_equations(Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, Complex *solution); static void convert_solution(double *real_solution, Complex *solution, int num_columns); static void save_chern_simons(Triangulation *manifold, ChernSimonsInfo *chern_simons_info); static void restore_chern_simons(Triangulation *manifold, ChernSimonsInfo *chern_simons_info); static void allocate_arrays(Triangulation *manifold, TetShape **save_shapes, CuspInfo **save_cusp_info); static void save_filled_solution(Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info); static void restore_filled_solution(Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info); static void validate_null_history(Triangulation *manifold); static void free_arrays(TetShape *save_shapes, CuspInfo *save_cusp_info); static void copy_ultimate_to_penultimate(Triangulation *manifold); static void suppress_imaginary_parts(Triangulation *manifold); SolutionType find_complete_hyperbolic_structure( Triangulation *manifold) { Boolean *is_complete_array; double *m_array, *l_array; /* * Set all Tetrahedra to be regular ideal tetrahedra. * Allocate the TetShapes if necessary. * Clear the shape_histories if necessary. */ initialize_tet_shapes(manifold); /* * We don't want to destroy any preexisting Dehn filling * coefficients, so copy them out to arrays. */ allocate_cusp_status_arrays(manifold, &is_complete_array, &m_array, &l_array); record_cusp_status(manifold, is_complete_array, m_array, l_array); /* * Complete all the cusps. */ complete_all_cusps(manifold); /* * Call do_Dehn_filling(). * In general it thinks it's finding a filled hyperbolic structure, * but since all the cusps are complete it's really finding the * complete hyperbolic structure. */ do_Dehn_filling(manifold); /* * Copy the "filled solution" (which is really the complete * solution) to where the complete solution belongs. */ copy_solution(manifold, filled, complete); /* * Restore the preexisting Dehn filling coefficients. */ restore_cusp_status(manifold, is_complete_array, m_array, l_array); free_cusp_status_arrays(is_complete_array, m_array, l_array); /* * Done. */ return manifold->solution_type[complete]; } void initialize_tet_shapes( Triangulation *manifold) { Tetrahedron *tet; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { for (i = 0; i < 2; i++) /* i = complete, filled */ { if (tet->shape[i] == NULL) tet->shape[i] = NEW_STRUCT(TetShape); for (j = 0; j < 3; j++) tet->shape[i]->cwl[ultimate][j] = regular_shape; } clear_shape_history(tet); } } static void allocate_cusp_status_arrays( Triangulation *manifold, Boolean **is_complete_array, double **m_array, double **l_array) { *is_complete_array = NEW_ARRAY(manifold->num_cusps, Boolean); *m_array = NEW_ARRAY(manifold->num_cusps, double); *l_array = NEW_ARRAY(manifold->num_cusps, double); } static void free_cusp_status_arrays( Boolean *is_complete_array, double *m_array, double *l_array) { my_free(is_complete_array); my_free(m_array); my_free(l_array); } static void record_cusp_status( Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { is_complete_array[cusp->index] = cusp->is_complete; m_array[cusp->index] = cusp->m; l_array[cusp->index] = cusp->l; } } static void restore_cusp_status( Triangulation *manifold, Boolean is_complete_array[], double m_array[], double l_array[]) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->is_complete = is_complete_array[cusp->index]; cusp->m = m_array[cusp->index]; cusp->l = l_array[cusp->index]; } } void complete_all_cusps( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; } } void copy_solution( Triangulation *manifold, FillingStatus source, /* complete or filled */ FillingStatus dest) /* filled or complete */ { copy_tet_shapes(manifold, source, dest); copy_cusp_shapes(manifold, source, dest); manifold->solution_type[dest] = manifold->solution_type[source]; } static void copy_tet_shapes( Triangulation *manifold, FillingStatus source, /* complete or filled */ FillingStatus dest) /* filled or complete */ { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { *tet->shape[dest] = *tet->shape[source]; clear_one_shape_history(tet, dest); copy_shape_history(tet->shape_history[source], &tet->shape_history[dest]); } } static void copy_cusp_shapes( Triangulation *manifold, FillingStatus source, /* complete/initial or filled/current */ FillingStatus dest) /* filled/current or complete/initial */ { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->cusp_shape[dest] = cusp->cusp_shape[source]; cusp->shape_precision[dest] = cusp->shape_precision[source]; } } /* * do_Dehn_filling() uses complex gluing equations for oriented * manifolds and real gluing equations for unoriented manifolds. * To keep the structure of its algorithm as clear as possible, * do_Dehn_filling() passes variables for both the complex_equations * and real_equations to the lower level routines, and lets the lower * level routines sort out which is the correct one to use for the * given manifold. */ SolutionType do_Dehn_filling( Triangulation *manifold) { Complex **complex_equations, *delta; double **real_equations, distance_to_solution, distance_ratio; int num_rows, num_columns, iterations, result; Boolean convergence_is_quadratic, solution_was_found, iteration_limit_exceeded; /* * Notify the UI that a potentially long computation is beginning. * The user may abort the computation if desired. */ uLongComputationBegins("Computing hyperbolic structure . . .", TRUE); /* * Check that the Dehn filling coefficients are valid. */ verify_coefficients(manifold); /* * Number the Tetrahedra. This implicitly assigns each Tetrahedron * to one of the complex variables. */ number_the_tetrahedra(manifold); /* * The following call to compute_holonomies() will rarely be needed, * but it guarantees holonomy[penultimate][] will be correct * even if Newton's method terminates after only one iteration. */ compute_holonomies(manifold); /* * allocate_equations() not only allocates the appropriate * set of equations, it also associates each equation to an edge * or cusp in the manifold. This is why the equations are not * explicitly passed to compute_equations(). */ allocate_equations( manifold, &complex_equations, &real_equations, &num_rows, &num_columns); /* * Allocate an array to hold the changes to the Tetrahedron shapes * specified by Newton's method. */ delta = NEW_ARRAY(manifold->num_tetrahedra, Complex); /* * distance_to_solution is initialized to RIGHT_BALLPARK * to get the proper behavior the first time through the loop. */ distance_to_solution = RIGHT_BALLPARK; convergence_is_quadratic = FALSE; iterations = 0; iteration_limit_exceeded = FALSE; do { choose_coordinate_system(manifold); compute_gluing_equations(manifold); /* * We're done if either * * (1) the solution has converged, or * * (2) the solution is degenerate (in which case it * would take a long, long time to converge). */ if ( check_convergence( manifold->orientability, complex_equations, real_equations, num_rows, num_columns, &distance_to_solution, &convergence_is_quadratic, &distance_ratio) || ( solution_is_degenerate(manifold) && iterations > DEGENERACY_ITERATIONS && distance_ratio > DEGENERACY_RATIO ) ) { solution_was_found = TRUE; break; /* break out of the do {} while (TRUE) loop */ } /* * iterations almost never exceeds ITERATION_LIMIT. * In fact, SnapPea was used for years without this check, and * it always found solutions. The first examples where the * solutions didn't converge were the meridional Dehn fillings * on the nonorientable 6-tetrahedron census manifolds * x045, x048, x063, x084 and x175. For further comments, * please see the file "failure to solve gluing eqns". */ if (iterations > ITERATION_LIMIT && distance_ratio >= 1.0) { iteration_limit_exceeded = TRUE; solution_was_found = FALSE; break; /* break out of the do {} while (TRUE) loop */ } result = solve_equations( manifold->orientability, complex_equations, real_equations, num_rows, num_columns, delta); if (result == func_cancelled || result == func_failed) { solution_was_found = FALSE; break; /* break out of the do {} while (TRUE) loop */ } update_shapes(manifold, delta); iterations++; } while (TRUE); /* The loop terminates in one of the break statements. */ /* * In the rare case that distance_to_solution is exactly zero, * copy the ultimate solution to the penultimate one, to indicate * that we've solved the equations to full accuracy. */ if (distance_to_solution == 0.0) copy_ultimate_to_penultimate(manifold); free_equations(manifold, complex_equations, real_equations, num_rows); my_free(delta); if (solution_was_found == TRUE) identify_solution_type(manifold); else if (iteration_limit_exceeded == TRUE) manifold->solution_type[filled] = no_solution; else switch (result) { case func_cancelled: manifold->solution_type[filled] = not_attempted; break; case func_failed: manifold->solution_type[filled] = no_solution; break; } /* * 96/1/12 Craig has requested that for flat solutions SnapPea's * complex length function provide consistent signs for rotation * angles of elliptic isometries (see complex_length.c). I was * concerned about distinguishing flat solutions from almost flat * solutions, so here we check whether the solution is provably flat, * and if so set the imaginary parts of all tet shapes to zero. * * Proposition. If a solution (to the gluing equations) is * almost flat and the Dehn filling coefficients are all integers, * then the solution obtained by setting the imaginary parts * of all tetrahedron shapes to zero is stable, in the sense that * Newton's method would keep all imaginary parts zero. * * Proof. In Newton's method, both the derivative matrix and the * "right hand side" would be real, so the computed array "delta" * would also be real. QED */ if (manifold->solution_type[filled] == flat_solution && all_Dehn_coefficients_are_integers(manifold) == TRUE) suppress_imaginary_parts(manifold); compute_cusp_shapes(manifold, current); compute_CS_value_from_fudge(manifold); uLongComputationEnds(); return manifold->solution_type[filled]; } /* * verify_coefficients() alerts the user and exits if the current set * of Dehn filling coefficients includes * * (0,0) Dehn filling on any cusp, or * * (p,q) Dehn filling, with q != 0, on a nonorientable cusp. * * set_cusp_info() should have already checked the coefficients * for errors, so verify_coefficients() should be unnecessary. It is * included to guard against programming errors (e.g. passing a manifold * whose coefficients have not been set at all), not user errors. */ static void verify_coefficients( Triangulation *manifold) { Cusp *cusp; for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if ( cusp->is_complete ? cusp->m != 0.0 || cusp->l != 0.0 : (cusp->m == 0.0 && cusp->l == 0.0) || (cusp->topology == Klein_cusp && cusp->l != 0.0) ) uFatalError("verify_coefficients", "hyperbolic_structure"); } /* * allocate_equations() allocates space for the equations as a matrix, * and also associates each equation to an edge or cusp in the manifold. */ static void allocate_equations( Triangulation *manifold, Complex ***complex_equations, double ***real_equations, int *num_rows, int *num_columns) { if (manifold->orientability == oriented_manifold) { real_equations = NULL; allocate_complex_equations(manifold, complex_equations, num_rows, num_columns); associate_complex_eqns_to_edges_and_cusps(manifold, *complex_equations); } else { complex_equations = NULL; allocate_real_equations(manifold, real_equations, num_rows, num_columns); associate_real_eqns_to_edges_and_cusps(manifold, *real_equations); } } static void free_equations( Triangulation *manifold, Complex **complex_equations, double **real_equations, int num_rows) { if (manifold->orientability == oriented_manifold) free_complex_equations(complex_equations, num_rows); else free_real_equations(real_equations, num_rows); dissociate_eqns_from_edges_and_cusps(manifold); } /* * allocate_complex_equations() sets *num_rows and *num_columns, * and allocates memory for a complex matrix of dimensions * (*num_rows) x (*num_columns + 1). The extra column will * hold the constant on the right hand side of the equations. */ static void allocate_complex_equations( Triangulation *manifold, Complex ***complex_equations, int *num_rows, int *num_columns) { int i; /* * We'll have an equation for each edge, and also an equation * for each cusp. The number of edges in an ideal triangulation * equals the number of tetrahedra, by an Euler characteristic * argument. */ *num_rows = manifold->num_tetrahedra + manifold->num_cusps; /* * We'll have one complex variable for each ideal tetrahedron. */ *num_columns = manifold->num_tetrahedra; /* * The matrix is stored as an array of row pointers. */ *complex_equations = NEW_ARRAY(*num_rows, Complex *); for (i = 0; i < *num_rows; i++) (*complex_equations)[i] = NEW_ARRAY(*num_columns + 1, Complex); } /* * allocate_real_equations() sets *num_rows and *num_columns, * and allocates memory for a real matrix of dimensions * 2*(*num_rows) x 2*(*num_columns + 1). The extra column will * hold the constant on the right hand side of the equations. */ static void allocate_real_equations( Triangulation *manifold, double ***real_equations, int *num_rows, int *num_columns) { int i; /* * Cf. allocate_complex_equations() above. */ *num_rows = 2 * (manifold->num_tetrahedra + manifold->num_cusps); *num_columns = 2 * manifold->num_tetrahedra; *real_equations = NEW_ARRAY(*num_rows, double *); for (i = 0; i < *num_rows; i++) (*real_equations)[i] = NEW_ARRAY(*num_columns + 1, double); } /* * free_complex_equations() frees the memory allocated * in allocate_complex_equations(). */ static void free_complex_equations( Complex **complex_equations, int num_rows) { int i; for (i = 0; i < num_rows; i++) my_free(complex_equations[i]); my_free(complex_equations); } /* * free_real_equations() frees the memory allocated * in allocate_real_equations(). */ static void free_real_equations( double **real_equations, int num_rows) { int i; for (i = 0; i < num_rows; i++) my_free(real_equations[i]); my_free(real_equations); } /* * associate_complex_eqns_to_edges_and_cusps() associates the first * num_tetrahedra equations to edge classes, and the remaining * num_cusps equations to cusps. */ static void associate_complex_eqns_to_edges_and_cusps( Triangulation *manifold, Complex **complex_equations) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = *complex_equations++; edge->real_edge_equation_re = NULL; edge->real_edge_equation_im = NULL; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = *complex_equations++; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; } } /* * associate_real_eqns_to_edges_and_cusps() associates the first * 2*num_tetrahedra equations to edge classes, and the remaining * 2*num_cusps equations to cusps. */ static void associate_real_eqns_to_edges_and_cusps( Triangulation *manifold, double **real_equations) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = NULL; edge->real_edge_equation_re = *real_equations++; edge->real_edge_equation_im = *real_equations++; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = *real_equations++; cusp->real_cusp_equation_im = *real_equations++; } } /* * dissociate_eqns_from_edges_and_cusps() dissociates the gluing * equations from the edges and cusps. Note that this function * works for both complex and real equations. */ static void dissociate_eqns_from_edges_and_cusps( Triangulation *manifold) { EdgeClass *edge; Cusp *cusp; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { edge->complex_edge_equation = NULL; edge->real_edge_equation_re = NULL; edge->real_edge_equation_im = NULL; } for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; } } /* * The shape of an ideal tetrahedron is traditionally parameterized * by one of the three forms of its cross ratio. Cross ratios of * 0, 1 and infinity represent degenerate tetrahedra. Near these * points, bad things happen. The two main problems are that (1) some * of the entries in the derivative matrix (used in Newton's method) * approach infinity, and (2) incrementing the solution can move it * too close to a singularity, resulting in wild swings in the arguments * of the cross ratios. Switching the coordinates from the cross * ratio to the log of the cross ratio helps a bit. Rather than having * two singularities (0 and 1) embedded in the parameter space, you * have only one (the singularity which used to be at 1 is now at 0, * but the singularity which used to be at 0 has been happily pushed * out to infinity). * * This scheme can be further improved by choosing a (logarithmic) * coordinate system based on the current shape of * the tetrahedron. The coordinate system is chosen so that the * current shape of the tetrahedron stays away from the singularity * in the parameter space. Specifically, let * * z0 = z * * 1 * z1 = ----- * 1 - z * * z - 1 * z2 = ----- * z * * and divide the complex plane into three regions: * * region A: |z-1| > 1 && Re(z) < 1/2 * region B: |z| > 1 && Re(z) > 1/2 * region C: |z-1| < 1 && |z| < 1 * * Viewed on the Riemann sphere, the singularities are equally * spaced points on the equator, and the regions are separated * by meridians spaced 120 degrees apart. The points along the * boundaries may be arbitrarily assigned to either neighboring region. * * In region A, use log(z0) coordinates. * In region B, use log(z1) coordinates. * In region C, use log(z2) coordinates. * * Each entry in the derivative matrix used in Newton's method is * a linear combination of the derivatives of log(z0), log(z1) * and log(z2). The above choice of coordinates implies that each * such derivative will have modulus less than or equal to one. * Here's the proof. First compute * * d(log z0) 1 * --------- = - * dz z * * d(log z1) 1 * --------- = ----- * dz 1 - z * * d(log z2) 1 * --------- = -------- * dz z(z - 1) * * Now take ratios of the above to compute * * d(log z0) d(log z0) 1 - z d(log z0) * --------- = 1 --------- = ----- --------- = z - 1 * d(log z0) d(log z1) z d(log z2) * * d(log z1) z d(log z1) d(log z1) * --------- = ----- --------- = 1 --------- = -z * d(log z0) 1 - z d(log z1) d(log z2) * * d(log z2) 1 d(log z2) -1 d(log z2) * --------- = ----- --------- = ----- --------- = 1 * d(log z0) z - 1 d(log z1) z d(log z2) * * Say z lies in region A, and we have chosen log(z0) coordinates * as indicated previously. The derivatives in the first column of the * above table have modulus less than or equal to 1. This is obvious * for the first entry in the column. For the third entry it's an * immediate consequence of the condition |1 - z| > 1. For the second * entry, note that * * | Im(z) | = | Im(1 - z) | * and * | Re(z) | < | Re(1 - z) | iff Re(z) < 1/2 * * hence |z| < |1-z|. * * Similar arguments show that when z lies in region B (resp. region C) * the derivatives in the second column (resp. third column) have * modulus less than or equal to 1. (In fact, the derivatives all * lie in region C, as can be seen from the fact that the two nonconstant * derivatives in each column sum to -1. For our purposes, though, it's * enough just to know that the derivatives are bounded, so the entries * in the derivative matrix used in Newton's method cannot diverge to * infinity.) * * Theoretical note: I briefly entertained the idea of finding a * single coordinate system which avoids all three singularities. * Picard's Little Theorem shows that this is not possible for an * analytic function. It might be possible for a nonanalytic function * (perhaps a simple function of z and z-bar?) but I haven't pursued * this, and in any case such a function wouldn't be conformal. * However, each Tetrahedron's shape_history fields record the topological * information such a master coordinate system would contain. */ static void choose_coordinate_system( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if ( tet->shape[filled]->cwl[ultimate][0].log.real < 0.0 /* |z| < 1 */ && tet->shape[filled]->cwl[ultimate][1].log.real > 0.0 /* |z-1| < 1 */ ) tet->coordinate_system = 2; /* region C, log(z2) coordinates */ else if (tet->shape[filled]->cwl[ultimate][0].rect.real > 0.5) /* Re(z) < 1/2 */ tet->coordinate_system = 1; /* region B, log(z1) coordinates */ else tet->coordinate_system = 0; /* region A, log(z0) coordinates */ } /* * check_convergence() checks whether Newton's method has converged to * a solution. We check for convergence in the range rather than the * domain. In other words, we check how precisely the gluing equations * are satisfied, without regard to whether the logs of the tetrahedra's * edge parameters are converging. The reason for this is that degenerate * equations will be satisfied more and more precisely by edge parameters * whose logs are diverging to infinity. * * We know Newton's method has converged when it begins making * small random changes. We check this by seeing whether * * (1) it's in the right ballpark (meaning it should be * converging quadratically), and * * (2) the new distance is greater than the old one. * * We also offer a shortcut, to avoid the possibility of having to * wait through several essentially random iterations of Newton's * method which just happen to decrease the distance to the solution * each time. The shortcut is that we note when quadratic convergence * begins, and then as soon as it ends we know we've converged. * * Finally, if the equations are satisfied perfectly, we return TRUE. * I realize this is not very likely, but it makes the function * logically correct. (Without this provision a perfect solution * would cycle endlessly through Newton's method.) * * check_convergence() returns TRUE when it considers Newton's method * to have converged, and FALSE otherwise. */ static Boolean check_convergence( Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, double *distance_to_solution, Boolean *convergence_is_quadratic, double *distance_ratio) { double old_distance; old_distance = *distance_to_solution; *distance_to_solution = orientability == oriented_manifold ? compute_distance_complex(complex_equations, num_rows, num_columns) : compute_distance_real(real_equations, num_rows, num_columns); *distance_ratio = *distance_to_solution / old_distance; if (*distance_ratio < QUADRATIC_THRESHOLD) *convergence_is_quadratic = TRUE; return ( (*distance_to_solution < RIGHT_BALLPARK && *distance_ratio > 1.0) || (*convergence_is_quadratic && *distance_ratio > 0.5) || (*distance_to_solution == 0.0) /* seems unlikely, but who knows */ ); } static double compute_distance_complex( Complex **complex_equations, int num_rows, int num_columns) { double distance_squared; int i; distance_squared = 0.0; for (i = 0; i < num_rows; i++) distance_squared += complex_modulus_squared(complex_equations[i][num_columns]); return sqrt(distance_squared); /* no need for safe_sqrt() */ } static double compute_distance_real( double **real_equations, int num_rows, int num_columns) { double distance_squared; int i; distance_squared = 0.0; for (i = 0; i < num_rows; i++) distance_squared += real_equations[i][num_columns] * real_equations[i][num_columns]; return sqrt(distance_squared); /* no need for safe_sqrt() */ } /* * In practice a typecast would suffice to convert the real_solution * to the Complex solution, since an array of n Complex numbers is stored * as an array of 2n reals. But we do an explicit conversion anyhow, * in the interest of good style and robust code (and also in the * interest of maintaining solve_real_equations() as a general purpose * routine for solving real equations). */ static FuncResult solve_equations( Orientability orientability, Complex **complex_equations, double **real_equations, int num_rows, int num_columns, Complex *solution) { double *real_solution; FuncResult result; if (orientability == oriented_manifold) result = solve_complex_equations(complex_equations, num_rows, num_columns, solution); else { real_solution = NEW_ARRAY(num_columns, double); result = solve_real_equations(real_equations, num_rows, num_columns, real_solution); if (result == func_OK) convert_solution(real_solution, solution, num_columns); my_free(real_solution); } return result; } static void convert_solution( double *real_solution, Complex *solution, int num_columns) { int count; for (count = num_columns/2; --count >= 0; ) { solution->real = *real_solution++; solution->imag = *real_solution++; solution++; } } void remove_hyperbolic_structures( Triangulation *manifold) { Tetrahedron *tet; int i; /* * If TetShapes are present, remove them. */ if (manifold->solution_type[complete] != not_attempted) for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { for (i = 0; i < 2; i++) /* i = complete, filled */ { my_free(tet->shape[i]); tet->shape[i] = NULL; } clear_shape_history(tet); } /* * Set solution_type[complete] and solution_type[filled] * to not_attempted. */ for (i = 0; i < 2; i++) /* i = complete, filled */ manifold->solution_type[i] = not_attempted; } void polish_hyperbolic_structures( Triangulation *manifold) { TetShape *save_shapes; CuspInfo *save_cusp_info; ChernSimonsInfo chern_simons_info; if (manifold->solution_type[complete] == not_attempted) uFatalError("polish_hyperbolic_structures", "polish_hyperbolic_structures"); save_chern_simons(manifold, &chern_simons_info); allocate_arrays(manifold, &save_shapes, &save_cusp_info); save_filled_solution(manifold, save_shapes, save_cusp_info); complete_all_cusps(manifold); copy_tet_shapes(manifold, complete, filled); validate_null_history(manifold); do_Dehn_filling(manifold); copy_solution(manifold, filled, complete); restore_filled_solution(manifold, save_shapes, save_cusp_info); validate_null_history(manifold); do_Dehn_filling(manifold); free_arrays(save_shapes, save_cusp_info); restore_chern_simons(manifold, &chern_simons_info); } static void save_chern_simons( Triangulation *manifold, ChernSimonsInfo *chern_simons_info) { /* * Why do we need to save and restore the Chern-Simons info? * * polish_hyperbolic_structures() is called just after a * Triangulation has been modified (e.g. by basic_simplification() * or randomize_triangulation()). At this point the TetShapes are * slightly inaccurate, the CS_value is accurate, and the * CS_fudge is completely wrong. We don't want to call * compute_CS_fudge_from_value() just yet, because then the * CS_fudge would inherit the inaccuracies of the TetShapes. * But it we call find_complete_hyperbolic_structure() or * do_Dehn_filling() right way, they will recompute the CS_value * based on the completely wrong CS_fudge. So we save the * CS_value until after we've polished the hyperbolic structure, * then we restore it and compute the CS_fudge using the accurate * TetShapes. */ /* * Record the Chern-Simons data. */ chern_simons_info->CS_value_is_known = manifold->CS_value_is_known; chern_simons_info->CS_fudge_is_known = manifold->CS_fudge_is_known; chern_simons_info->CS_value[ultimate] = manifold->CS_value[ultimate]; chern_simons_info->CS_value[penultimate] = manifold->CS_value[penultimate]; chern_simons_info->CS_fudge[ultimate] = manifold->CS_fudge[ultimate]; chern_simons_info->CS_fudge[penultimate] = manifold->CS_fudge[penultimate]; /* * Pretend it's no longer there, to save some useless computations. */ manifold->CS_value_is_known = FALSE; manifold->CS_fudge_is_known = FALSE; } static void restore_chern_simons( Triangulation *manifold, ChernSimonsInfo *chern_simons_info) { manifold->CS_value_is_known = chern_simons_info->CS_value_is_known; manifold->CS_fudge_is_known = chern_simons_info->CS_fudge_is_known; manifold->CS_value[ultimate] = chern_simons_info->CS_value[ultimate]; manifold->CS_value[penultimate] = chern_simons_info->CS_value[penultimate]; manifold->CS_fudge[ultimate] = chern_simons_info->CS_fudge[ultimate]; manifold->CS_fudge[penultimate] = chern_simons_info->CS_fudge[penultimate]; /* * It might makes sense to call compute_CS_fudge_from_value() at * this point, but in the interest of modularity I decided not to. */ } static void allocate_arrays( Triangulation *manifold, TetShape **save_shapes, CuspInfo **save_cusp_info) { *save_shapes = NEW_ARRAY(manifold->num_tetrahedra, TetShape); *save_cusp_info = NEW_ARRAY(manifold->num_cusps, CuspInfo); } static void save_filled_solution( Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info) { int i; Tetrahedron *tet; Cusp *cusp; /* * Save the Tetrahedron shapes. */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) save_shapes[i] = *tet->shape[filled]; /* * Save the Cusp information. */ for (i = 0, cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; i++, cusp = cusp->next) { save_cusp_info[i].is_complete = cusp->is_complete; save_cusp_info[i].m = cusp->m; save_cusp_info[i].l = cusp->l; } } static void restore_filled_solution( Triangulation *manifold, TetShape *save_shapes, CuspInfo *save_cusp_info) { int i; Tetrahedron *tet; Cusp *cusp; /* * Restore the Tetrahedron shapes. */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) *tet->shape[filled] = save_shapes[i]; /* * Restore the Cusp information. */ for (i = 0, cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; i++, cusp = cusp->next) { cusp->is_complete = save_cusp_info[i].is_complete; cusp->m = save_cusp_info[i].m; cusp->l = save_cusp_info[i].l; } } static void validate_null_history( Triangulation *manifold) { /* * The basic_simplification() and randomize_triangulation() * functions can't be guaranteed to find correct arguments * for the logarithmic forms of the TetShapes, let alone produce * a valid history for each new Tetrahedron it introduces. * * validate_null_history() enforces a trivial shape_history * for each Tetrahedron. It does this by * * (1) clearing all shape_histories, * * (2) making sure all Tetrahedra are positively oriented, and * * (3) making sure all logs lie in the range (0, pi). * * In the nice case that these conditions are all already met, * validate_null_history() doesn't change anything, and * polish_hyperbolic_structures() ends up making only small * changes to the hyperbolic structures ("polishing" them). * * If the conditions are not met, validate_null_history() sets * the offending Tetrahedron shapes to something acceptable, and * in effect the hyperbolic structure is recomputed from scratch. */ Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { clear_one_shape_history(tet, filled); /* * The algorithm in update_shapes() guarantees that the * three shapes (for edge j = 0, 1, 2) will have the same sign * for rect.imag, regardless of roundoff errors, so if. * * Only the ultimate shapes are relevant. The penultimate * shapes are ignored. */ for (i = 0; i < 3; i++) { if (tet->shape[filled]->cwl[ultimate][i].rect.imag <= 0.0) tet->shape[filled]->cwl[ultimate][i] = regular_shape; tet->shape[filled]->cwl[ultimate][i].log = complex_log( tet->shape[filled]->cwl[ultimate][i].rect, PI_OVER_2); } } } static void free_arrays( TetShape *save_shapes, CuspInfo *save_cusp_info) { my_free(save_shapes); my_free(save_cusp_info); } static void copy_ultimate_to_penultimate( Triangulation *manifold) { Tetrahedron *tet; int i; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) tet->shape[filled]->cwl[penultimate][i] = tet->shape[filled]->cwl[ultimate][i]; } static void suppress_imaginary_parts( Triangulation *manifold) { Tetrahedron *tet; int i, j; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) /* ultimate, penultimate */ for (j = 0; j < 3; j++) { tet->shape[filled]->cwl[i][j].rect.imag = 0.0; tet->shape[filled]->cwl[i][j].log = complex_log( tet->shape[filled]->cwl[i][j].rect, tet->shape[filled]->cwl[i][j].log.imag); } } extern SolutionType remove_Dehn_fillings(Triangulation *manifold) { /* * Set all cusps to be unfilled. */ complete_all_cusps(manifold); /* * Copy the complete solution to the "filled" solution. */ copy_solution(manifold, complete, filled); /* * Call do_Dehn_filling(), to insure that all internal * data (such as the Chern-Simons invariant) are updated * correctly. This invokes an unnecessary computation, * but it keeps the code simple, and guarantees that * all internal data will be up-to-date. */ return do_Dehn_filling(manifold); } regina-4.95/engine/snappea/kernel/identify_solution_type.c000644 000765 000024 00000011226 12235724562 023751 0ustar00babstaff000000 000000 /* * identify_solution_type.c * * This file provides the function * * void identify_solution_type(Triangulation *manifold); * * which identifies the type of solution contained in the * tet->shape[filled] structures of the Tetrahedra of Triangulation *manifold, * and writes the result to manifold->solution_type[filled]. Possible * values are given by the SolutionType enum (see SnapPea.h). * * Its subroutine * * Boolean solution_is_degenerate(Triangulation *manifold); * * Is also available within the kernel, so do_Dehn_filling() can tell * whether it is converging towards a degenerate structure. */ #include "kernel.h" /* * A solution must have volume at least VOLUME_EPSILON to count * as a positive volume solution. Otherwise the volume will be * considered zero or negative. */ #define VOLUME_EPSILON 1e-2 /* * DEGENERACY_EPSILON defines how close a tetrahedron shape must * be to zero to count as zero. It is given in logarithmic form. * E.g., if DEGENERACY_EPSILON is -6, then the tetrahedron shape * (in rectangular form) must lie within a distance exp(-6) = 0.0024... * of the origin. */ #define DEGENERACY_EPSILON -6 /* * A solution is considered flat iff it's not degenerate and the * argument of each edge parameter is within FLAT_EPSILON of 0.0 or PI. */ #define FLAT_EPSILON 1e-2 static Boolean solution_is_flat(Triangulation *manifold); static Boolean solution_is_geometric(Triangulation *manifold); void identify_solution_type( Triangulation *manifold) { if (solution_is_degenerate(manifold)) { manifold->solution_type[filled] = degenerate_solution; return; } if (solution_is_flat(manifold)) { manifold->solution_type[filled] = flat_solution; return; } if (solution_is_geometric(manifold)) { manifold->solution_type[filled] = geometric_solution; return; } if (volume(manifold, NULL) > VOLUME_EPSILON) { manifold->solution_type[filled] = nongeometric_solution; return; } manifold->solution_type[filled] = other_solution; } Boolean solution_is_degenerate( Triangulation *manifold) { Tetrahedron *tet; int i; /* * If any complex edge parameter of any Tetrahedron is * close to zero, return TRUE. Otherwise return FALSE. * * Note that it's enough to check for shapes close to * zero: if an edge parameter is close to one or infinity, * then some other edge parameter of the same Tetrahedron * will be close to zero. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) if (tet->shape[filled]->cwl[ultimate][i].log.real < DEGENERACY_EPSILON) return TRUE; return FALSE; } static Boolean solution_is_flat( Triangulation *manifold) { Tetrahedron *tet; int i; double the_angle; /* * If any edge parameter has angle more than FLAT_EPSILON away * from 0.0 or PI, return FALSE. Otherwise, return TRUE. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 3; i++) { the_angle = tet->shape[filled]->cwl[ultimate][i].log.imag; if (fabs(the_angle) > FLAT_EPSILON && fabs(the_angle - PI) > FLAT_EPSILON) return FALSE; } return TRUE; } static Boolean solution_is_geometric( Triangulation *manifold) { Tetrahedron *tet; /* * If any edge parameter has argument less than minus FLAT_EPSILON * or greater than PI + FLAT_EPSILON, return FALSE. * Otherwise, return TRUE. * * This allows a solution with some flat tetrahedra to count as geometric. * However, if all the tetrahedra were flat, the SolutionType would have * been previously identified as flat_solution, and we wouldn't have * gotten to this function. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tetrahedron_is_geometric(tet) == FALSE) return FALSE; return TRUE; } Boolean tetrahedron_is_geometric( Tetrahedron *tet) { int i; double the_angle; /* * See comments in solution_is_geometric() above. */ for (i = 0; i < 3; i++) { the_angle = tet->shape[filled]->cwl[ultimate][i].log.imag; if (the_angle < - FLAT_EPSILON || the_angle > PI + FLAT_EPSILON) return FALSE; } return TRUE; } regina-4.95/engine/snappea/kernel/interface.c000644 000765 000024 00000036165 12235724562 021112 0ustar00babstaff000000 000000 /* * interface.c * * This file contains the following functions, which the * user interface uses to read the fields in the Triangulation * data structure. * * char *get_triangulation_name(Triangulation *manifold); * char set_triangulation_name(Triangulation *manifold, char *new_name); * SolutionType get_complete_solution_type(Triangulation *manifold); * SolutionType get_filled_solution_type(Triangulation *manifold); * int get_num_tetrahedra(Triangulation *manifold); * Orientability get_orientability(Triangulation *manifold); * int get_num_cusps(Triangulation *manifold); * int get_num_or_cusps(Triangulation *manifold); * int get_num_nonor_cusps(Triangulation *manifold); * int get_max_singularity(Triangulation *manifold); * int get_num_generators(Triangulation *manifold); * void get_cusp_info( Triangulation *manifold, * int cusp_index, * CuspTopology *topology, * Boolean *is_complete, * double *m, * double *l, * Complex *initial_shape, * Complex *current_shape, * int *initial_shape_precision, * int *current_shape_precision, * Complex *initial_modulus, * Complex *current_modulus); * FuncResult set_cusp_info( Triangulation *manifold, * int cusp_index, * Boolean cusp_is_complete, * double m, * double l); * void get_holonomy( Triangulation *manifold, * int cusp_index, * Complex *meridional_holonomy, * Complex *longitudinal_holonomy, * int *meridional_precision, * int *longitudinal_precision); * void get_tet_shape( Triangulation *manifold, * int which_tet, * Boolean fixed_alignment, * double *shape_rect_real, * double *shape_rect_imag, * double *shape_log_real, * double *shape_log_imag, * int *precision_rect_real, * int *precision_rect_imag, * int *precision_log_real, * int *precision_log_imag, * Boolean *is_geometric); * int get_num_edge_classes( * Triangulation *manifold, * int edge_class_order, * Boolean greater_than_or_equal); * * The Triangulation data structure itself, as well as its * component data structures, remain private to the kernel. * * These functions are documented more thoroughly in SnapPea.h. */ #include "kernel.h" static int longest_side(Tetrahedron *tet); char *get_triangulation_name( Triangulation *manifold) { return manifold->name; } void set_triangulation_name( Triangulation *manifold, char *new_name) { /* * Free the old name, if there is one. */ if (manifold->name != NULL) my_free(manifold->name); /* * Allocate space for the new name . . . */ manifold->name = NEW_ARRAY(strlen(new_name) + 1, char); /* * . . . and copy it in. */ strcpy(manifold->name, new_name); } SolutionType get_complete_solution_type( Triangulation *manifold) { return manifold->solution_type[complete]; } SolutionType get_filled_solution_type( Triangulation *manifold) { return manifold->solution_type[filled]; } int get_num_tetrahedra( Triangulation *manifold) { return manifold->num_tetrahedra; } Orientability get_orientability( Triangulation *manifold) { return manifold->orientability; } int get_num_cusps( Triangulation *manifold) { return manifold->num_cusps; } int get_num_or_cusps( Triangulation *manifold) { return manifold->num_or_cusps; } int get_num_nonor_cusps( Triangulation *manifold) { return manifold->num_nonor_cusps; } int get_max_singularity( Triangulation *manifold) { Cusp *cusp; int m, l, singularity, max_singularity; max_singularity = 1; for ( cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) { if (cusp->is_complete == FALSE) { m = (int) cusp->m; l = (int) cusp->l; if ( cusp->m == (double) m && cusp->l == (double) l) { singularity = gcd(m, l); if (max_singularity < singularity) max_singularity = singularity; } } } return max_singularity; } int get_num_generators( Triangulation *manifold) { return manifold->num_generators; } void get_cusp_info( Triangulation *manifold, int cusp_index, CuspTopology *topology, Boolean *is_complete, double *m, double *l, Complex *initial_shape, Complex *current_shape, int *initial_shape_precision, int *current_shape_precision, Complex *initial_modulus, Complex *current_modulus) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); /* * Write information corresponding to nonNULL pointers. */ if (topology != NULL) *topology = cusp->topology; if (is_complete != NULL) *is_complete = cusp->is_complete; if (m != NULL) *m = cusp->m; if (l != NULL) *l = cusp->l; if (initial_shape != NULL) *initial_shape = cusp->cusp_shape[initial]; /* = Zero if initial hyperbolic structure is degenerate */ if (current_shape != NULL) *current_shape = cusp->cusp_shape[current]; /* = Zero if cusp is filled or hyperbolic structure is degenerate */ if (initial_shape_precision != NULL) *initial_shape_precision = cusp->shape_precision[initial]; /* = 0 if initial hyperbolic structure is degenerate */ if (current_shape_precision != NULL) *current_shape_precision = cusp->shape_precision[current]; /* = 0 if cusp is filled or hyperbolic structure is degenerate */ if (initial_modulus != NULL) { if (cusp->shape_precision[initial] > 0) *initial_modulus = cusp_modulus(cusp->cusp_shape[initial]); else *initial_modulus = Zero; } if (current_modulus != NULL) { if (cusp->shape_precision[current] > 0) *current_modulus = cusp_modulus(cusp->cusp_shape[current]); else *current_modulus = Zero; } } FuncResult set_cusp_info( Triangulation *manifold, int cusp_index, Boolean cusp_is_complete, double m, double l) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); /* MC 2008-09-30 */ if ( cusp == NULL) return func_failed; /* * Write the given Dehn coefficients into the cusp. */ if (cusp_is_complete) { cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; } else { /* * Check the input. * * The comment at the top of holonomy.c explains why only * (m,0) Dehn fillings are possible on nonorientable cusps. */ if (m == 0.0 && l == 0.0) { uAcknowledge("Can't do (0,0) Dehn filling."); return func_bad_input; } if (cusp->topology == Klein_cusp && l != 0.0) { uAcknowledge("Only (p,0) Dehn fillings are possible on a nonorientable cusp."); return func_bad_input; } /* * Copy the input into the cusp. */ cusp->is_complete = FALSE; cusp->m = m; cusp->l = l; } return func_OK; } void get_holonomy( Triangulation *manifold, int cusp_index, Complex *meridional_holonomy, Complex *longitudinal_holonomy, int *meridional_precision, int *longitudinal_precision) { Cusp *cusp; cusp = find_cusp(manifold, cusp_index); if (meridional_holonomy != NULL) *meridional_holonomy = cusp->holonomy[ultimate][M]; if (longitudinal_holonomy != NULL) { *longitudinal_holonomy = cusp->holonomy[ultimate][L]; /* * Longitudes on Klein bottle cusps are stored as their * double covers (cf. peripheral_curves.c), so we divide * by two to compensate. (Recall that this isn't actually * the holonomy, but the log of the holonomy, i.e. the * complex length.) * * As explained at the top of holonomy.c, the holonomy * in this case must be real, so we clear any roundoff * error in the imaginary part. */ if (cusp->topology == Klein_cusp) { longitudinal_holonomy->real /= 2.0; longitudinal_holonomy->imag = 0.0; } } if (meridional_precision != NULL) *meridional_precision = complex_decimal_places_of_accuracy( cusp->holonomy[ ultimate ][M], cusp->holonomy[penultimate][M]); if (longitudinal_precision != NULL) *longitudinal_precision = complex_decimal_places_of_accuracy( cusp->holonomy[ ultimate ][L], cusp->holonomy[penultimate][L]); } void get_tet_shape( Triangulation *manifold, int which_tet, Boolean fixed_alignment, double *shape_rect_real, double *shape_rect_imag, double *shape_log_real, double *shape_log_imag, int *precision_rect_real, int *precision_rect_imag, int *precision_log_real, int *precision_log_imag, Boolean *is_geometric) { int count, the_coordinate_system; Tetrahedron *tet; ComplexWithLog *ultimate_shape, *penultimate_shape; /* * If no solution is present, return all zeros. */ if (manifold->solution_type[filled] == not_attempted) { *shape_rect_real = 0.0; *shape_rect_imag = 0.0; *shape_log_real = 0.0; *shape_log_imag = 0.0; *precision_rect_real = 0; *precision_rect_imag = 0; *precision_log_real = 0; *precision_log_imag = 0; *is_geometric = FALSE; return; } /* * Check that which_tet is within bounds. */ if (which_tet < 0 || which_tet >= manifold->num_tetrahedra) uFatalError("get_tet_shape", "interface"); /* * Find the Tetrahedron in position which_tet. */ for (tet = manifold->tet_list_begin.next, count = 0; tet != &manifold->tet_list_end && count != which_tet; tet = tet->next, count++) ; /* * If we went all the way through the list of Tetrahedra * without finding position which_tet, then something * is very wrong. */ if (tet == &manifold->tet_list_end) uFatalError("get_tet_shape", "interface"); /* * If fixed_alignment is TRUE, use a fixed coordinate system. * Otherwise choose the_coordinate_system so that the longest side * of the triangle is the initial side of the angle. */ if (fixed_alignment == TRUE) the_coordinate_system = 0; else the_coordinate_system = (longest_side(tet) + 1) % 3; /* * Note the addresses of the ultimate and penultimate shapes. */ ultimate_shape = &tet->shape[filled]->cwl[ ultimate ][the_coordinate_system]; penultimate_shape = &tet->shape[filled]->cwl[penultimate][the_coordinate_system]; /* * Report the ultimate shapes. */ *shape_rect_real = ultimate_shape->rect.real; *shape_rect_imag = ultimate_shape->rect.imag; *shape_log_real = ultimate_shape->log.real; *shape_log_imag = ultimate_shape->log.imag; /* * Estimate the precision. */ *precision_rect_real = decimal_places_of_accuracy(ultimate_shape->rect.real, penultimate_shape->rect.real); *precision_rect_imag = decimal_places_of_accuracy(ultimate_shape->rect.imag, penultimate_shape->rect.imag); *precision_log_real = decimal_places_of_accuracy(ultimate_shape->log.real, penultimate_shape->log.real); *precision_log_imag = decimal_places_of_accuracy(ultimate_shape->log.imag, penultimate_shape->log.imag); /* * Check whether the tetrahedron is geometric. */ *is_geometric = tetrahedron_is_geometric(tet); } static int longest_side( Tetrahedron *tet) { int i, desired_index; double sine[3], max_sine; /* * longest_side() returns the index (0, 1 or 2) of the edge opposite * the longest side of tet's triangular vertex cross section. * * We'll use the Law of Sines, which says that the lengths of a triangle's * sides are proportional to the sines of the opposite angles. * * We take the absolute value of each sine, just in case the * Tetrahedron is negatively oriented. */ for (i = 0; i < 3; i++) sine[i] = fabs(tet->shape[filled]->cwl[ultimate][i].rect.imag) / complex_modulus(tet->shape[filled]->cwl[ultimate][i].rect); max_sine = -1.0; for (i = 0; i < 3; i++) if (sine[i] > max_sine) { max_sine = sine[i]; desired_index = i; } return desired_index; } int get_num_edge_classes( Triangulation *manifold, int edge_class_order, Boolean greater_than_or_equal) { int count; EdgeClass *edge; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if ( greater_than_or_equal ? edge->order >= edge_class_order : edge->order == edge_class_order) count++; return count; } regina-4.95/engine/snappea/kernel/intersection_numbers.c000644 000765 000024 00000024154 12235724562 023406 0ustar00babstaff000000 000000 /* * intersection_numbers.c * * This file provides the kernel function * * void compute_intersection_numbers(Triangulation *manifold); * * which computes the intersection numbers of the curves stored * in the scratch_curve[][][][][] fields of the Tetrahedra, and * writes the results to the intersection_number[][] fields of * the Cusps. That is, * * intersection_number[M][M] will be the intersection number of * scratch_curve[0][M] with scratch_curve[1][M], * * intersection_number[M][L] will be the intersection number of * scratch_curve[0][M] with scratch_curve[1][L], * * intersection_number[L][M] will be the intersection number of * scratch_curve[0][L] with scratch_curve[1][M], * * intersection_number[L][L] will be the intersection number of * scratch_curve[0][L] with scratch_curve[1][L]. * * Each intersection number is the algebraic sum of the crossing * numbers. As viewed from infinity (looking toward the fat * part of the manifold), each crossing of the form * * scratch_curve[1] * ^ * | contributes +1, * ----|---> scratch_curve[0] * | * | * * while each crossing of the form * * * scratch_curve[0] * ^ * | contributes -1, * ----|---> scratch_curve[1] * | * | * * This file also provides the utility * * void copy_curves_to_scratch( Triangulation *manifold, * int which_set, * Boolean double_copy_on_tori); * * which copies the current peripheral curves to the scratch_curves[which_set] * fields of the manifold's Tetrahedra. If double_copy_on_tori is TRUE, * it copies peripheral curves on orientable cusps to both sheets of * the Cusps' orientation double covers. * * * Overview of Intersection Number Algorithm. * * Consider the triangulation of the boundary components by the * triangles at the (truncated) ideal vertices. As explained * in peripheral_curves.c, we work in the orientation double cover, * so in fact each ideal vertex contributes two triangles, one * left_handed and the other right_handed. Relative to the * orientation on the cusp (and even nonorientable cusps are * effectively oriented, since we work in the orientation double * cover) we imagine each scratch_curve[0] entering a given triangle * on the right side of a given edge, and each scratch_curve[1] * entering on the left: * * /\ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / \ * / 1 \ / 0 \ * /__________\/__________\ * \ /\ / * \ 0 / \ 1 / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \ / * \/ * * Of necessity, the curves must cross on the edge (if both are * nonzero). There may be additional crossings in the interior of * the triangle, depending on where the various curves are entering * and exiting. To avoid counting the intersections on the edges * twice (once for each of the two incident triangles) we make the * convention to count edge crossings only where scratch_curve[0] is * entering (not exiting) the triangle. */ #include "kernel.h" void compute_intersection_numbers( Triangulation *manifold) { Cusp *cusp; Tetrahedron *tet; int f, g, h, i, j, face_on_the_left, face_on_the_right; /* * Initialize all the intersection numbers to zero. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = M, L */ for (j = 0; j < 2; j++) /* j = M, L */ cusp->intersection_number[i][j] = 0; /* * Count the intersections on the edges. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) /* which side of the vertex */ for (j = 0; j < 4; j++) { if (i == j) continue; /* which sheet (right_handed or left_handed) */ for (f = 0; f < 2; f++) /* which scratch_curve[0] (meridian or longitude) */ for (g = 0; g < 2; g++) /* which scratch_curve[1] (meridian or longitude) */ for (h = 0; h < 2; h++) /* * Recall the convention (described at the top * of this file) that edge crossings are counted * only where scratch_curve[0] is entering -- * not exiting -- the triangle. */ if (tet->scratch_curve[0][g][f][i][j] > 0) tet->cusp[i]->intersection_number[g][h] += tet->scratch_curve[0][g][f][i][j] * tet->scratch_curve[1][h][f][i][j]; } /* * Count the intersections in the interiors of triangles. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) /* which side of the vertex */ for (j = 0; j < 4; j++) { if (i == j) continue; /* * Name the two remaining faces of the Tetrahedron * according to the right_handed orientation of * the Tetrahedron. */ face_on_the_left = remaining_face[i][j]; face_on_the_right = remaining_face[j][i]; /* which scratch_curve[0] (meridian or longitude) */ for (g = 0; g < 2; g++) /* which scratch_curve[1] (meridian or longitude) */ for (h = 0; h < 2; h++) { /* * We'll count only those intersections on the * strand of scratch_curve[0] running from the * current side of the triangle (side j) towards * the right. The other possibilities will be * handled by other values of j. * * When we see the Tetrahedron as left_handed * relative to the Orientation of the cusp, the * face on the right is face_on_the_left. * Got that? */ tet->cusp[i]->intersection_number[g][h] += FLOW(tet->scratch_curve[0][g][right_handed][i][j], tet->scratch_curve[0][g][right_handed][i][face_on_the_right]) * tet->scratch_curve[1][h][right_handed][i][face_on_the_right]; tet->cusp[i]->intersection_number[g][h] += FLOW(tet->scratch_curve[0][g][left_handed][i][j], tet->scratch_curve[0][g][left_handed][i][face_on_the_left]) * tet->scratch_curve[1][h][left_handed][i][face_on_the_left]; } } } void copy_curves_to_scratch( Triangulation *manifold, int which_set, Boolean double_copy_on_tori) { Tetrahedron *tet; int i, j, k, l; /* * When computing intersection numbers on orientable cusps * (especially in nonorientable manifolds) there is a danger that * scratch_curves[0] will lie on one sheet of the Cusp's orientation * double cover while scratch_curves[1] lies on the other sheet. * To avoid this danger, copy_curves_to_scratch() offers the * option to copy the peripheral curves on orientable cusps to * both sheets of the double cover. You should use this option * for one set of scratch_curves (e.g. scratch_curves[0]) but not * the other (scratch_curves[1]) to guarantee correct intersection * numbers. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) if (tet->cusp[k]->topology == torus_cusp && double_copy_on_tori == TRUE) tet->scratch_curve[which_set][i][right_handed][k][l] = tet->scratch_curve[which_set][i][ left_handed][k][l] = tet->curve[i][right_handed][k][l] + tet->curve[i][ left_handed][k][l]; else /* * tet->cusp[k]->topology == Klein_cusp * || double_copy_on_tori == FALSE */ for (j = 0; j < 2; j++) tet->scratch_curve[which_set][i][j][k][l] = tet->curve[i][j][k][l]; } regina-4.95/engine/snappea/kernel/isometry.h000644 000765 000024 00000015157 12235724571 021030 0ustar00babstaff000000 000000 /* * isometry.h * * This file provides the definition of an IsometryList. It is private * to the kernel. * * An isometry is a map which takes the Tetrahedra of one Triangulation * to the Tetrahedra of another, preserving the gluings. Strictly speaking * this is a combinatorial equivalence of the two Triangulations, but * the Triangulations are typically the canonical ones for their respective * manifolds, so the notions of combinatorial equivalence and isometry * coincide. * * The two Triangulations need not be distinct -- if they're the same * then an "isometry" is what one normally calls a symmetry. * * An isometry may be represented in two ways: internally within the * Triangulation data structure, and externally using the Isometry data * structure. * * (1) Internal representation. The Triangulation data structure may * represent at most one isometry at a time. Each Tetrahedron contains * the fields * Tetrahedron *image; * Permutation map; * * The image field contains a pointer to the image of the given * Tetrahedron under the isometry. The map field says how the * vertices of the given Tetrahedron are taken to the vertices * of its image. E.g. if the Permutation is 2013 (meaning 3210 -> 2013) * then vertex 0 of the given Tetrahedron maps to vertex 3 of the image * Tetrahedron, vertex 1 maps to vertex 1 of the image, vertex 2 maps * to vertex 0, and vertex 3 maps to vertex 2. * * (2) External representation. The Isometry data structure is similar * to the internal representation described in the preceding paragraph, * only it refers to Tetrahedra by indices rather than pointers. * Each Tetrahedron in each manifold is numbered by setting its index * field equal to the Tetrahedron's position in its Triangulation's * doubly-linked list (the indices run from 0 through n-1). * * The images and maps are stored in the tet_image and tet_map arrays * in the Isometry data structure. The elements of each array are * implicitly indexed by the indices of the Tetrahedra in the domain * manifold. For example, if * * my_isometry->tet_image = {1, 0, 2} * and * my_isometry->tet_map = {3102, 3201, 1023}, * * then Tetrahedron #0 of the domain Triangulation will map to * Tetrahedron #1 of the image Triangulation via the Permutation * 3102; Tetrahedron #1 will map to Tetrahedron #0 via 3201; * and Tetrahedron #2 will map to Tetrahedron #2 via 1023. * * In addition, the Isometry data structure records the action * of the Isometry on the Cusps, as explained in the Isometry * definition below. * * The IsometryList data structure contains an array of pointers to * Isometries, an integer saying how many there are, and an integer * say how many Tetrahedra each Triangulation has. The file SnapPea.h * contains the "opaque typedef" * * typedef struct IsometryList IsometryList; * * which lets the UI declare and pass pointers to IsometryLists without * actually knowing what they are. This file provides the kernel with * the actual definition. * * The Isometry data structure also contains a "next" field, which the * function compute_cusped_isometries() uses internally while assembling * its IsometryList. Other functions ignore the "next" field. */ #ifndef _isometry_ #define _isometry_ #include "kernel.h" typedef struct Isometry Isometry; struct Isometry { /* * How many Tetrahedra and Cusps are there? */ int num_tetrahedra, num_cusps; /* * The Isometry sends Tetrahedron n in the domain * manifold to Tetrahedron tet_image[n] in the image manifold. */ int *tet_image; /* * The Isometry sends vertex v of Tetrahedron n in the * domain manifold to vertex EVALUATE(tet_map[n], v) of * Tetrahedron tet_image[n] in the image manifold. */ Permutation *tet_map; /* * The Isometry sends Cusp k of the domain manifold * to Cusp cusp_image[k] in the image manifold. */ int *cusp_image; /* * The matrix cusp_map[k][][] takes the peripheral curves * of Cusp k in the domain manifold to the peripheral curves * of Cusp cusp_image[k] in the image manifold. That is, * the image of a meridian of Cusp k in the domain is * cusp_matrix[k][M][M] meridians plus cusp_matrix[k][L][M] * longitudes in the image, and similarly for the image of * a longitude. * * Note that the cusp_map matrix is defined even for * nonorientable Cusps, since the peripheral curves are stored * in the Cusp's orientation double cover. For nonorientable * cusps, cusp_matrix[k][L][M] will be a diagonal matrix. * The entry cusp_matrix[k][L][L] tells whether the direction * of the cusp is reversed. Det(cusp_matrix[k]) tells whether * the Isometry acts in an orientation-preserving or orientation- * reversing way on the Cusp's orientation double cover (Question: * what is the significance of this information, if any?). * * This scheme applies only to the real cusps -- finite vertices * (whose cusp indices are negative) are ignored. */ MatrixInt22 *cusp_map; /* * Does this Isometry from one cusped manifold to another extend * to the closed manifolds obtained by meridional Dehn fillings? * If the cusp manifolds are link complements (in any manifolds, * not necessarily 3-spheres) this is equivalent to asking whether * the Isometry extends to a link homeomorphism. */ Boolean extends_to_link; /* * This "next" field is used internally in isometry_cusped.c * while assembling the IsometryList. (The Isometries are * temporarily stored on a linked list, then eventually * transferred to an array of pointers for external use.) */ Isometry *next; }; struct IsometryList { /* * How many Isometries are on the list? */ int num_isometries; /* * isometry[n] is a pointer to the n-th Isometry * on the list. (The "isometry" field itself * contains a pointer to an array. Each element of * the array is a pointer to an Isometry.) * * If there are no isometries (num_isometries == 0) * then the isometry field is set to NULL. That is, * we don't try to allocate an array of zero pointers. */ Isometry **isometry; }; #endif regina-4.95/engine/snappea/kernel/kernel.h000644 000765 000024 00000002262 12236247215 020422 0ustar00babstaff000000 000000 /* * kernel.h * * This file #includes all header files needed for the kernel. * It should be #included in all kernel .c files, but nowhere else. */ /* * Allow inclusion of this header in c++ projects. */ #ifdef __cplusplus extern "C" { #endif #ifndef _kernel_ #define _kernel_ #include "SnapPea.h" #include #include #include #include /* Some C implementations define DBL_MAX, DBL_MIN, FLT_MAX, */ /* and FLT_MIN in limits.h as well as float.h, leading to */ /* "redefinition" warnings. If this is the case on your system, */ /* uncomment the following lines and insert them between */ /* "#include " and "#include " above. */ /* */ /* #undef DBL_MAX */ /* #undef DBL_MIN */ /* #undef FLT_MAX */ /* #undef FLT_MIN */ #include "kernel_typedefs.h" #include "triangulation.h" #include "positioned_tet.h" #include "isometry.h" #include "symmetry_group.h" #include "dual_one_skeleton_curve.h" #include "terse_triangulation.h" #include "kernel_prototypes.h" #include "tables.h" #endif #ifdef __cplusplus } #endif regina-4.95/engine/snappea/kernel/kernel_prototypes.h000644 000765 000024 00000137236 12235724571 022750 0ustar00babstaff000000 000000 /* * kernel_prototypes.h * * This file contains prototypes for functions which are externally * available within the kernel, but not available to the user-interface. */ #ifndef _kernel_prototypes_ #define _kernel_prototypes_ #include "SnapPea.h" #include "positioned_tet.h" /************************************************************************/ /* */ /* chern_simons.c */ /* */ /************************************************************************/ extern void compute_CS_value_from_fudge(Triangulation *manifold); extern void compute_CS_fudge_from_value(Triangulation *manifold); /* * Compute the Chern-Simons value in terms of the fudge factor, and * vice-versa. Please see chern_simons.c for details. */ /************************************************************************/ /* */ /* choose_generators.c */ /* */ /************************************************************************/ extern void choose_generators( Triangulation *manifold, Boolean compute_corners, Boolean centroid_at_origin); /* * Chooses a set of generators for the fundamental group of the * Triangulation *manifold. Various functions which use generators all * call choose_generators(), so they are sure to be using the same * generator set, and their results are directly comparable. * If compute_corners is TRUE, choose_generators() computes the location * on the sphere at infinity of each ideal vertex of each Tetrahedron, * using the hyperbolic structure of the Dehn filled manifold. * If centroid_at_origin is TRUE, the initial tetrahedron is positioned * with its centroid at the origin; otherwise the initial tetrahedron * is positioned with its vertices at {0, 1, infinity, z}. * If compute_corners is FALSE, centroid_at_origin is ignored. */ void compute_fourth_corner( Complex corner[4], VertexIndex missing_corner, Orientation orientation, ComplexWithLog cwl[3]); /* * Given the location on the sphere at infinity of three of a Tetrahedron's * ideal vertices, compute the location of the fourth. */ /************************************************************************/ /* */ /* close_cusps.c */ /* */ /************************************************************************/ extern void close_cusps(Triangulation *manifold, Boolean fill_cusp[]); /* * Permanently closes the cusps of *manifold for which fill_cusp[cusp->index] * is TRUE. Assumes *manifold is triangulated as in subdivide(). */ /************************************************************************/ /* */ /* complex.c */ /* */ /************************************************************************/ extern Complex Zero, One, Two, Four, MinusOne, I, TwoPiI, Infinity; /************************************************************************/ /* */ /* core_geodesics.c */ /* */ /************************************************************************/ extern void compute_core_geodesic( Cusp *cusp, int *singularity_index, Complex length[2]); /* * This function is similar to the function core_geodesic() defined in * SnapPea.h, only it accepts a Cusp pointer as input and returns * the complex length relative to the ultimate and penultimate * hyperbolic structures (rather than reporting a precision). */ /************************************************************************/ /* */ /* cusps.c */ /* */ /************************************************************************/ extern void create_cusps(Triangulation *manifold); /* * Creates Cusp data structures for a Triangulation with valid * neighbor and gluing fields (and perhaps nothing else). */ extern void error_check_for_create_cusps(Triangulation *manifold); /* * Checks that no Cusps are present, and that all tet->cusp[] * fields are NULL. */ extern void create_one_cusp(Triangulation *manifold, Tetrahedron *tet, Boolean is_finite, VertexIndex v, int cusp_index); /* * Creates a single Cusp, incident to the given Tetrahedron at * the given ideal vertex. Assumes ideal vertices which haven't * been assigned to Cusps have tetrahedron->cusp[vertex] == NULL. */ extern void create_fake_cusps(Triangulation *manifold); /* * Creates Cusp data structures for the "fake cusps" corresponding to * finite vertices. */ extern void count_cusps(Triangulation *manifold); /* * counts the Cusps of each CuspTopology, and sets manifold->num_cusps, * manifold->num_or_cusps and manifold->num_nonor_cusps. */ extern Boolean mark_fake_cusps(Triangulation *manifold); /* * Distinguishes real cusps from fake cusps ( = finite vertices) by * computing the Euler characteristic. Sets is_finite to TRUE for * fake cusps, and renumbers all cusps so that real cusps have * consecutive nonnegative indices beginning at 0 and fake cusps * have consecutive negative indices beginning at -1. */ /************************************************************************/ /* */ /* cusp_cross_sections.c */ /* */ /************************************************************************/ extern void allocate_cross_sections(Triangulation *manifold); /* * Allocates a VertexCrossSections structure for each tet->cross_section * in manifold. */ extern void free_cross_sections(Triangulation *manifold); /* * Frees the VertexCrossSections structure and resets the * tet->cross_section pointer to NULL for each tet in manifold. */ extern void compute_cross_sections(Triangulation *manifold); /* * Sets the (already allocated) VertexCrossSections to correspond to * cusp cross sections bounding equal volumes. In general these cross * sections will NOT represent maximal disjoint horoball neighborhoods * of the cusps. */ extern void compute_tilts(Triangulation *manifold); /* * Applies the Tilt Theorem to compute the tilts from the * VertexCrossSections. Assumes the TetShapes are correct. */ extern void compute_three_edge_lengths( Tetrahedron *tet, VertexIndex v, FaceIndex f, double known_length); /* * Sets tet->cross_section->edge_length[v][f] to known_length, computes * the remaining two edge_lengths at vertex v in terms of it, and sets * the has_been_set flag to TRUE. */ extern void compute_tilts_for_one_tet(Tetrahedron *tet); /* * Applies the Tilt Theorem to compute the tilts from the * VertexCrossSections. Assumes the TetShapes are correct. */ /************************************************************************/ /* */ /* cusp_neighborhoods.c */ /* */ /************************************************************************/ extern void cn_find_third_corner( Tetrahedron *tet, Orientation h, VertexIndex v, FaceIndex f0, FaceIndex f1, FaceIndex f2); /* * Given the locations of corners f0 and f1 on the given triangle, * compute and record the location of corner f2. * This function is in spirit local to cusp_neighborhoods.c, but we * want to make it available to the 2-3 and 3-2 simplifications * in simplify_triangulation.c. */ /************************************************************************/ /* */ /* cusp_shapes.c */ /* */ /************************************************************************/ extern void compute_cusp_shapes(Triangulation *manifold, FillingStatus which_structure); /* * Computes the shape of each unfilled cusp and stores the result in * cusp->cusp_shape[which_structure]. (which_structure = initial or * current) Stores the number of decimal places of accuracy in * cusp->shape_precision[which_structure]. */ /************************************************************************/ /* */ /* Dehn_coefficients.c */ /* */ /************************************************************************/ extern Boolean all_Dehn_coefficients_are_integers(Triangulation *manifold); /* * Returns FALSE if some cusp has noninteger Dehn filling coefficients. * Returns TRUE if each cusp is either unfilled, or has integer * Dehn filling coefficients. */ extern Boolean Dehn_coefficients_are_integers(Cusp *cusp); /* * Returns FALSE if the Dehn filling coefficients of *cusp are * nonintegers. * Returns TRUE if *cusp is unfilled, or has integer Dehn filling * coefficients. */ extern Boolean all_Dehn_coefficients_are_relatively_prime_integers( Triangulation *manifold); extern Boolean Dehn_coefficients_are_relatively_prime_integers(Cusp *cusp); /* * Same as above, but integer coefficients must be relatively prime. */ extern Boolean all_cusps_are_complete(Triangulation *manifold); extern Boolean all_cusps_are_filled(Triangulation *manifold); /* * Returns TRUE if all cusps are complete (resp. filled), FALSE otherwise. */ /************************************************************************/ /* */ /* direct_product.c */ /* */ /************************************************************************/ extern Boolean is_group_direct_product(SymmetryGroup *the_group); /* * Checks whether the_group is a nonabelian, nontrivial direct product, * and sets it is_direct_product and factor[] fields accordingly. */ /************************************************************************/ /* */ /* edge_classes.c */ /* */ /************************************************************************/ extern void create_edge_classes(Triangulation *manifold); /* * Adds EdgeClasses to a partially constructed manifold which does not * yet have them. */ extern void replace_edge_classes(Triangulation *manifold); /* * Removes all EdgeClasses from a manifold and addes fresh ones. */ extern void orient_edge_classes(Triangulation *manifold); /* * Orients a neighborhood of each EdgeClass, and fills in the fields * tet->edge_class[] to described how the EdgeClass views each * incident Tetrahedron. */ /************************************************************************/ /* */ /* elements_generate_group.c */ /* */ /************************************************************************/ extern Boolean elements_generate_group(SymmetryGroup *the_group, int num_possible_generators, int possible_generators[]); /* * The array possible_generators[] contains num_possible_generators * elements of the_group. Do these elements generate the_group? */ /************************************************************************/ /* */ /* find_cusp.c */ /* */ /************************************************************************/ extern Cusp *find_cusp(Triangulation *manifold, int cusp_index); /* * Converts a cusp_index to a Cusp pointer. */ /************************************************************************/ /* */ /* finite_vertices.c */ /* */ /************************************************************************/ extern void remove_finite_vertices(Triangulation *manifold); /* * Removes finite vertices from the manifold. */ /************************************************************************/ /* */ /* gcd.c */ /* */ /************************************************************************/ extern long int gcd(long int a, long int b); /* * Returns the greatest common divisor of two nonnegative long integers, * at least one of which is nonzero. */ extern long int euclidean_algorithm(long int m, long int n, long int *a, long int *b); /* * Returns the greatest common divisor of two long integers m and n, * and also finds long integers a and b such that am + bn = gcd(m,n). * The integers m and n may be negative, but may not both be zero. */ extern long int Zq_inverse(long int p, long int q); /* * Returns the inverse of p in the ring Z/q. Assumes p and q are * relatively prime integers satisfying 0 < p < q. */ /************************************************************************/ /* */ /* gluing_equations.c */ /* */ /************************************************************************/ extern void compute_gluing_equations(Triangulation *manifold); /* * compute_gluing_equations() computes the complex gluing equations * if Triangulation *manifold is orientable, and the real gluing * equations if it's nonorientable. It assumes that space for the * appropriate set of equations has already been assigned to the cusps * and edges. */ /************************************************************************/ /* */ /* holonomy.c */ /* */ /************************************************************************/ extern void compute_holonomies(Triangulation *manifold); /* * Computes the log of the holonomy of each cusp based on the current * shapes of the tetrahedra, and stores the results into the * holonomy[ultimate][] field of the Cusp data structure. The previous * contents of that field are transferred to holonomy[penultimate][]. * * compute_holonomies() is called whenever a hyperbolic structure is * computed. Therefore if you have a manifold with a hyperbolic structure * you may assume correct values of the holonomy are already in place. */ extern void compute_the_holonomies( Triangulation *manifold, Ultimateness which_iteration); /* * Computes the holonomies for either the ultimate or penultimate * solution, according to the value of which_iteration. */ extern void compute_edge_angle_sums(Triangulation *manifold); /* * For each EdgeClass, computes the sum of the logs of the complex * edge parameters and records the result in the edge_angle_sum field. * compute_edge_angle_sums() is used in finding the hyperbolic structure; * once the hyperbolic structure is found, each edge_angle_sum will of * course be 2 pi i. */ /************************************************************************/ /* */ /* hyperbolic_structure.c */ /* */ /************************************************************************/ extern void remove_hyperbolic_structures(Triangulation *manifold); /* * Frees the TetShapes (if any) pointed to by each tet->shape[] and sets * manifold->solution_type[complete] and manifold->solution_type[filled] * to not_attempted. */ extern void initialize_tet_shapes(Triangulation *manifold); /* * Sets all Tetrahedra to be regular ideal tetrahedra. Allocates the * TetShapes if necessary. Clears the shape_histories if necessary. */ extern void polish_hyperbolic_structures(Triangulation *manifold); /* * Attempts to increase the accuracy of both the complete and the Dehn * filled hyperbolic structures already present in *manifold. It's * designed to be called following retriangulation operations which * diminish the accuracy of the TetShapes. */ extern void copy_solution(Triangulation *manifold, FillingStatus source, FillingStatus dest); extern void complete_all_cusps(Triangulation *manifold); /* * These are low-level routines used mainly within hyperbolic_structure.c, * but also to transfer the Chern-Simons invariant in drilling.c. */ /************************************************************************/ /* */ /* identify_solution_type.c */ /* */ /************************************************************************/ extern void identify_solution_type(Triangulation *manifold); /* * Identifies the type of solution contained in the *tet->shape[filled] * structures of the Tetrahedra of Triangulation *manifold, and writes * the result to manifold->solution_type[filled]. Possible values are * given by the SolutionType enum. */ extern Boolean solution_is_degenerate(Triangulation *manifold); /* * Returns TRUE if any TetShape is close to {0, 1, infinity}. * Otherwise returns FALSE. */ extern Boolean tetrahedron_is_geometric(Tetrahedron *tet); /* * Returns TRUE if all tetrahedra are geometric; returns FALSE otherwise. * A tetrahedron is geometric iff all dihedral angles lie in the * range [-FLAT_EPSILON, pi + FLAT_EPSILON]. */ /************************************************************************/ /* */ /* intersection_numbers.c */ /* */ /************************************************************************/ extern void compute_intersection_numbers(Triangulation *manifold); /* * Computes the intersection numbers of the curves stored * in the scratch_curve[][][][][] fields of the Tetrahedra, and * writes the results to the intersection_number[][] fields of * the Cusps. Please see intersection_numbers.c for details. */ extern void copy_curves_to_scratch( Triangulation *manifold, int which_set, Boolean double_copy_on_tori); /* * Copies the current peripheral curves to the scratch_curves[which_set] * fields of the manifold's Tetrahedra. If double_copy_on_tori is TRUE, * it copies peripheral curves on orientable cusps to both sheets of * the Cusps' orientation double covers. */ /************************************************************************/ /* */ /* isometry_closed.c */ /* */ /************************************************************************/ extern FuncResult compute_closed_isometry( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric); /* * If it determines with absolute rigor that manifold0 and manifold1 are * isometric, sets *are_isometric to TRUE and returns func_OK. * If it determines with absolute rigor that manifold0 and manifold1 are * nonhomeomorphic, sets *are_isometric to FALSE and returns func_OK. * If it fails to decide, returns func_failed. */ /************************************************************************/ /* */ /* isometry_cusped.c */ /* */ /************************************************************************/ extern FuncResult compute_cusped_isometries( Triangulation *manifold0, Triangulation *manifold1, IsometryList **isometry_list, IsometryList **isometry_list_of_links); /* * Finds all isometries from manifold0 to manifold1 (ignoring Dehn * fillings), stores them in an IsometryList data structure, and sets * isometry_list to point to it. If isometry_list_of_links is not NULL, * it copies those Isometries which extend to Isometries of the associated * links (i.e. those which take meridians to plus-or-minus meridians) * onto a separate list, and stores its address in *isometry_list_of_links. * If manifold0 == manifold1, this function is finding all symmetries. */ extern void compute_cusped_isomorphisms( Triangulation *manifold0, Triangulation *manifold1, IsometryList **isometry_list, IsometryList **isometry_list_of_links); /* * Computes a list of combinatorial isomorphisms between two * triangulations. It is identical to compute_cusped_isometries, * except it does not make any changes to the triangulations and in * particular does not attempt to compute canonical triangulations. * Thus it can be used on triangulations of non-hyperbolic manifolds. */ extern Boolean same_triangulation( Triangulation *manifold0, Triangulation *manifold1); /* * Simply reports the existence of a combinatorial isomorphism, * without finding a list. This is also usable with triangulations * of non-hyperbolic manifolds. */ /************************************************************************/ /* */ /* Moebius_transformations.c */ /* */ /************************************************************************/ extern CONST MoebiusTransformation Moebius_identity; extern void Moebius_copy( MoebiusTransformation *dest, MoebiusTransformation *source); extern void Moebius_invert( MoebiusTransformation *mt, MoebiusTransformation *mt_inverse); extern void Moebius_product(MoebiusTransformation *a, MoebiusTransformation *b, MoebiusTransformation *product); /* * These functions do what you would expect. */ /************************************************************************/ /* */ /* my_malloc.c */ /* */ /************************************************************************/ extern void *my_malloc(size_t bytes); /* * Calls malloc() to request a block of memory of size "bytes". * If successful, returns a pointer to the memory. * If unsuccessful, calls uAcknowledge() to notify the user, then exits. */ extern void my_free(void *ptr); /* * Calls free() to deallocate the block of memory pointed to by ptr. */ extern int malloc_calls(void); /* * Returns the number of calls to my_malloc() minus the number of * calls to my_free(), for use in debugging. */ /************************************************************************/ /* */ /* normal_surface_construction.c */ /* */ /************************************************************************/ extern void recognize_embedded_surface( Triangulation *manifold, Boolean *connected, Boolean *orientable, Boolean *two_sided, int *Euler_characteristic); /* * Reports the connectedness, orientability, two-sidedness and Euler * characteristic of the normal surface described in the parallel_edge, * num_squares and num_triangles fields of the manifold's Tetrahedra. * The present implementation assumes the manifold has no filled cusps. */ /************************************************************************/ /* */ /* o31_matrices.c */ /* */ /************************************************************************/ extern O31Matrix O31_identity; extern void o31_copy(O31Matrix dest, O31Matrix source); extern void o31_invert(O31Matrix m, O31Matrix m_inverse); extern FuncResult gl4R_invert(GL4RMatrix m, GL4RMatrix m_inverse); extern void o31_product(O31Matrix a, O31Matrix b, O31Matrix product); extern Boolean o31_equal(O31Matrix a, O31Matrix b, double epsilon); extern double o31_deviation(O31Matrix m); extern void o31_GramSchmidt(O31Matrix m); extern void o31_conjugate(O31Matrix m, O31Matrix t, O31Matrix Tmt); extern double o31_inner_product(O31Vector u, O31Vector v); extern void o31_matrix_times_vector(O31Matrix m, O31Vector v, O31Vector product); extern void o31_constant_times_vector(double r, O31Vector v, O31Vector product); extern void o31_copy_vector(O31Vector dest, O31Vector source); extern void o31_vector_sum(O31Vector a, O31Vector b, O31Vector sum); extern void o31_vector_diff(O31Vector a, O31Vector b, O31Vector diff); /* * These functions all do what you would expect. * o31_conjugate() replaces m with (t^-1) m t. */ /************************************************************************/ /* */ /* orient.c */ /* */ /************************************************************************/ extern void orient(Triangulation *manifold); /* * Attempts to consistently orient the Tetrahedra of the * Triangulation *manifold. Sets manifold->orientability to * oriented_manifold or nonorientable_manifold, as appropriate. */ extern void extend_orientation( Triangulation *manifold, Tetrahedron *initial_tet); /* * Extends the orientation of the given initial_tet to the entire manifold. */ extern void fix_peripheral_orientations(Triangulation *manifold); /* * Makes sure each {meridian, longitude} pairs obeys the right-hand rule. * Should be called only for orientable manifolds, typically following * a call to orient(). */ /************************************************************************/ /* */ /* peripheral_curves.c */ /* */ /************************************************************************/ extern void peripheral_curves(Triangulation *manifold); /* * Puts a meridian and longitude on each cusp, and records each cusp's * CuspTopology in the field cusp->topology. If the manifold is * oriented, the meridian and longitude adhere to the usual * orientation convention (see peripheral_curves.c for details). */ extern void peripheral_curves_as_needed(Triangulation *manifold); /* * Like peripheral_curves(), but puts a meridian and longitude * only onto cusps which don't already have them. Pre-exisiting * meridians and longitudes are left untouched. */ /************************************************************************/ /* */ /* polyhedral_group.c */ /* */ /************************************************************************/ extern Boolean is_group_polyhedral(SymmetryGroup *the_group); /* * Checks whether the_group is a polyhedral group (i.e. (binary) dihedral, * tetrahedral, octahedral or icosahedral), and fills in the_group's * is_polyhedral, is_full_group, p, q and r fields accordingly. Assumes * the_group's is_dihedral field has already been set (but this * restriction could be eliminated -- see the documentation at the top * of triangle_group.c). */ /************************************************************************/ /* */ /* positioned_tet.c */ /* */ /************************************************************************/ extern void veer_left(PositionedTet *ptet); extern void veer_right(PositionedTet *ptet); /* * Accepts a PositionedTet and replaces it with the neighboring * tetrahedron incident to the left (resp. right) face. The positioning * is as if you rotated the old tetrahedron onto the new one about the * axis defined by the edge of the old tetrahedron lying between * near_face and left_face (resp. right_face). */ extern void veer_backwards(PositionedTet *ptet); /* * Accepts a PositionedTet and replaces it with the neighboring * tetrahedron incident to the near_face. The positioning * is as if you rotated the old tetrahedron onto the new one * about an axis running up the center of the near_face from * the bottom_face to the ideal vertex opposite the bottom_face. */ extern Boolean same_positioned_tet(PositionedTet *ptet0, PositionedTet *ptet1); /* * Returns TRUE if the two PositionedTets are equal, FALSE otherwise. */ extern void set_left_edge(EdgeClass *edge, PositionedTet *ptet); /* * Fills in the fields of *ptet so as to position the EdgeClass *edge * between the near_face and the left_face of *ptet. */ /************************************************************************/ /* */ /* precision.c */ /* */ /************************************************************************/ extern int decimal_places_of_accuracy(double x, double y); extern int complex_decimal_places_of_accuracy(Complex x, Complex y); /* * Returns the number of decimal places which x and y have in * common. Typically x and y will be two estimates of the same * computed quantity (e.g. the volume of a manifold), and * decimal_places_of_accuracy() will be used to tell the user * interface how many decimal places should be printed. */ /************************************************************************/ /* */ /* simplify_triangulation.c */ /* */ /************************************************************************/ /* * The high-level functions basic_simplification() and * randomize_triangulation() are declared in SnapPea.h. */ extern FuncResult cancel_tetrahedra(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); extern FuncResult three_to_two(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); extern FuncResult two_to_three(Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr); extern void one_to_four(Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index); /* * Low-level functions for * * cancelling two Tetrahedra which share a common edge of order 2, * * replacing three Tetrahedra surrounding a common edge with * two Tetrahedra sharing a common face * * replacing two Tetrahedra sharing a common face with * three Tetrahedra surrounding a common edge * * replacing a Tetrahedron with four Tetrahedra meeting a point. * * If an operation cannot be performed because of a topological or geometric * obstruction, the function does nothing and returns func_failed. * Otherwise, it performs the operation and returns func_OK. * * The function one_to_four() will always succeed, and therefore returns void. * It introduces a finite vertex at the center of the Tetrahedron, and therefore * cannot be used when a hyperbolic structure is present. * * The three_to_two(), two_to_three() and one_to_four() operations each correspond * to a projection of a 4-simplex. * * For further details, see the documentation in simplify_triangulation.c, * both at the top of file and immediately preceding each function. */ /************************************************************************/ /* */ /* sl2c_matrices.c */ /* */ /************************************************************************/ extern void sl2c_copy(SL2CMatrix dest, CONST SL2CMatrix source); extern void sl2c_invert(CONST SL2CMatrix a, SL2CMatrix inverse); extern void sl2c_complex_conjugate(CONST SL2CMatrix a, SL2CMatrix conjugate); extern void sl2c_product(CONST SL2CMatrix a, CONST SL2CMatrix b, SL2CMatrix product); extern void sl2c_adjoint(CONST SL2CMatrix a, SL2CMatrix adjoint); extern void sl2c_normalize(SL2CMatrix a); extern Boolean sl2c_matrix_is_real(CONST SL2CMatrix a); /************************************************************************/ /* */ /* solve_equations.c */ /* */ /************************************************************************/ extern FuncResult solve_complex_equations(Complex **complex_equations, int num_rows, int num_columns, Complex *solution); extern FuncResult solve_real_equations(double **real_equations, int num_rows, int num_columns, double *solution); /* * These functions solve num_rows linear equations in num_columns * variables. For more information, see solve_equations.c. */ /************************************************************************/ /* */ /* subdivide.c */ /* */ /************************************************************************/ extern Triangulation *subdivide(Triangulation *manifold, char *new_name); /* * Returns a pointer to a subdivision of *manifold. See subdivide.c for * more details. subdivide() does not change *manifold in any way. */ /************************************************************************/ /* */ /* symmetric_group.c */ /* */ /************************************************************************/ extern Boolean is_group_S5(SymmetryGroup *the_group); /* * Is the_group S5? */ /************************************************************************/ /* */ /* symmetry_group_cusped.c */ /* */ /************************************************************************/ extern FuncResult compute_cusped_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link); /* * Computes the SymmetryGroup of a cusped manifold, and also the * SymmetryGroup of the corresponding link (defined at the top of * symmetry_group_cusped.c). */ extern void compute_orders_of_elements(SymmetryGroup *the_group); /* * Assumes the_group->order and the_group->product[][] are already * in place, and computes the_group->order_of_element[]. */ extern void compute_inverses(SymmetryGroup *the_group); /* * Assumes the_group->order and the_group->product[][] are already * in place, and computes the_group->inverse[]. */ extern void recognize_group(SymmetryGroup *the_group); /* * Attempts to recognize the_group as abelian, cyclic, dihedral, * polyhedral or a direct product. */ /************************************************************************/ /* */ /* symmetry_group_closed.c */ /* */ /************************************************************************/ extern FuncResult compute_closed_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group, Triangulation **symmetric_triangulation, Boolean *is_full_group); /* * Attempts to compute the symmetry group of a closed manifold. * Also provides a symmetry Dehn filling description. */ /************************************************************************/ /* */ /* terse_triangulation.c */ /* */ /************************************************************************/ extern TerseTriangulation *alloc_terse(int num_tetrahedra); /* * Allocates a TerseTriangulation. */ extern TerseTriangulation *tri_to_terse_with_base( Triangulation *manifold, Tetrahedron *base_tetrahedron, Permutation base_permutation); /* * Similar to tri_to_terse(), but allows an explicit choice * of base tetrahedron. */ /************************************************************************/ /* */ /* tet_shapes.c */ /* */ /************************************************************************/ extern void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1, Tetrahedron *tet2, EdgeIndex e2); /* * Add the angles of edge e0 of tet0 and edge e1 of tet1 and writes the * results to edge e2 of tet2. Accounts for edge_orientations. The * EdgeIndices should be in the range 0-5, not 0-2. Chooses arguments * in the range [(-1/2) pi, (3/2) pi], regardless of the angles of the * summands. */ extern Boolean angles_sum_to_zero(Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1); /* * angles_sum_to_zero() returns TRUE iff one of the angles * (shape[complete]->cwl[ultimate] or shape[filled]->cwl[ultimate]) * at edge e0 of tet0 cancels the corresponding angle at edge e1 * of tet1 (mod 2 pi). Accounts for edge_orientations. */ extern void compute_remaining_angles(Tetrahedron *tet, EdgeIndex e); /* * Assumes the angle at edge e is correct, and computes the remaining * angles in terms of it. Chooses arguments in the range [(-1/2) pi, * (3/2) pi]. */ /************************************************************************/ /* */ /* tidy_peripheral_curves.c */ /* */ /************************************************************************/ extern void tidy_peripheral_curves(Triangulation *manifold); /* * Replaces the existing peripheral curves on *manifold with an * equivalent set which is efficient and contains no trivial loops. */ /************************************************************************/ /* */ /* transcendentals.c */ /* */ /************************************************************************/ extern double safe_acos(double x); extern double safe_asin(double x); extern double safe_sqrt(double x); /* * These are like the usual acos(), asin() and sqrt(), * except that they round almost-legal values to legal ones. * E.g. safe_acos(1.00000001) = acos(1.0) = 0.0, not NaN. */ extern double arcsinh(double x); extern double arccosh(double x); /* * The inverse hyperbolic sine and cosine, which the standard ANSI * libraries lack. [Some but not all platforms now include asinh() * and acosh(), so I've renamed my own implementations arcsinh() * and arccosh() to avoid conflicts. JRW 2000/02/20] */ /************************************************************************/ /* */ /* triangulations.c */ /* */ /************************************************************************/ extern void initialize_triangulation(Triangulation *manifold); /* * Initializes the fields of *manifold to correspond to the * empty Triangulation. */ extern void initialize_tetrahedron(Tetrahedron *tet); extern void initialize_cusp(Cusp *cusp); extern void initialize_edge_class(EdgeClass *edge_class); /* * Initialize the fields of Tetrahedra, Cusps and EdgeClasses to the * most benign values possible. */ extern void free_tetrahedron(Tetrahedron *tet); /* * Frees a Tetrahedron and all attached data structures, but does NOT * remove the Tetrahedron from any doubly linked list it may be on. */ extern void clear_shape_history(Tetrahedron *tet); extern void copy_shape_history(ShapeInversion *source, ShapeInversion **dest); extern void clear_one_shape_history(Tetrahedron *tet, FillingStatus which_history); /* * What you'd expect. See triangulation.c for details. */ extern FuncResult check_Euler_characteristic_of_boundary(Triangulation *manifold); /* * Returns func_OK if the Euler characteristic of the total boundary of * the manifold is zero. Otherwise returns func_failed. */ extern void number_the_tetrahedra(Triangulation *manifold); /* * Sets each Tetrahedron's index field equal to its position in * the linked list. Indices range from 0 to (num_tetrahedra - 1). */ extern void number_the_edge_classes(Triangulation *manifold); /* * Sets each EdgeClass's index field equal to its position in the * linked list. Indices range from 0 to ((number of EdgeClasses) - 1). */ extern Permutation compose_permutations(Permutation p1, Permutation p0); /* * Returns the composition of two permutations. Permutations are * composed right-to-left: the composition p1 o p0 is what you get * by first doing p0, then p1. */ /************************************************************************/ /* */ /* update_shapes.c */ /* */ /************************************************************************/ extern void update_shapes(Triangulation *manifold, Complex *delta); /* * Updates the shapes of the tetrahedra in *manifold by the amounts * specified in the array delta. If necessary, delta is first scaled * so that no delta[i].real or delta[i].imag exceeds the limit * specified by the constant allowable_change (see update_shapes.c * for more details). The entries in delta are interpreted relative * to the coordinate system given by the coordinate_system field of * each Tetrahedron, and the indexing of delta is assumed to correspond * to the index field of each tetrahedron. */ /************************************************************************/ /* */ /* volume.c */ /* */ /************************************************************************/ extern double birectangular_tetrahedron_volume( O31Vector a, O31Vector b, O31Vector c, O31Vector d); /* * Computes the volume of a birectangular tetrahedron using Vinberg's * article. Please see volume.c for a citation to Vinberg's article. */ #endif regina-4.95/engine/snappea/kernel/kernel_typedefs.h000644 000765 000024 00000042206 12235724571 022333 0ustar00babstaff000000 000000 /* * kernel_typedefs.h * * This file contains #defines, typedefs and enums common to * many parts of the SnapPea kernel (but hidden from the UI). * Typedefs for more complicated data structures are found in * separate files (e.g. triangulation.h). * * For C++ compatibility I've avoided definitions of the form * * typedef enum * { * foo_up, * foo_down * } Foo; * * in favor of * * typedef int Foo; * enum * { * foo_up, * foo_down * }; * * The problem with the former definition is that C++ insists on an * explicit typecast to assign an integer to a variable of type Foo, * so code like "for (foo = 0; foo < 2; foo++)" won't compile. */ #ifndef _kernel_typedefs_ #define _kernel_typedefs_ #include "SnapPea.h" #define NEW_STRUCT(struct_type) (struct_type *) my_malloc((size_t) sizeof(struct_type)) #define NEW_ARRAY(n, struct_type) (struct_type *) my_malloc((size_t) (n) * sizeof(struct_type)) #define INSERT_BEFORE(new, old) { \ (new)->next = (old); \ (new)->prev = (old)->prev; \ (new)->prev->next = (new); \ (new)->next->prev = (new); \ } #define INSERT_AFTER(new, old) { \ (new)->prev = (old); \ (new)->next = (old)->next; \ (new)->prev->next = (new); \ (new)->next->prev = (new); \ } #define REMOVE_NODE(node) { \ (node)->next->prev = (node)->prev; \ (node)->prev->next = (node)->next; \ } #define ABS(x) (((x) >= 0) ? (x) : -(x)) /* * Gluings of ideal tetrahedra. * * The neighbor and gluing fields of a Tetrahedron tell how the Tetrahedron * is glued to the other Tetrahedra in the Triangulation. The vertices of * each Tetrahedron in the Triangulation are implicitly indexed by the integers * {0,1,2,3}, and the faces are indexed according to the index of the opposite * vertex. The field tet->neighbor[i] contains a pointer to the Tetrahedron * which face i of Tetrahedron tet glues to. The field tet->gluing[i] describes * the gluing as a Permutation of the set {0,1,2,3}, using the following scheme * devised by Bill Thurston. If j {j = 0,1,2,3} does not equal i, then j is the * index of a vertex on face i of Tetrahedron tet, and the Permutation * tet->gluing[i] applied to j gives the index of the vertex of Tetrahedron * tet->neighbor[i] that vertex j glues to. If j equals i, then j is the index * of the vertex of Tetrahedron tet opposite face i, and the Permutation * tet->gluing[i] applied to j gives the index of the vertex of Tetrahedron * tet->neighbor[i] opposite the image of face i; equivalently -- and more * usefully -- j is the index of the given face on Tetrahedron tet, and the * Permutation applied to j gives the index of its image face on * tet->neighbor[i]. * * Each gluing field contains a permutation on {0,1,2,3}, represented in a * single byte as follows. The byte is conceptually divided into four two-bit * fields. The two high-order bits contain the image of 3 under the * permutation, the next two bits contain the image of 2, etc. So, for example, * the permutation which takes 3210 to 2013 would be represented as 10000111. * This representation is space-efficient, and also allows the definition of * the following macro for evaluating permuations. */ typedef unsigned char Permutation; /* * EVALUATE(p,n) is the image of n (n = 0,1,2,3) under the permutation p. */ #define EVALUATE(p,n) ( ((p) >> 2*(n)) & 0x03 ) /* * CREATE_PERMUTATION() is, roughly speaking, the inverse of EVALUATE(). * CREATE_PERMUTATION(a,pa,b,pb,c,pc,d,pd) creates the one-byte Permutation * which takes a to pa, b to pb, c to pc and d to pd. For example, * CREATE_PERMUTATION(1,1,3,2,0,3,2,0) defines the permutation 2013 (base 4), * which equals 10000111 (binary) or 0x87 (hex). Symbolically this is * the permutation {3->2, 2->0, 1->1, 0->3}. */ #define CREATE_PERMUTATION(a,pa,b,pb,c,pc,d,pd) ((Permutation) (((pa) << 2*(a)) + ((pb) << 2*(b)) + ((pc) << 2*(c)) + ((pd) << 2*(d)))) /* * For convenience, we #define the IDENTITY_PERMUTATION. */ #define IDENTITY_PERMUTATION 0xE4 /* = 11100100 = 3210 */ /* * FLOW(,) is used in reconstructing simple closed curves from * homological information. Specifically, let A be the number of times * a curve intersects one side of a triangle, and B be the number of times * it intersects a different side (of the same triangle). Then FLOW(A,B) * is the number of strands of the curve passing from the first side to * the second. */ #define FLOW(A,B) ( ((A<0)^(B<0)) ? \ (((A<0)^(A+B<0)) ? A : -B) : \ 0 ) #define MAX(A,B) ((A) > (B) ? (A) : (B)) #define MIN(A,B) ((A) < (B) ? (A) : (B)) #define DET2(M) ((M[0][0])*(M[1][1]) - (M[0][1])*(M[1][0])) /* Some unix C libraries define PI in math.h, */ /* and complain about a second definition. */ #ifndef PI #define PI 3.14159265358979323846 #endif #define TWO_PI 6.28318530717958647693 #define FOUR_PI 12.56637061435917295385 #define PI_OVER_2 1.57079632679489661923 #define PI_OVER_3 1.04719755119659774615 #define THREE_PI_OVER_2 4.71238898038468985769 #define ROOT_3_OVER_2 0.86602540378443864676 #define ROOT_3 1.73205080756887729352 #define TRUE 1 #define FALSE 0 typedef signed char VertexIndex, EdgeIndex, FaceIndex; /* * The Orientation of a tetrahedron is determined by placing your hand * inside the tetrahedron with your wrist at face 0, your thumb at face 1, * your index finger at face 2 and your middle finger at face 3. If this * is most comfortably accomplished with your right hand, then the * tetrahedron is right_handed. Otherwise it's left_handed. * * Portions of the code assume that right_handed == 0 and * left_handed ==1, so please don't change them. */ typedef int Orientation; enum { right_handed = 0, left_handed = 1, unknown_orientation }; typedef MatrixParity GluingParity; /* * The constants complete and filled facilitate reference * to the shape of a Tetrahedron as part of the complete or * Dehn filled hyperbolic structure, respectively. */ typedef int FillingStatus; enum { complete, filled }; /* * The constants initial and current are synonymous with complete * and filled, respectively. They are used to refer to cusp shapes. * That is, cusp_shape[initial] is the cusp shape defined by the * complete hyperbolic structure, and cusp_shape[current] is the * cusp shape defined by the current Dehn filling. In both cases * the cusp in question is complete (even though other cusps might * not be) which is why I decided to use the terms initial and current * instead of complete and filled. */ enum { initial, current }; /* * The constants ultimate and penultimate facilitate reference * to the approximate solutions at the ultimate and penultimate * iterations of Newton's method. */ typedef int Ultimateness; enum { ultimate, penultimate }; /* * The ShapeInversion data structure records the event that * a Tetrahedron changes it shape, i.e. goes from having z.real >= 0 * to z.real < 0. This allows the Chern-Simons code (and potentially * other, future additions to SnapPea) to work out the exact path * of the Tetrahedron shape through the parameter space, up to isotopy. * * Each ShapeInversion records which of the three edge parameters * (0, 1 or 2) passed through pi (mod 2 pi) as the Tetrahedron changed * shape. The other two parameters will have passed through 0 (mod 2 pi). * * A stack of ShapeInversions represents the history of a shape. The * stack is implemented as a NULL-terminated linked list. Two consecutive * ShapeInversions with the same edge parameter passing through pi * will cancel. * * Each Tetrahedron has two ShapeInversion stacks, one for the complete * hyperbolic structure and the other for the filled hyperbolic structure. * Both are computed relative to the ultimate hyperbolic structure. */ typedef struct ShapeInversion { /* * Which edge parameter passed through pi (mod 2 pi) ? */ EdgeIndex wide_angle; /* * The next field points to the next ShapeInversion on the * linked list, or is NULL if there are no more. */ struct ShapeInversion *next; } ShapeInversion; /* * The constants M and L provide indices for 2-element arrays * and 2 x 2 matrices which refer to peripheral curves. * * For example, the Tetrahedron data structure records a meridian * as curve[M] and a longitude as curve[L]. * * The file i/o routines assume M == 0 and L == 1, so please * don't change them. */ typedef int PeripheralCurve; enum { M = 0, L = 1 }; /* * The TraceDirection typedef is used to specify whether a curve * should be traced forwards or backwards. */ typedef int TraceDirection; enum { trace_forwards, trace_backwards }; /* * The GeneratorStatus typedef specifies the existence and direction * of a generator of a manifold's fundamental group. See choose_generators.c * for details. */ typedef int GeneratorStatus; enum { unassigned_generator, /* the algorithm has not yet considered the face */ outbound_generator, /* the generator is directed outwards */ inbound_generator, /* the generator is directed inwards */ not_a_generator /* the face does not correspond to a generator */ }; /* * The VertexCrossSections data structure represents a cross section * of each ideal vertex of the Tetrahedron which owns it. * The vertex cross section at vertex v of Tetrahedron tet is a * triangle. The length of its edge incident to face f of tet is * stored as tet->cross_section->edge_length[v][f]. (The edge_length * is undefined when v == f.) Please see cusp_cross_sections.c for * more details. * * By convention, * * when no cusp cross sections are in place, the cross_section field * of each Tetrahedron is set to NULL, and * * when cusp cross sections are created, the routine that creates * them must allocate the VertexCrossSections structures. * * Thus, routines which modify a triangulation (e.g. the two_to_three() * and three_to_two() moves) know that they must keep track of cusp cross * sections if and only if the cross_section fields of the Tetrahedra are * not NULL. */ typedef struct { double edge_length[4][4]; Boolean has_been_set[4]; } VertexCrossSections; /* * cusp_neighborhoods.c needs to maintain a consistent coordinate system * on each cusp cross section, so that horoballs etc. appear at consistent * positions even as the canonical Triangulation changes. * Each Tetrahedron's cusp_nbhd_position field keeps a pointer to a * struct CuspNbhdPosition while cusp neighborhoods are being maintained; * at all other times that pointer is set to NULL. This offers the same * two advantages described in triangulation.h for the TetShape pointer, * namely, * * (1) we save memory when CuspNbhdPositions aren't needed, and * * (2) the low-level retriangulation routines can tell whether * they need to maintain CuspNbhdPositions or not. * * * The cusp cross sections intersect each ideal tetrahedron in four small * triangles. The CuspNbhdPosition structure records the positions of * each of the four triangles' three vertices. Actually, we keep track * of the left_handed and right_handed sheets separately, so we can work * with the double covers of Klein bottle cusps; that way we're always * working with a torus. The indices of x[h][v][f] are as follows: * * h = left_handed or right_handed tells the sheet we're on, * v = 0,1,2,3 tells the vertex * f = 0,1,2,3 (for f != v) tells the face opposite the * ideal vertex of interest * * Note: The fields x[h][v][v], which are not needed for storing * the corner coordinates of triangles, are pressed into service in * get_cusp_neighborhood_Ford_domain() to store the locations of the * Ford domain vertices. Cf. the FORD_VERTEX(h,v) macro below. * * The Boolean field in_use[h][v] records which x[h][v][] fields * are actually in use. For a Klein bottle cusp, they all will be in use * (because we work with the double cover). For a torus cusp only half * will be in use (in an orientable manifold they'll be the ones on the * right_handed sheet, in a nonorientable manifold one sheet is chosen * arbitrarily). */ typedef struct { Complex x[2][4][4]; Boolean in_use[2][4]; } CuspNbhdPosition; #define FORD_VERTEX(x,h,v) x[h][v][v] /* * The CanonizeInfo data structure records information used by * canonical_retriangulation() in canonize_part_2.c. It is almost local * to that file, but not quite. The low-level retriangulation functions * two_to_three() and one_to_four() in simplify_triangulation.c need to be * able to check whether CanonizeInfo is present, and if so transfer the * information correctly to the new Tetrahedra they create. Thus we make * the convention that the canonize_info field of each Tetrahedron is NULL * if no CanonizeInfo is present, and points to a CanonizeInfo structure if * such information is present. */ typedef int FaceStatus; enum { opaque_face, /* The face lies in the 2-skeleton of the */ /* canonical cell decomposition. */ transparent_face, /* The face lies in the interior of a 3-cell */ /* in the canonical cell decomposition, but */ /* has not yet been worked into the coned */ /* polyhedron. */ inside_cone_face /* The face lies in the interior of a 3-cell */ /* in the canonical cell decomposition, and */ /* also lies in the interior of the coned */ /* polyhedron. */ }; typedef struct { FaceStatus face_status[4]; Boolean part_of_coned_cell; } CanonizeInfo; /* * In the definition of a Tetrahedron, it's useful to include a * general purpose pointer which different kernel modules may use * to temporarily append different data structures to the Tetrahedron. * Even though different modules will append different temporary * data structures, we must have a single global declaration of the * pointer in triangulation.h. One solution would be to declare the * pointer as a pointer to a void, but this leads to a lot of messy * typecasting. Instead, we use the following opaque typedef of a * (globally nonexistent) struct extra. Each module may then define * struct extra as it sees fit. There is no need for the definition * of struct extra found in one source file to be consistent with * that of another source file, although obviously when a function in * one file calls a function in another the programmer must be absolutely * certain that the files do not make conflicting definitions of * struct extra. * * As a guard against conflicts, we make the convention that the * Extra field in the Tetrahedron data structure is always set to NULL * when not in use. Any module that wants to use the fields first * checks whether it is NULL, and reports an error and exits if it isn't. */ typedef struct extra Extra; /* * Normally one expects all the code to be compiled with the same set * of calling conventions. An exception arises when using C++ * in conjunction with ANSI library routines that require callback functions. * Specifically, qsort() wants a comparision function declared using * C calling conventions. Typically this requires that the C++ code * declare the callback function as cdecl or _cdecl ("C declaration"). * If this doesn't work, try the following syntax (which will require * something like "#define CDECL_END }" to provide the closing bracket). * * extern "C" * { * int comp(const void *a, const void *b) * { * ... * } * } * * If all else fails, read the documentation for your C++ compiler, * and contact weeks@northnet.org if you have problems. If this * gets to be a headache, I can replace the standard library's qsort() * with a hard-coded qsort (such as the one on page 120 of K&R 2nd ed.). */ #ifdef __cplusplus #define CDECL _cdecl //#define CDECL cdecl #else #define CDECL /* We're not using C++, so CDECL may be empty. */ #endif #endif regina-4.95/engine/snappea/kernel/link_projection.h000644 000765 000024 00000010617 12235724571 022342 0ustar00babstaff000000 000000 /* Modified 8/19/98 by NMD to add a label field to each crossing */ /* * link_projection.h * * This file provides the data format in which the UI passes * link projections to the kernel. All typedefs begin with * "KLP" ("Kernel Link Projection") to avoid name conflicts * with the UI's private link projection data structure. */ #ifndef _link_projection_ #define _link_projection_ typedef struct KLPCrossing KLPCrossing; typedef struct KLPProjection KLPProjection; /* * The KLPStrandType and KLPDirectionType enums are used to index * array entires, so their values must be 0 and 1. (But the code * does not rely on which has value 0 and which has value 1.) * * JRW 2000/11/12 Use fake "typedef enums" instead of real ones, * for the reasons explained at the top of kernel_typedefs.h. */ /* * If you view a crossing (from above) so that the strands go in the * direction of the postive x- and y-axes, then the strand going in * the x-direction is the KLPStrandX, and the strand going in the * y-direction is the KLPStrandY. Note that this definition does not * depend on which is the overstrand and which is the understrand. * * KLPStrandY * ^ * | * ----+---> KLPStrandX * | * | */ typedef int KLPStrandType; enum { KLPStrandX = 0, KLPStrandY, KLPStrandUnknown }; /* * The backward and forward directions are what you would expect. * * KLPBackward ---------> KLPForward */ typedef int KLPDirectionType; enum { KLPBackward = 0, KLPForward, KLPDirectionUnknown }; /* * A crossing is either a clockwise (CL) or counterclockwise (CCL) * half twist. * * _ _ _ _ * |\ /| |\ /| * \ / \ / * / \ * / \ / \ * / \ / \ * KLPHalfTwistCL KLPHalfTwistCCL */ typedef int KLPCrossingType; enum { KLPHalfTwistCL, KLPHalfTwistCCL, KLPCrossingTypeUnknown }; /* * A link projection is essentially an array of crossings. */ struct KLPProjection { /* * How many crossings are there? */ int num_crossings; /* * How many free loops (i.e. link components with no crossings) are * there? For a hyperbolic link, the number of free loops must be 0. */ int num_free_loops; /* * How many link components (including the free loops) are there? */ int num_components; /* * Here's a pointer to the array of crossings. */ KLPCrossing *crossings; }; /* * Each crossing has pointers to its four neighbors, * along with information about its own handedness. */ struct KLPCrossing { /* * The four neighbors are * * neighbor[KLPStrandX][KLPBackward] * neighbor[KLPStrandX][KLPForward ] * neighbor[KLPStrandY][KLPBackward] * neighbor[KLPStrandY][KLPForward ] * * For example, if you follow the x-strand in the backward direction, * you'll arrive at neighbor[KLPStrandX][KLPBackward]. */ KLPCrossing *neighbor[2][2]; /* * When you arrive at a neighbor, you could arrive at either the * x-strand or the y-strand. The strand[][] field says which it is. * * For example, if you follow the x-strand in the backward direction, * you'll arrive at strand[KLPStrandX][KLPBackward]. */ KLPStrandType strand[2][2]; /* * The crossing is either a clockwise or counterclockwise half twist. */ KLPCrossingType handedness; /* * To which component of the link does each strand belong? A link * component is given by an integer from 0 to (#components - 1). * For example, if component[KLPStrandX] == 0 and * component[KLPStrandY] == 2, then the x-strand is part of component * number 0 and the y-strand is part of component number 2. */ int component[2]; /* Field added 8/19/98 by NMD to store info about wirt. presentation */ /* first index should be a KLPStrand and the second a KLPDirectionType */ int label[2][2]; }; #endif regina-4.95/engine/snappea/kernel/my_malloc.c000644 000765 000024 00000013201 12235724562 021110 0ustar00babstaff000000 000000 /* * my_malloc.c * * This file provides an interface to malloc() and free() which * * (1) Keeps track of the number of calls to malloc() minus the number * of calls to free(). This allows easy detection of programming * errors which allocate more memory than they free (or vice versa). * * (2) Aborts the program if malloc() returns NULL. This saves having * to check whether malloc() == NULL at each point where malloc() * is called. * * All kernel routines use my_malloc() and my_free(); * no UI routines do so. * * The UI should call verify_my_malloc_usage() upon exit, to verify * that the number of calls to my_malloc() was exactly balanced by * the number of calls to my_free(). * * The remainder of this comment deals with a debugging feature, and * may be safely ignored until such time as you start freeing memory * you haven't allocated, or writing off the ends of arrays. * * If the constant DEBUG_MALLOC is #defined to be 1 (see below) then * my_malloc() and my_free() maintain a linked list * of the addresses of the blocks of memory which have been allocated. * If some other part of the kernel attempts to free memory which has * not been allocated (or free the same memory twice), free() generates * and error message and exits. Because this feature is for debugging * purposes only, no attempt is made to be efficient. (Obviously a * binary tree would be more efficient than a linked list, but you'd have * to account for the fact that the memory is most likely allocated * in linear order. Reversing the order of the bytes would be simpler * than implementing a balanced tree.) * * Note that the DEBUG_MALLOC feature itself uses memory, but does not * record its own usage in the linked list. This is OK. Its purpose * is to help debug SnapPea, not malloc(). * * If DEBUG_MALLOC is turned on, my_malloc() tacks four extra bytes on * the end of every requested block of memory, and writes in an * arbitrary (but well defined) sequence of four characters. When the * memory is freed, my_free() checks those four characters to see whether * they've been overwritten. This is not a perfect guarantee against * writing past the ends of array, but it should detect at least some * errors. */ #include "kernel.h" #include /* needed for malloc() */ #include /* needed for sprintf() */ static int net_malloc_calls = 0; /* * The debugging feature is normally off. */ #define DEBUG_MALLOC 0 #if DEBUG_MALLOC #define MAX_BYTES 50000 typedef struct memnode { void *address; size_t bytes; struct memnode *next; } MemNode; static MemNode mem_list = {NULL, 0, NULL}; static const char secret_code[5] = "Adam"; static Boolean message_given = FALSE; #endif void *my_malloc( size_t bytes) { void *ptr; #if DEBUG_MALLOC MemNode *new_mem_node; char *error_bytes; int i; #endif #if DEBUG_MALLOC if (message_given == FALSE) { uAcknowledge("The my_malloc() memory allocator is in debugging mode."); message_given = TRUE; } #endif #if DEBUG_MALLOC if (bytes < 0) { uAcknowledge("A negative number of bytes were requested in my_malloc()."); exit(3); } if (bytes > MAX_BYTES) uAcknowledge("Too many bytes were requested in my_malloc()."); #endif /* * Most likely malloc() and free() would correctly handle * a request for zero bytes, but why take chances? */ if (bytes == 0) bytes = 1; #if DEBUG_MALLOC ptr = malloc(bytes + 4); #else ptr = malloc(bytes); #endif if (ptr == NULL) uAbortMemoryFull(); net_malloc_calls++; #if DEBUG_MALLOC error_bytes = (char *) ptr + bytes; for (i = 0; i < 4; i++) error_bytes[i] = secret_code[i]; new_mem_node = (MemNode *) malloc((size_t) sizeof(MemNode)); if (new_mem_node == NULL) { uAcknowledge("out of memory"); exit(4); } new_mem_node->address = ptr; new_mem_node->bytes = bytes; new_mem_node->next = mem_list.next; mem_list.next = new_mem_node; #endif return ptr; } void my_free( void *ptr) { #if DEBUG_MALLOC Boolean old_node_found; MemNode *old_mem_node, *prev_mem_node; size_t bytes; char *error_bytes; int i; #endif #if DEBUG_MALLOC old_node_found = FALSE; for ( prev_mem_node = &mem_list, old_mem_node = mem_list.next; old_mem_node != NULL; old_mem_node = old_mem_node->next, prev_mem_node = prev_mem_node->next) if (old_mem_node->address == ptr) { old_node_found = TRUE; bytes = old_mem_node->bytes; prev_mem_node->next = old_mem_node->next; free(old_mem_node); break; } if (old_node_found == FALSE) { uAcknowledge("A bad address was passed to my_free()."); exit(5); } error_bytes = (char *) ptr + bytes; for (i = 0; i < 4; i++) if (error_bytes[i] != secret_code[i]) { uAcknowledge("my_free() received a corrupted array."); exit(6); } #endif free(ptr); net_malloc_calls--; } int malloc_calls() { return net_malloc_calls; } void verify_my_malloc_usage() { char the_message[256]; if (net_malloc_calls != 0) { sprintf(the_message, "Memory allocation error:\rThere were %d %s calls to my_malloc() than to my_free().", net_malloc_calls > 0 ? net_malloc_calls : - net_malloc_calls, net_malloc_calls > 0 ? "more" : "fewer"); uAcknowledge(the_message); } } regina-4.95/engine/snappea/kernel/o31_matrices.c000644 000765 000024 00000035073 12235724562 021440 0ustar00babstaff000000 000000 /* * o31_matrices.c * * This file provides the following functions for working with O31Matrices: * * void o31_copy(O31Matrix dest, O31Matrix source); * void o31_invert(O31Matrix m, O31Matrix m_inverse); * FuncResult gl4R_invert(GL4RMatrix m, GL4RMatrix m_inverse); * double gl4R_determinant(GL4RMatrix m); * void o31_product(O31Matrix a, O31Matrix b, O31Matrix product); * Boolean o31_equal(O31Matrix a, O31Matrix b, double epsilon); * double o31_trace(O31Matrix m); * double o31_deviation(O31Matrix m); * void o31_GramSchmidt(O31Matrix m); * void o31_conjugate(O31Matrix m, O31Matrix t, O31Matrix Tmt); * double o31_inner_product(O31Vector u, O31Vector v); * void o31_matrix_times_vector(O31Matrix m, O31Vector v, O31Vector product); * void o31_constant_times_vector(double r, O31Vector v, O31Vector product); * void o31_copy_vector(O31Vector dest, O31Vector source); * void o31_vector_sum(O31Vector a, O31Vector b, O31Vector sum); * void o31_vector_diff(O31Vector a, O31Vector b, O31Vector diff); */ #include "kernel.h" /* * gl4R_invert will consider a matrix to be singular iff one of the * absolute value of one of the pivots is less than SINGULAR_MATRIX_EPSILON. */ #define SINGULAR_MATRIX_EPSILON 1e-2 #define COLUMN_PRODUCT(m, i, j) \ (-m[0][i]*m[0][j] + m[1][i]*m[1][j] + m[2][i]*m[2][j] + m[3][i]*m[3][j]) O31Matrix O31_identity = { {1.0, 0.0, 0.0, 0.0}, {0.0, 1.0, 0.0, 0.0}, {0.0, 0.0, 1.0, 0.0}, {0.0, 0.0, 0.0, 1.0} }; void o31_copy( O31Matrix dest, O31Matrix source) { int i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) dest[i][j] = source[i][j]; } void o31_invert( O31Matrix m, O31Matrix m_inverse) { /* * The inverse of an O(3,1) matrix may be computed by taking * the transpose and then negating both the zeroth row and the * zeroth column. The proof follows easily from the fact that * multiplying an O(3,1) matrix by its transpose is almost the * same thing as computing the inner product of each pair of * columns. (For O(4) matrices, the transpose is precisely * the inverse, because there are no minus sign in the metric * to fuss over.) * * We first write the inverse into the O31Matrix temp, so that if * the parameters m and m_inverse are the same O31Matrix, we don't * overwrite m[j][i] before computing m[i][j]. */ int i, j; O31Matrix temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) temp[i][j] = ((i == 0) == (j == 0)) ? m[j][i] : -m[j][i]; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) m_inverse[i][j] = temp[i][j]; } FuncResult gl4R_invert( GL4RMatrix m, GL4RMatrix m_inverse) { double row[4][8]; double *mm[4], *temp_row, multiple; int i, j, k; for (i = 0; i < 4; i++) mm[i] = row[i]; /* * Copy m -- don't alter the original. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) mm[i][j] = m[i][j]; /* * Initialize the four right hand columns to the identity. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) mm[i][4 + j] = (i == j) ? 1.0 : 0.0; /* * Forward elimination. */ for (j = 0; j < 4; j++) { /* * Partial pivoting. */ for (i = j+1; i < 4; i++) if (fabs(mm[j][j]) < fabs(mm[i][j])) { temp_row = mm[j]; mm[j] = mm[i]; mm[i] = temp_row; } /* * Is the matrix singular? */ if (fabs(mm[j][j]) < SINGULAR_MATRIX_EPSILON) return func_bad_input; /* * Divide through to get a 1.0 on the diagonal. */ multiple = 1.0 / mm[j][j]; for (k = j; k < 8; k++) mm[j][k] *= multiple; /* * Clear out that column. */ for (i = j+1; i < 4; i++) { multiple = mm[i][j]; for (k = j; k < 8; k++) mm[i][k] -= multiple * mm[j][k]; } } /* * Back substitution. */ for (j = 4; --j >= 0; ) for (i = j; --i >= 0; ) for (k = 4; k < 8; k++) mm[i][k] -= mm[i][j] * mm[j][k]; /* * Copy out the solution. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) m_inverse[i][j] = mm[i][4 + j]; return func_OK; } double gl4R_determinant( GL4RMatrix m) { /* * Two approaches come to mind for computing a 4 x 4 determinant. * * (1) Work out the 4! = 24 terms -- each a product of four * matrix entries -- and form their alternating sum. * * (2) Use Gaussian elimination. * * I doubt there is much difference in efficiency between the two * methods, so I've chosen method (2) on the assumption (which I * haven't thoroughly thought out) that it will be numerically more * robust. Numerical effects could be noticeable, because O(3,1) * matrices tend to have large entires. On the other hand, the * determinant will always be plus or minus one, so it's not worth * getting too concerned about the precision. */ /* * gl4R_determinant() no longer calls uFatalError() when the determinant * is something other than +1 or -1. This lets compute_approx_volume() * in Dirichlet_extras.c compute the determinant of matrices which * are in gl(2,C) but not O(3,1). Note that matrix_io.c already calls * O31_determinants_OK() to validate matrices read from files, and * that SnapPea has had no trouble with determinants of internally * computed O(3,1) matrices. * * JRW 94/11/30 */ int r, c, cc, pivot_row, row_swaps; double max_abs, this_abs, temp, factor, det; O31Matrix mm; /* * First copy the matrix, to avoid destroying it as * we compute its determinant. */ o31_copy(mm, m); /* * Put the matrix in upper triangular form. * * Count the number of row swaps, so we can get the * correct sign at the end. * * Technical comment: We don't actually write zeros into the * lower part of the matrix; we just pretend. */ row_swaps = 0; for (c = 0; c < 4; c++) { /* * Find the pivot row. */ max_abs = -1.0; for (r = c; r < 4; r++) { this_abs = fabs(mm[r][c]); if (this_abs > max_abs) { max_abs = this_abs; pivot_row = r; } } if (max_abs == 0.0) /* * The determinant of an O(3,1) matrix should always * be plus or minus one, never zero. */ /* uFatalError("gl4R_determinant", "o31_matrices"); */ return 0.0; /* JRW 94/11/30 (see explanation above) */ /* * Swap the pivot row into position. */ if (pivot_row != c) { for (cc = c; cc < 4; cc++) { temp = mm[c][cc]; mm[c][cc] = mm[pivot_row][cc]; mm[pivot_row][cc] = temp; } row_swaps++; } /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < 4; r++) { factor = - mm[r][c] / mm[c][c]; for (cc = c + 1; cc < 4; cc++) mm[r][cc] += factor * mm[c][cc]; } } /* * The determinant is now the product of the diagonal entries. */ det = 1.0; for (c = 0; c < 4; c++) det *= mm[c][c]; if (row_swaps % 2) det = - det; /* * Do a quick error check, just to be safe. * The determinant of an O31_matrix should be +1 or -1. */ /* commented out by JRW 94/11/30 (see explanation above) if (fabs(fabs(det) - 1.0) > 0.01) uFatalError("gl4R_determinant", "o31_matrices"); */ return det; } void o31_product( O31Matrix a, O31Matrix b, O31Matrix product) { register int i, j, k; register double sum; O31Matrix temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { sum = 0.0; for (k = 0; k < 4; k++) sum += a[i][k] * b[k][j]; temp[i][j] = sum; } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) product[i][j] = temp[i][j]; } Boolean o31_equal( O31Matrix a, O31Matrix b, double epsilon) { /* * There are a number of different ways one could decide whether two * O(3,1) matrices are the same or not. The fancier ways, such as * computing the sum of the squares of the differences of corresponding * entries, are numerically more time consuming. For now let's just * check that all entries are equal to within epsilon. This offers the * advantage that when scanning down lists, the vast majority of * matrices are diagnosed as different after the comparision of a * single pair of numbers. The epsilon can be fairly large, since to * qualify as equal, two matrices must have ALL their entries equal to * within that precision. */ int i, j; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) if (fabs(a[i][j] - b[i][j]) > epsilon) return FALSE; return TRUE; } double o31_trace( O31Matrix m) { int i; double trace; trace = 0.0; for (i = 0; i < 4; i++) trace += m[i][i]; return trace; } double o31_deviation( O31Matrix m) { /* * The matrix m is, in theory, an element of SO(3,1), * so the inner product of column i with column j should be * * -1 if i = j = 0, * +1 if i = j != 0, or * 0 if i != j. * * Return the greatest deviation from these values, so the * calling function has some idea how precise the matrix is. * * The simplest way to code this is to multiply the matrix times its * inverse. Note that this approach relies on the fact that * o31_inverse() transposes the matrix and negates the appropriate * entries. If o31_inverse() did Gaussian elimination to numerically * invert the matrix, we'd have to rewrite the following code. */ O31Matrix the_inverse, the_product; double error, max_error; int i, j; o31_invert(m, the_inverse); o31_product(m, the_inverse, the_product); max_error = 0.0; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { error = fabs(the_product[i][j] - (i == j ? 1.0 : 0.0)); if (error > max_error) max_error = error; } return max_error; } void o31_GramSchmidt( O31Matrix m) { /* * Given a matrix m whose columns are almost orthonormal (in the sense * of O(3,1), not O(4)), use the Gram-Schmidt process to make small * changes to the matrix entries so that the columns become orthonormal * to the highest precision possible. */ int r, c, cc; double length, length_of_projection; for (c = 0; c < 4; c++) { /* * Adjust column c to have length -1 (if c == 0) or +1 (if c > 0). * We are assuming m is already close to being in O(3,1), so * it suffices to divide column c by sqrt(fabs(length)). */ length = sqrt(fabs(COLUMN_PRODUCT(m, c, c))); /* no need for safe_sqrt() */ for (r = 0; r < 4; r++) m[r][c] /= length; /* * We want to make all subsequent columns be orthogonal to column c, * so subtract off their components in the direction of column c. * Because column c is now a unit vector, the inner product * gives plus or minus the length of the * projection of column cc onto column c, according to whether or * not c == 0. */ for (cc = c + 1; cc < 4; cc++) { length_of_projection = COLUMN_PRODUCT(m, c, cc); if (c == 0) length_of_projection = - length_of_projection; for (r = 0; r < 4; r++) m[r][cc] -= length_of_projection * m[r][c]; } } } void o31_conjugate( O31Matrix m, O31Matrix t, O31Matrix Tmt) { /* * Replace m with (t^-1) m t. */ O31Matrix t_inverse, temp; o31_invert(t, t_inverse); o31_product(t_inverse, m, temp); o31_product(temp, t, Tmt); } double o31_inner_product( O31Vector u, O31Vector v) { int i; double sum; sum = - u[0]*v[0]; for (i = 1; i < 4; i++) sum += u[i]*v[i]; return sum; } void o31_matrix_times_vector( O31Matrix m, O31Vector v, O31Vector product) { register int i, j; register double sum; O31Vector temp; for (i = 0; i < 4; i++) { sum = 0.0; for (j = 0; j < 4; j++) sum += m[i][j] * v[j]; temp[i] = sum; } for (i = 0; i < 4; i++) product[i] = temp[i]; } void o31_constant_times_vector( double r, O31Vector v, O31Vector product) { int i; for (i = 0; i < 4; i++) product[i] = r * v[i]; } void o31_copy_vector( O31Vector dest, O31Vector source) { int i; for (i = 0; i < 4; i++) dest[i] = source[i]; } void o31_vector_sum( O31Vector a, O31Vector b, O31Vector sum) { int i; for (i = 0; i < 4; i++) sum[i] = a[i] + b[i]; } void o31_vector_diff( O31Vector a, O31Vector b, O31Vector diff) { int i; for (i = 0; i < 4; i++) diff[i] = a[i] - b[i]; } regina-4.95/engine/snappea/kernel/orient.c000644 000765 000024 00000062007 12235724562 020444 0ustar00babstaff000000 000000 /* * orient.c * * This file provides the functions * * void orient(Triangulation *manifold); * void reorient(Triangulation *manifold); * void fix_peripheral_orientations(Triangulation *manifold); * * orient() attempts to consistently orient the Tetrahedra of the * Triangulation *manifold. It begins with manifold->tet_list_begin.next * and recursively reorients its neighbors as necessary until either * all the tetrahedra are consistently oriented, or it discovers that * the manifold is nonorientable. (In particular, the Orientation of * an orientable Triangulation will match the initial Orientation of * manifold->tet_list_begin.next, which is important in subdivide.c and * terse_triangulation.c.) orient() sets the manifold->orientability * field to oriented_manifold or nonorientable_manifold, as appropriate. * Its method for reorienting a tetrahedron is to swap the indices * of vertices 2 and 3, and adjust the relevant fields accordingly * (including the gluing fields of neighboring tetrahedra). * It is aware of the fact that some fields may not yet be set, * and doesn't try to modify data structures if they aren't present * (all it requires are the neighbor and gluing fields). * * If the manifold is orientable, then * * (1) All peripheral curves (i.e. the meridians and longitudes) * are transferred to the right_handed sheets of the orientation * double covers of the cusps. This makes their holonomies complex * analytic functions of the tetrahedron shapes, rather than * complex conjugates of complex analytic functions. (However, * the {meridian, longitude} pair may no longer be right-handed. * To fix that, call fix_peripheral_orientations().) * * (2) All edge_orientations are set to right_handed. * * * orient() and orient_edge_classes() may be called in either order. * * If orient() is called first, then * If the manifold is orientable, * orient() will set all edge_orientations to right_handed, and then * orient_edge_classes() will (redundantly) do the same thing. * If the manifold is nonorientable, * orient() won't set the edge_orientations, but * orient_edge_classes() will. * * If orient_edge_classes() is called first, then * If the manifold is orientable, * orient_edge_classes() will assign an arbitrary Orientation * to each EdgeClass, and then * orient() will overwrite it with the right_handed Orientation. * If the manifold is nonorientable, * orient_edge_classes() will assign an arbitrary Orientation * to each EdgeClass, and * orient() will preserve it. * * * orient() could be made available to the UI with no modifications * beyond moving the prototype. * * * reorient() reverses the orientation of all the Tetrahedra * in the Triangulation, by swapping VertexIndices 2 and 3 as described * above. If the manifold is orientable, reorient() also * transfers the peripheral curves to the right_handed sheets of the * double covers and sets all edge_orientations to right_handed, as * described above. It reverses the directions of all meridians, so the * peripheral curves will continue to adhere to the standard orientation * convention. reorient() is intended for oriented manifolds, but * nothing terrible will happen if you pass it a nonorientable manifold. * * fix_peripheral_orientations() makes sure each {meridian, longitude} * pairs obeys the right-hand rule. It should be called only for * orientable manifolds, typically following a call to orient(). * * Note to myself: Eventually I may want to make transfer_peripheral_curves() * responsible for making the peripheral curves adhere to the standard * orientation convention. If this is done, reorient() won't have to * explicitly change the directions of meridians. (However, this * change would require that orient() check whether Cusps are present.) */ #include "kernel.h" static void reverse_orientation(Tetrahedron *tet); static void renumber_neighbors_and_gluings(Tetrahedron *tet); static void renumber_cusps(Tetrahedron *tet); static void renumber_peripheral_curves(Tetrahedron *tet); static void renumber_edge_classes(Tetrahedron *tet); static void renumber_shapes(Tetrahedron *tet); static void renumber_shape_histories(Tetrahedron *tet); static void renumber_one_part(ComplexWithLog edge_parameters[3]); static void swap_rows(int m[4][4], int a, int b); static void swap_columns(int m[4][4], int a, int b); static void swap_sheets(int m[2][4][4]); static void transfer_peripheral_curves(Triangulation *manifold); static void make_all_edge_orientations_right_handed(Triangulation *manifold); static void reverse_all_meridians(Triangulation *manifold); void orient( Triangulation *manifold) { /* * Pick an arbitrary initial Tetrahedron, * and try to extend its orientation to the whole manifold. */ extend_orientation(manifold, manifold->tet_list_begin.next); } void extend_orientation( Triangulation *manifold, Tetrahedron *initial_tet) { Tetrahedron **queue, *tet; int queue_first, queue_last; FaceIndex f; /* * Set all the tet->flag fields to FALSE, * to show that no Tetrahedra have been visited. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->flag = FALSE; /* * Tentatively assume the manifold is orientable. */ manifold->orientability = oriented_manifold; /* * Allocate space for a queue of pointers to the Tetrahedra. * Each Tetrahedron will appear on the queue exactly once, * so an array of length manifold->num_tetrahedra will be just right. */ queue = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); /* * Put the initial Tetrahedron on the queue, * and mark it as visited. */ queue_first = 0; queue_last = 0; queue[0] = initial_tet; queue[0]->flag = TRUE; /* * Start processing the queue. */ do { /* * Pull a Tetrahedron off the front of the queue. */ tet = queue[queue_first++]; /* * Look at the four neighboring Tetrahedra. */ for (f = 0; f < 4; f++) { /* * If the neighbor hasn't been visited... */ if (tet->neighbor[f]->flag == FALSE) { /* * ...reverse its orientation if necessary... */ if (parity[tet->gluing[f]] == orientation_reversing) reverse_orientation(tet->neighbor[f]); /* * ...mark it as visited... */ tet->neighbor[f]->flag = TRUE; /* * ...and put it on the back of the queue. */ queue[++queue_last] = tet->neighbor[f]; } /* * If the neighbor has been visited . . . */ else { /* * ...check whether its orientation is consistent. */ if (parity[tet->gluing[f]] == orientation_reversing) manifold->orientability = nonorientable_manifold; } } } /* * Keep going until either we discover the manifold is nonorientable, * or the queue is exhausted. */ while (manifold->orientability == oriented_manifold && queue_first <= queue_last); /* * Free the memory used for the queue. */ my_free(queue); /* * An "unnecessary" (but quick) error check. */ if (manifold->orientability == oriented_manifold && ( queue_first != manifold->num_tetrahedra || queue_last != manifold->num_tetrahedra - 1)) uFatalError("orient", "orient"); /* * Another error check. * We should have oriented a manifold before attempting to * compute the Chern-Simons invariant. */ if (manifold->CS_value_is_known || manifold->CS_fudge_is_known) uFatalError("orient", "orient"); /* * Respect the conventions for peripheral curves and * edge orientations in oriented manifolds. */ if (manifold->orientability == oriented_manifold) { transfer_peripheral_curves(manifold); make_all_edge_orientations_right_handed(manifold); } } /* * reverse_orientation() reverses the orientation of a Tetrahedron * by swapping the indices of vertices 2 and 3. It adjusts all * relevant fields, including the gluing fields of neighboring * tetrahedra. */ static void reverse_orientation( Tetrahedron *tet) { renumber_neighbors_and_gluings(tet); renumber_cusps(tet); renumber_peripheral_curves(tet); renumber_edge_classes(tet); renumber_shapes(tet); renumber_shape_histories(tet); } static void renumber_neighbors_and_gluings( Tetrahedron *tet) { Tetrahedron *temp_neighbor; Permutation temp_gluing; int i, j, d[4], temp_digit; Tetrahedron *nbr_tet; /* * Renumbering the neighbors is easy: we simply swap * neighbor[2] and neighbor[3]. */ temp_neighbor = tet->neighbor[2]; tet->neighbor[2] = tet->neighbor[3]; tet->neighbor[3] = temp_neighbor; /* * Renumbering the gluings is trickier, because three * changes are required: * * Change A: Swap gluing[2] and gluing[3]. * * Change B: Within each gluing of tet, swap the image of * vertex 2 and the image of vertex 3, e.g. 0312 -> 3012. * * Change C: For each gluing of a face (typically of a Tetrahedron * other than tet) that glues to tet, interchange * 2 and 3, e.g. 0312 -> 0213. */ /* * Change A: Swap gluing[2] and gluing[3]. */ temp_gluing = tet->gluing[2]; tet->gluing[2] = tet->gluing[3]; tet->gluing[3] = temp_gluing; /* * Changes B and C are carried out for each of the four gluings of tet. */ for (i = 0; i < 4; i++) { /* * Change B: Swap the image of vertex 2 and the image of vertex 3. */ /* * Unpack the digits of the gluing. */ for (j = 0; j < 4; j++) { d[j] = tet->gluing[i] & 0x3; tet->gluing[i] >>= 2; } /* * Swap the digits in positions 2 and 3. */ temp_digit = d[3]; d[3] = d[2]; d[2] = temp_digit; /* * Repack the digits. */ for (j = 4; --j >= 0; ) { tet->gluing[i] <<= 2; tet->gluing[i] += d[j]; } /* * Change C: Fix up the inverse of tet->gluing[i]. * * If tet->neighbor[i] != tet, we simply write the inverse of * tet->gluing[i] into the appropriate gluing field of the neighbor. * * If tet->neighbor[i] == tet, the simple approach doesn't work * because of the messy interaction between Changes B and C. * Instead, we exploit the fact that tet->gluing[i] is the inverse * of some other gluing of tet, and apply Change C directly to * tet->gluing[i] rather than its inverse. */ nbr_tet = tet->neighbor[i]; if (nbr_tet != tet) /* * Write the inverse directly. */ nbr_tet->gluing[EVALUATE(tet->gluing[i],i)] = inverse_permutation[tet->gluing[i]]; else { /* * Perform Change C on tet->gluing[i]. */ /* * Unpack the digits. */ for (j = 0; j < 4; j++) { d[j] = tet->gluing[i] & 0x3; tet->gluing[i] >>= 2; } /* * Swap 2 and 3 in the images. */ for (j = 0; j < 4; j++) switch (d[j]) { case 0: case 1: /* leave d[j] alone */ break; case 2: d[j] = 3; break; case 3: d[j] = 2; break; } /* * Repack the digits. */ for (j = 4; --j >= 0; ) { tet->gluing[i] <<= 2; tet->gluing[i] += d[j]; } } } } static void renumber_cusps( Tetrahedron *tet) { Cusp *temp_cusp; temp_cusp = tet->cusp[2]; tet->cusp[2] = tet->cusp[3]; tet->cusp[3] = temp_cusp; } static void renumber_peripheral_curves( Tetrahedron *tet) { int i, j; for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { swap_rows (tet->curve[i][j], 2, 3); swap_columns(tet->curve[i][j], 2, 3); } swap_sheets(tet->curve[i]); } } static void renumber_edge_classes( Tetrahedron *tet) { EdgeClass *temp_edge_class; Orientation temp_edge_orientation; EdgeIndex i; temp_edge_class = tet->edge_class[1]; tet->edge_class[1] = tet->edge_class[2]; tet->edge_class[2] = temp_edge_class; temp_edge_class = tet->edge_class[3]; tet->edge_class[3] = tet->edge_class[4]; tet->edge_class[4] = temp_edge_class; for (i = 1; i < 5; i++) if (tet->edge_class[i] != NULL) { tet->edge_class[i]->incident_tet = tet; tet->edge_class[i]->incident_edge_index = i; } temp_edge_orientation = tet->edge_orientation[1]; tet->edge_orientation[1] = tet->edge_orientation[2]; tet->edge_orientation[2] = temp_edge_orientation; temp_edge_orientation = tet->edge_orientation[3]; tet->edge_orientation[3] = tet->edge_orientation[4]; tet->edge_orientation[4] = temp_edge_orientation; for (i = 0; i < 6; i++) tet->edge_orientation[i] = ! tet->edge_orientation[i]; } static void renumber_shapes( Tetrahedron *tet) { /* * Renumber the TetShapes iff they are actually present. */ int i, j; if (tet->shape[complete] != NULL) for (i = 0; i < 2; i++) /* i = complete, filled */ for (j = 0; j < 2; j++) /* j = ultimate, penultimate */ renumber_one_part(tet->shape[i]->cwl[j]); } static void renumber_one_part( ComplexWithLog edge_parameters[3]) { ComplexWithLog temp; int i; /* * Swap the indices on edges 1 and 2. */ temp = edge_parameters[1]; edge_parameters[1] = edge_parameters[2]; edge_parameters[2] = temp; /* * Invert the modulus of each edge parameter, but leave * the angles the same. */ for (i = 0; i < 3; i++) { edge_parameters[i].log.real = - edge_parameters[i].log.real; edge_parameters[i].rect = complex_exp(edge_parameters[i].log); } } static void renumber_shape_histories( Tetrahedron *tet) { int i; ShapeInversion *shape_inversion; for (i = 0; i < 2; i++) for ( shape_inversion = tet->shape_history[i]; shape_inversion != NULL; shape_inversion = shape_inversion->next) switch (shape_inversion->wide_angle) { case 0: shape_inversion->wide_angle = 0; break; case 1: shape_inversion->wide_angle = 2; break; case 2: shape_inversion->wide_angle = 1; break; } } static void swap_rows( int m[4][4], int a, int b) { int j, temp; for (j = 0; j < 4; j++) { temp = m[a][j]; m[a][j] = m[b][j]; m[b][j] = temp; } } static void swap_columns( int m[4][4], int a, int b) { int i, temp; for (i = 0; i < 4; i++) { temp = m[i][a]; m[i][a] = m[i][b]; m[i][b] = temp; } } static void swap_sheets( int m[2][4][4]) { int i, j, temp; for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { temp = m[right_handed][i][j]; m[right_handed][i][j] = m[left_handed ][i][j]; m[left_handed ][i][j] = temp; } } /* * We want the peripheral curves of an oriented manifold to lie * on the right_handed sheets of the orientation double covers * of the cusps. transfer_peripheral_curves() adds the curves * from the left_handed sheet to the right_handed sheet, and * sets the contents of the left_handed sheet to zero. */ static void transfer_peripheral_curves( Triangulation *manifold) { Tetrahedron *tet; PeripheralCurve c; VertexIndex v; FaceIndex f; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (c = 0; c < 2; c++) for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) { tet->curve[c][right_handed][v][f] += tet->curve[c][left_handed][v][f]; tet->curve[c][left_handed] [v][f] = 0; } } static void make_all_edge_orientations_right_handed( Triangulation *manifold) { Tetrahedron *tet; EdgeIndex e; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (e = 0; e < 6; e++) tet->edge_orientation[e] = right_handed; } void reorient( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) reverse_orientation(tet); if (manifold->orientability == oriented_manifold) { /* * The peripheral curves haven't gone anywhere, * but the sheets they are on are now considered * the left_handed sheets rather than the right_handed * sheets. So transfer them to what used to be the * left_handed sheets but are now the right_handed sheets. */ transfer_peripheral_curves(manifold); /* * To adhere to the orientation conventions for peripheral curves * (see the documentation at the top of peripheral_curves.c) * we must reverse the directions of all meridians. * * Note that it was the act of transferring the peripheral curves * from the left_handed to right_handed sheets -- note the reversal * of the Tetrahedra -- that caused the violation of the orientaiton * convention. In particular, curves in nonorientable manifold, even * on (double covers of) Klein bottle cusps, still respect the convention. */ reverse_all_meridians(manifold); /* * Adjust the edge orientations, too. */ make_all_edge_orientations_right_handed(manifold); } /* * The Chern-Simons invariant of the manifold will be negated, * and the fudge factor will be different. */ if (manifold->CS_value_is_known) { manifold->CS_value[ultimate] = - manifold->CS_value[ultimate]; manifold->CS_value[penultimate] = - manifold->CS_value[penultimate]; } compute_CS_fudge_from_value(manifold); } static void reverse_all_meridians( Triangulation *manifold) { Tetrahedron *tet; Cusp *cusp; int i, j, k; /* * Change the directions of all meridians. */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->curve[M][i][j][k] = - tet->curve[M][i][j][k]; /* * Negating the m coefficient of all Dehn fillings compensates for * the fact that we reversed the meridian, and gives us the same * (oriented) Dehn filling curve as before. However, this curve * will now wind clockwise around the core geodesics, relative to * the new orientation on the manifold. This causes a whole new * solution to be found to the gluing equations. To avoid this, * we reverse the direction of the Dehn filling curve (i.e. we * negate both the m and l coefficients). The net effect is that * we negate the l coefficient. * * This reversal of the Dehn filling curve is not really * necessary, and could be eliminated if it's ever causes problems. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) cusp->l = - cusp->l; /* * Adjust all cusp_shapes. * (The current cusp_shape of a filled Cusp will be Zero, but that's OK.) */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = initial, current */ cusp->cusp_shape[i].real = - cusp->cusp_shape[i].real; /* * Adjust the holonomies. * * Changing the orientation of the manifold negates the imaginary * parts of log(H(m)) and log(H(l)). * * But we also reversed the direction of the meridian, which * negates both the real and imaginary parts of log(H(m)), so * the net effect on log(H(m)) is that its real part is negated. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ { cusp->holonomy[i][M].real = - cusp->holonomy[i][M].real; cusp->holonomy[i][L].imag = - cusp->holonomy[i][L].imag; } } void fix_peripheral_orientations( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; FaceIndex f; Cusp *cusp; Boolean reversed_meridian = FALSE; /* * This function should get called only for orientable manifolds. */ if (manifold->orientability != oriented_manifold) uFatalError("fix_peripheral_orientations", "orient"); /* * Compute the intersection number of the meridian and longitude. */ copy_curves_to_scratch(manifold, 0, FALSE); copy_curves_to_scratch(manifold, 1, FALSE); compute_intersection_numbers(manifold); /* * Reverse the meridian on cusps with intersection_number[L][M] == -1. */ /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (v = 0; v < 4; v++) if (tet->cusp[v]->intersection_number[L][M] == -1) /* which side of the vertex */ for (f = 0; f < 4; f++) if (v != f) { tet->curve[M][right_handed][v][f] = - tet->curve[M][right_handed][v][f]; if (tet->curve[M][left_handed][v][f] != 0.0 || tet->curve[L][left_handed][v][f] != 0.0) uFatalError("fix_peripheral_orientations", "orient"); } /* * When we reverse the meridian we must also negate the meridional * Dehn filling coefficient in order to maintain the same (oriented) * Dehn filling curve as before. However, this Dehn filling curve * will wind clockwise around the core geodesics, relative to * the global orientation on the manifold (because the global * orientation disagrees with the local orientation we had been using * on the nonorientable manifold's torus cusp). This forces a whole * new solution to be found to the gluing equations. To avoid this, * we reverse the direction of the Dehn filling curve (i.e. we * negate both the m and l coefficients). The net effect is that * we negate the l coefficient. * * This reversal of the Dehn filling curve is not really * necessary, and could be eliminated if it's ever causes problems. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->intersection_number[L][M] == -1) { reversed_meridian = TRUE; cusp->l = - cusp->l; } if( reversed_meridian ) uAcknowledge("Meridians have been reversed to ensure right-handed {M,L} pairs."); } regina-4.95/engine/snappea/kernel/peripheral_curves.c000644 000765 000024 00000132763 12235724562 022675 0ustar00babstaff000000 000000 /* * peripheral_curves.c * * * This file provides the function * * void peripheral_curves(Triangulation *manifold); * * which puts a meridian and longitude on each cusp. If the * manifold is oriented, the meridian and longitude adhere to * the usual orientation convention; this is, if you place your * right hand on the torus with your fingers pointing in the * direction of the meridian and your thumb pointing in the * direction of the longitude, then your palm will face the * cusp while the back of your hand faces the fat part of the * manifold. Note that this corresponds to the usual convention * for orienting meridians and longitudes on link complements. * * Even if the manifold isn't oriented, the peripheral curves * adhere to the standard orientation convention relative to * the orientation of each Cusp's orientation double cover (more * on this below). * * peripheral_curves() does not need to know the CuspTopology * ahead of time. It figures it out for itself and records the * result in the field cusp->topology. * * The remainder of this documentation will * * (1) Define the meridian and the longitude. * * (2) Describe the data structure used to store * meridians and longitudes. * * (3) Explain the algorithm which peripheral_curves() * uses to find meridians and longitudes. * * * (1) Definition of the meridian and the longitude. * * The meridian and the longitude of an orientable cusp are * any pair of simple closed curves which intersect exactly * once. If the manifold is orientable, they will adhere to * the orientation convention described above. peripheral_curves() * finds meridians and longitudes which are reasonably short * in the sense that they pass through a small number of * triangles in the triangulation of the boundary torus. * * The meridian and the longitude of a nonorientable cusp are * defined more precisely. There are exactly four nontrivial * simple closed curves on a Klein bottle, up to isotopy. I'd * like to provide a picture of them, but, alas, there's no way * to include a picture in an ASCII file, so I must ask you to * draw your own picture as you read along. Define the * Klein bottle as a cylinder with ends glued. Parameterize * the cylinder by (x, theta), where -1 <= x <= +1 and theta * is defined mod 2pi as for a circle. To make the Klein * bottle, identify the cylinder's ends via the mapping * (-1, theta) -> (+1, -theta). With this notation, the four * simple closed curves on the Klein bottle are * * A: theta = 0 * B: theta = pi * C: (theta = pi/2) U (theta = - pi/2) * D: x = 0 * * The longitude will be either curve A or curve B. Thus, * counting orientation, there are four possible longitudes. * (Note: as explained in section (2) immediately following, * the longitude of a Klein bottle is actually stored as its * preimage in the Klein bottle's double cover.) * The meridian will be curve D. Curve D cannot be oriented, * because it's isotopic to its inverse. * * The holonomies of the longitude and meridian of a Klein * bottle have some very special properties, which are * discussed in the comment at the top of holonomy.c. * * * (2) How meridians and longitudes are stored. * * The meridian and longitude are stored not on the boundary * component itself, but on its orientation double cover. * The double cover of a Klein bottle is a torus. The double * cover of a torus is the union of two tori, only one of * which is actually used (the right_handed one, if the * manifold is oriented). The reason we want to always * store curves on tori and never on Klein bottles directly * is that the fundamental group of the torus is abelian: * isotopy classes of curves can be recovered from homological * information. The reason only the right_handed sheet * of the cover is used when the manifold is oriented is * that the holonomy of a Dehn filling curve on the left_handed * sheet is not a complex analytic function of the tetrahedron * shapes, but rather the complex conjugate of such a function; * we need complex analytic functions to compute the * derivative matrix in the complex version of Newton's * method used to find hyperbolic structures for oriented * manifolds. * * Each torus is the union of triangular cross sections of * ideal vertices. Each ideal vertex of each Tetrahedron * contributes two triangles, one with the right_handed * orientation and one with the left_handed orientation. * Visualize the right-handed triangle as containing a * counter-clockwise oriented circle, and the left-handed * triangle as containing a clockwise-oriented circle, as viewed * from infinity relative to the right_handed orientation of the * Tetrahedron. The triangles piece together so that orientations * of neighboring circles agree: if two neighboring Tetrahedra * have opposite orientations, then the right_handed triangles * of one connect with the left_handed triangles of the other; * if the two tetrahedra have the same orientation, then the * right_handed triangles of one match to the right_handed triangles * of the other, and left_handed to left_handed. Note that * the components of the orientation double cover of the Cusps * are all oriented, not just orientable (use the rule which * says to view the circles so that all appear counterclockwise * as seen from infinity). * * The meridian and longitude are specified by their * intersection numbers with the triangular cross sections. * tet->curve[M][right_handed][v][f] is the net number of * times the meridian crosses side f of the right_handed * triangle at vertex v, and similarly for the * longitude and the left_handed triangle. * A positive intersection number means the curve is * entering the triangle. The sides of the triangle are * numbered according to the faces of the tetrahedron * containing them. E.g., the sides of the triangle at * vertex 2 will be numbered 0, 1 and 3. The array vt_side[][] * in tables.c lets you refer to the sides of a triangle by * the integers 0, 1, 2, if this suits your purposes. * Note that these intersection numbers are sufficient to * reconstruct a simple closed curve up to isotopy. * * The longitude of the Klein bottle is a special case in * that its preimage in the double cover is connected. It * is stored as the complete preimage. All other curves are * stored as one component of their two-component preimages. * By the way, the two candidates for the longitude (curves * A and B in the discussion above) lift to the same curve in * the double cover. * * * (3) How peripheral_curves() finds the meridian and the * longitude. * * There are three main steps: * * Compute a fundamental domain for the boundary component. * * Identify it as a torus or Klein bottle. * * Find the longitude and meridian. * * The plan for finding a fundamental domain is * conceptually simple: initialize the domain as a single * triangular vertex cross section, and then expand it outward * by adding neighboring triangles in a breadth-first * fashion, until the domain fills the entire cusp. * * This plan is implemented with the PerimeterPiece data * structure. At each step, a circular linked list of * PerimeterPieces defines the boundary of the domain. * (Each PerimeterPiece corresponds to one edge of one * triangle on the perimeter of the domain.) The algorithm * is to keep going around the perimeter, adding new * PerimeterPieces as the domain expands across triangles * which were not previously included. * * While this process goes on, a second data structure is * being created. An array of 4 Extra fields attached to * each tetrahedron (one Extra field for each vertex) records * * (a) whether the vertex has been included in the domain, * * and, if so, * * (b) which other vertex (the "parent vertex") was * responsible for including it. * * The result is a tree structure, with the root at the * original triangle and the leaves at the perimeter. In * a moment we'll see how this tree is used to create the * longitudes and meridians. * * Once the domain fills the entire cusp, we check whether * there are any vertices of order one on the perimeter, and * if so we remove them by cancelling the adjacent * PerimeterPieces. * * Conceptually (but NOT in the code) we imagine removing * vertices of order two, thereby fusing the adjacent edges * into one. This must yield a fundamental domain which is * either a square or a hexagon. Here's the proof. Assume * a 2n-gon has pairs of sides glued so as to form a torus * or a Klein bottle, and assume all vertices have order * three or greater. Since the 2n-gon itself has 2n vertices, * there can be at most 2n/3 vertices in the resulting * cell-decomposition of the torus or Klein bottle. * Compute the Euler characteristic: * * 0 = Euler characteristic * = vertices - edges + faces * <= 2n/3 - n + 1 * = 1 - n/3 * * => n <= 3 * => the 2n-gon is a bigon, a square or a hexagon * * A case-by-case analysis reveals that the only possible * gluings are * * square hexagon * ______________________ * torus | abAB | abcABC | * |---------+------------| * Klein bottle | abAb | abcAcb | * | aabb | aabccB | * ---------------------- * * where the notation is what you would expect. I wish I could * provide an illustration of each gluing, but in platform- * independent text file this just isn't possible. So I ask * that you make your own illustration of each gluing. * * It is now straightforward to apply the definitions of the * longitude and meridian to each gluing. The details are * spelled out in the documentation contained in the function * find_meridian_and_longitude() and, especially, the functions * it calls. */ #include "kernel.h" typedef struct PerimeterPiece PerimeterPiece; struct extra { /* * Has this vertex been included in the fundamental domain? */ Boolean visited; /* * Which vertex of which tetrahedron is its parent in the * tree structure? * (parent_tet == NULL at the root.) */ Tetrahedron *parent_tet; VertexIndex parent_vertex; /* * Which side of this vertex faces the parent vertex? * Which side of the parent vertex faces this vertex? */ FaceIndex this_faces_parent, parent_faces_this; /* * What is the orientation of this vertex in the * fundamental domain? */ Orientation orientation; /* * Which PerimeterPiece, if any, is associated with * a given edge of the triangle at this vertex? * (As you might expect, its_perimeter_piece[i] refers * to the edge of the triangle contained in face i of * the Tetrahedron.) */ PerimeterPiece *its_perimeter_piece[4]; /* * When computing intersection numbers in * adjust_Klein_cusp_orientations() we want to allow for * the possibility that the Triangulation's scratch_curves * are already in use, so we copy them to scratch_curve_backup, * and restore them when we're done. */ int scratch_curve_backup[2][2][2][4][4]; }; struct PerimeterPiece { Tetrahedron *tet; VertexIndex vertex; FaceIndex face; Orientation orientation; /* How the PerimeterPiece sees the tetrahedron */ Boolean checked; PerimeterPiece *mate; /* the PerimeterPiece this one is glued to . . . */ GluingParity gluing_parity; /* . . . and how they match up */ PerimeterPiece *next; /* the neighbor in the counterclockwise direction */ PerimeterPiece *prev; /* the neighbor in the clockwise direction */ }; /* * The following enum lists the six possible gluing * patterns for a torus or Klein bottle. */ typedef int GluingPattern; enum { abAB, /* square torus */ abcABC, /* hexagonal torus */ abAb, /* standard square Klein bottle */ aabb, /* P^2 # P^2 square Klein bottle */ abcAcb, /* standard hexagonal Klein bottle */ aabccB /* P^2 # P^2 hexagonal Klein bottle */ }; static void zero_peripheral_curves(Triangulation *manifold); static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void initialize_flags(Triangulation *manifold); static Boolean cusp_has_curves(Triangulation *manifold, Cusp *cusp); static void do_one_cusp(Triangulation *manifold, Cusp *cusp); static void pick_base_tet(Triangulation *manifold, Cusp *cusp, Tetrahedron **base_tet, VertexIndex *base_vertex); static void set_up_perimeter(Tetrahedron *base_tet, VertexIndex base_vertex, PerimeterPiece **perimeter_anchor); static void expand_perimeter(PerimeterPiece *perimeter_anchor); static void find_mates(PerimeterPiece *perimeter_anchor); static void simplify_perimeter(PerimeterPiece **perimeter_anchor); static void find_meridian_and_longitude(PerimeterPiece *perimeter_anchor, CuspTopology *cusp_topology); static void advance_to_next_side(PerimeterPiece **pp); static GluingPattern determine_gluing_pattern(PerimeterPiece *side[6], int num_sides); static void do_torus(PerimeterPiece *side[6]); static void do_standard_Klein_bottle(PerimeterPiece *side[6], int num_sides); static void do_P2P2_Klein_bottle(PerimeterPiece *side[6], int num_sides); static void trace_curve(PerimeterPiece *start, PeripheralCurve trace_which_curve, TraceDirection trace_direction, Boolean use_opposite_orientation); static void free_perimeter(PerimeterPiece *perimeter_anchor); static void adjust_Klein_cusp_orientations(Triangulation *manifold); static void reverse_meridians_where_necessary(Triangulation *manifold); static void backup_scratch_curves(Triangulation *manifold); static void restore_scratch_curves(Triangulation *manifold); void peripheral_curves( Triangulation *manifold) { Cusp *cusp; zero_peripheral_curves(manifold); attach_extra(manifold); initialize_flags(manifold); for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE) /* 97/2/4 Added to accomodate finite vertices. */ do_one_cusp(manifold, cusp); adjust_Klein_cusp_orientations(manifold); free_extra(manifold); } void peripheral_curves_as_needed( Triangulation *manifold) { /* * Add peripheral curves only to cusps for which all the * tet->curve[][][][] fields are zero. */ Cusp *cusp; attach_extra(manifold); initialize_flags(manifold); for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) if (cusp->is_finite == FALSE && cusp_has_curves(manifold, cusp) == FALSE) do_one_cusp(manifold, cusp); adjust_Klein_cusp_orientations(manifold); free_extra(manifold); } static void zero_peripheral_curves( Triangulation *manifold) { Tetrahedron *tet; int i, j, k, l; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) tet->curve[i][j][k][l] = 0; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "peripheral_curves"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_ARRAY(4, Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void initialize_flags( Triangulation *manifold) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) tet->extra[v].visited = FALSE; } static Boolean cusp_has_curves( Triangulation *manifold, Cusp *cusp) { Tetrahedron *tet; VertexIndex v; FaceIndex f; PeripheralCurve c; Orientation h; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) for (f = 0; f < 4; f++) if (f != v) for (c = 0; c < 2; c++) /* c = M, L */ for (h = 0; h < 2; h++) /* h = right_handed, left_handed */ if (tet->curve[c][h][v][f] != 0) return TRUE; return FALSE; } static void do_one_cusp( Triangulation *manifold, Cusp *cusp) { Tetrahedron *base_tet; VertexIndex base_vertex; PerimeterPiece *perimeter_anchor; pick_base_tet(manifold, cusp, &base_tet, &base_vertex); set_up_perimeter(base_tet, base_vertex, &perimeter_anchor); expand_perimeter(perimeter_anchor); find_mates(perimeter_anchor); simplify_perimeter(&perimeter_anchor); find_meridian_and_longitude(perimeter_anchor, &cusp->topology); free_perimeter(perimeter_anchor); } static void pick_base_tet( Triangulation *manifold, Cusp *cusp, Tetrahedron **base_tet, VertexIndex *base_vertex) { Tetrahedron *tet; VertexIndex v; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (v = 0; v < 4; v++) if (tet->cusp[v] == cusp) { *base_tet = tet; *base_vertex = v; return; } /* * If pick_base_tet() didn't find any vertex belonging * to the specified cusp, we're in big trouble. */ uFatalError("pick_base_tet", "peripheral_curves"); } static void set_up_perimeter( Tetrahedron *base_tet, VertexIndex base_vertex, PerimeterPiece **perimeter_anchor) { int i; PerimeterPiece *pp[3]; base_tet->extra[base_vertex].visited = TRUE; base_tet->extra[base_vertex].parent_tet = NULL; base_tet->extra[base_vertex].orientation = right_handed; for (i = 0; i < 3; i++) pp[i] = NEW_STRUCT(PerimeterPiece); for (i = 0; i < 3; i++) { pp[i]->tet = base_tet; pp[i]->vertex = base_vertex; pp[i]->face = vt_side[base_vertex][i]; pp[i]->orientation = right_handed; pp[i]->checked = FALSE; pp[i]->next = pp[(i+1)%3]; pp[i]->prev = pp[(i+2)%3]; } *perimeter_anchor = pp[0]; } /* * expand_perimeter() starts with the initial triangular * perimeter found by set_up_perimeter() and expands it in * breadth-first fashion. It keeps going around and around * the perimeter, pushing it outwards wherever possible. * To know when it's done, it keeps track of the number * of PerimeterPieces which have not yet been been checked. * When this number is zero, it's done. */ static void expand_perimeter( PerimeterPiece *perimeter_anchor) { int num_unchecked_pieces; PerimeterPiece *pp, *new_piece; Permutation gluing; Tetrahedron *nbr_tet; VertexIndex nbr_vertex; FaceIndex nbr_back_face, nbr_left_face, nbr_right_face; Orientation nbr_orientation; for (num_unchecked_pieces = 3, pp = perimeter_anchor; num_unchecked_pieces; pp = pp->next) if (pp->checked == FALSE) { gluing = pp->tet->gluing[pp->face]; nbr_tet = pp->tet->neighbor[pp->face]; nbr_vertex = EVALUATE(gluing, pp->vertex); if (nbr_tet->extra[nbr_vertex].visited) { pp->checked = TRUE; num_unchecked_pieces--; } else { /* * Extend the tree to the neighboring vertex. */ nbr_back_face = EVALUATE(gluing, pp->face); if (parity[gluing] == orientation_preserving) nbr_orientation = pp->orientation; else nbr_orientation = ! pp->orientation; if (nbr_orientation == right_handed) { nbr_left_face = remaining_face[nbr_vertex][nbr_back_face]; nbr_right_face = remaining_face[nbr_back_face][nbr_vertex]; } else { nbr_left_face = remaining_face[nbr_back_face][nbr_vertex]; nbr_right_face = remaining_face[nbr_vertex][nbr_back_face]; } nbr_tet->extra[nbr_vertex].visited = TRUE; nbr_tet->extra[nbr_vertex].parent_tet = pp->tet; nbr_tet->extra[nbr_vertex].parent_vertex = pp->vertex; nbr_tet->extra[nbr_vertex].this_faces_parent = nbr_back_face; nbr_tet->extra[nbr_vertex].parent_faces_this = pp->face; nbr_tet->extra[nbr_vertex].orientation = nbr_orientation; /* * Extend the perimeter across the neighboring * vertex. The new PerimeterPiece is added on * the right side of the old one, so that the * pp = pp->next step in the loop moves us past * both the old and new perimeter pieces. This * causes the perimeter to expand uniformly in * all directions. */ new_piece = NEW_STRUCT(PerimeterPiece); new_piece->tet = nbr_tet; new_piece->vertex = nbr_vertex; new_piece->face = nbr_right_face; new_piece->orientation = nbr_orientation; new_piece->checked = FALSE; new_piece->next = pp; new_piece->prev = pp->prev; pp->prev->next = new_piece; pp->tet = nbr_tet; pp->vertex = nbr_vertex; pp->face = nbr_left_face; pp->orientation = nbr_orientation; pp->checked = FALSE; /* unchanged */ pp->next = pp->next; /* unchanged */ pp->prev = new_piece; /* * Increment the count of unchecked pieces. */ num_unchecked_pieces++; } } } static void find_mates( PerimeterPiece *perimeter_anchor) { PerimeterPiece *pp; Tetrahedron *nbr_tet; Permutation gluing; VertexIndex nbr_vertex; FaceIndex nbr_face; /* * First tell the tetrahedra about the PerimeterPieces. */ pp = perimeter_anchor; do { pp->tet->extra[pp->vertex].its_perimeter_piece[pp->face] = pp; pp = pp->next; } while (pp != perimeter_anchor); /* * Now let each PerimeterPiece figure out who its mate is. */ pp = perimeter_anchor; do { nbr_tet = pp->tet->neighbor[pp->face]; gluing = pp->tet->gluing[pp->face]; nbr_vertex = EVALUATE(gluing, pp->vertex); nbr_face = EVALUATE(gluing, pp->face); pp->mate = nbr_tet->extra[nbr_vertex].its_perimeter_piece[nbr_face]; pp->gluing_parity = (pp->orientation == pp->mate->orientation) == (parity[gluing] == orientation_preserving) ? orientation_preserving : orientation_reversing; pp = pp->next; } while (pp != perimeter_anchor); } static void simplify_perimeter( PerimeterPiece **perimeter_anchor) { PerimeterPiece *pp, *stop, *dead0, *dead1; /* * The plan here is to cancel adjacent edges of the form * * --o--->---o---<---o-- * * Travelling around the perimeter looking for such * edges is straightforward. For each PerimeterPiece (pp), * we check whether it will cancel with its lefthand * neighbor (pp->next). If it doesn't cancel, we advance * one step to the left (pp = pp->next). If it does cancel, * we move back one step to the right, to allow further * cancellation in case the previously cancelled edges were * part of a sequence * * . . . --o--->>---o--->---o---<---o---<<---o-- . . . * * One could no doubt devise a clever and efficient * way of deciding when to stop (readers are invited * to submit solutions), but to save wear and tear on * the programmer's brain, the present algorithm simply * keeps going until it has made a complete trip around * the perimeter without doing any cancellation. The * variable "stop" records the first noncancelling * PerimeterPiece which was encountered after the most * recent cancellation. Here's the loop in skeleton form: * * pp = perimeter_anchor; * stop = NULL; * * while (pp != stop) * { * if (pp cancels with its neighbor) * { * pp = pp->prev; * stop = NULL; * } * else (pp doesn't cancel with its neighbor) * { * if (stop == NULL) * stop = pp; * pp = pp->next; * } * } */ pp = *perimeter_anchor; stop = NULL; while (pp != stop) { /* * Check whether pp and the PerimeterPiece to * its left will cancel each other. */ if (pp->next == pp->mate && pp->gluing_parity == orientation_preserving) { /* * Note the addresses of the PerimeterPieces * which cancel . . . */ dead0 = pp; dead1 = pp->next; /* * . . . then remove them from the perimeter. */ dead0->prev->next = dead1->next; dead1->next->prev = dead0->prev; /* * Move pp back to the previous PerimeterPiece to * allow further cancellation. */ pp = dead0->prev; /* * Deallocate the cancelled PerimeterPieces. */ my_free(dead0); my_free(dead1); /* * We don't want to leave *perimeter_anchor * pointing to a dead PerimeterPiece, so set * it equal to a piece we know is still alive. */ *perimeter_anchor = pp; /* * We just did a cancellation, so set the * variable stop to NULL. */ stop = NULL; } else { /* * If this is the first noncancelling PerimeterPiece * after a sequence of one or more cancellations, * record its address in the variable stop. */ if (stop == NULL) stop = pp; /* * Advance to the next PerimeterPiece. */ pp = pp->next; } } } static void find_meridian_and_longitude( PerimeterPiece *perimeter_anchor, CuspTopology *cusp_topology) { PerimeterPiece *pp, *side[6]; int i, num_sides; /* * As explained in the documentation at the top of * this file, the fundamental domain for the cusp * will be either a square or a hexagon. Go around * the perimeter, recording the first PerimeterPiece * on each side of the square or hexagon. */ pp = perimeter_anchor; for (i = 0; i < 6; i++) { advance_to_next_side(&pp); side[i] = pp; } /* * Is it a square or a hexagon? */ if (side[0] == side[4]) num_sides = 4; else num_sides = 6; /* * Split into cases, according to how the square or * hexagon's edges are glued. The six types of gluings * are explained in the documentation at the top of * this file. */ switch (determine_gluing_pattern(side, num_sides)) { case abAB: case abcABC: do_torus(side); *cusp_topology = torus_cusp; break; case abAb: case abcAcb: do_standard_Klein_bottle(side, num_sides); *cusp_topology = Klein_cusp; break; case aabb: case aabccB: do_P2P2_Klein_bottle(side, num_sides); *cusp_topology = Klein_cusp; break; } } /* * advance_to_next_side() advances the pointer *pp to * point to the first PerimeterPiece on the next side * of the square or hexagon, travelling counterclockwise. */ static void advance_to_next_side( PerimeterPiece **pp) { PerimeterPiece *p0, *p1; /* * Let p0 and p1 point to the given PerimeterPiece * and its mate. */ p0 = *pp; p1 = (*pp)->mate; /* * Move along the perimeter until p0 and p1 part company, * or until their relative orientation changes. */ if (p0->gluing_parity == orientation_preserving) do { p0 = p0->next; p1 = p1->prev; } while ( p0->mate == p1 && p0->gluing_parity == orientation_preserving); else /* (*pp)->gluing_parity == orientation_reversing */ do { p0 = p0->next; p1 = p1->next; } while ( p0->mate == p1 && p0->gluing_parity == orientation_reversing); /* * p0 now points to the first PerimeterPiece in the next * edge of the square or hexagon. Write its value into *pp. */ *pp = p0; } static GluingPattern determine_gluing_pattern( PerimeterPiece *side[6], int num_sides) { int i; /* * Please draw pictures of the six possible gluings * shown in the table in the documentation at the * top of this file. They will show that the logic * of this function, as summarized in the following * skeleton code, is correct. * * if (there is an orientation reversing side) * if (there are two adjacent, matching, orientation reversing sides) * if (num_sides == 4) * return(P^2 # P^2 square Klein bottle) * else (num_sides == 6) * return(P^2 # P^2 hexagonal Klein bottle) * else * if (num_sides == 4) * return(standard square Klein bottle) * else (num_sides == 6) * return(standard hexagonal Klein bottle) * else (all sides are orientation preserving) * if (num_sides == 4) * return(square torus) * else (num_sides == 6) * return(hexagonal torus) */ /* * Look for an orientation reversing side. * If one is found, check whether it's adjacent to its mate. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { if (side[i]->mate == side[(i+1)%num_sides] || side[i]->mate == side[(i-1+num_sides)%num_sides]) { if (num_sides == 4) return aabb; /* P^2 # P^2 square Klein bottle */ else return aabccB; /* P^2 # P^2 hexagonal Klein bottle */ } else { if (num_sides == 4) return abAb; /* standard square Klein bottle */ else return abcAcb; /* standard hexagonal Klein bottle */ } } /* * No orientation reversing side was found. * The surface is a torus. */ if (num_sides == 4) return abAB; /* square torus */ else return abcABC; /* hexagonal torus */ } static void do_torus( PerimeterPiece *side[6] /* int num_sides */) { /* * The following calls to trace_curve() will always produce * a meridian and longitude which intersect exactly once. * If the manifold is orientable, they will adhere to the * orientation convention described at the top of this file. * (The proof of this relies on the fact that * set_up_perimeter() views the base vertex with the * right_handed orientation. Thus in an oriented manifold * all vertices are viewed with the right_handed orientation.) */ trace_curve(side[0], L, trace_backwards, FALSE); trace_curve(side[0]->mate, L, trace_forwards, FALSE); trace_curve(side[1], M, trace_backwards, FALSE); trace_curve(side[1]->mate, M, trace_forwards, FALSE); } static void do_standard_Klein_bottle( PerimeterPiece *side[6], int num_sides) { int i; /* * Let the meridian connect the unique pair of * orientation_preserving sides. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_preserving) { trace_curve(side[i], M, trace_backwards, FALSE); trace_curve(side[i]->mate, M, trace_forwards, FALSE); break; } /* * Let the longitude connect a pair of orientation_reversing * sides. Store it as its complete preimage in the double * cover, as explained in the documentation at the top of * this file. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { trace_curve(side[i], L, trace_backwards, FALSE); trace_curve(side[i]->mate, L, trace_forwards, FALSE); trace_curve(side[i], L, trace_backwards, TRUE); trace_curve(side[i]->mate, L, trace_forwards, TRUE); break; } } static void do_P2P2_Klein_bottle( PerimeterPiece *side[6], int num_sides) { int i; PerimeterPiece *side0a, *side0b, *side1a, *side1b; /* * Let the longitude connect either pair of * orientation_reversing sides, and store it as its * complete preimage in the double cover, as in * do_standard_Klein_bottle() above. */ for (i = 0; i < num_sides; i++) if (side[i]->gluing_parity == orientation_reversing) { trace_curve(side[i], L, trace_backwards, FALSE); trace_curve(side[i]->mate, L, trace_forwards, FALSE); trace_curve(side[i], L, trace_backwards, TRUE); trace_curve(side[i]->mate, L, trace_forwards, TRUE); break; } /* * The meridian is trickier. If you refer to pictures of * the aabb and aabccB Klein bottles and the definition * of the meridian, you will see the meridian is obtained * by gluing each orientation reversing side to the mate * of the opposite side of the square or hexagon. For * global consistency, the two pieces of the meridian * must be drawn on different preimages of the fundamental * domain (in the torus double cover). */ /* * Look for a side followed by its mate. */ for (i = 0; i < num_sides; i++) if (side[i]->mate == side[(i+1)%num_sides]) { /* * Name the four relevant sides. */ side0a = side[ i ]; side0b = side[(i + 1) %num_sides]; side1a = side[(i + num_sides/2)%num_sides]; side1b = side[(i + 1 + num_sides/2)%num_sides]; /* * Trace out the meridian. */ trace_curve(side0a, M, trace_backwards, FALSE); trace_curve(side1b, M, trace_forwards, FALSE); trace_curve(side1a, M, trace_backwards, TRUE); trace_curve(side0b, M, trace_forwards, TRUE); break; } } /* * trace_curve() traces out a curve on a cusp, beginning * at start and following the tree structure in Extra * back to the base vertex. The result is written directly * into the Tetrahedra's meridian or longitude fields, * according to whether trace_which_curve is M * or L. The curve is directed toward the * perimeter if trace_direction is trace_backwards, and * toward the base vertex if trace_direction is trace_forwards. * The orientation specified by start is used iff * use_opposite_orientation is FALSE. * * To trace a curve from one point on the perimeter to another, * you make two calls to trace_curve(), each of which traces * from the perimeter to the center. Note that some cancellation * is possible, so the final curve need not pass through the * center. The final curve will be the unique shortest path * in the tree structure. */ static void trace_curve( PerimeterPiece *start, PeripheralCurve trace_which_curve, TraceDirection trace_direction, Boolean use_opposite_orientation) { int out_sign, in_sign, (*curve)[4][4]; Tetrahedron *tet, *next_tet; VertexIndex vertex, next_vertex; Extra *tet_extra, *next_extra; /* * Based on the direction of the curve, decide which * sign (+1 or -1) is required where the curve leaves * a triangle going towards the perimeter, and which * is required where it is going towards the center. */ if (trace_direction == trace_backwards) { out_sign = -1; in_sign = +1; } else { out_sign = +1; in_sign = -1; } /* * Record where the curve hits the perimeter. */ curve = start->tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ start->orientation] [start->vertex] [start->face] += out_sign; /* * Now trace back to the root. */ tet = start->tet; vertex = start->vertex; tet_extra = &tet->extra[vertex]; while (tet_extra->parent_tet != NULL) { /* * Note where the curve leaves the present vertex . . . */ curve = tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ tet_extra->orientation] [vertex] [tet_extra->this_faces_parent] += in_sign; /* * . . . and where it enters the parent vertex. */ next_tet = tet_extra->parent_tet; next_vertex = tet_extra->parent_vertex; next_extra = &next_tet->extra[next_vertex]; curve = next_tet->curve[trace_which_curve]; curve[use_opposite_orientation ^ next_extra->orientation] [next_vertex] [tet_extra->parent_faces_this] += out_sign; /* * Move on to the parent vertex. */ tet = next_tet; vertex = next_vertex; tet_extra = next_extra; } } static void free_perimeter( PerimeterPiece *perimeter_anchor) { PerimeterPiece *pp, *dead; pp = perimeter_anchor; do { dead = pp; pp = pp->next; my_free(dead); } while (pp != perimeter_anchor); } static void adjust_Klein_cusp_orientations( Triangulation *manifold) { /* * As explained at the top of this file, a Cusp's peripheral curves * live in the its orientation double cover. When I first wrote this * file, I didn't worry about the orientation of peripheral curves * in nonorientable manifolds. Subsequently it became clear that * they should have the standard orientation relative to the Cusp's * (oriented, not just orientable) orientation double cover. In * the case of a torus Cusp, the peripheral curves live in one * (arbitrarily chosen) component of the orientation double cover; * in the case of a Klein bottle Cusp, the orientation double cover * is connected. * * Fortunately, it's very easy to check whether the peripheral curves * have the standard orientation, and to correct them if necessary. * The definition of the standard orientation for peripheral curves on * a torus is that when the fingers of your right hand point in the * direction of the meridian and your thumb points in the direction * of the longitude, the palm of your hand should face the cusp and * the back of your hand should face the fat part of the manifold. * Combining this with the definition of the intersection number * found at the top of intersection_numbers.c reveals that the * intersection number of the longitude and the meridian (in that * order) should be +1. If it happens to be -1, we must reverse * the meridian. */ /* * If the manifold is oriented, then the peripheral curves will * already have the correct orientation. In fact, they will lie * on the right handed sheet of the Cusp's orientation double * cover, relative to the orientation of the manifold. */ if (manifold->orientability == oriented_manifold) return; /* * The scratch curves might already be in use, so let's make * a copy of whatever's there. */ backup_scratch_curves(manifold); /* * Copy the peripheral curves to both sets of scratch_curve fields. */ copy_curves_to_scratch(manifold, 0, FALSE); copy_curves_to_scratch(manifold, 1, FALSE); /* * Compute their intersection numbers. */ compute_intersection_numbers(manifold); /* * Restore whatever used to be in the scratch_curves. */ restore_scratch_curves(manifold); /* * On Cusps where the intersection number of the longitude and * meridian is -1, reverse the meridian. */ reverse_meridians_where_necessary(manifold); } static void reverse_meridians_where_necessary( Triangulation *manifold) { Tetrahedron *tet; int i, j, k; /* which Tetrahedron */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) /* which ideal vertex */ for (i = 0; i < 4; i++) if (tet->cusp[i]->intersection_number[L][M] == -1) /* which side of the vertex */ for (j = 0; j < 4; j++) if (i != j) /* which sheet (right_handed or left_handed) */ for (k = 0; k < 2; k++) tet->curve[M][k][i][j] = - tet->curve[M][k][i][j]; } static void backup_scratch_curves( Triangulation *manifold) { Tetrahedron *tet; int g, h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (g = 0; g < 2; g++) for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->extra->scratch_curve_backup[g][h][i][j][k] = tet->scratch_curve[g][h][i][j][k]; } static void restore_scratch_curves( Triangulation *manifold) { Tetrahedron *tet; int g, h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (g = 0; g < 2; g++) for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->scratch_curve[g][h][i][j][k] = tet->extra->scratch_curve_backup[g][h][i][j][k]; } regina-4.95/engine/snappea/kernel/positioned_tet.c000644 000765 000024 00000006221 12235724562 022171 0ustar00babstaff000000 000000 /* * positioned_tet.c * * This file provides the following functions for working with PositionedTets: * * void veer_left(PositionedTet *ptet); * void veer_right(PositionedTet *ptet); * void veer_backwards(PositionedTet *ptet); * Boolean same_positioned_tet(PositionedTet *ptet0, PositionedTet *ptet1); * void set_left_edge(EdgeClass *edge, PositionedTet *ptet); * * Their use is described in kernel_prototypes.h. */ #include "kernel.h" void veer_left( PositionedTet *ptet) { Permutation left_gluing; FaceIndex temp; left_gluing = ptet->tet->gluing[ptet->left_face]; ptet->tet = ptet->tet->neighbor[ptet->left_face]; temp = ptet->near_face; ptet->near_face = EVALUATE(left_gluing, ptet->left_face); ptet->left_face = EVALUATE(left_gluing, temp); ptet->right_face = EVALUATE(left_gluing, ptet->right_face); ptet->bottom_face = EVALUATE(left_gluing, ptet->bottom_face); if (parity[left_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } void veer_right( PositionedTet *ptet) { Permutation right_gluing; FaceIndex temp; right_gluing = ptet->tet->gluing[ptet->right_face]; ptet->tet = ptet->tet->neighbor[ptet->right_face]; temp = ptet->near_face; ptet->near_face = EVALUATE(right_gluing, ptet->right_face); ptet->right_face = EVALUATE(right_gluing, temp); ptet->left_face = EVALUATE(right_gluing, ptet->left_face); ptet->bottom_face = EVALUATE(right_gluing, ptet->bottom_face); if (parity[right_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } void veer_backwards( PositionedTet *ptet) { Permutation near_gluing; FaceIndex temp; near_gluing = ptet->tet->gluing[ptet->near_face]; ptet->tet = ptet->tet->neighbor[ptet->near_face]; temp = ptet->left_face; ptet->left_face = EVALUATE(near_gluing, ptet->right_face); ptet->right_face = EVALUATE(near_gluing, temp); ptet->near_face = EVALUATE(near_gluing, ptet->near_face); ptet->bottom_face = EVALUATE(near_gluing, ptet->bottom_face); if (parity[near_gluing] == orientation_reversing) ptet->orientation = ! ptet->orientation; } Boolean same_positioned_tet( PositionedTet *ptet0, PositionedTet *ptet1) { return ( ptet0->tet == ptet1->tet && ptet0->near_face == ptet1->near_face && ptet0->left_face == ptet1->left_face && ptet0->right_face == ptet1->right_face); /* * If three faces match, so must the fourth, and so must the orientation. */ } void set_left_edge( EdgeClass *edge, PositionedTet *ptet) { ptet->tet = edge->incident_tet; ptet->near_face = one_face_at_edge[edge->incident_edge_index]; ptet->left_face = other_face_at_edge[edge->incident_edge_index]; ptet->right_face = remaining_face[ptet->left_face][ptet->near_face]; ptet->bottom_face = remaining_face[ptet->near_face][ptet->left_face]; ptet->orientation = right_handed; } regina-4.95/engine/snappea/kernel/positioned_tet.h000644 000765 000024 00000001670 12235724571 022201 0ustar00babstaff000000 000000 /* * positioned_tet.h * * The PositionedTet data structure records the position in which a * Tetrahedron is currently being considered. The file positioned_tet.c * contains routines for working with PositionedTets. * * Imagine a tetrahedron sitting on a table with one of its faces * facing toward you. The face on the table is bottom_face and the one * facing toward you is near_face. The faces facing away from you * are left_face and right_face. * * The orientation field specifies whether the numbering on the * tetrhedron is right_handed or left_handed when viewed in this position. */ #ifndef _positioned_tet_ #define _positioned_tet_ #include "SnapPea.h" #include "kernel_typedefs.h" #include "triangulation.h" typedef struct { Tetrahedron *tet; FaceIndex near_face, left_face, right_face, bottom_face; Orientation orientation; } PositionedTet; #endif regina-4.95/engine/snappea/kernel/precision.c000644 000765 000024 00000003406 12235724562 021135 0ustar00babstaff000000 000000 /* * precision.c * * This file contains the functions * * int decimal_places_of_accuracy(double x, double y); * int complex_decimal_places_of_accuracy(Complex x, Complex y); * * which are used within the kernel to determine how many decimal * places of accuracy the doubles (resp. Complexes) x and y have in common. * Typically x and y will be two estimates of the same computed quantity * (e.g. the volume of a manifold as computed at the last and the * next-to-the-last iterations of Newton's method in * hyperbolic_structures.c), and decimal_places_of_accuracy() * will be used to tell the user interface how many decimal places * should be printed. We compute the number of decimal places of * accuracy instead of the number of significant figures, because * this is what printf() requires in its formatting string. */ #include "kernel.h" int decimal_places_of_accuracy( double x, double y) { int digits; if (x == y) { if (x == 0.0) digits = DBL_DIG; else digits = DBL_DIG - (int) ceil(log10(fabs(x))); } else digits = - (int) ceil(log10(fabs(x - y))); /* * Typically the difference between the computed values * of a quantity at the penultimate and ultimate iterations * of Newton's method is a little less than the true error. * So we fudge a bit. */ digits -= 4; if (digits < 0) digits = 0; return digits; } int complex_decimal_places_of_accuracy( Complex x, Complex y) { int real_precision, imag_precision; real_precision = decimal_places_of_accuracy(x.real, y.real); imag_precision = decimal_places_of_accuracy(x.imag, y.imag); return MIN(real_precision, imag_precision); } regina-4.95/engine/snappea/kernel/README.txt000644 000765 000024 00000001602 12236247215 020464 0ustar00babstaff000000 000000 SnapPea Kernel -------------- This directory contains portions of the SnapPea kernel, as downloaded from http://www.math.uic.edu/t3m/. This is currently synced with the variant of the SnapPea kernel bundled with SnapPy 2.0.3 (23 Oct 2013). This variant includes additional patches not included in the original SnapPea kernel. SnapPy and the corresponding SnapPea kernel are distributed under the terms of the GNU General Public License, version 2 or any later version, as published by the Free Software Foundation. The full text of this license can be found in the file LICENSE.txt in Regina's top-level source directory. The SnapPea kernel was written by Jeff Weeks, and SnapPy was written by Marc Culler, Nathan Dunfield, and others. M. Culler, N. M. Dunfield, and J. R. Weeks. SnapPy, a computer program for studying the geometry and topology of 3-manifolds, http://snappy.computop.org . regina-4.95/engine/snappea/kernel/shortest_cusp_basis.c000644 000765 000024 00000024777 12236524106 023237 0ustar00babstaff000000 000000 /* * shortest_cusp_basis.c * * This file provides the functions * * Complex cusp_modulus( Complex cusp_shape); * * void shortest_cusp_basis( Complex cusp_shape, * MatrixInt22 basis_change); * * Complex transformed_cusp_shape( Complex cusp_shape, * CONST MatrixInt22 basis_change); * * void install_shortest_bases( Triangulation *manifold); * * cusp_modulus() accepts a cusp_shape (longitude/meridian) and returns * the cusp modulus. Loosely speaking, the cusp modulus is defined as * (second shortest translation)/(shortest translation); it is a * complex number z lying in the region |Re(z)| <= 1/2 && |z| >= 1. * If z lies on the boundary of this region, we choose it so that * Re(z) >= 0. * * shortest_cusp_basis() accepts a cusp_shape (longitude/meridian) and * computes the 2 x 2 integer matrix which transforms the old basis * (u, v ) = (meridian, longitude) to the new basis * (u', v') = (shortest, second shortest). That is, * * | u' | | | | u | * | | = | basis_change | | | * | v' | | | | v | * * 2 x 1 2 x 2 2 x 1 * complex integer complex * vector matrix vector * * (u', v') is such that v'/u' is the cusp modulus defined above. * * Occasionally the shortest or second shortest curve won't be * unique. In most cases the conventions for the cusp modulus stated * above (in particular the convention that Re(z) >= 0 when z is on * the boundary of the fundamental domain) serve to uniquely specify * (u', v') in spite of the nonuniqueness of lengths. The only * exceptions are the hexagonal lattice, where three different curves * all have minimal length, and the square lattice, where two different * curves have minimal length. In these cases the ambiguity is not * resolved, and the choice of (u', v') may be machine dependent. * * transformed_cusp_shape() accepts a cusp_shape and a basis_change, * and computes the shape of the cusp relative to the basis (u', v') * defined by * * | u' | | | | u | * | | = | basis_change | | | * | v' | | | | v | * * (u', v') need not be the (shortest, second shortest) basis. * * install_shortest_bases() installs the (shortest, second shortest) * basis on each torus Cusp of manifold. It does not change the bases * on Klein bottle cusps. As explained for shortest_cusp_basis() * above, the (shortest, second shortest) is not well defined for a * hexagonal lattice, and the results may be machine dependent. * * * shortest_cusp_basis() uses the following algorithm. In principle * u and v could be any two translations which generate the fundamental * group of a torus, although in shortest_cusp_basis(), u is initially 1 * and v is initially the cusp_shape. * * do * { * * if (|u + v| < |u|) * u += v; * * if (|u - v| < |u|) * u -= v; * * if (|v + u| < |v|) * v += u; * * if (|v - u| < |v|) * v -= u; * * } while (still making progress); * * if (|u| > |v|) * replace (u, v) with (v, -u) * * if (Im(v/u) < 0) * flag an error -- the original orientation was wrong * * if (v/u is on the boundary of the fundamental domain described above) * make sure Re(v/u) >= 0 * * Theorem. The above algorithm computes the * (shortest, second shortest) basis. * * Proof. The angle between u and v must be between pi/3 and 2 pi/3; * otherwise the length of the projection of v onto u would be * |v| cos(theta) > |u| cos(pi/3) = |u|/2, and we would have added * +-u to v, thereby shortening v. This shows that |Re(v/u)| <= 1/2. * Because we chose |u| <= |v|, |v/u| >= 1. Therefore v/u lies within * the fundamental domain described above. It is also easy to see that * v is the shortest translation which is linearly independent of u. * The reason is that the row of lattice points 2v + nu is a distance * at least 2 |v| sin(theta) >= 2 |u| sin(pi/3) = sqrt(3) |u| away from * the row 0v + nu. */ #include "kernel.h" #define EPSILON (1e5 * DBL_EPSILON) #define MAXITER 20000 Complex cusp_modulus( Complex cusp_shape) { MatrixInt22 basis_change; shortest_cusp_basis(cusp_shape, basis_change); return transformed_cusp_shape(cusp_shape, basis_change); } void shortest_cusp_basis( Complex cusp_shape, MatrixInt22 basis_change) /* basis_change is an output variable here */ { Complex u, v, u_plus_v, u_minus_v, temp, cusp_modulus; double mod_u, /* These are the complex moduli */ mod_v, /* of the preceding variables. */ mod_u_plus_v, mod_u_minus_v; int i, j, temp_int, iterations; Boolean progress; /* * For an explanation of this algorithm, see the documentation above. */ /* * Make sure cusp_shape is nondegenerate. */ if (fabs(cusp_shape.imag) < EPSILON) { for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = 0; return; } u = One; v = cusp_shape; mod_u = complex_modulus(u); mod_v = complex_modulus(v); for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) basis_change[i][j] = (i == j); iterations = 0; do { progress = FALSE; u_plus_v = complex_plus(u,v); mod_u_plus_v = complex_modulus(u_plus_v); if (mod_u - mod_u_plus_v > EPSILON) { u = u_plus_v; mod_u = mod_u_plus_v; for (j = 0; j < 2; j++) basis_change[0][j] += basis_change[1][j]; progress = TRUE; } else if (mod_v - mod_u_plus_v > EPSILON) { v = u_plus_v; mod_v = mod_u_plus_v; for (j = 0; j < 2; j++) basis_change[1][j] += basis_change[0][j]; progress = TRUE; } u_minus_v = complex_minus(u,v); mod_u_minus_v = complex_modulus(u_minus_v); if (mod_u - mod_u_minus_v > EPSILON) { u = u_minus_v; mod_u = mod_u_minus_v; for (j = 0; j < 2; j++) basis_change[0][j] -= basis_change[1][j]; progress = TRUE; } else if (mod_v - mod_u_minus_v > EPSILON) { v = complex_negate(u_minus_v); mod_v = mod_u_minus_v; for (j = 0; j < 2; j++) basis_change[1][j] -= basis_change[0][j]; progress = TRUE; } iterations += 1; } while (progress && iterations < MAXITER); if (mod_u > mod_v + EPSILON) { temp = u; u = v; v = complex_negate(temp); for (j = 0; j < 2; j++) { temp_int = basis_change[0][j]; basis_change[0][j] = basis_change[1][j]; basis_change[1][j] = - temp_int; } } cusp_modulus = complex_div(v,u); if (cusp_modulus.imag < 0){ /* Things have gone very wrong, bailing with garbage answer */ cusp_modulus.imag = 0; cusp_modulus.real = 0; } if (cusp_modulus.real < -0.5 + EPSILON) { /* * Do an extra v += u. */ cusp_modulus.real = 0.5; for (j = 0; j < 2; j++) basis_change[1][j] += basis_change[0][j]; } if (complex_modulus(cusp_modulus) < 1.0 + EPSILON) { if (cusp_modulus.real < -EPSILON) { /* * Replace (u,v) with (v,-u). */ cusp_modulus.real = - cusp_modulus.real; for (j = 0; j < 2; j++) { temp_int = basis_change[0][j]; basis_change[0][j] = basis_change[1][j]; basis_change[1][j] = - temp_int; } } } } Complex transformed_cusp_shape( Complex cusp_shape, CONST MatrixInt22 basis_change) /* basis_change is an input variable here */ { Complex u, v; u = complex_plus( complex_real_mult( basis_change[0][0], One ), complex_real_mult( basis_change[0][1], cusp_shape ) ); v = complex_plus( complex_real_mult( basis_change[1][0], One ), complex_real_mult( basis_change[1][1], cusp_shape ) ); if (complex_modulus(u) < EPSILON) return Infinity; else return complex_div(v,u); } void install_shortest_bases( Triangulation *manifold) { Cusp *cusp; MatrixInt22 *change_matrices; int i, j; /* * Allocate an array to store the change of basis matrices. */ change_matrices = NEW_ARRAY(manifold->num_cusps, MatrixInt22); /* * Compute the change of basis matrices. */ for (cusp = manifold->cusp_list_begin.next; cusp != &manifold->cusp_list_end; cusp = cusp->next) /* MC 2013-03-06 I changed this so it finds shortest curves on every complete torus cusp, using the *current* shapes. Previously, it would find shortest curves on *every* torus cusp, using the *initial* shapes, i.e. the shapes from the original unfilled hyperbolic structure. This meant that changes in the geometry of unfilled cusps were ignored when other cusps were filled. */ if (cusp->topology == torus_cusp && cusp->is_complete ) shortest_cusp_basis( cusp->cusp_shape[current], change_matrices[cusp->index]); else for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) change_matrices[cusp->index][i][j] = (i == j); /* * Install the change of basis matrices. */ if (change_peripheral_curves(manifold, change_matrices) != func_OK) uFatalError("install_shortest_bases", "shortest_cusp_basis"); /* * Free the array used to store the change of basis matrices. */ my_free(change_matrices); } regina-4.95/engine/snappea/kernel/simplify_triangulation.c000644 000765 000024 00000272475 12235724562 023754 0ustar00babstaff000000 000000 /* * simplify_triangulation.c * * This file contains the following low-level routines for locally * modifying a triangulation * * FuncResult cancel_tetrahedra(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); * FuncResult three_to_two(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); * FuncResult two_to_three(Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr); * * as well as the following high-level routines which call them: * * void basic_simplification(Triangulation *manifold); * void randomize_triangulation(Triangulation *manifold); * * It also includes the low-level routine * * void one_to_four(Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index); * * which is not used by basic_simplification() or randomize_triangulation(), * but is called from canonize_part_2.c. * * The low-level routines are as follows * * cancel_tetrahedra() cancels two Tetrahedra which share * a common edge of order 2. * * three_to_two() replaces three Tetrahedra surrounding a common * edge with two Tetrahedra sharing a common face. * * two_to_three() replaces two Tetrahedra sharing a common face * with three Tetrahedra surrounding a common edge. * * one_to_four() replaces one Tetrahedron with four Tetrahedra * meeting at a point. * * If an operation cannot be performed because of a topological or * geometric obstruction, the function does nothing and returns func_failed. * Otherwise, it performs the operation and returns func_OK. * * The function one_to_four() will always succeed, and therefore returns * void. It introduces a finite vertex at the center of the Tetrahedron, * and therefore cannot be used when a hyperbolic structure is present. * * The three_to_two(), two_to_three() and one_to_four() operations each * correspond to a projection of a 4-simplex. * * For further details, please see the comments preceding each low-level * function. * * * In practice, other SnapPea routines will most likely call the * high-level functions basic_simplification() and randomize_triangulation(). * * basic_simplification() first does easy simplifications * (namely retriangulating neighborhoods of EdgeClasses of * order 1, 2 and 3 to reduce the number of Tetrahedra whenever * possible, and retriangulating suspended pentagons using five * Tetrahedra instead of six), and then retriangulates octahedra * (choosing a different one of the three possible axes for the * subdivision into four Tetrahedra) in hopes of making further * easy simplifications possible. * * randomize_triangulation() randomizes the Triangulation, and then * resimplifies it. * * basic_simplification() and randomize_triangulation() may be called * for manifolds with or without a hyperbolic structure present. * The final Triangulation may depend on whether or not the hyperbolic * structure is present, because when a hyperbolic structure is present * the low-level routines will refuse to create degenerate Tetrahedra. * * Most routines in SnapPea keep track of edge angles "mod 0" rather * than just "mod 2 pi", so that, e.g., a ComplexWithLog with * log.imag equal to (3/2) pi is different than one with log.imag * equal to (-1/2) pi. Unfortunately, the mod 0 angles for a given * Triangulation are somewhat arbitrary, in the sense that the following * procedure converts one mod 0 solution to a different mod 0 solution. * * Pick an EdgeClass ec in the Triangulation, and consider all the * Tetrahedra incident to it. If the incident edges don't all belong * to distinct Tetrahedra, work in the universal cover, so that the * Tetrahedra will at least appear distinct. For each Tetrahedron, * call the angle incident to the EdgeClass ec gamma, and call the * opposite angle gamma as well (they will of course be equal, due to * the symmetry of the ideal tetrahedron). Call one remaining pair * of opposite edges alpha, and the other pair beta. Make the choice * of alphas and betas consistent for all the Tetrahedra incident to * the EdgeClass ec; that is, each alpha of one Tetrahedron should * be incident to a beta of an adjacent Tetrahedron. Now add 2 pi i * to the log of each alpha edge angle, and subtract 2 pi i from the * log of each beta edge angle. Note that * * (1) The sum of the logs of the edge angles remains pi i for * each Tetrahedron. * * (2) The sum of the logs of the angles surrounding each EdgeClass * remains 2 pi i. * * (3) The holonomies of the cusps are unaltered. (At least in the * generic case -- I haven't thought through what happens when * the Tetrahedra incident to the EdgeClass ec are not all distinct.) * * The point of all this is that the mod 0 edge angles in a Triangulation * are not uniquely defined. If all the Tetrahedra are positively * oriented, then one typically expects to find a solution with mod 0 * edge angles in the range [0, pi], but if some of the Tetrahedra * are negatively oriented, then the choice of edge angles becomes * murkier. * * When I first started writing the low-level routines in this file * (i.e. two_to_three(), three_to_two() and cancel_tetrahedra()) * I naively expected to keep track of the mod 0 edge angles. This * was no problem in the three_to_two() move. It was a little more * difficult in the two_to_three() move because some arbitrary choices * were involved, and I couldn't see how to prove that the angles sums * would be preserved both at EdgeClasses and in each Tetrahedron. * The scheme broke down entirely in cancel_tetrahedra(), because * a pair of allegedly cancelling angles could differ by 2 pi i. * One could correct the problem locally, but only at the risk of * creating a solution whose edge angles differed from the "preferred" * ones by multiples of 2 pi i, as described above. Given that * * (1) I couldn't see how to simplify the mod 0 angles in any * reasonable and canonical way, and * * (2) We are mainly interested in solutions with positively * oriented Tetrahedra, or at worst with angles in the * range [(-1/2) pi, (3/2) pi], * * I decided that the low-level routines in this file should only * keep track of the mod 2 pi angles, choosing values in the * range [(-1/2) pi, (3/2) pi]. Just before returning, * basic_simplification() calls polish_solution() (that's polish, * not Polish). In the generic case (when the mod 0 angles are * valid, but the TetShapes have lost some accuracy) the effect of * polish_solution() is to recover the lost accuracy, without * substantially changing the solution. In the exceptional case * that the edge angles don't add up correctly around a Tetrahedron * or EdgeClass, polish_solution() will find an entirely new * solution to the gluing equations. * * randomize_triangulation() calls basic_simplification(), so its * solutions also get polished. * * 97/2/3 Modified to strip off the geometric structure (if any) * at the start of basic_simplification() and randomize_triangulation(), * and (if there was a geometric structure) recompute it at the end. * The old system was working fine for hyperbolic manifolds, but now that * SnapPea is working with degenerate solutions (to split along normal * surfaces) one wants to be able to randomize and simplify them too. * 98/5/20 Modified *not* to strip off the geometric structure * when the cusp_nbhd_position is present. The low-level routines * need the hyperbolic structure to maintain the cusp_nbhd_position. * * 97/2/4 Modified to handle Triangulations containing finite vertices. * The 4-1 move, in which four tetrahedra surrounding a common finite * vertex are replaced by a single tetrahedron, is handled implicitly * as a 3-2 move (on one of the edge classes incident to the finite * vertex) followed by a 2-0 move on a pair of tetrahedra having three * faces and a finite vertex in common. The code in cancel_tetrahedra() * was modified to accomodate this. When the finite vertex is removed, * a gap remains in the (negative) numbering of the Cusps structures * for finite vertices, but this isn't a problem. */ #include "kernel.h" #include /* needed for rand() */ /* * ORDER_FOUR_ITERATIONS_IN_SIMPLIFY tells how many times * basic_simplification() should pass unsuccessfully down * the list of EdgeClasses before giving up. */ #define ORDER_FOUR_ITERATIONS_IN_SIMPLIFY 6 /* * RANDOMIZATION_MULTIPLE tells how long randomize_triangulation() * should keep randomizing before it resimplifies the manifold. * It will attempt RANDOMIZATION_MULTIPLE * manifold->num_tetrahedra * two-to-three moves, each followed by some rudimentary resimplification * to avoid wasting time in degenerate situations. */ #define RANDOMIZATION_MULTIPLE 4 static Tetrahedron *get_tet(Triangulation *manifold, int desired_index); static void check_for_cancellation(Triangulation *manifold); static Boolean easy_simplification(Triangulation *manifold); static FuncResult remove_edge_of_order_one(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static Boolean this_way_works(Tetrahedron *tet, FaceIndex left_face, FaceIndex right_face, FaceIndex bottom_face); static FuncResult cancel_tetrahedra_with_finite_vertex(Tetrahedron *tet, VertexIndex finite_vertex, EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult edges_of_order_four(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult try_adjacent_fours(Tetrahedron *tet0, FaceIndex f0, FaceIndex f1, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static FuncResult create_new_order_four(EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr); static Boolean four_tetrahedra_are_distinct(PositionedTet ptet); static void set_inverse_neighbor_and_gluing(Tetrahedron *tet, FaceIndex f); void basic_simplification( Triangulation *manifold) { SolutionType original_solution_type[2]; int iter; EdgeClass *edge, *where_to_resume; Boolean hyperbolic_structure_was_removed; /* * 97/2/3 Strip off the geometric structure if there is one. * * 98/5/20 Oops. We don't want to strip off the hyperbolic * structure if the cusp_nbhd_position is present, because the * low-level routines need the hyperbolic structure to maintain * cusp_nbhd_position. */ if (manifold->tet_list_begin.next->cusp_nbhd_position == NULL) { original_solution_type[complete] = manifold->solution_type[complete]; original_solution_type[filled] = manifold->solution_type[filled]; remove_hyperbolic_structures(manifold); hyperbolic_structure_was_removed = TRUE; } else hyperbolic_structure_was_removed = FALSE; /* * First do all the easy simplifications, namely removing * EdgeClasses of order 1, 2 and 3 when possible, and * retriangulating suspended pentagons with five Tetrahedra * instead of six. */ easy_simplification(manifold); /* * Go down the list retriangulating the octahedra surrounding * EdgeClasses of order 4, in the hope of creating new, more * useful EdgeClasses of order 4. Keep doing this until we've * gone through the list ORDER_FOUR_ITERATIONS_IN_SIMPLIFY times * with no further progress. * * The operation of the inner loop is complicated by the * appearance and disappearance of EdgeClasses as the * algorithm proceeds. To avoid possible infinite loops, * and also to avoid possible "resonance" phenomena, we * pseudorandomly decide whether or not to perform each * potential retriangulation we encounter. */ for (iter = 0; iter < ORDER_FOUR_ITERATIONS_IN_SIMPLIFY; iter++) for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) if ((rand() & 3) > 0 /* proceed with probability 3/4 */ && create_new_order_four(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { if (easy_simplification(manifold) == TRUE) { iter = -1; break; } else edge = where_to_resume; } /* * Clean up. * * 97/2/3 If we trashed the tet shapes, reinitialize * them and then call polish_hyperbolic_structure(). Obviously * polish_hyperbolic_structure() will be recomputing the geometric * structure from scratch, not just "polishing" it. */ tidy_peripheral_curves(manifold); if (hyperbolic_structure_was_removed && original_solution_type[complete] != not_attempted) { manifold->solution_type[complete] = original_solution_type[complete]; manifold->solution_type[filled] = original_solution_type[filled]; initialize_tet_shapes(manifold); polish_hyperbolic_structures(manifold); } /* * The Chern-Simons invariant of the manifold is still the * same, but the fudge factor may have changed. */ compute_CS_fudge_from_value(manifold); } void randomize_triangulation( Triangulation *manifold) { SolutionType original_solution_type[2]; int count; Boolean hyperbolic_structure_was_removed; /* * 97/2/3 Strip off the geometric structure if there is one. * * 98/5/20 Oops. We don't want to strip off the hyperbolic * structure if the cusp_nbhd_position is present, because the * low-level routines need the hyperbolic structure to maintain * cusp_nbhd_position. */ if (manifold->tet_list_begin.next->cusp_nbhd_position == NULL) { original_solution_type[complete] = manifold->solution_type[complete]; original_solution_type[filled] = manifold->solution_type[filled]; remove_hyperbolic_structures(manifold); hyperbolic_structure_was_removed = TRUE; } else hyperbolic_structure_was_removed = FALSE; /* * Randomize the triangulation, doing only minimal * simplifications along the way. The minimal simplifications * are crucial -- otherwise the algorithm would create, * say, a pair of potentially cancelling Tetrahedra, and * then waste all it's remaining efforts making the union * of those two Tetrahedra more and more complex. * * By the way, not all the calls to two_to_three() will * succeed (e.g. because some Tetrahedra may be glued to * themselves), but that's OK. */ for (count = RANDOMIZATION_MULTIPLE * manifold->num_tetrahedra; --count >= 0; ) if (two_to_three( get_tet(manifold, rand() % manifold->num_tetrahedra), rand() % 4, &manifold->num_tetrahedra) == func_OK) check_for_cancellation(manifold); /* * Resimplify the manifold. * basic_simplification() will tidy up the peripheral curves, * recompute the hyperbolic structure (if one is present), * and recompute the CS_fudge. */ if (hyperbolic_structure_was_removed && original_solution_type[complete] != not_attempted) { manifold->solution_type[complete] = original_solution_type[complete]; manifold->solution_type[filled] = original_solution_type[filled]; initialize_tet_shapes(manifold); /* unnecessary, but robust */ } basic_simplification(manifold); } static Tetrahedron *get_tet( Triangulation *manifold, int desired_index) { int i; Tetrahedron *tet; /* * Return a pointer to the i-th Tetrahedron on the list, * with implicit numbering 0 through (num_tetrahedra - 1). */ for (i = 0, tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; i++, tet = tet->next) if (i == desired_index) return tet; /* * If we get to here, something went wrong. */ uFatalError("get_tet", "simplify_triangulation"); /* * The C++ compiler would like a return value, even though * we never return from the uFatalError() call. */ return NULL; } static void check_for_cancellation( Triangulation *manifold) { Boolean progress; EdgeClass *edge, *where_to_resume; /* * This function is similar to easy_simplification() (see below), * except that it checks only for EdgeClasses of order 1 or 2. */ do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) switch (edge->order) { case 1: if (remove_edge_of_order_one(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; edge = where_to_resume; } break; case 2: if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; edge = where_to_resume; } break; default: break; } } while (progress == TRUE); } /* * easy_simplification() removes edges of order 1, 2 and 3 * whenever possible, and simplifies the neighborhoods of adjacent * edges of order 4 when the six incident Tetrahedra are distinct. * * easy_simplification() returns TRUE if it simplifies the * Triangulation, FALSE otherwise. * * create_new_order_four() undertakes more daring operations * with EdgeClasses of order 4. */ static Boolean easy_simplification( Triangulation *manifold) { Boolean progress, triangulation_was_simplified; EdgeClass *edge, *where_to_resume; /* * Our plan is to keep going down the list of EdgeClasses, * removing EdgeClasses of order 1, 2 or 3 whenever possible, * and retriangulating suspended pentagons with five Tetrahedra * instead of six. When no further progress can be made, we're done. * * The low-level routines set the variable where_to_resume to point * to some valid EdgeClass. This allows the for(;;) loop to continue * down the list, rather than restarting at the beginning each time * a simplification occurs. (If only one EdgeClass is deleted, * where_to_resume points to its predecessor.) * * Technical comment: This function would run a tiny bit faster * if the EdgeClasses were shuffled about on various queues, * as in the old snappea, but the present system is simpler. */ triangulation_was_simplified = FALSE; do { progress = FALSE; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) switch (edge->order) { case 1: if (remove_edge_of_order_one(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 2: if (cancel_tetrahedra(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 3: if (three_to_two(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; case 4: if (edges_of_order_four(edge, &where_to_resume, &manifold->num_tetrahedra) == func_OK) { progress = TRUE; triangulation_was_simplified = TRUE; edge = where_to_resume; } break; default: break; } } while (progress == TRUE); return triangulation_was_simplified; } static FuncResult remove_edge_of_order_one( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet; FaceIndex left_face, right_face, bottom_face; /* * remove_edge_of_order_one() contains no explicit low-level * retriangulation. Instead, each call to remove_edge_of_order_one() * calls two_to_three() to increase the order of EdgeClass *edge from * one to two, and then calls cancel_tetrahedra() to remove *edge. * Because two_to_three() increases the number of Tetrahedra by * one and cancel_tetrahedra() decreases it by two, there is a net * loss of one Tetrahedron. * * remove_edge_of_order_one() checks ahead of time whether the * calls to two_to_three() and cancel_tetrahedra() will be able * to succeed; if not (e.g. because of an embedded annulus), * remove_edge_of_order_one() does nothing and returns func_failed. * * The new EdgeClass created in the call to two_to_three() has * its order reduced to one in the call to cancel_tetrahedra(). * Thus, remove_edge_of_order_one() always leave a new EdgeClass * of order one. Eventually remove_edge_of_order_one() will be * blocked by an annulus. Typically this annulus is trivial, * and opposite the EdgeClass of order 1 there is an EdgeClass of * order two, whose removal (by an independent call to * cancel_tetrahedra()) also destroys the EdgeClass of order 1. * * I'd like to draw some illustrations, but it just isn't possible * in a text-only file. So I'll leave it as an exercise for the * reader to illustrate what happens in the successive calls * to remove_edge_of_order_one(). */ /* * Label the Tetrahedron and the two faces incident to the * EdgeClass of order one. */ tet = edge->incident_tet; left_face = one_face_at_edge[edge->incident_edge_index]; right_face = other_face_at_edge[edge->incident_edge_index]; /* * EdgeClasses of order 1 should never occur when a hyperbolic * structure is present. */ if (tet->shape[complete] != NULL) uFatalError("remove_edge_of_order_one", "simplify_triangulation"); /* * Let bottom_face be a candidate face for performing the * two-to-three move. Check ahead of time whether the calls * to two-to-three() and cancel_tetrahedra() will succeed. */ if (this_way_works(tet, left_face, right_face, remaining_face[left_face][right_face]) == TRUE) bottom_face = remaining_face[left_face][right_face]; else if (this_way_works(tet, left_face, right_face, remaining_face[right_face][left_face]) == TRUE) bottom_face = remaining_face[right_face][left_face]; else return func_failed; /* * Call two_to_three() and cancel_tetrahedra(). */ if ( two_to_three(tet, bottom_face, num_tetrahedra_ptr) == func_failed || edge->order != 2 || cancel_tetrahedra(edge, where_to_resume, num_tetrahedra_ptr) == func_failed ) uFatalError("remove_edge_of_order_one", "simplify_triangulation"); return func_OK; } static Boolean this_way_works( Tetrahedron *tet, FaceIndex left_face, FaceIndex right_face, FaceIndex bottom_face) { Tetrahedron *tet1; FaceIndex left1, right1, bottom1; EdgeClass *edgeA, *edgeB; /* * The left_ and right_faces fold together to form the EdgeClass * of order one. * The bottom_face cannot be glued to the remaining face of tet, * because if it were we'd have a manifold with only one Tetrahedron * but at least two EdgeClasses, which violates the proposition * that in a manifold with cusp cross sections of Euler characteristic * zero, the number of EdgeClasses must equal the number of * Tetrahedra. */ /* * Oops! The reasoning in the preceding paragraph fails us * for finite triangulations (with honest vertices instead of * ideal vertices). In such cases it suffices simply to report * that the triangulation cannot be simplified. JRW 2002/08/26 */ if (tet->neighbor[bottom_face] == tet) /* uFatalError("this_way_works", "simplify_triangulation"); */ return FALSE; /* * We want to locate the two EdgeClasses which would be combined * when remove_edge_of_order_one() calls cancel_tetrahedra(). */ tet1 = tet->neighbor[bottom_face]; left1 = EVALUATE(tet->gluing[bottom_face], left_face); right1 = EVALUATE(tet->gluing[bottom_face], right_face); bottom1 = EVALUATE(tet->gluing[bottom_face], bottom_face); edgeA = tet1->edge_class[edge_between_vertices[bottom1][ left1]]; edgeB = tet1->edge_class[edge_between_vertices[bottom1][right1]]; return (edgeA != edgeB); } /* * cancel_tetrahedra() checks whether the two Tetrahedra * incident to the EdgeClass edge contain an annulus or * Moebius strip, and if they do not, it cancels them and * returns func_OK. If they do contain an annulus or Moebius * strip, cancel_tetrahedra() does nothing and returns func_failed. * * Comments in the code below explain how the cancellation * occurs, and why no other degenerate situations can occur. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. * * 97/2/4 Modified to allow for the possibility of two Tetrahedra * sharing three faces and the enclosed finite vertex. In this * case the annulus referred to above encloses a solid cylinder. * The tetrahedra are cancelled and the finite vertex is removed. */ FuncResult cancel_tetrahedra( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet[2], *nbr[2], *t; VertexIndex v[2][4], w[2][4]; Orientation orientation[2]; EdgeClass *outer_edge[2]; Boolean are_whole_manifold; int c, i, ii, j, k; int delta[2][2][2]; VertexIndex active_vertex; Boolean tet_orientations_agree, edge_orientations_agree, edge_class_orientations_agree; PositionedTet ptet, ptet0; EdgeIndex left_edge; Permutation gluing[2]; /* * Just to be safe . . . */ if (edge->order != 2) uFatalError("cancel_tetrahedra", "simplify_triangulation"); /* * Let tet[0] and tet[1] be the two Tetrahedra incident * to EdgeClass *edge, and v[i][j] be their vertices. * Vertex v[0][i] is glued to vertex v[1][i]. * Vertices v[i][0] and v[i][1] are incident to the * EdgeClass *edge. */ tet[0] = edge->incident_tet; v[0][0] = one_vertex_at_edge[edge->incident_edge_index]; v[0][1] = other_vertex_at_edge[edge->incident_edge_index]; v[0][2] = remaining_face[v[0][1]][v[0][0]]; v[0][3] = remaining_face[v[0][0]][v[0][1]]; orientation[0] = right_handed; if (tet[0]->neighbor[v[0][2]] != tet[0]->neighbor[v[0][3]] || tet[0]->gluing [v[0][2]] != tet[0]->gluing [v[0][3]]) uFatalError("cancel_tetrahedra", "simplify_triangulation"); tet[1] = tet[0]->neighbor[v[0][2]]; for (i = 0; i < 4; i++) v[1][i] = EVALUATE(tet[0]->gluing[v[0][2]], v[0][i]); orientation[1] = (parity[tet[0]->gluing[v[0][2]]] == orientation_preserving) ? orientation[0] : ! orientation[0]; /* * It's easy to prove that if the manifold has only torus and Klein * bottle cusp cross sections, then tet[0] and tet[1] are distinct. * * 97/2/4 I assume that the presence of at least one torus or * Klein bottle cusp is enough to guarantee that tet[0] != tet[1], * but I haven't thought through the details. Even if we eventually * wanted to use this code to simplify non-ideal triangulations * of closed manifolds, we could simply replace the uFatalError() * call with func_failed. * * 99/06/04 Indeed we do want this code to simplify non-ideal * triangulations of closed manifolds, so I replaced * uFatalError("cancel_tetrahedra", "simplify_triangulation"); * with * return func_failed; */ if (tet[0] == tet[1]) return func_failed; /* * If the edge connecting v[0][2] to v[0][3] belongs to the same * EdgeClass as the edge connecting v[1][2] to v[1][3], then the * union of tet[0] and tet[1] contains an embedded annulus or * Moebius strip, and we should return func_failed. * * 97/2/4 Check whether tet[0] and tet[1] share three faces, * and enclose a finite vertex. If so, we may cancel the Tetrahedra, * and also remove the finite vertex. Obviously these changes will * never be invoked for ideal triangulations (i.e. with no finite * vertices). * * 2000/03/14 Oops! In the 97/2/4 change I overlooked the possibility * that tet[0] and tet[1] comprise the entire manifold (in which * case the manifold is a 3-sphere or L(3,1)). The code now tests * for this possibility, and returns func_failed when it occurs. */ for (i = 0; i < 2; i++) outer_edge[i] = tet[i]->edge_class[edge_between_vertices[v[i][2]][v[i][3]]]; if (outer_edge[0] == outer_edge[1]) { for (i = 0; i < 2; i++) if (tet[0]->cusp[v[0][i]]->is_finite == TRUE && tet[0]->neighbor[v[0][!i]] == tet[1] && tet[0]->neighbor[v[0][ i]] != tet[1] && tet[0]->gluing[v[0][!i]] == tet[0]->gluing[v[0][2]]) return cancel_tetrahedra_with_finite_vertex(tet[0], v[0][i], edge, where_to_resume, num_tetrahedra_ptr); return func_failed; } /* * The plan is to flatten the two Tetrahedra. To prove rigorously * that this does not change the topology of the manifold, first * imagine compressing the strip lying between the edge from * v[0][2] to v[0][3] and the edge from v[1][2] to v[1][3]. * This is valid iff the two edges are in distinct EdgeClasses, * and we just checked that they are. Then imagine flattening * the two triangular pillows. This is OK iff the two triangular * pillows don't make up the whole manifold, which they don't * because otherwise the boundary would contain a sphere. * Q.E.D. * * 2000/03/14 Test explicitly whether the two triangular pillows * make up the whole manifold. */ are_whole_manifold = TRUE; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) if (tet[i]->neighbor[v[i][j]] != tet[0] && tet[i]->neighbor[v[i][j]] != tet[1]) are_whole_manifold = FALSE; if (are_whole_manifold == TRUE) return func_failed; /* * Before compressing the aforementioned strip, we need to clear * the peripheral curves away from the strip we're going to * collapse. While we're at it, we'll relabel all edges in * EdgeClass outer_edge[1] as EdgeClass outer_edge[0], and adjust * their edge_orientation if necessary, in preparation for merging * the two classes. */ for (c = 0; c < 2; c++) /* M or L */ for (j = 0; j < 2; j++) /* top (= 0) or bottom (= 1) */ for (i = 0; i < 2; i++) /* right_handed or left_handed */ { ii = (orientation[0] == orientation[1]) ? i : !i; delta[c][j][i] = tet[1]->curve[c][ i][v[1][j+2]][v[1][0]] + tet[0]->curve[c][ii][v[0][j+2]][v[0][0]]; } tet_orientations_agree = (orientation[0] == orientation[1]); edge_orientations_agree = (tet[0]->edge_orientation[edge_between_faces[v[0][0]][v[0][1]]] == tet[1]->edge_orientation[edge_between_faces[v[1][0]][v[1][1]]]); edge_class_orientations_agree = (tet_orientations_agree == edge_orientations_agree); ptet0.tet = tet[1]; ptet0.near_face = v[1][1]; ptet0.left_face = v[1][0]; ptet0.right_face = v[1][3]; ptet0.bottom_face = v[1][2]; ptet0.orientation = orientation[1]; ptet = ptet0; do { /* * Adjust the peripheral curves. */ for (c = 0; c < 2; c++) for (j = 0; j < 2; j++) { active_vertex = (j == 0) ? ptet.bottom_face : ptet.right_face; for (i = 0; i < 2; i++) { ii = (ptet.orientation == ptet0.orientation) ? i : !i; ptet.tet->curve[c][i][active_vertex][ptet.left_face] -= delta[c][j][ii]; ptet.tet->curve[c][i][active_vertex][ptet.near_face] += delta[c][j][ii]; } } /* * For convenience, note the EdgeIndex of the left edge. */ left_edge = edge_between_faces[ptet.near_face][ptet.left_face]; /* * Adjust the EdgeClass. */ ptet.tet->edge_class[left_edge] = outer_edge[0]; /* * Adjust the edge_orientation. */ if ( ! edge_class_orientations_agree) ptet.tet->edge_orientation[left_edge] = ! ptet.tet->edge_orientation[left_edge]; /* * Move on. */ veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); /* * Adjust the EdgeClass sizes. */ outer_edge[0]->order += outer_edge[1]->order; for (i = 0; i < 2; i++) for (j = 0; j < 6; j++) tet[i]->edge_class[j]->order--; /* * We are about to delete EdgeClasses edge and outer_edge[1]. * Set *where_to_resume to point to the EdgeClass just * just before the spot where edge was. */ if (edge->prev != outer_edge[1]) *where_to_resume = edge->prev; else *where_to_resume = outer_edge[1]->prev; /* * Free the unused EdgeClasses. */ REMOVE_NODE(edge); REMOVE_NODE(outer_edge[1]); my_free(edge); my_free(outer_edge[1]); /* * Set the incident_tet and incident_edge_index fields * for all EdgeClasses which lost members. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) { t = tet[i]->neighbor[v[i][j]]; if (t != tet[0] && t != tet[1]) for (k = 0; k < 6; k++) { t->edge_class[k]->incident_tet = t; t->edge_class[k]->incident_edge_index = k; } } /* * Set neighbors and gluings. */ for (i = 0; i < 2; i++) { /* which face */ for (j = 0; j < 2; j++) /* which Tetrahedron */ { nbr[j] = tet[j]->neighbor[v[j][i]]; gluing[j] = tet[j]->gluing [v[j][i]]; for (k = 0; k < 4; k++) /* which vertex */ w[j][k] = EVALUATE(gluing[j], v[j][k]); } for (j = 0; j < 2; j++) /* which Tetrahedron */ { nbr[j]->neighbor[w[j][i]] = nbr[!j]; nbr[j]->gluing [w[j][i]] = CREATE_PERMUTATION( w[j][0], w[!j][0], w[j][1], w[!j][1], w[j][2], w[!j][2], w[j][3], w[!j][3]); } } /* * Free the collapsed Tetrahedra. */ for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr -= 2; return func_OK; } static FuncResult cancel_tetrahedra_with_finite_vertex( Tetrahedron *tet, VertexIndex finite_vertex, EdgeClass *edge, /* needed only for setting *where_to_resume */ EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *nbr, *tet_outer, *nbr_outer; Permutation gluing; FaceIndex f, ff, tet_outer_f, nbr_outer_f; VertexIndex v, nbr_finite; EdgeIndex e; Cusp *dead_cusp; EdgeClass *dead_edge; /* * The three faces of the tet surrounding the finite_vertex * are glued to the neighboring Tetrahedron in the obvious way, * forming a triangular pillow with a finite vertex and three * EdgeClasses in its interior. */ nbr = tet->neighbor[!finite_vertex]; gluing = tet->gluing [!finite_vertex]; if (tet->cusp[finite_vertex]->is_finite != TRUE) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); for (f = 0; f < 4; f++) if (f != finite_vertex) if (tet->neighbor[f] != nbr || tet->gluing [f] != gluing) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * If tet and nbr had four faces in common, then the manifold * couldn't have any torus or Klein bottle boundary components. * We assume this isn't the case. */ if (tet->neighbor[finite_vertex] == nbr) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * The peripheral curves will match up correctly after the cancellation. * No explicit preparation is required. */ /* * Remove the Cusp structure representing the finite vertex * in the triangular pillow's interior. Finite vertices aren't * counted in a Triangulation's num_cusps field. By removing * this Cusp we may leave a gap in the (negative) indexing of * finite vertex Cusps, but that's OK. */ dead_cusp = tet->cusp[finite_vertex]; REMOVE_NODE(dead_cusp); my_free(dead_cusp); /* * Remove the three EdgeClasses from the pillow's interior. * * Make sure the calling program is left with a valid pointer * to an EdgeClass, to continue its loop where it left off. */ *where_to_resume = edge->prev; for (v = 0; v < 4; v++) if (v != finite_vertex) { dead_edge = tet->edge_class[edge_between_vertices[v][finite_vertex]]; if (dead_edge == *where_to_resume) *where_to_resume = dead_edge->prev; REMOVE_NODE(dead_edge); my_free(dead_edge); } /* * Note which Tetrahedra border the triangular pillow's outer faces. */ tet_outer = tet->neighbor[finite_vertex]; tet_outer_f = EVALUATE(tet->gluing[finite_vertex], finite_vertex); nbr_finite = EVALUATE(gluing, finite_vertex); nbr_outer = nbr->neighbor[nbr_finite]; nbr_outer_f = EVALUATE(nbr->gluing[nbr_finite], nbr_finite); /* * Make sure the three EdgeClasses around the pillow's boundary * "see" Tetrahedra other than the ones we are about to cancel. * Reduce the order of each such EdgeClass by two. */ for (f = 0; f < 4; f++) if (f != finite_vertex) { ff = EVALUATE(tet->gluing[finite_vertex], f); e = edge_between_faces[tet_outer_f][ff]; tet_outer->edge_class[e]->incident_tet = tet_outer; tet_outer->edge_class[e]->incident_edge_index = e; tet_outer->edge_class[e]->order -= 2; } /* * Glue tet_outer and nbr_outer to one another. * (Note: compose_permutations() composes right-to-left.) */ tet_outer->neighbor[tet_outer_f] = nbr_outer; tet_outer->gluing[tet_outer_f] = compose_permutations(gluing, tet_outer->gluing[tet_outer_f]); tet_outer->gluing[tet_outer_f] = compose_permutations(nbr->gluing[nbr_finite], tet_outer->gluing[tet_outer_f]); nbr_outer->neighbor[nbr_outer_f] = tet_outer; nbr_outer->gluing[nbr_outer_f] = compose_permutations(inverse_permutation[gluing], nbr_outer->gluing[nbr_outer_f]); nbr_outer->gluing[nbr_outer_f] = compose_permutations(tet->gluing[finite_vertex], nbr_outer->gluing[nbr_outer_f]); if (nbr_outer->gluing[nbr_outer_f] != inverse_permutation[tet_outer->gluing[tet_outer_f]] || EVALUATE(tet_outer->gluing[tet_outer_f], tet_outer_f) != nbr_outer_f) uFatalError("cancel_tetrahedra_with_finite_vertex", "simplify_triangulation"); /* * Remove tet and nbr. */ REMOVE_NODE(tet); REMOVE_NODE(nbr); free_tetrahedron(tet); free_tetrahedron(nbr); *num_tetrahedra_ptr -= 2; return func_OK; } /* * If the three Tetrahedra surrounding the EdgeClass *edge are distinct, * three_to_two() replaces them with two Tetrahedra sharing a common * face, and returns func_OK. Otherwise it does nothing and returns * func_failed. * * The Orientations of the two new Tetrahedra are set to match the * Orientation of one of the three old ones, so that the Orientability * of the Triangulation (if there is one) will be preserved. * * The two new Tetrahedra created by three_to_two() take the place * of one of the doomed ones in the list of Tetrahedra. The doomed * Tetrahedron are removed from the list before being destroyed. * Similarly, the EdgeClass edge is removed from its list before * being destroyed. * * If the three original Tetrahedra are nondegenerate, the two * two new ones must perforce be nondegenerate as well. Proof: * if a pair of ideal vertices coincides in a new Tetrahedra, * that pair must have coincided in one of the three original * Tetrahedra as well. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. */ FuncResult three_to_two( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { int c, h, hh, i, j, j1, j2; Tetrahedron *tet[3], *new_tet[2]; VertexIndex v[3][4], w[2][4]; Orientation old_orientation[3]; Permutation gluing; EdgeIndex old_h_edge_index, old_v_edge_index, new_h_edge_index, new_v_edge_index; /* * Just to be safe . . . */ if (edge->order != 3) uFatalError("three_to_two", "simplify_triangulation"); /* * The three Tetrahedra incident to the EdgeClass *edge will be * called tet[0], tet[1] and tet[2]. The vertices of tet[i] will * be v[i][0-3]. * * I recommend making a sketch of tet[0-2] to consult as you * read through the following code. The EdgeClass *edge is * vertical. Vertex v[i][0] of each tet[i] is at the bottom * of the edge, and vertex v[i][1] is at the top. Vertices * v[i][2] and v[i][3] are on the "equator", with v[i][3] * being counterclockwise from v[i][2] as viewed from above. */ /* * Locate one Tetrahedron incident to EdgeClass *edge. * Choose the v[0][j] so that tet[0] is viewed with * the right_handed Orientation. */ tet[0] = edge->incident_tet; v[0][0] = one_vertex_at_edge[edge->incident_edge_index]; v[0][1] = other_vertex_at_edge[edge->incident_edge_index]; v[0][2] = remaining_face[v[0][0]][v[0][1]]; v[0][3] = remaining_face[v[0][1]][v[0][0]]; old_orientation[0] = right_handed; /* * Locate the two remaining Tetrahedra. * If the Triangulation is oriented, they will also be positioned * with the right_handed Orientation. */ for (i = 0; i < 2; i++) { tet[i+1] = tet[i]->neighbor[v[i][2]]; gluing = tet[i]->gluing[v[i][2]]; v[i+1][0] = EVALUATE(gluing, v[i][0]); v[i+1][1] = EVALUATE(gluing, v[i][1]); v[i+1][2] = EVALUATE(gluing, v[i][3]); v[i+1][3] = EVALUATE(gluing, v[i][2]); old_orientation[i+1] = (parity[gluing] == orientation_preserving) ? old_orientation[i] : ! old_orientation[i]; } /* * If the three Tetrahedra are not distinct, we can't do any * simplification, so return func_failed. */ for (i = 0; i < 3; i++) if (tet[i] == tet[(i+1)%3]) return func_failed; /* * This function should never be invoked when canonize_info is present. */ if (tet[0]->canonize_info != NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Create the new Tetrahedra. * * new_tet[0] occupies the northern half of the picture as described * above, and new_tet[1] occupies the southern half. Vertex w[0][3] of * new_tet[0] is at the north pole, and vertex w[1][3] of new_tet[1] is at * the south pole. Face w[i][j] (j = 0,1,2) of new_tet[i] coincides with * face v[j][i] of tet[j]. The actual values of w[][] give both * new_tet[0] and new_tet[1] the right_handed Orientation. */ for (i = 0; i < 2; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } w[0][0] = 0; w[0][1] = 1; w[0][2] = 3; w[0][3] = 2; w[1][0] = 0; w[1][1] = 1; w[1][2] = 2; w[1][3] = 3; /* * Set the gluing and neighbor fields. * * Note that this code works correctly even if some of the faces * of the tet[i] were glued to each other. */ for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) { new_tet[i]->neighbor[w[i][j]] = tet[j]->neighbor[v[j][i]]; new_tet[i]->gluing [w[i][j]] = CREATE_PERMUTATION( w[i][j], EVALUATE(tet[j]->gluing[v[j][i]], v[j][i]), w[i][(j+1)%3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][2]), w[i][(j+2)%3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][3]), w[i][3], EVALUATE(tet[j]->gluing[v[j][i]], v[j][!i]) ); set_inverse_neighbor_and_gluing(new_tet[i], w[i][j]); } new_tet[i]->neighbor[w[i][3]] = new_tet[!i]; new_tet[i]->gluing [w[i][3]] = CREATE_PERMUTATION( w[i][0], w[!i][0], w[i][1], w[!i][1], w[i][2], w[!i][2], w[i][3], w[!i][3]); } /* * Set the cusp fields. */ for (i = 0; i < 2; i++) { for (j = 0; j < 3; j++) new_tet[i]->cusp[w[i][j]] = tet[(j+1)%3]->cusp[v[(j+1)%3][3]]; new_tet[i]->cusp[w[i][3]] = tet[0]->cusp[v[0][!i]]; } /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) /* which sheet */ for (i = 0; i < 2; i++) { /* which tetrahedron */ /* * Set the equatorial vertices. */ for (j = 0; j < 3; j++) /* which vertex */ { j1 = (j+1) % 3; j2 = (j+2) % 3; hh = (old_orientation[j1] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][j]][w[i][j1]] = tet[j1]->curve[c][hh][v[j1][3]][v[j1][i]]; hh = (old_orientation[j2] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][j]][w[i][j2]] = tet[j2]->curve[c][hh][v[j2][2]][v[j2][i]]; new_tet[i]->curve[c][h][w[i][j]][w[i][3]] = - (new_tet[i]->curve[c][h][w[i][j]][w[i][j1]] + new_tet[i]->curve[c][h][w[i][j]][w[i][j2]]); } /* * Set the polar vertices. */ for (j = 0; j < 3; j++) { /* which side of vertex 3 */ hh = (old_orientation[j] == right_handed) ? h : !h; new_tet[i]->curve[c][h][w[i][3]][w[i][j]] = tet[j]->curve[c][hh][v[j][!i]][v[j][i]]; } } /* * Set where_to_resume to the predecessor of the EdgeClass about * to be killed, so that the loop in the calling function can * continue at the correct spot in the list. */ *where_to_resume = edge->prev; /* * Kill the EdgeClass at the center of the three old Tetrahedra. */ REMOVE_NODE(edge); my_free(edge); /* * Update the surviving EdgeClasses. */ for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) { j1 = (j+1) % 3; j2 = (j+2) % 3; old_h_edge_index = edge_between_vertices[v[j2][2]][v[j2][ 3]]; old_v_edge_index = edge_between_vertices[v[j2][2]][v[j2][!i]]; new_h_edge_index = edge_between_vertices[w[ i][j]][w[ i][j1]]; new_v_edge_index = edge_between_vertices[w[ i][j]][w[ i][ 3]]; new_tet[i]->edge_class[new_h_edge_index] = tet[j2]->edge_class[old_h_edge_index]; new_tet[i]->edge_class[new_v_edge_index] = tet[j2]->edge_class[old_v_edge_index]; if (old_orientation[j2] == right_handed) { new_tet[i]->edge_orientation[new_h_edge_index] = tet[j2]->edge_orientation[old_h_edge_index]; new_tet[i]->edge_orientation[new_v_edge_index] = tet[j2]->edge_orientation[old_v_edge_index]; } else { new_tet[i]->edge_orientation[new_h_edge_index] = ! tet[j2]->edge_orientation[old_h_edge_index]; new_tet[i]->edge_orientation[new_v_edge_index] = ! tet[j2]->edge_orientation[old_v_edge_index]; } new_tet[i]->edge_class[new_v_edge_index]->order--; if (i == 0) new_tet[i]->edge_class[new_h_edge_index]->order++; new_tet[i]->edge_class[new_h_edge_index]->incident_tet = new_tet[i]; new_tet[i]->edge_class[new_v_edge_index]->incident_tet = new_tet[i]; new_tet[i]->edge_class[new_h_edge_index]->incident_edge_index = new_h_edge_index; new_tet[i]->edge_class[new_v_edge_index]->incident_edge_index = new_v_edge_index; } /* * Compute the shapes of the new Tetrahedra iff * the old tetrahedra had shapes. */ if (tet[0]->shape[complete] != NULL) { /* * Allocate space for the TetShapes of the new Tetrahedra. */ for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) new_tet[i]->shape[j] = NEW_STRUCT(TetShape); /* * Add the complex edge angles of the old Tetrahedra * to get those of the new Tetrahedra. Use the * edge_orientation[] to get the orientations correct. * Note that add_edge_angles chooses angles in the range * [(-1/2) pi, (3/2) pi], regardless of the angles of * summands. */ for (i = 0; i < 2; i++) /* which new Tetrahedron */ for (j = 0; j < 3; j++) /* which EdgeClass */ add_edge_angles( tet[(j+1)%3], edge_between_vertices[v[(j+1)%3][3]][v[(j+1)%3][!i]], tet[(j+2)%3], edge_between_vertices[v[(j+2)%3][2]][v[(j+2)%3][!i]], new_tet[i], edge_between_vertices[w[i][j]][w[i][3]] ); } /* * Compute VertexCrossSections for the new Tetrahedra * iff the old Tetrahedra had VertexCrossSections. */ if (tet[0]->cross_section != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Allocate space for the VertexCrossSections of the new Tetrahedra. */ for (i = 0; i < 2; i++) new_tet[i]->cross_section = NEW_STRUCT(VertexCrossSections); /* * Compute the VertexCrossSections for each of the two new Tetrahedra. */ for (i = 0; i < 2; i++) { /* * Compute the polar VertexCrossSections. */ for (j = 0; j < 3; j++) new_tet[i]->cross_section->edge_length[w[i][3]][w[i][j]] = tet[j]->cross_section->edge_length[v[j][!i]][v[j][i]]; new_tet[i]->cross_section->has_been_set[w[i][3]] = TRUE; /* * Compute the equatorial VertexCrossSections. */ for (j = 0; j < 3; j++) compute_three_edge_lengths(new_tet[i], w[i][(j+1)%3], w[i][j], tet[j]->cross_section->edge_length[v[j][2]][v[j][i]]); } /* * Update the tilts. */ for (i = 0; i < 2; i++) compute_tilts_for_one_tet(new_tet[i]); } /* * Compute CuspNbhdPositions for the new Tetrahedra iff * the old Tetrahedra had CuspNbhdPositions. */ if (tet[0]->cusp_nbhd_position != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("three_to_two", "simplify_triangulation"); /* * Allocate space for the CuspNbhdPositions of the new Tetrahedra. */ for (i = 0; i < 2; i++) new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); /* * Compute the CuspNbhdPositions for each of the two new Tetrahedra. */ for (i = 0; i < 2; i++) /* * Consider both the right_handed and left_handed sheets. */ for (h = 0; h < 2; h++) { /* * Compute the polar CuspNbhdPositions. * * The first approach which comes to mind is simply to copy * the relevant coordinates from the old Tetrahedra to the * new ones. Unfortunately the CuspNbhdPositions of the * three old tetrahedra may differ by covering translations. * * This problem is not insurmountable, but the code will be * cleaner if we simply copy the coordinates of two corners, * and then call cn_find_third_corner() to compute the * remaining corner. Recall that both new Tetrahedra are * seen in the right_handed Orientation, as is old tet[0]. */ if (tet[0]->cusp_nbhd_position->in_use[h][v[0][!i]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][1]] = tet[0]->cusp_nbhd_position->x[h][v[0][!i]][v[0][2]]; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][2]] = tet[0]->cusp_nbhd_position->x[h][v[0][!i]][v[0][3]]; cn_find_third_corner(new_tet[i], h, w[i][3], w[i][1], w[i][2], w[i][0]); new_tet[i]->cusp_nbhd_position->in_use[h][w[i][3]] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][1]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][2]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][3]][w[i][0]] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][w[i][3]] = FALSE; } /* * Compute the equatorial CuspNbhdPositions. * * Technical note: The new_tets are both seen with the * right_handed Orientation. So when old_orientation[] * is also right_handed, we want to read the new sheet h * from the old sheet h. But when old_orientation[] is * left_handed, we want to read the new sheet h from the * old sheet !h. Because left_handed == 1, the expression * (old_orientation[] ^ h) gives the correct old sheet * to read from. */ for (j = 0; j < 3; j++) { if (tet[j]->cusp_nbhd_position->in_use[old_orientation[j]^h][v[j][2]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][(j+2)%3]] = tet[j]->cusp_nbhd_position->x[old_orientation[j]^h][v[j][2]][v[j][3]]; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][3]] = tet[j]->cusp_nbhd_position->x[old_orientation[j]^h][v[j][2]][v[j][!i]]; cn_find_third_corner(new_tet[i], h, w[i][(j+1)%3], w[i][(j+2)%3], w[i][3], w[i][j]); new_tet[i]->cusp_nbhd_position->in_use[h][w[i][(j+1)%3]] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i][(j+2)%3]] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i] [3] ] = Zero; new_tet[i]->cusp_nbhd_position->x[h][w[i][(j+1)%3]][w[i] [j] ] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][w[i][(j+1)%3]] = FALSE; } } } } /* * Put the new Tetrahedra on the list, and remove and free * the old ones. */ for (i = 0; i < 2; i++) INSERT_BEFORE(new_tet[i], tet[0]); for (i = 0; i < 3; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr -= 1; return func_OK; } /* * The three new Tetrahedra created by two_to_three() take tet0's place * in the list of Tetrahedra. Tet0 and the other doomed Tetrahedron are * removed from the list before being destroyed. Similarly, the new * EdgeClass is added to the list of EdgeClasses just in front of one * of the existing EdgeClasses. * * The Orientations of the three new Tetrahedra are set to match the * Orientation of one of the two old ones, so that the Orientability * of the Triangulation (if there is one) will be preserved. * * two_to_three() returns func_failed if either * * (1) the two initial Tetrahedra are not not distinct (i.e. tet0 * is glued to itself at face f), or * * (2) a hyperbolic structure is present, and even though the two * initial Tetrahedra are combinatorially distinct, they are * superimposed in hyperbolic space (i.e. the vertices opposite * their common face, though combinatorially distinct, lie at the * same point on the sphere at infinity). In this case, performing * the two_to_three() move would create degenerate Tetrahedra. * * The imaginary parts of the logarithmic forms of the TetShapes * are computed mod 2 pi i, as explained at the top of this file. */ FuncResult two_to_three( Tetrahedron *tet0, FaceIndex f, int *num_tetrahedra_ptr) { Tetrahedron *tet[2], *new_tet[3]; VertexIndex v[2][4]; Orientation old_orientation[2]; int c, h, hh, i, i1, i2, j, k; EdgeClass *new_class; /* * two_to_three() is the inverse of three_to_two(), and * is implemented similarly. In particular, the picture to * imagine (or, better yet, draw on a scrap of paper before * diving into this code) is virtually identical to that from * three_to_two(), only what was tet[] there is new_tet[] here, * and vice versa. */ /* * Label tet[0] and tet[1]. */ tet[0] = tet0; v[0][3] = f; v[0][0] = !f; /* v[0][0] is some vertex other than v[0][3] */ v[0][1] = remaining_face[v[0][3]][v[0][0]]; /* tet[0] will be seen */ v[0][2] = remaining_face[v[0][0]][v[0][3]]; /* as left_handed */ old_orientation[0] = left_handed; tet[1] = tet[0]->neighbor[f]; for (i = 0; i < 4; i++) v[1][i] = EVALUATE(tet[0]->gluing[f], v[0][i]); old_orientation[1] = (parity[tet[0]->gluing[f]] == orientation_preserving) ? old_orientation[0] : ! old_orientation[0]; /* * If tet[0] and tet[1] are not distinct, we cannot proceed. */ if (tet[0] == tet[1]) return func_failed; /* * If a hyperbolic structure is present and the 2-3 move would create * degenerate Tetrahedra, we do not want to proceed. Degenerate * Tetrahedra will be created iff vertices v[0][3] and v[1][3] coincide. * (People usually think of degeneracy in terms of the dihedral angles * approaching {0, 1, infinity}, but in this context we also think in * terms of the equivalent definition that a Tetrahedron is degenerate * iff two or more vertices coincide.) * * angles_sum_to_zero() check whether the angles sum to zero mod 2 pi. */ if (tet[0]->shape[complete] != NULL) if (angles_sum_to_zero( tet[0], edge_between_vertices[v[0][0]][v[0][1]], tet[1], edge_between_vertices[v[1][0]][v[1][1]])) return func_failed; /* * Allocate the three new Tetrahedra. */ for (i = 0; i < 3; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } /* * Note that here we can refer to the VertexIndices of the new_tet[i] * directly, because a symmetrical indexing scheme is consistent * with a fixed orientation. In three_to_two(), the symmetrical * indexing scheme was not consistent with a fixed orientation, * so we had to use the w[][] to store the true indices of the * new Tetrahedra. */ /* * Set "internal" neighbors and gluings. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->neighbor[2] = new_tet[i1]; new_tet[i]->neighbor[3] = new_tet[i2]; new_tet[i]->gluing[2] = CREATE_PERMUTATION(0, 0, 1, 1, 2, 3, 3, 2); new_tet[i]->gluing[3] = CREATE_PERMUTATION(0, 0, 1, 1, 2, 3, 3, 2); } /* * Set "external" neighbors and gluings. * This code works even if some of the external faces of tet[0] * and tet[1] are glued to each other. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ for (j = 0; j < 2; j++) /* which face */ { new_tet[i]->neighbor[j] = tet[j]->neighbor[v[j][i]]; new_tet[i]->gluing[j] = CREATE_PERMUTATION( j, EVALUATE(tet[j]->gluing[v[j][i]], v[j][i]), !j, EVALUATE(tet[j]->gluing[v[j][i]], v[j][3]), 2, EVALUATE(tet[j]->gluing[v[j][i]], v[j][(i+1)%3]), 3, EVALUATE(tet[j]->gluing[v[j][i]], v[j][(i+2)%3]) ); set_inverse_neighbor_and_gluing(new_tet[i], j); } /* * Set the cusp fields. */ for (i = 0; i < 3; i++) { new_tet[i]->cusp[0] = tet[1]->cusp[v[1][3]]; new_tet[i]->cusp[1] = tet[0]->cusp[v[0][3]]; new_tet[i]->cusp[2] = tet[0]->cusp[v[0][(i+1)%3]]; new_tet[i]->cusp[3] = tet[0]->cusp[v[0][(i+2)%3]]; } /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) { /* which sheet */ /* * Set the exterior sides of the polar vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 0; j < 2; j++) { /* which vertex */ hh = (old_orientation[!j] == left_handed) ? h : !h; new_tet[i]->curve[c][h][j][!j] = tet[!j]->curve[c][hh][v[!j][3]][v[!j][i]]; } /* * Set the interior sides of the polar vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 0; j < 2; j++) /* which vertex */ for (k = 2; k < 4; k++) /* which side */ new_tet[i]->curve[c][h][j][k] = - FLOW( new_tet[ i ]->curve[c][h][j][!j], new_tet[(i+k-1)%3]->curve[c][h][j][!j]); /* * Set the equatorial vertices. */ for (i = 0; i < 3; i++) /* which tetrahedron */ for (j = 2; j < 4; j++) { /* which vertex */ for (k = 0; k < 2; k++) /* which side */ { hh = (old_orientation[k] == left_handed) ? h : !h; new_tet[i]->curve[c][h][j][k] = tet[k]->curve[c][hh][v[k][(i+j-1)%3]][v[k][i]]; } new_tet[i]->curve[c][h][j][5-j] = - (new_tet[i]->curve[c][h][j][0] + new_tet[i]->curve[c][h][j][1]); } } /* * Create the new EdgeClass. */ new_class = NEW_STRUCT(EdgeClass); initialize_edge_class(new_class); new_class->order = 3; new_class->incident_tet = new_tet[0]; new_class->incident_edge_index = edge_between_vertices[0][1]; /* * Insert the new EdgeClass at an arbitrary spot in the linked list. */ INSERT_BEFORE(new_class, tet[0]->edge_class[0]); /* * Set the EdgeClasses. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->edge_class[edge_between_vertices[0][1]] = new_class; new_tet[i]->edge_class[edge_between_vertices[0][2]] = tet[1]->edge_class[edge_between_vertices[v[1][3]][v[1][i1]]]; new_tet[i]->edge_class[edge_between_vertices[0][3]] = tet[1]->edge_class[edge_between_vertices[v[1][3]][v[1][i2]]]; new_tet[i]->edge_class[edge_between_vertices[1][2]] = tet[0]->edge_class[edge_between_vertices[v[0][3]][v[0][i1]]]; new_tet[i]->edge_class[edge_between_vertices[1][3]] = tet[0]->edge_class[edge_between_vertices[v[0][3]][v[0][i2]]]; new_tet[i]->edge_class[edge_between_vertices[2][3]] = tet[0]->edge_class[edge_between_vertices[v[0][i1]][v[0][i2]]]; } /* * Set the edge_orientations. */ for (i = 0; i < 3; i++) { i1 = (i+1) % 3; i2 = (i+2) % 3; new_tet[i]->edge_orientation[edge_between_vertices[0][1]] = right_handed; new_tet[i]->edge_orientation[edge_between_vertices[0][2]] = (old_orientation[1] == left_handed) ? tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i1]]] : ! tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i1]]]; new_tet[i]->edge_orientation[edge_between_vertices[0][3]] = (old_orientation[1] == left_handed) ? tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i2]]] : ! tet[1]->edge_orientation[edge_between_vertices[v[1][3]][v[1][i2]]]; new_tet[i]->edge_orientation[edge_between_vertices[1][2]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i1]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i1]]]; new_tet[i]->edge_orientation[edge_between_vertices[1][3]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i2]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][3]][v[0][i2]]]; new_tet[i]->edge_orientation[edge_between_vertices[2][3]] = (old_orientation[0] == left_handed) ? tet[0]->edge_orientation[edge_between_vertices[v[0][i1]][v[0][i2]]] : ! tet[0]->edge_orientation[edge_between_vertices[v[0][i1]][v[0][i2]]]; } /* * Adjust the EdgeClass orders. */ for (i = 0; i < 3; i++) { new_tet[i]->edge_class[edge_between_vertices[0][2]]->order++; new_tet[i]->edge_class[edge_between_vertices[1][2]]->order++; new_tet[i]->edge_class[edge_between_vertices[2][3]]->order--; } /* * Set incident_tets and incident_edge_indices. */ for (i = 0; i < 3; i++) for (j = 0; j < 6; j++) { new_tet[i]->edge_class[j]->incident_tet = new_tet[i]; new_tet[i]->edge_class[j]->incident_edge_index = j; } /* * Compute the shapes of the new Tetrahedra iff * the old tetrahedra had shapes. */ if (tet[0]->shape[complete] != NULL) { /* * Allocate space for the TetShapes of the new Tetrahedra. */ for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) new_tet[i]->shape[j] = NEW_STRUCT(TetShape); /* * First compute the TetShapes for the equatorial angles. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ add_edge_angles( tet[0], edge_between_vertices[v[0][(i+1)%3]][v[0][(i+2)%3]], tet[1], edge_between_vertices[v[1][(i+1)%3]][v[1][(i+2)%3]], new_tet[i], edge_between_vertices[2][3] ); /* * Now compute the remaining complex angles of each Tetrahedron, * with log.imag in the range [(-1/2) pi, (3/2) pi]. */ for (i = 0; i < 3; i++) /* which new Tetrahedron */ compute_remaining_angles(new_tet[i], edge3_between_vertices[2][3]); } /* * Compute VertexCrossSections for the new Tetrahedra * iff the old Tetrahedra had VertexCrossSections. */ if (tet[0]->cross_section != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("two_to_three", "simplify_triangulation"); /* * Allocate space for the VertexCrossSections of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->cross_section = NEW_STRUCT(VertexCrossSections); /* * Compute the VertexCrossSections for each of * the three new Tetrahedra. */ for (i = 0; i < 3; i++) { /* * Compute the polar vertices. */ for (j = 0; j < 2; j++) compute_three_edge_lengths(new_tet[i], !j, j, tet[j]->cross_section->edge_length[v[j][3]][v[j][i]]); /* * Compute the equatorial vertices. */ for (j = 2; j < 4; j++) compute_three_edge_lengths(new_tet[i], j, 0, tet[0]->cross_section->edge_length[v[0][(i+j+2)%3]][v[0][i]]); } /* * Update the tilts. */ for (i = 0; i < 3; i++) compute_tilts_for_one_tet(new_tet[i]); } /* * Provide CanonizeInfo for the new Tetrahedra * iff the old Tetrahedra had CanonizeInfo. */ if (tet[0]->canonize_info != NULL) { /* * Allocate space for the CanonizeInfo of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->canonize_info = NEW_STRUCT(CanonizeInfo); /* * Set part_of_coned_cell to TRUE for each new Tetrahedron. */ for (i = 0; i < 3; i++) new_tet[i]->canonize_info->part_of_coned_cell = TRUE; /* * Set each new "exterior" face to have the same face_status * as the corresponding old face. */ for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) new_tet[i]->canonize_info->face_status[j] = tet[j]->canonize_info->face_status[v[j][i]]; /* * Set each new "interior" face to have face_status inside_cone_face. */ for (i = 0; i < 3; i++) for (j = 2; j < 4; j++) new_tet[i]->canonize_info->face_status[j] = inside_cone_face; } /* * Compute CuspNbhdPositions for the new Tetrahedra iff * the old Tetrahedra had CuspNbhdPositions. */ if (tet[0]->cusp_nbhd_position != NULL) { /* * Begin with a quick error check. */ if (new_tet[0]->shape[complete] == NULL) uFatalError("two_to_three", "simplify_triangulation"); /* * Allocate space for the CuspNbhdPositions of the new Tetrahedra. */ for (i = 0; i < 3; i++) new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); /* * Compute the CuspNbhdPositions for each of the three new Tetrahedra. */ for (i = 0; i < 3; i++) /* * Consider both the right_handed and left_handed sheets. */ for (h = 0; h < 2; h++) { /* * Compute the polar CuspNbhdPositions. * * Technical note: The new_tets are all seen with the * left_handed Orientation. So when old_orientation[] * is also left_handed, we want to read the new sheet h * from the old sheet h. But when old_orientation[] is * right_handed, we want to read the new sheet h from the * old sheet !h. Because left_handed == 1, the expression * (old_orientation[] == h) gives the correct old sheet * to read from. */ for (j = 0; j < 2; j++) { if (tet[j]->cusp_nbhd_position->in_use[old_orientation[j]==h][v[j][3]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][!j][2] = tet[j]->cusp_nbhd_position->x[old_orientation[j]==h][v[j][3]][v[j][(i+1)%3]]; new_tet[i]->cusp_nbhd_position->x[h][!j][3] = tet[j]->cusp_nbhd_position->x[old_orientation[j]==h][v[j][3]][v[j][(i+2)%3]]; cn_find_third_corner(new_tet[i], h, !j, 2, 3, j); new_tet[i]->cusp_nbhd_position->in_use[h][!j] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][!j][2] = Zero; new_tet[i]->cusp_nbhd_position->x[h][!j][3] = Zero; new_tet[i]->cusp_nbhd_position->x[h][!j][j] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][!j] = FALSE; } } /* * Compute the equatorial CuspNbhdPositions. * * The three new Tetrahedra are seen in the left_handed * Orientation, as is the old tet[0]. So if we coordinates * coordinates from tet[0] we know the sheets will match up. */ for (j = 2; j < 4; j++) { if (tet[0]->cusp_nbhd_position->in_use[h][v[0][(i+j+2)%3]] == TRUE) { new_tet[i]->cusp_nbhd_position->x[h][j][5-j] = tet[0]->cusp_nbhd_position->x[h][v[0][(i+j+2)%3]][v[0][(4+i-j)%3]]; new_tet[i]->cusp_nbhd_position->x[h][j][1] = tet[0]->cusp_nbhd_position->x[h][v[0][(i+j+2)%3]][v[0][3]]; cn_find_third_corner(new_tet[i], h, j, 5-j, 1, 0); new_tet[i]->cusp_nbhd_position->in_use[h][j] = TRUE; } else { new_tet[i]->cusp_nbhd_position->x[h][j][5-j] = Zero; new_tet[i]->cusp_nbhd_position->x[h][j][ 1 ] = Zero; new_tet[i]->cusp_nbhd_position->x[h][j][ 0 ] = Zero; new_tet[i]->cusp_nbhd_position->in_use[h][j] = FALSE; } } } } /* * Put the new Tetrahedra on the list, and remove and free * the old ones. */ for (i = 0; i < 3; i++) INSERT_BEFORE(new_tet[i], tet[0]); for (i = 0; i < 2; i++) { REMOVE_NODE(tet[i]); free_tetrahedron(tet[i]); } *num_tetrahedra_ptr += 1; return func_OK; } /* * one_to_four() performs a one-to-four move, replacing a Tetrahedron * with four new Tetrahedra meeting at a finite vertex. It adds * the four new Tetrahedra and the four new EdgeClasses to the * appropriate lists. */ void one_to_four( Tetrahedron *tet, int *num_tetrahedra_ptr, int new_cusp_index) { int c, h, i, j, k; Tetrahedron *new_tet[4]; Cusp *new_cusp; EdgeClass *new_class[4]; /* * It doesn't make sense to call this function when a hyperbolic * structure, VertexCrossSections or a CuspNbhdPosition are present. */ if (tet->shape[complete] != NULL || tet->cross_section != NULL || tet->cusp_nbhd_position != NULL) uFatalError("one_to_four", "simplify_triangulation"); /* * To understand this code, I recommend you first make a drawing * of a truncated ideal tetrahedron, and draw its subdivision into * four tetrahedra meeting at the center. The four new Tetrahedra * are indexed in the natural way: each vertex which coincides with * a vertex of the old Tetrahedron inherits the latter's VertexIndex, * while the vertex at the center gets the VertexIndex from the * "opposite" vertex of the old Tetrahedron. * * Note that if the manifold is oriented, this scheme preserves * the orientation. */ /* * Allocate space for the new Tetrahedra. * * new_tet[i] will be the new Tetrahedron incident to face i * of the old Tetrahedron. */ for (i = 0; i < 4; i++) { new_tet[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet[i]); } /* * Set neighbors and gluings. * * This code works even if some of tet's external faces * are glued to each other. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) if (j == i) /* "external" neighbor */ { new_tet[i]->neighbor[j] = tet->neighbor[i]; new_tet[i]->gluing[j] = tet->gluing[i]; set_inverse_neighbor_and_gluing(new_tet[i], j); } else { /* "internal" neighbor */ new_tet[i]->neighbor[j] = new_tet[j]; new_tet[i]->gluing[j] = CREATE_PERMUTATION( remaining_face[i][j], remaining_face[i][j], remaining_face[j][i], remaining_face[j][i], i, j, j, i); } /* * Create a Cusp structure for the finite vertex. */ new_cusp = NEW_STRUCT(Cusp); initialize_cusp(new_cusp); new_cusp->is_finite = TRUE; new_cusp->index = new_cusp_index; INSERT_BEFORE(new_cusp, tet->cusp[0]); /* * Set the new Tetrahedra's cusp fields. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) new_tet[i]->cusp[j] = j == i ? new_cusp : /* finite vertex */ tet->cusp[j]; /* ideal vertex */ /* * Set the peripheral curves. */ for (c = 0; c < 2; c++) /* which curve */ for (h = 0; h < 2; h++) /* which sheet */ for (i = 0; i < 4; i++) /* which tetrahedron */ for (j = 0; j < 4; j++) /* which vertex */ if (j == i) /* * Set the peripheral curves on the finite vertex * to zero. */ for (k = 0; k < 4; k++) /* which side */ new_tet[i]->curve[c][h][i][k] = 0; else /* * Set the curves on the new ideal vertices in * terms of those on the old. */ for (k = 0; k < 4; k++) /* which side */ if (k == j) /* * The j-th vertex has no j-th side. * Do nothing. */ ; else if (k == i) /* * The i-th side of vertex j on new * Tetrahedron i coinicdes with the * i-th side of vertex j on the old * Tetrahedron. */ new_tet[i]->curve[c][h][j][i] = tet->curve[c][h][j][i]; else /* * Compute the new side as a FLOW * between two of the old sides. */ new_tet[i]->curve[c][h][j][k] = FLOW( tet->curve[c][h][j][k], tet->curve[c][h][j][i]); /* * Create the new EdgeClasses. */ for (i = 0; i < 4; i++) { new_class[i] = NEW_STRUCT(EdgeClass); initialize_edge_class(new_class[i]); new_class[i]->order = 3; new_class[i]->incident_tet = new_tet[!i]; new_class[i]->incident_edge_index = edge_between_vertices[i][!i]; } /* * Insert the new EdgeClasses at an arbitrary spot in the linked list. */ for (i = 0; i < 4; i++) INSERT_BEFORE(new_class[i], tet->edge_class[0]); /* * Set the edge_class and edge_orientation fields of the new Tetrahedra. */ for (i = 0; i < 4; i++) /* which Tetrahedron */ for (j = 0; j < 4; j++) /* VertexIndex at one end of edge */ for (k = j + 1; k < 4; k++) /* VertexIndex at other end of edge */ { new_tet[i]->edge_class[edge_between_vertices[j][k]] = (j == i || k == i) ? (j == i ? new_class[k] : new_class[j]) : tet->edge_class[edge_between_vertices[j][k]]; new_tet[i]->edge_orientation[edge_between_vertices[j][k]] = (j == i || k == i) ? right_handed : tet->edge_orientation[edge_between_vertices[j][k]]; } /* * Adjust the EdgeClass orders of the preexisting EdgeClasses. * (The orders of the new EdgeClasses were set above.) */ for (i = 0; i < 6; i++) tet->edge_class[i]->order++; /* * Set incident_tets and incident_edge_indices for the preexisting * EdgeClasses. (Those for the new EdgeClasses were set above.) */ for (i = 0; i < 6; i++) { tet->edge_class[i]->incident_tet = new_tet[one_face_at_edge[i]]; tet->edge_class[i]->incident_edge_index = i; } /* * Provide CanonizeInfo for the new Tetrahedra iff the old Tetrahedron * had CanonizeInfo. */ if (tet->canonize_info != NULL) { /* * For each new Tetrahedron . . . */ for (i = 0; i < 4; i++) { /* * Allocate space for the CanonizeInfo. */ new_tet[i]->canonize_info = NEW_STRUCT(CanonizeInfo); /* * Set part_of_coned_cell to TRUE. */ new_tet[i]->canonize_info->part_of_coned_cell = TRUE; /* * Set face_status to inside_cone_face for each "interior" face, * and have it match the old values for the "exterior" faces. */ for (j = 0; j < 4; j++) /* which face */ new_tet[i]->canonize_info->face_status[j] = j == i ? tet->canonize_info->face_status[j] : /* exterior face */ inside_cone_face; /* interior face */ } } /* * Put the new Tetrahedra on the list, and remove and free the old one. */ for (i = 0; i < 4; i++) INSERT_BEFORE(new_tet[i], tet); REMOVE_NODE(tet); free_tetrahedron(tet); *num_tetrahedra_ptr += 3; } static FuncResult edges_of_order_four( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { PositionedTet ptet0, ptet; /* * *edge is an EdgeClass of order 4. Look for another EdgeClass * of order 4 which shares a triangle with *edge. If the six * Tetrahedra incident to the two EdgeClasses are all distinct, * then their union is a suspended pentagon which will be * retriangulated with only five Tetrahedra. */ ptet0.tet = edge->incident_tet; ptet0.bottom_face = one_vertex_at_edge[edge->incident_edge_index]; ptet0.right_face = other_vertex_at_edge[edge->incident_edge_index]; ptet0.near_face = remaining_face[ptet0.bottom_face][ptet0.right_face]; ptet0.left_face = remaining_face[ptet0.right_face][ptet0.bottom_face]; ptet0.orientation = right_handed; ptet = ptet0; do { if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face]]->order == 4) if (try_adjacent_fours(ptet.tet, ptet.near_face, ptet.bottom_face, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->order == 4) if (try_adjacent_fours(ptet.tet, ptet.near_face, ptet.right_face, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); return func_failed; } static FuncResult try_adjacent_fours( Tetrahedron *tet0, FaceIndex f0, FaceIndex f1, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { Tetrahedron *tet[6]; FaceIndex f2, f3, g2, g3; int i, j; EdgeClass *class0, *class1; /* * Two nonantipodal EdgeClasses of order 4 lies on tet. The * face between them has index f0. The face not incident to * either has index f1. */ /* * Find the six Tetrahedra adjacent to the EdgeClasses of order 4. */ tet[0] = tet0; f2 = remaining_face[f0][f1]; f3 = remaining_face[f1][f0]; tet[1] = tet0->neighbor[f0]; g2 = EVALUATE(tet0->gluing[f0], f2); g3 = EVALUATE(tet0->gluing[f0], f3); tet[2] = tet[0]->neighbor[f2]; tet[3] = tet[0]->neighbor[f3]; tet[4] = tet[1]->neighbor[g2]; tet[5] = tet[1]->neighbor[g3]; /* * If the six Tetrahedra aren't all distinct, return func_failed. * (Thought question: Might simplification sometimes be possible * even if all six Tetrahedra aren't distinct? Hmmm . . . seems * unlikely.) */ for (i = 0; i < 6; i++) for (j = i + 1; j < 6; j++) if (tet[i] == tet[j]) return func_failed; /* * Note the two EdgeClasses which now have order four. */ class0 = tet0->edge_class[edge_between_faces[f0][f2]]; class1 = tet0->edge_class[edge_between_faces[f0][f3]]; /* * The following two-to-three move increases the number of * Tetrahedra by one, but it creates two EdgeClasses of * order three . . . */ if (two_to_three(tet0, f0, num_tetrahedra_ptr) == func_failed) { /* * (There can't be any topological obstruction to the * retriangulation, but there might be a geometric obstruction, * namely that the two_to_three() move might require creation * of degenerate Tetrahedra. So if two_to_three() fails when * a hyperbolic structure is present, we assume (potential) * degenerate Tetrahedra are the cause, and we return func_failed. * Otherwise we call uFatalError().) */ if (tet0->shape[complete] != NULL) return func_failed; else uFatalError("try_adjacent_fours", "simplify_triangulation"); } /* * . . . each of which can be used to reduce the number of * Tetrahedra by one. */ if (three_to_two(class0, where_to_resume, num_tetrahedra_ptr) == func_failed || three_to_two(class1, where_to_resume, num_tetrahedra_ptr) == func_failed) uFatalError("try_adjacent_fours", "simplify_triangulation"); /* * Note that where_to_resume will come out pointing to some * valid EdgeClass. We won't worry too much about just which * one it points at. */ return func_OK; } static FuncResult create_new_order_four( EdgeClass *edge, EdgeClass **where_to_resume, int *num_tetrahedra_ptr) { PositionedTet ptet0, ptet; if (edge->order != 4) return func_failed; /* * create_new_order_four() is similar to edges_of_order_four(). * * *edge is an EdgeClass of order 4. Look for another EdgeClass * of order 5 or less which shares a triangle with *edge. * If the four Tetrahedra incident to *edge are all distinct, * then their union is an octagon which will be retriangulated * so as to create a new EdgeClass of order 4 or less. */ ptet0.tet = edge->incident_tet; ptet0.bottom_face = one_vertex_at_edge[edge->incident_edge_index]; ptet0.right_face = other_vertex_at_edge[edge->incident_edge_index]; ptet0.near_face = remaining_face[ptet0.bottom_face][ptet0.right_face]; ptet0.left_face = remaining_face[ptet0.right_face][ptet0.bottom_face]; ptet0.orientation = right_handed; if (four_tetrahedra_are_distinct(ptet0) == FALSE) return func_failed; ptet = ptet0; do { if (ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.right_face ]]->order <= 5 || ptet.tet->edge_class[edge_between_faces[ptet.near_face][ptet.bottom_face]]->order <= 5) { if (two_to_three(ptet.tet, ptet.near_face, num_tetrahedra_ptr) == func_OK) { if (three_to_two(edge, where_to_resume, num_tetrahedra_ptr) == func_OK) return func_OK; else uFatalError("create_new_order_four", "simplify_triangulation"); } else { /* * The call to two_to_three() failed. It can't fail for * topological reasons (we checked that the four Tetrahedra * surrounding the EdgeClass of order 4 are distinct), but * if a hyperbolic structure is present it might fail * because two antipodal vertices of the octahedron * coincide. In the latter case, we simply move on in * the hope that a different retriangulation will work. */ if (ptet.tet->shape[complete] == NULL) uFatalError("create_new_order_four", "simplify_triangulation"); /* * else continue with do loop */ } } veer_left(&ptet); } while ( ! same_positioned_tet(&ptet, &ptet0)); return func_failed; } static Boolean four_tetrahedra_are_distinct( PositionedTet ptet) { int i, j; Tetrahedron *tet[4]; for (i = 0; i < 4; i++) { tet[i] = ptet.tet; veer_left(&ptet); } for (i = 0; i < 4; i++) for (j = i + 1; j < 4; j++) if (tet[i] == tet[j]) return FALSE; return TRUE; } static void set_inverse_neighbor_and_gluing( Tetrahedron *tet, FaceIndex f) { tet->neighbor[f]->neighbor[EVALUATE(tet->gluing[f], f)] = tet; tet->neighbor[f]->gluing [EVALUATE(tet->gluing[f], f)] = inverse_permutation[tet->gluing[f]]; } regina-4.95/engine/snappea/kernel/SnapPea.h000644 000765 000024 00000313537 12236247215 020503 0ustar00babstaff000000 000000 /* * SnapPea.h * * This file defines the interface between SnapPea's comptational kernel * ("the kernel") and the user-interface ("the UI"). Both parts * must #include this file, and anything shared between the two parts * must be declared in this file. The only communication between the * two parts is via function calls -- no external variables are shared. * * All external symbols in the UI must begin with 'u' followed by a * capital letter. Nothing in the kernel should begin in this way. * * Typedef names use capitals for the first letter of each word, * e.g. Triangulation, CuspIndex. * * SnapPea 2.0 was funded by the University of Minnesota's * Geometry Center and the U.S. National Science Foundation. * SnapPea 3.0 is funded by the U.S. National Science Foundation * and the MacArthur Foundation. SnapPea and its source code may * be used freely for all noncommercial purposes. Please direct * questions, problems and suggestions to Jeff Weeks (weeks@northnet.org). * * Copyright 1999 by Jeff Weeks. All rights reserved. */ #ifndef _SnapPea_ #define _SnapPea_ /* * Note: values of the SolutionType enum are stored as integers in * the triangulation.doc file format. Changing the order of the * entries in the enum would therefore invalidate all previously stored * triangulations. */ typedef int SolutionType; enum { not_attempted, /* solution not attempted, or user cancelled */ geometric_solution, /* all positively oriented tetrahedra; not flat or degenerate */ nongeometric_solution, /* positive volume, but some negatively oriented tetrahedra */ flat_solution, /* all tetrahedra flat, but no shapes = {0, 1, infinity} */ degenerate_solution, /* at least one tetrahedron has shape = {0, 1, infinity} */ other_solution, /* volume <= 0, but not flat or degenerate */ no_solution /* gluing equations could not be solved */ }; typedef int FuncResult; enum { func_OK = 0, func_cancelled, func_failed, func_bad_input }; typedef struct { double real, imag; } Complex; /*MC 02/01/08*/ typedef char Boolean; /* * The values of MatrixParity should not be changed. * (They must correspond to the values in the parity[] table in tables.c.) */ typedef int MatrixParity; enum { orientation_reversing = 0, orientation_preserving = 1 }; /* * SnapPea represents a Moebius transformation as a matrix * in SL(2,C) plus a specification of whether the Moebius * transformation is orientation_preserving or orientation_reversing. * * If mt->parity is orientation_preserving, then mt->matrix is * interpreted in the usual way as the Moebius transformation * * az + b * f(z) = -------- * cz + d * * * If mt->parity is orientation_reversing, then mt->matrix is * interpreted as a function of the complex conjugate z' ("z-bar") * * az' + b * f(z) = --------- * cz' + d */ typedef Complex SL2CMatrix[2][2]; typedef struct { SL2CMatrix matrix; MatrixParity parity; } MoebiusTransformation; /* * Matrices in O(3,1) represent isometries in the Minkowski space * model of hyperbolic 3-space. The matrices are expressed relative * to a coordinate system in which the metric is * * -1 0 0 0 * 0 1 0 0 * 0 0 1 0 * 0 0 0 1 * * That is, the first coordinate is timelike, and the remaining * three are spacelike. O(3,1) matrices represent both * orientation_preserving and orientation_reversing isometries. */ typedef double O31Matrix[4][4]; typedef double GL4RMatrix[4][4]; /* * An O31Vector is a vector in (3,1)-dimensional Minkowski space. * The 0-th coordinate is the timelike one. */ typedef double O31Vector[4]; /* * MatrixInt22 is a 2 x 2 integer matrix. A MatrixInt22 * may, for example, describe how the peripheral curves of * one Cusp map to those of another. */ typedef int MatrixInt22[2][2]; /* * An AbelianGroup is represented as a sequence of torsion coefficients. * A torsion coefficient of 0 represents an infinite cyclic factor. * For example, the group Z + Z + Z/2 + Z/5 is represented as the * sequence (0, 0, 2, 5). We make the convention that torsion coefficients * are always nonnegative. * * The UI may declare pointers to AbelianGroups, but only the kernel * may allocate or deallocate the actual memory used to store an * AbelianGroup. (This allows the kernel to keep track of memory * allocation/deallocation as a debugging aid.) */ typedef struct { int num_torsion_coefficients; /* number of torsion coefficients */ long int *torsion_coefficients; /* pointer to array of torsion coefficients */ } AbelianGroup; /* * A closed geodesic may be topologically a circle or a mirrored interval. */ typedef int Orbifold1; enum { orbifold1_unknown, orbifold_s1, /* circle */ orbifold_mI /* mirrored interval */ }; /* * The following 2-orbifolds may occur as the link of an * edge midpoint in a cell decomposition of a 3-orbifold. * * 94/10/4. The UI will see only types orbifold_nn * and orbifold_xnn. Edges of the other types have 0-cells * of the singular set at their midpoints, and are now * subdivided in Dirichlet_extras.c. JRW */ typedef int Orbifold2; enum { orbifold_nn, /* (nn) 2-sphere with two cone points (n may be 1) */ orbifold_no, /* (n|o) cross surface with cone point (n may be 1) */ orbifold_xnn, /* (*nn) disk with mirror boundary with two */ /* corner reflectors */ orbifold_2xn, /* (2*n) disk with order two cone point and mirror */ /* boundary with one corner reflector */ orbifold_22n /* (22n) sphere with three cone points */ }; /* * A MultiLength records the complex length of a geodesic together with a * parity telling whether it preserves or reverses orientation, a topology * telling whether it's a circle or a mirrored interval, and a multiplicity * telling how many distinct geodesics have that complex length, parity and * topology. */ typedef struct { Complex length; MatrixParity parity; Orbifold1 topology; int multiplicity; } MultiLength; /* * A CuspNbhdHoroball records a horoball to be drawn as part of a * picture of a cusp cross section. Only the kernel should allocate * and free CuspNbhdHoroballs and CuspNbhdHoroballLists. These * definitions are provided to the UI so it access the data easily. */ typedef struct { Complex center; double radius; int cusp_index; } CuspNbhdHoroball; typedef struct { /* * The horoball field points to an array * of num_horoballs CuspNbhdHoroballs. */ int num_horoballs; CuspNbhdHoroball *horoball; } CuspNbhdHoroballList; /* * A CuspNbhdSegment records a 1-cell to be drawn as part of a * picture of a cusp cross section. (Typically it's either part of * a triangulation of the cusp cross section, or part of a Ford domain.) * Only the kernel should allocate and free CuspNbhdSegments and * CuspNbhdSegmentLists. These definitions are provided to the UI * so it can easily access the data. * * JRW 99/03/17 When the CuspNbhdSegment describes a triangulation * (as opposed to a Ford domain), * * the start_index tells the edge index of the vertical edge * that runs from the given segment's beginning * to the viewer's eye, * * the middle_index tells the edge index of the given segment, and * * the end_index tells the edge index of the vertical edge * that runs from the given segment's end * to the viewer's eye. * * These indices let the viewer see how the horoball picture * "connects up" to form the manifold. */ typedef struct { Complex endpoint[2]; int start_index, middle_index, end_index; } CuspNbhdSegment; typedef struct { /* * segment is a pointer to an array of num_segments CuspNbhdSegments. */ int num_segments; CuspNbhdSegment *segment; } CuspNbhdSegmentList; typedef int Orientability; enum { oriented_manifold, nonorientable_manifold, unknown_orientability }; typedef int CuspTopology; enum { torus_cusp, Klein_cusp, unknown_topology }; typedef int DirichletInteractivity; enum { Dirichlet_interactive, Dirichlet_stop_here, Dirichlet_keep_going }; /* * An LRFactorization specifies the monodromy for a punctured torus * bundle over a circle. The factorization is_available whenever * (det(monodromy) = +1 and |trace(monodromy)| >= 2) or * (det(monodromy) = -1 and |trace(monodromy)| >= 1). * LR_factors points to an array of L's and R's, interpreted as factors * * L = ( 1 0 ) R = ( 1 1 ) * ( 1 1 ) ( 0 1 ) * * The factors act on a column vector, beginning with the last * (i.e. rightmost) factor. * * If negative_determinant is TRUE, the product is left-multiplied by * * ( 0 1 ) * ( 1 0 ) * * If negative_trace is TRUE, the product is left-multiplied by * * (-1 0 ) * ( 0 -1 ) * * When the factorization is unavailable, is_available is set to FALSE, * num_LR_factors is set to zero, and LR_factors is set to NULL. * But the negative_determinant and negative_trace flags are still set, * so the UI can display this information correctly. */ typedef struct { Boolean is_available, negative_determinant, negative_trace; int num_LR_factors; char *LR_factors; } LRFactorization; /* * The full definition of a Shingling appears near the top of shingling.c. * But computationally a Shingling is just a collection of planes in * hyperbolic space (typically viewed as circles on the sphere at infinity). * Each plane has an index (which defines the color of the circle at * infinity). */ typedef struct { /* * A plane in hyperbolic 3-space defines a hyperplane through * the origin in the Minkowski space model. Use the hyperplane's * normal vector to represent the original plane. [Note: the * normal is computed once, in the standard coordinate system, * and does not change as the UI rotates the polyhedron.] */ O31Vector normal; /* * A plane in hyperbolic 3-space intersects the sphere at infinity * in a circle. It's easy to draw the circle if we know its center * and two orthogonal "radials". (The 0-components of the center * and radials may be ignored.) [Note: the center and radials are * rotated in real time according to the polyhedron's current * position, and are scaled according to the window's pixel size.] */ O31Vector center, radialA, radialB; /* * The face planes of the original Dirichlet domain have index 0, * the face planes of the next layer (cf. shingling.c) have index 1, * and so on. */ int index; } Shingle; typedef struct { /* * A Shingling is just an array of Shingles. */ int num_shingles; Shingle *shingles; } Shingling; /* * The following are "opaque typedefs". They let the UI declare and * pass pointers to Triangulations, IsometryLists, etc. without * knowing what a Triangulation, IsometryList, etc. is. The definitions * of struct Triangulation, struct IsometryList, etc. are private to the * kernel. SymmetryLists and IsometryLists are represented by the same * data structure because Symmetries are just Isometries from a manifold * to itself. */ typedef struct Triangulation Triangulation; typedef struct IsometryList IsometryList; typedef struct SymmetryGroup SymmetryGroup; typedef struct SymmetryGroupPresentation SymmetryGroupPresentation; typedef struct DualOneSkeletonCurve DualOneSkeletonCurve; typedef struct TerseTriangulation TerseTriangulation; typedef struct GroupPresentation GroupPresentation; typedef struct CuspNeighborhoods CuspNeighborhoods; typedef struct NormalSurfaceList NormalSurfaceList; /* * winged_edge.h describes the winged edge data structure used * to describe Dirichlet domains. */ #include "winged_edge.h" /* * tersest_triangulation.h describes the most compressed form * for a Triangulation. The UI must have the actual definition * (not just an opaque typedef) because to read one from the * middle of a file it needs to know how long they are. */ #include "tersest_triangulation.h" /* * link_projection.h describes the format in which the UI passes * link projections to the kernel. */ #include "link_projection.h" /* * When the UI reads a Triangulation from disk, it passes the results * to the kernel using the format described in triangulation_io.h. */ #include "triangulation_io.h" /* * covers.h defines a representation of a manifold's fundamental group * into the symmetric group on n letters. */ #include "covers.h" /* MC 01/26/08 * homology.h defines a structure used to store a relation matrix for * the first homology group. */ #include "homology.h" /* To guarantee thread-safety, it's useful to declare */ /* global variables to be "const", for example */ /* */ /* static const Complex minus_i = {0.0, -1.0}; */ /* */ /* Unfortunately the current gcc compiler complains when */ /* non-const variables are passed to functions expecting */ /* const arguments. Obviously this is harmless, but gcc */ /* complains anyhow. So for now let's use the following */ /* CONST macro, to allow the const declarations to be */ /* reactivated if desired. */ /* */ /* Note: In Win32, windef.h also defines CONST = const, */ /* so clear its definition before making our own. */ #undef CONST #define CONST /* #define CONST const */ #ifdef __cplusplus extern "C" { #endif /************************************************************************/ /* * The UI provides the following functions for use by the kernel: */ extern void uAcknowledge(const char *message); /* * Presents the string *message to the user and waits for acknowledgment ("OK"). */ extern int uQuery(const char *message, const int num_responses, const char *responses[], const int default_response); /* * Presents the string *message to the user and asks the user to choose * one of the responses. Returns the number of the chosen response * (numbering starts at 0). In an interactive context, the UI should * present the possible responses evenhandedly -- none should be * presented as a default. However, in a batch context (when no human * is present), uQuery should return the default_response. */ extern void uFatalError(const char *function, const char *file); /* * Informs the user that a fatal error has occurred in the given * function and file, and then exits. */ extern void uAbortMemoryFull(void); /* * Informs the user that the available memory has been exhausted, * and aborts SnapPea. */ extern void uPrepareMemFullMessage(void); /* * uMemoryFull() is a tricky function, because the system may not find * enough memory to display an error message. (I tried having it stash * away some memory and then free it to support the desired dialog box, * but at least on the Mac this didn't work for some unknown reason.) * uPrepareMemFullMessage() gives the system a chance to prepare * a (hidden) dialog box. Call it once when the UI initializes. */ extern void uLongComputationBegins(char *message, Boolean is_abortable); extern FuncResult uLongComputationContinues(void); extern void uLongComputationEnds(void); /* * The kernel uses these three functions to inform the UI of a long * computation. The UI relays this information to the user in whatever * manner it considers appropriate. For example, it might wait a second * or two after the beginning of a long computation, and then display * a dialog box containing *message (a typical message might be * "finding canonical triangulation" or "computing hyperbolic structure"). * If is_abortable is TRUE, the dialog box would contain an abort button. * The reason for waiting a second or two before displaying the dialog * box is to avoid annoying the user with flashing dialog boxes for * computations which turn out not to be so long after all. * * The kernel is responsible for calling uLongComputationContinues() at * least every 1/60 second or so during a long computation. * uLongComputationContinues() serves two purposes: * * (1) It lets the UI yield time to its window system. (This is * crucial for smooth background operation in the Mac's * cooperative multitasking environment. I don't know whether * it is necessary in X or NeXT.) * * (2) If the computation is abortable, it checks whether the user * has asked to abort, and returns the result (func_cancelled * to abort, func_OK to continue). * * While the kernel is responsible for making sure uLongComputationContinues() * is called often enough, uLongComputationContinues() itself must take * responsibility for not repeating time-consuming operations too often. * For example, it might return immediately from a call if less than * 1/60 of a second has elapsed since the last time it carried out * its full duties. * * uLongComputationEnds() signals that the long computation is over. * The kernel must call uLongComputationEnds() even after an aborted * computation. ( uLongComputationContinues() merely informs the kernel * that the user punched the abort button. The kernel must still call * uLongComputationEnds() to dismiss the dialog box in the usual way.) * * If the UI receives a call to uLongComputationEnds() when no long * computation is in progress, or a call to uLongComputationBegins() * when a long computation is already in progress, it should notify * the user of the error and exit. * * If the UI receives a call to uLongComputationContinues() when in * fact no long computation is in progress, it should simply take * care of any background responsibilities (see (1) above) and not * complain. The reason for this provision is that the calls to * uLongComputationBegins() and uLongComputationEnds() occur in high * level functions, while the calls to uLongComputationContinues() * occur at the lowest level, perhaps in a different file. Someday * those low-level functions (for example, the routines for solving * simultaneous linear equations) might be called as part of some quick, * non-abortable computation. */ /************************************************************************/ /************************************************************************/ /* * The kernel provides the following functions for use by the UI. * * A brief specification follows each function prototype. * Complete documentation appears in the corresponding source file. */ /************************************************************************/ /* */ /* abelian_group.c */ /* */ /************************************************************************/ extern void expand_abelian_group(AbelianGroup *g); /* * Expands an AbelianGroup into its most factored form, * e.g. Z/2 + Z/2 + Z/4 + Z/3 + Z/9 + Z. * Each nonzero torsion coefficient is a power of a prime. */ extern void compress_abelian_group(AbelianGroup *g); /* * Compresses an AbelianGroup into its least factored form, * Z/2 + Z/6 + Z/36 + Z. * Each torsion coefficient divides all subsequent torsion coefficients. */ extern void free_abelian_group(AbelianGroup *g); /* * Frees the storage used to hold the AbelianGroup *g. */ /************************************************************************/ /* */ /* canonize.c */ /* canonize_part_1.c */ /* canonize_part_2.c */ /* */ /************************************************************************/ extern FuncResult canonize(Triangulation *manifold); /* * Replaces the given Triangulation with the canonical retriangulation * of the canonical cell decomposition. Returns func_OK upon success, * func_failed if it cannot find a hyperbolic structure for *manifold. */ extern FuncResult proto_canonize(Triangulation *manifold); extern void canonical_retriangulation(Triangulation *manifold); /* * These functions comprise the two halves of canonize() in canonize.c. * * proto_canonize() replaces a Triangulation by the canonical * triangulation of the same manifold (if the canonical cell * decomposition is a triangulation) or by an arbitrary subdivision * of the canonical cell decomposition into Tetrahedra (if the canonical * cell decomposition contains cells other than tetrahedra). * Returns func_OK upon success, func_failed if it cannot find a * hyperbolic structure for *manifold. * * canonical_retriangulation() replaces the given subdivision of the * canonical cell decomposition with the canonical retriangulation. * This operation introduces finite vertices whenever the canonical cell * decomposition is not a triangulation to begin with. The hyperbolic * structure is discarded. */ extern Boolean is_canonical_triangulation(Triangulation *manifold); /* * Given a subdivision of the canonical cell decomposition as produced * by proto_canonize(), says whether it is the canonical decomposition * itself. In other words, it says whether the canonical cell decomposition * is a triangulation. */ /************************************************************************/ /* */ /* change_peripheral_curves.c */ /* */ /************************************************************************/ extern FuncResult change_peripheral_curves( Triangulation *manifold, CONST MatrixInt22 change_matrices[]); /* * If all the change_matrices have determinant +1, installs the * corresponding new peripheral curves and returns func_OK. * (See change_peripheral_curves.c for details.) * Otherwise does nothing and returns func_bad_input. */ /************************************************************************/ /* */ /* chern_simons.c */ /* */ /************************************************************************/ extern void set_CS_value( Triangulation *manifold, double a_value); /* * Set the Chern-Simons invariant of *manifold to a_value. */ extern void get_CS_value( Triangulation *manifold, Boolean *value_is_known, double *the_value, int *the_precision, Boolean *requires_initialization); /* * If the Chern-Simons invariant of *manifold is known, sets * *value_is_known to TRUE and writes the current value and its precision * (the number of significant digits to the right of the decimal point) * to *the_value and *the_precision, respectively. * * If the Chern-Simons invariant is not known, sets *value_is_known to * FALSE, and then sets *requires_initialization to TRUE if the_value * is unknown because the computation has not been initialized, or * to FALSE if the_value is unknown because the solution contains * negatively oriented Tetrahedra. The UI might want to convey * these situations to the user in different ways. */ /************************************************************************/ /* */ /* complex.c */ /* */ /************************************************************************/ extern Complex complex_minus (Complex z0, Complex z1), complex_plus (Complex z0, Complex z1), complex_mult (Complex z0, Complex z1), complex_div (Complex z0, Complex z1), complex_sqrt (Complex z), complex_conjugate (Complex z), complex_negate (Complex z), complex_real_mult (double r, Complex z), complex_exp (Complex z), complex_log (Complex z, double approx_arg); extern double complex_modulus (Complex z); extern double complex_modulus_squared (Complex z); extern Boolean complex_nonzero (Complex z); extern Boolean complex_infinite (Complex z); /* * The usual complex arithmetic functions. * * Standard complex constants (Zero, One, etc.) are defined in the kernel. */ /************************************************************************/ /* */ /* complex_length.c */ /* */ /************************************************************************/ extern Complex complex_length_mt(MoebiusTransformation *mt); extern Complex complex_length_o31(O31Matrix m); /* * Computes the complex length of an isometry. Please see * complex_length.c for full definitions and explanations. * complex_length_mt() and complex_length_o31() are identical except * for the form in which the input is given. */ /************************************************************************/ /* */ /* continued_fractions.c */ /* */ /************************************************************************/ extern Boolean appears_rational(double x0, double x1, double confidence, long *num, long *den); /* * Checks whether a finite-precision real number x known to lie in the * interval (x0, x1) appears to be a rational number p/q. If it does, * it sets *num and *den to p and q, respectively, and returns TRUE. * Otherwise it sets *num and *den to 0 and returns FALSE. * The confidence parameter gives the maximal acceptable probability * of a "false positive". */ /************************************************************************/ /* */ /* core_geodesic.c */ /* */ /************************************************************************/ extern void core_geodesic( Triangulation *manifold, int cusp_index, int *singularity_index, Complex *core_length, int *precision); /* * Examines the Cusp of index cusp_index in *manifold. * * If the Cusp is unfilled or the Dehn filling coefficients are not * integers, sets *singularity_index to zero and leaves *core_length * undefined. * * If the Cusp has relatively prime integer Dehn filling coefficients, * sets *singularity_index to 1 and *core_length to the complex length * of the central geodesic. * * If the Cusp has non relatively prime integer Dehn filling coefficients, * sets *singularity_index to the index of the singular locus, and * *core_length to the complex length of the central geodesic in the * smallest manifold cover of a neighborhood of the singular set. * * In the latter two cases, if the precision pointer is not NULL, * *precision is set to the number of decimal places of accuracy in * the computed value of *core_length. * * core_geodesic() is intended for use by the UI. Kernel function may * find compute_core_geodesic() (declared in kernel_prototypes.h) more * convenient. */ /************************************************************************/ /* */ /* cover.c */ /* */ /************************************************************************/ Triangulation *construct_cover( Triangulation *base_manifold, RepresentationIntoSn *representation, int n); /* * Constructs the n-sheeted cover of the given base_manifold defined * by the given transitive representation. */ /************************************************************************/ /* */ /* current_curve_basis.c */ /* */ /************************************************************************/ extern void current_curve_basis( Triangulation *manifold, int cusp_index, MatrixInt22 basis_change); extern void install_current_curve_bases(Triangulation *manifold); /* * current_curve_basis() accepts a Triangulation and a cusp index, * and computes a 2 x 2 integer matrix basis_change with the property * that * * if the Cusp of index cusp_index is filled, and has * relatively prime integer Dehn filling coefficients, * * the first row of basis_change is set to the current * Dehn filling coefficients, and * the second row of basis_change is set to the shortest * curve which completes a basis. * * else * * basis_change is set to the identity * * install_current_curve_bases() installs the above basis * on all cusps of the manifold. */ /************************************************************************/ /* */ /* cusp_neighborhoods.c */ /* */ /************************************************************************/ extern CuspNeighborhoods *initialize_cusp_neighborhoods( Triangulation *manifold); /* * Initializes a CuspNeighborhoods data structure. * It works with a copy of manifold, leaving the original untouched. * It does all indicated Dehn fillings. * Returns a pointer to the CuspNeighborhoods structure upon success, * of NULL if the "manifold" isn't a cusped hyperbolic 3-manifold. */ extern void free_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods); /* * Frees the CuspNeighborhoods structure, including the copy of * the Triangulation it contains. */ extern int get_num_cusp_neighborhoods( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the number of cusps. This will be the number of unfilled * cusps in the original manifold, which may be less than the total * number of cusps. */ extern CuspTopology get_cusp_neighborhood_topology( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the CuspTopology of the given cusp. */ extern double get_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the (linear) displacement of the horospherical cross * section of the given cusp from its home position. At the home * position the cusp cross section has area (3/8)sqrt(3) and * encloses a volume of (3/16)sqrt(3) in the cusp. At its home * position, a cusp cannot overlap itself, nor can it overlap any * other cusp which does not already overlap itself. Please see * cusp_neighborhoods.c for details. */ extern Boolean get_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Says whether this cusp's neighborhood is tied to other cusps'. */ extern double get_cusp_neighborhood_cusp_volume( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the volume enclosed by the horospherical cross section * of the given cusp. */ extern double get_cusp_neighborhood_manifold_volume( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the volume of the manifold. */ extern Triangulation *get_cusp_neighborhood_manifold( CuspNeighborhoods *cusp_neighborhoods); /* * Returns a pointer to a copy of the manifold. The UI may do as it * pleases with the copy, and should free it when it's done. */ extern double get_cusp_neighborhood_reach( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns the displacement at which the cusp cross section first * bumps into itself. */ extern double get_cusp_neighborhood_max_reach( CuspNeighborhoods *cusp_neighborhoods); /* * Returns the maximum reach over the whole manifold. */ extern double get_cusp_neighborhood_stopping_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); extern int get_cusp_neighborhood_stopper_cusp_index( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Return the displacement at which the cusp first bumps into another * cusp (or possibly into itself), and the cusp it bumps into. * Unlike the reach, the stopper and the stopping displacement depend * on the current displacements of all the cusps in the triangulation. * They vary dynamically as the user moves the cusp cross sections. */ extern void set_cusp_neighborhood_displacement( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, double new_displacement); /* * Sets the cusp neighborhood's displacement to the requested value, * clipping it to the range [0, stopping_displacement] if necessary. * Recomputes the canonical cell decomposition. */ extern void set_cusp_neighborhood_tie( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean new_tie); /* * Tells the kernel whether this cusp's neighborhood should be * tied to other cusps (which have previously been "tied"). * The kernel makes all tied cusps have the same displacement. */ extern void get_cusp_neighborhood_translations( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Complex *meridian, Complex *longitude); /* * Returns the meridional and longitudinal translation vectors * for the given cusp cross section, taking into account its current * displacement. For a Klein bottle cusp, the longitudinal translation * will be that of the double cover. As a convenience, the longitude * will always point in the x-direction. */ extern CuspNbhdHoroballList *get_cusp_neighborhood_horoballs( CuspNeighborhoods *cusp_neighborhoods, int cusp_index, Boolean full_list, double cutoff_height); /* * Returns a list of horoballs seen from the given cusp, taking into * account the cusp cross sections' current displacements. Only one * translate is given for each horoball -- to draw the full picture the * UI must find all visible translates using the meridian and longitude * provided by get_cusp_neighborhood_translations(). For a Klein bottle * cusp, get_cusp_neighborhood_horoballs() reports data for the double * cover. If full_list is TRUE, get_cusp_neighborhood_horoballs() * reports all horoballs whose Euclidean height in the upper half space * model is at least cutoff_height. If full_list is FALSE, it reports * only a few of the largest horoballs (the cutoff_height is ignored). * This lets the UI draw a simpler picture while the user is changing * something in real time, and then draw a more complete picture afterwards. */ extern void free_cusp_neighborhood_horoball_list( CuspNbhdHoroballList *horoball_list); /* * Frees a CuspNbhdHoroballList when the UI's done with it. */ extern CuspNbhdSegmentList *get_cusp_neighborhood_triangulation( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns a list of edges in the restriction of the canonical cell * decomposition to the cusp cross section, taking into account the * cusp cross section's current displacement. Only one translate is * given for each edge -- to draw the full picture the UI must find all * visible translates using the meridian and longitude provided by * get_cusp_neighborhood_translations(). For a Klein bottle cusp, * get_cusp_neighborhood_triangulation() reports data for the double cover. */ extern CuspNbhdSegmentList *get_cusp_neighborhood_Ford_domain( CuspNeighborhoods *cusp_neighborhoods, int cusp_index); /* * Returns a list of edges in the Ford domain, taking into account the * cusp cross section's current displacement. Only one translate is * given for each edge -- to draw the full picture the UI must find all * visible translates using the meridian and longitude provided by * get_cusp_neighborhood_translations(). For a Klein bottle cusp, * get_cusp_neighborhood_Ford_domain() reports data for the double cover. */ extern void free_cusp_neighborhood_segment_list( CuspNbhdSegmentList *segment_list); /* * Frees a CuspNbhdSegmentList when the UI's done with it. */ /************************************************************************/ /* */ /* Dirichlet.c */ /* */ /************************************************************************/ extern WEPolyhedron *Dirichlet( Triangulation *manifold, double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Computes a Dirichlet domain for the given manifold or orbifold. * Returns NULL if the Dehn filling coefficients are not all integers, * of if roundoff errors lead to topological problems. * Returns a pointer to the Dirichlet domain otherwise. */ extern WEPolyhedron *Dirichlet_with_displacement( Triangulation *manifold, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Like Dirichlet(), only allows an arbitrary displacement * of the basepoint. The displacement is in tangent space * coordinates, so the distances can't be interpreted too literally. * Reasonable displacements are to the order of 0.1. * Large displacements are possible, but degrade the numerical * accuracy of the resulting Dirichlet domain. */ extern WEPolyhedron *Dirichlet_from_generators( O31Matrix generators[], int num_generators, double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Like Dirichlet(), only starts with a set of O(3,1) matrix generators * instead of a Triangulation. */ extern WEPolyhedron *Dirichlet_from_generators_with_displacement( O31Matrix generators[], int num_generators, double displacement[3], double vertex_epsilon, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Combines the functionality of Dirichlet_with_displacement() and * Dirichlet_from_generators(). */ extern void change_basepoint( WEPolyhedron **polyhedron, Triangulation *manifold, O31Matrix *generators, int num_generators, double displacement[3], double vertex_epsilon, Boolean centroid_at_origin, DirichletInteractivity interactivity, Boolean maximize_injectivity_radius); /* * Reads the face pairing matrices from the polyhedron, shifts the * basepoint by the given displacement (optionally letting the basepoint * move to a local maximum of the injectivity radius function), and * recomputes the Dirichlet domain. * If *polyhedron is NULL, computes the Dirichlet domain directly from * the manifold, but with the given displacement of the initial basepoint. * In either case, a pointer to the resulting Dirichlet domain (or NULL * if an error occurs as described in Dirichlet() above) is written * to *polyhedron. */ extern void free_Dirichlet_domain(WEPolyhedron *Dirichlet_domain); /* * Frees the storage occupied by a WEPolyhedron. */ /************************************************************************/ /* */ /* Dirichlet_rotate.c */ /* */ /************************************************************************/ extern void set_identity_matrix(O31Matrix position); /* * Sets the matrix to the identity. */ extern void update_poly_position(O31Matrix position, O31Matrix velocity); /* * Multiplies the position by the velocity. */ extern void update_poly_vertices(WEPolyhedron *polyhedron, O31Matrix position, double scale); /* * Multiplies the standard vertex coordinates x[] by the position matrix * to obtain the rotated coordinates xx[], and then multiplies the * rotated coordinates by the constant "scale". */ extern void update_poly_visibility(WEPolyhedron *polyhedron, O31Matrix position, O31Vector direction); /* * Checks which vertices, edges and faces are visible to the user with * the polyhedron in its present position, and sets their visibility * fields accordingly. */ /************************************************************************/ /* */ /* Dirichlet_conversion.c */ /* */ /************************************************************************/ extern Triangulation *Dirichlet_to_triangulation(WEPolyhedron *polyhedron); /* * Converts a Dirichlet domain to a Triangulation, leaving the * Dirichlet domain unchanged. For closed manifolds, drills out * an arbitrary curve and expresses the manifold as a Dehn filling. */ /************************************************************************/ /* */ /* double_cover.c */ /* */ /************************************************************************/ extern Triangulation *double_cover(Triangulation *manifold); /* * Returns a pointer to the double cover of the nonorientable * Triangulation *manifold. */ /************************************************************************/ /* */ /* dual_curves.c */ /* */ /************************************************************************/ extern void dual_curves( Triangulation *manifold, int max_size, int *num_curves, DualOneSkeletonCurve ***the_curves); /* * Computes a reasonable selection of simple closed curves in * a manifold's dual 1-skeleton. */ extern void get_dual_curve_info( DualOneSkeletonCurve *the_curve, Complex *complete_length, Complex *filled_length, MatrixParity *parity); /* * Reports the complex length of a curve in the dual 1-skeleton, * relative to both the complete and filled hyperbolic structures, * and also its parity (orientation_preserving or orientation_reversing). */ extern void free_dual_curves( int num_curves, DualOneSkeletonCurve **the_curves); /* * Frees the array of curves computed by dual_curves(). */ /************************************************************************/ /* */ /* drilling.c */ /* */ /************************************************************************/ extern Triangulation *drill_cusp( Triangulation *old_manifold, DualOneSkeletonCurve *curve_to_drill, char *new_name); /* * Drills a curve out of the dual 1-skeleton of an n-cusp manifold to * create an (n+1)-cusp manifold. */ /************************************************************************/ /* */ /* filling.c */ /* */ /************************************************************************/ extern Triangulation *fill_cusps( Triangulation *manifold, Boolean fill_cusp[], char *new_name, Boolean fill_all_cusps); /* * Permanently fills k of the cusps of an n-cusp manifold. * Typically fill_all_cusps is FALSE, and the function returns * an ideal Triangulation of the resulting (n - k)-cusp manifold. * fill_cusp[] is a Boolean array specifying which k cusps (k < n) * are to be filled. * * In the exceptional case that fill_all_cusps is TRUE, the function * returns a triangulation with finite vertices only. * Such triangulations are unacceptable for most SnapPea routines, * and should be used only for writing to disk. When fill_all_cusps * is TRUE, fill_cusp is ignored and may be NULL. * * new_name is the name to be given to the new Triangulation. */ extern Triangulation *fill_reasonable_cusps(Triangulation *manifold); /* * Makes reasonable choices for fill_cusp[] and new_name, and calls * fill_cusps(). Specifically, it will fill all cusps with relatively * prime Dehn filling coefficients, unless this would leave no cusps * unfilled, in which case it leaves cusp 0 unfilled. It copies the * name from the original manifold. */ extern Boolean cusp_is_fillable(Triangulation *manifold, int cusp_index); /* * Returns TRUE if a cusp has relatively prime integer Dehn filling * coefficients, FALSE otherwise. */ extern Boolean is_closed_manifold(Triangulation *manifold); /* * Returns TRUE iff all cusps are filled and the coefficients * are relatively prime integers. */ /************************************************************************/ /* */ /* fundamental_group.c */ /* */ /************************************************************************/ extern GroupPresentation *fundamental_group( Triangulation *manifold, Boolean simplify_presentation, Boolean fillings_may_affect_generators, Boolean minimize_number_of_generators); /* * Computes the fundamental group of the manifold, taking into account * Dehn fillings, and returns a pointer to it. Please see * fundamental_group.c for an explanation of the arguments. */ extern int fg_get_num_generators(GroupPresentation *group); /* * Returns the number of generators in the GroupPresentation. */ extern int fg_get_num_orig_gens(GroupPresentation *group); /* * Returns the number of standard geometric generators. */ extern Boolean fg_integer_fillings(GroupPresentation *group); /* * Says whether the underlying space is a manifold or orbifold, * as opposed to some other generalized Dehn filling. */ extern FuncResult fg_word_to_matrix( GroupPresentation *group, int *word, O31Matrix result_O31, MoebiusTransformation *result_Moebius); /* * Converts an abstract word in the fundamental group to a matrix * in the matrix representation. The abstract word is given as a * string of integers. The integer 1 means the first generator, * 2 means the second, etc., while -1 is the inverse of the first * generator, -2 is the inverse of the second, etc. The integer 0 * indicates the end of the string. The result is given both as * an O31Matrix and a MoebiusTransformation. Returns func_OK if * successful, or func_bad_input if the input word is not valid. */ extern int fg_get_num_relations(GroupPresentation *group); /* * Returns the number of relations in the GroupPresentation. */ extern int *fg_get_relation( GroupPresentation *group, int which_relation); /* * Returns the specified relation (using 0-based indexing). * It allocates the memory for it, so you should pass the pointer * back to fg_free_relation() when you're done with it. * Each relation is a string of integers. The integer 1 means * the first generator, 2 means the second, etc., while -1 is the * inverse of the first generator, -2 is the inverse of the second, etc. * The integer 0 indicates the end of the string. */ extern void fg_free_relation(int *relation); /* * Frees a relation allocated by fg_get_relation(). */ extern int fg_get_num_cusps(GroupPresentation *group); /* * Returns the number of cusps of the underlying manifold. * This *includes* the filled cusps. So, for example, if you do (5,1) * Dehn filling on the figure eight knot complement, you can see the * words in the fundamental group corresponding to the (former!) cusp's * meridian and longitude. */ extern int *fg_get_meridian( GroupPresentation *group, int which_cusp); extern int *fg_get_longitude( GroupPresentation *group, int which_cusp); /* * Returns the word corresponding to a meridian or longitude, in the * same format used by fg_get_relation() above. They allocate the * memory for the string of integers, so you should pass the pointer * back to fg_free_relation() when you're done with it. Meridians and * longitudes are available whether the cusps are filled or not, as * explained for fg_get_num_cusps() above. */ extern int *fg_get_original_generator( GroupPresentation *group, int which_generator); /* * Returns a word which expresses one of the standard geometric * generators (as defined in choose_generators.c) in terms of the * group presentation's generators. The word is in the same format * used by fg_get_relation() above. Note that which_generator is * given relative to 0-based indexing, but the letters in the word * you get out use 1-based numbering, as in fg_get_relation(). * Please free the word with fg_free_relation() when you're done. */ extern int *fg_get_word_moves(GroupPresentation *group); /* * Returns a something describing how the current generators are * expressed in terms of the geometric generators. See * fundamental_group.c for details. Please free the word with * fg_free_relation() when you're done. */ extern void free_group_presentation(GroupPresentation *group); /* * Frees the storage occupied by a GroupPresentation. */ /************************************************************************/ /* */ /* homology.c */ /* */ /************************************************************************/ extern AbelianGroup *homology(Triangulation *manifold); /* * If all Dehn filling coefficients are integers, returns a pointer to * the first homology group of *manifold. In particular, it will * happily compute homology groups of orbifolds. If one or more Dehn * filling coefficients are not integers, returns NULL. This function * allocates the memory for the AbelianGroup; the UI should call * free_abelian_group() (no pun intended) to release it. * * 96/12/11 Checks for overflows, and returns NULL if any occur. */ extern AbelianGroup *homology_from_fundamental_group( GroupPresentation *group); /* * Abelianizes a group presentation and returns the result. * Returns NULL if overflows occur. */ extern void homology_presentation( Triangulation *manifold, RelationMatrix *relation_matrix); /* * Fills in a RelationMatrix structure. Sets relation_matrix->relations * to NULL if overflows occurs while computing the matrix. * MC 01/26/08 */ extern void free_relations(RelationMatrix *relation_matrix); /* Frees the memory pointed to by relation_matrix->relations. */ /************************************************************************/ /* */ /* hyperbolic_structure.c */ /* */ /************************************************************************/ extern SolutionType find_complete_hyperbolic_structure(Triangulation *manifold); /* * Attempts to find a complete hyperbolic structure for the * Triangulation *manifold. Sets the solution_type[complete] member of * *manifold to the type of solution found. If this type is anything * other than no_solution, stores the hyperbolic structure by setting * the *shape[complete] field of each Tetrahedron in the Triangulation. The * solution is also stored as the initial filled solution, by setting the * solution_type[filled] member of *manifold and the *shape[filled] fields * of the Tetrahedra; the is_complete flag of each Cusp is set to TRUE. * * The hyperbolic structure is computed using Newton's method, beginning * with all tetrahedra regular. * * Returns: the type of solution found. */ extern SolutionType do_Dehn_filling(Triangulation *manifold); /* * Attempts to find a hyperbolic structure for a *manifold, based on * the current Dehn filling coefficients. Sets the solution_type[filled] * member of *manifold to the type of solution found. If * this type is anything other than no_solution, stores the hyperbolic * structure by setting the *shape[filled] field of each Tetrahedron in * the Triangulation. * * The hyperbolic structure is computed using Newton's method; the * initial guess is the previous Dehn filled solution. * * Returns: the type of solution found. */ extern SolutionType remove_Dehn_fillings(Triangulation *manifold); /* * Removes all Dehn fillings. * * Returns: the type of solution restored. */ /************************************************************************/ /* */ /* index_to_hue.c */ /* */ /************************************************************************/ extern double index_to_hue(int index); /* * Maps the nonnegative integers to a set of easily distinguishable hues. * * index 0 1 2 3 4 5 6 . . . * hue 0 1/2 1/4 3/4 1/8 5/8 3/8 . . . */ extern double horoball_hue(int index); /* * Provides hand chosen hues for indices 0-5, and uses index_to_hue() * to interpolate thereafter. The hope is for nicer looking horoball * packings. */ /************************************************************************/ /* */ /* interface.c */ /* */ /************************************************************************/ extern char *get_triangulation_name(Triangulation *manifold); /* * Return a pointer to the name of the Triangulation *manifold. * The pointer points to the actual name, not a copy. */ extern void set_triangulation_name(Triangulation *manifold, char *new_name); /* * Sets the Triangulation's name to new_name. */ extern SolutionType get_complete_solution_type(Triangulation *manifold); /* * Returns the SolutionType of the complete structure. */ extern SolutionType get_filled_solution_type(Triangulation *manifold); /* * Returns the SolutionType of the current Dehn filling. */ extern int get_num_tetrahedra(Triangulation *manifold); /* * Returns the number of tetrahedra in the Triangulation *manifold. */ extern Orientability get_orientability(Triangulation *manifold); /* * Returns the orientability of *manifold. */ extern int get_num_cusps(Triangulation *manifold); /* * Returns the number of cusps in *manifold. */ extern int get_num_or_cusps(Triangulation *manifold); /* * Returns the number of orientable cusps in *manifold. */ extern int get_num_nonor_cusps(Triangulation *manifold); /* * Returns the number of nonorientable cusps in *manifold. */ extern int get_max_singularity(Triangulation *manifold); /* * Returns the maximum value of gcd(m,l) over all integer Dehn filling * coefficients (m,l) for filled cusps in *manifold. */ extern int get_num_generators(Triangulation *manifold); /* * Returns the number of generators being used to represent *manifold's * fundamental group. */ extern void get_cusp_info( Triangulation *manifold, int cusp_index, CuspTopology *topology, Boolean *is_complete, double *m, double *l, Complex *initial_shape, Complex *current_shape, int *initial_shape_precision, int *current_shape_precision, Complex *initial_modulus, Complex *current_modulus); /* * Provides information about the cusp whose index is cusp_index in * *manifold. (The cusp indices run from zero to one less than the * number of cusps.) * * *topology is set to torus_cusp, Klein_cusp, or unknown_topology. * *is_complete is set to TRUE if the cusp is not Dehn filled, and * FALSE if it is. * *m and *l are set to the current Dehn filling coefficients. * They will be meaningful only if the cusp is filled. * If the cusp is nonorientable, only *m will be meaningful * (because *l must be zero for a Klein bottle cusp -- see * the comment at the top of holonomy.c). * *initial_shape is set to the initial shape (longitude/meridian) of the * cusp, i.e. the shape it had when all cusps were unfilled. * *current_shape is set to the cusp's current shape if the cusp is_complete, * zero otherwise. * *initial_shape_precision is set to the number of decimal places of accuracy * in the computed value of initial_shape. * *current_shape_precision is set to the number of decimal places of accuracy * in the computed value of current_shape. * *initial_modulus is set to the modulus ( (second shortest translation)/ * (shortest translation) ) of the initial cusp shape. * *current_modulus is set to the modulus of the current cusp shape. * * You may pass NULL for pointers to values you aren't interested in. */ extern FuncResult set_cusp_info(Triangulation *manifold, int cusp_index, Boolean cusp_is_complete, double m, double l); /* * Looks for a cusp with index cusp_index in Triangulation *manifold. * If not found, * alerts the user and exits (this should never occur * unless there is a bug in the UI). * If found, * if cusp_is_complete is TRUE, * sets the is_complete field of the cusp to TRUE, and * sets the Dehn filling coefficients to 0.0, * if cusp_is_complete is FALSE * sets the is_complete field of the cusp to FALSE, and * sets the Dehn filling coefficients to m and l. * * set_cusp_info() checks for errors in the values of m and l. * The (0,0) Dehn filling is never allowed, and only (p,0) fillings are * allowed on nonorientable cusps. If an error is detected, the cusp * will be left unchanged. * * Returns: * func_OK for success * func_bad_input for illegal Dehn filling coefficients */ extern void get_holonomy( Triangulation *manifold, int cusp_index, Complex *meridional_holonomy, Complex *longitudinal_holonomy, int *meridional_precision, int *longitudinal_precision); /* * Passes back the holonomies of the meridian and longitude, * and an estimate of their precision (number of decimal * digits to the right of the decimal point). */ extern void get_tet_shape( Triangulation *manifold, int which_tet, Boolean fixed_alignment, double *shape_rect_real, double *shape_rect_imag, double *shape_log_real, double *shape_log_imag, int *precision_rect_real, int *precision_rect_imag, int *precision_log_real, int *precision_log_imag, Boolean *is_geometric); /* * Provides information about the shape of the Tetrahedron in * position which_tet in the linked list (which_tet takes a value * in the range [0, (#tetrahedra - 1)] ). (Note: which_tet * does not explicitly refer to the "index" field of the Tetrahedron * data structure, although in practice it will coincide.) * get_tet_shape() provides the shape of the Tetrahedron in both * rectangular and logarithmic forms, relative to whatever coordinate * system was used most recently. This means that the rectangular * form will satisfy |z| < 1 and |z - 1| < 1. The last four arguments * give the precision of the preceding four, expressed as the number * of significant deciomal digits following the decimal point. * (Warning: the precision is only a rough estimate. The last * digit or two may sometimes be incorrect.) The flag *is_geometric * is set to TRUE iff all dihedral angles lie in the range [0,pi]. */ extern int get_num_edge_classes( Triangulation *manifold, int edge_class_order, Boolean greater_than_or_equal); /* * If greater_than_or_equal == TRUE, returns the number of EdgeClasses * whose order is greater than or equal to edge_class_order. * If greater_than_or_equal == FALSE, returns the number of EdgeClasses * whose order is exactly edge_class_order. */ /************************************************************************/ /* */ /* isometry.c */ /* */ /************************************************************************/ extern FuncResult compute_isometries( Triangulation *manifold0, Triangulation *manifold1, Boolean *are_isometric, IsometryList **isometry_list, IsometryList **isometry_list_of_links); /* * Checks whether manifold0 and manifold1 are isometric (taking into * account the Dehn fillings). If manifold0 and manifold1 are cusped * manifolds, sets *isometry_list and *isometry_list_of_links as * in compute_cusped_isometries() below. Returns * func_OK if all goes well, * func_bad_input if some Dehn filling coefficients are not * relatively prime integers, * func_failed if it can't decide. */ extern int isometry_list_size(IsometryList *isometry_list); /* * Returns the number of Isometries in the IsometryList. */ extern int isometry_list_num_cusps(IsometryList *isometry_list); /* * Returns the number of cusps in each of the underlying manifolds. * If the IsometryList is empty (as would be the case when the * underlying manifolds have different numbers of cusps), then * isometry_list_num_cusps()'s return value is undefined. */ extern void isometry_list_cusp_action( IsometryList *isometry_list, int anIsometryIndex, int aCusp, int *cusp_image, int cusp_map[2][2]); /* * Fills in the cusp_image and cusp_map[2][2] to describe the action * of the given Isometry on the given Cusp. */ extern Boolean isometry_extends_to_link(IsometryList *isometry_list, int i); /* * Returns TRUE if Isometry i extends to the associated links (i.e. if it * takes meridians to meridians), FALSE if it doesn't. */ extern void isometry_list_orientations( IsometryList *isometry_list, Boolean *contains_orientation_preserving_isometries, Boolean *contains_orientation_reversing_isometries); /* * Says whether the IsometryList contains orientation-preserving * and/or orientation-reversing elements. Assumes the underlying * Triangulations are oriented. */ extern void free_isometry_list(IsometryList *isometry_list); /* * Frees the IsometryList. */ /************************************************************************/ /* */ /* isometry_cusped.c */ /* */ /************************************************************************/ extern Boolean same_triangulation( Triangulation *manifold0, Triangulation *manifold1); /* * Check whether manifold0 and manifold1 have combinatorially * equivalent triangulations (ignoring Dehn fillings). * This function is less versatile than a call to * compute_isometries(manifold0, manifold1, &are_isometric, NULL, NULL) * but it's useful for batch processing, when you want to avoid the * overhead of constantly recomputing canonical retriangulations. */ /************************************************************************/ /* */ /* length_spectrum.c */ /* */ /************************************************************************/ extern void length_spectrum( WEPolyhedron *polyhedron, double cutoff_length, Boolean full_rigor, Boolean multiplicities, double user_radius, MultiLength **spectrum, int *num_lengths); /* * Takes as input a manifold in the form of a Dirichlet domain, and * finds all geodesics of length less than or equal to cutoff_length. * Please length_spectrum.c for details. */ extern void free_length_spectrum(MultiLength *spectrum); /* * Deallocates the memory used to store the length spectrum. */ /************************************************************************/ /* */ /* link_complement.c */ /* */ /************************************************************************/ extern Triangulation *triangulate_link_complement( KLPProjection *aLinkProjection); /* * Triangulate the complement of aLinkProjection. */ /************************************************************************/ /* */ /* matrix_conversion.c */ /* */ /************************************************************************/ extern void Moebius_to_O31(MoebiusTransformation *A, O31Matrix B); extern void O31_to_Moebius(O31Matrix B, MoebiusTransformation *A); /* * Convert matrices back and forth between SL(2,C) and O(3,1). */ extern void Moebius_array_to_O31_array( MoebiusTransformation arrayA[], O31Matrix arrayB[], int num_matrices); extern void O31_array_to_Moebius_array( O31Matrix arrayB[], MoebiusTransformation arrayA[], int num_matrices); /* * Convert arrays of matrices back and forth between SL(2,C) and O(3,1). */ extern Boolean O31_determinants_OK( O31Matrix arrayB[], int num_matrices, double epsilon); /* * Returns TRUE if all the O31Matrices in the array have determinants * within epsilon of plus or minus one, and FALSE otherwise. */ /************************************************************************/ /* */ /* matrix_generators.c */ /* */ /************************************************************************/ extern FuncResult matrix_generators( Triangulation *manifold, MoebiusTransformation generators[]); /* * Computes the MoebiusTransformations representing the action of the * generators of a manifold's fundamental group on the sphere at * infinity. Writes the MoebiusTransformations to the array * generators[], which it assumes has already been allocated. * Moreover, assumes that choose_generators has already been called * to compute the locations of the ideal vertices. You may use * get_num_generators() to determine how long an array to allocate. */ /************************************************************************/ /* */ /* my_malloc.c */ /* */ /************************************************************************/ extern void verify_my_malloc_usage(void); /* * The UI should call verify_my_malloc_usage() upon exit to verify that * the number of calls to my_malloc() was exactly balanced by the number * of calls to my_free(). In case of error, verify_my_malloc_usage() * passes an appropriate message to uAcknowledge. */ /************************************************************************/ /* */ /* normal_surface_construction.c */ /* */ /************************************************************************/ extern FuncResult find_normal_surfaces( Triangulation *manifold, NormalSurfaceList **surface_list); /* * Tries to find connected, embedded normal surfaces of nonnegative * Euler characteristic. If spheres or projective planes are found, * then tori and Klein bottles aren't reported, because from the point * of view of the Geometrization Conjecture, one wants to cut along * spheres and projective planes first. Surfaces are guaranteed to be * connected. They aren't guaranteed to be incompressible, although * typically they are. There is no guarantee that all such normal * surfaces will be found. Returns its result as a pointer to a * NormalSurfaceList, the internal structure of which is private to * the kernel. To get information about the normal surfaces on the list, * use the functions below. To split along a normal surface, call * split_along_normal_surface(). When you're done with the * NormalSurfaceList, free it using free_normal_surfaces(). * * The present implementation works only for cusped manifolds. * Returns func_bad_input for closed manifolds, or non-manifolds. */ extern int number_of_normal_surfaces_on_list( NormalSurfaceList *surface_list); /* * Returns the number of normal surfaces contained in the list. */ extern Boolean normal_surface_is_orientable( NormalSurfaceList *surface_list, int index); extern Boolean normal_surface_is_two_sided( NormalSurfaceList *surface_list, int index); extern int normal_surface_Euler_characteristic( NormalSurfaceList *surface_list, int index); /* * Return information about a given normal surface on the list. * The indices run from 0 through (number of surfaces - 1). */ extern void free_normal_surfaces(NormalSurfaceList *surface_list); /* * Frees an array of NormalSurfaceLists. */ /************************************************************************/ /* */ /* normal_surface_splitting.c */ /* */ /************************************************************************/ extern FuncResult split_along_normal_surface( NormalSurfaceList *surface_list, int index, Triangulation *pieces[2]); /* * Splits the manifold (stored privately in the NormalSurfaceList) * along the normal surface of the given index (indices range from 0 to * (number of surfaces - 1)). If the normal surface is a 2-sided * projective plane, split_along_normal_surface() returns func_bad_input; * otherwise it returns func_OK. If the normal surface is a sphere or * 1-sided projective plane, the resulting spherical boundary component(s) * are capped off with 3-ball(s); otherwise the new torus or Klein bottle * boundary component(s) become cusp(s). If the normal surface is * nonseparating, the result is returned in pieces[0], and pieces[1] * is set to NULL. If the normal surface is separating, the two pieces * are returned in pieces[0] and pieces[1]. */ /************************************************************************/ /* */ /* o31_matrices.c */ /* */ /************************************************************************/ /* * Most of the functions in o31_matrices.c are private to the kernel. * The following have been made available to the UI as well. */ extern double gl4R_determinant(GL4RMatrix m); extern double o31_trace(O31Matrix m); /************************************************************************/ /* */ /* orient.c */ /* */ /************************************************************************/ extern void reorient(Triangulation *manifold); /* * Reverse a manifold's orientation. */ /************************************************************************/ /* */ /* punctured_torus_bundles.c */ /* */ /************************************************************************/ extern void bundle_LR_to_monodromy( LRFactorization *anLRFactorization, MatrixInt22 aMonodromy); /* * Multiplies out anLRFactorization to obtain aMonodromy. */ extern void bundle_monodromy_to_LR( MatrixInt22 aMonodromy, LRFactorization **anLRFactorization); /* * If (det(aMonodromy) = +1 and |trace(aMonodromy)| >= 2) or * (det(aMonodromy) = -1 and |trace(aMonodromy)| >= 1), * then bundle_monodromy_to_LR() conjugates aMonodromy to a * nonnegative or nonpositive matrix, and factors it as * anLRFactorization. These cases include all monodromies of * hyperbolic manifolds, as well as the nonhyperbolic cases * (det(aMonodromy) = +1 and |trace(aMonodromy)| = 2), which * the user might want to see factored just for fun. * Otherwise bundle_monodromy_to_LR() sets * (*anLRFactorization)->is_available to FALSE, but nevertheless * sets negative_determinant and negative_trace correctly in case * the UI wants to display them. The UI should indicate that the * factorization is not available (e.g. by displaying "N/A") so * the user doesn't confuse this case with an empty factorization. */ extern LRFactorization *alloc_LR_factorization(int aNumFactors); extern void free_LR_factorization(LRFactorization *anLRFactorization); /* * Allocates/frees LRFactorizations. */ extern Triangulation *triangulate_punctured_torus_bundle( LRFactorization *anLRFactorization); /* * If the manifold is hyperbolic (i.e. if the number of LR factors * is at least two for an orientable bundle, or at least one for a * nonorientable bundle), triangulates the complement and returns * a pointer to it. Otherwise returns NULL. */ /************************************************************************/ /* */ /* rehydrate_census.c */ /* */ /************************************************************************/ extern void rehydrate_census_manifold( TersestTriangulation tersest, int which_census, int which_manifold, Triangulation **manifold); /* * Rehydrates a census manifold from a tersest description, resolving * any ambiguities in the choice of peripheral curves for the cusps. */ /************************************************************************/ /* */ /* representations.c */ /* */ /************************************************************************/ RepresentationList *find_representations( Triangulation *manifold, int n, PermutationSubgroup range); /* * Finds all transitive representations of a manifold's fundamental * group into Z/n or S(n), for use in constructing n-sheeted covers. * To dispose of the RepresentationList when you're done, use * free_representation_list() below. */ void free_representation_list( RepresentationList *representation_list); /* * Frees a RepresentationList. */ /* MC: The next two declarations would not have to be public, if the kernel provided a function that built a representation from permutation data. */ RepresentationIntoSn *initialize_new_representation( int num_original_generators, int n, int num_cusps); /* * Initializes a RepresentationIntoSn structure. (Added by MC 01/27/08) */ RepresentationIntoSn *convert_candidateSn_to_original_generators( int **candidateSn, int n, int num_original_generators, int **original_generators, Triangulation *manifold, int **meridians, int **longitudes); /* * This should be private to the kernel. (Added by MC 01/27/08) */ void free_representation( RepresentationIntoSn *representation, int num_generators, int num_cusps); /* * Frees a RepresentationIntoSn. (Added by MC 01/27/08) */ Boolean candidateSn_is_valid( int **candidateSn, int n, int **group_relations, int num_relations); /* * Does the candidate representation satisfy all of the relations? */ Boolean candidateSn_is_transitive( int **candidateSn, int num_generators, int n); /* * Is the candidate representation transitive? */ /************************************************************************/ /* */ /* shingling.c */ /* */ /************************************************************************/ extern Shingling *make_shingling(WEPolyhedron *polyhedron, int num_layers); /* * Constructs the shingling defined by the given Dirichlet domain. * Please see the top of shingling.c for detailed documentation. */ extern void free_shingling(Shingling *shingling); /* * Releases the memory occupied by the shingling. */ extern void compute_center_and_radials( Shingle *shingle, O31Matrix position, double scale); /* * Uses shingle->normal along with the given position and scale to * compute shingle->center, single->radialA and shingle->radialB. */ /************************************************************************/ /* */ /* shortest_cusp_basis.c */ /* */ /************************************************************************/ extern Complex cusp_modulus(Complex cusp_shape); /* * Accepts a cusp_shape (longitude/meridian) and returns the cusp modulus. * Loosely speaking, the cusp modulus is defined as * (second shortest translation)/(shortest translation); it is a complex * number z lying in the region |Re(z)| <= 1/2 && |z| >= 1. If z lies * on the boundary of this region, we choose it so that Re(z) >= 0. */ extern void shortest_cusp_basis( Complex cusp_shape, MatrixInt22 basis_change); /* * Accepts a cusp_shape (longitude/meridian) and computes the 2 x 2 integer * matrix which transforms the old basis (u, v) = (meridian, longitude) * to the new basis (u', v') = (shortest, second shortest). */ extern Complex transformed_cusp_shape( Complex cusp_shape, CONST MatrixInt22 basis_change); /* * Accepts a cusp_shape and a basis_change, and computes the shape of the * cusp relative to the transformed basis. The transformed basis may or * may not be the (shortest, second shortest) basis. */ extern void install_shortest_bases( Triangulation *manifold); /* * Installs the (shortest, second shortest) basis on each torus Cusp * of manifold, but does not change the bases on Klein bottle cusps. */ /************************************************************************/ /* */ /* simplify_triangulation.c */ /* */ /************************************************************************/ extern void basic_simplification(Triangulation *manifold); /* * Simplifies the triangulation in a speedy yet effective manner. */ extern void randomize_triangulation(Triangulation *manifold); /* * Randomizes the Triangulation, and then resimplifies it. */ /************************************************************************/ /* */ /* sl2c_matrices.c */ /* */ /************************************************************************/ /* * Most of the functions in sl2c_matrices.c are private to the kernel. * The following has been made available to the UI as well. */ extern Complex sl2c_determinant(CONST SL2CMatrix m); /* * Returns the determinant of m. */ /************************************************************************/ /* */ /* symmetry_group.c */ /* */ /************************************************************************/ extern FuncResult compute_symmetry_group( Triangulation *manifold, SymmetryGroup **symmetry_group_of_manifold, SymmetryGroup **symmetry_group_of_link, Triangulation **symmetric_triangulation, Boolean *is_full_group); /* * Computes the SymmetryGroup of a closed or cusped manifold. * If the manifold is cusped, also computes the SymmetryGroup of the * corresponding link (defined at the top of symmetry_group_cusped.c). */ extern void free_symmetry_group(SymmetryGroup *symmetry_group); /* * Frees a SymmetryGroup. */ /************************************************************************/ /* */ /* symmetry_group_info.c */ /* */ /************************************************************************/ extern Boolean symmetry_group_is_abelian( SymmetryGroup *symmetry_group, AbelianGroup **abelian_description); /* * If the SymmetryGroup is abelian, sets *abelian_description to point * to the SymmetryGroup's description as an AbelianGroup, and returns TRUE. * Otherwise sets *abelian_description to NULL and returns FALSE. */ extern Boolean symmetry_group_is_dihedral(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is dihedral, FALSE otherwise. */ extern Boolean symmetry_group_is_polyhedral(SymmetryGroup *symmetry_group, Boolean *is_full_group, int *p, int *q, int *r); /* * Returns TRUE if the SymmetryGroup is polyhedral, FALSE otherwise. * If the SymmetryGroup is polyhedral, reports whether it's the full group * (binary polyhedral, not just plain polyhedral), and reports the values * for (p,q,r). The pointers for is_full_group, p, q and r may be NULL * if this information is not desired. */ extern Boolean symmetry_group_is_S5(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is the symmetric group on 5 letters, * FALSE otherwise. */ extern Boolean symmetry_group_is_direct_product(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup is a nontrivial, nonabelian direct * product, FALSE otherwise. */ extern SymmetryGroup *get_symmetry_group_factor(SymmetryGroup *symmetry_group, int factor_number); /* * If the SymmetryGroup is a nontrivial, nonabelian direct product, * returns a pointer to factor "factor_number" (factor_number = 0 or 1). * Otherwise returns NULL. This is a pointer to the internal data * structure -- not a copy! -- so please don't free it. */ extern Boolean symmetry_group_is_amphicheiral(SymmetryGroup *symmetry_group); /* * Returns TRUE if the SymmetryGroup contains orientation-reversing * elements, FALSE otherwise. Assumes the underlying manifold is oriented. */ extern Boolean symmetry_group_invertible_knot(SymmetryGroup *symmetry_group); /* * Assumes the underlying manifold is oriented and has exactly * one Cusp. Returns TRUE if some Symmetry acts on the Cusp * via the matrix (-1, 0; 0, -1); returns FALSE otherwise. */ extern int symmetry_group_order(SymmetryGroup *symmetry_group); /* * Returns the order of the SymmetryGroup. */ extern int symmetry_group_product(SymmetryGroup *symmetry_group, int i, int j); /* * Returns the product of group elements i and j. We use the * convention that products of symmetries read right to left. * That is, the composition symmetry[i] o symmetry[j] acts by * first doing symmetry[j], then symmetry[i]. */ extern int symmetry_group_order_of_element(SymmetryGroup *symmetry_group, int i); /* * Returns the order of group element i. */ extern IsometryList *get_symmetry_list(SymmetryGroup *symmetry_group); /* * Returns the list of "raw" Isometries comprising a SymmetryGroup. */ extern SymmetryGroup *get_commutator_subgroup(SymmetryGroup *symmetry_group); extern SymmetryGroup *get_abelianization (SymmetryGroup *symmetry_group); /* * Compute the commutator subgroup [G,G] and the abelianization G/[G,G]. * The UI should eventually use free_symmetry_group() to free them. */ extern SymmetryGroup *get_center(SymmetryGroup *symmetry_group); /* * Computes the center of G, which is the subgroup consisting of * elements which commute with all elements in G. * The UI should eventually use free_symmetry_group() to free it. */ extern SymmetryGroupPresentation *get_symmetry_group_presentation( SymmetryGroup *symmetry_group); /* * Returns a presentation for the given SymmetryGroup. * The internal structure of the SymmetryGroupPresentation is private * to the kernel; use the functions below to get information about it. * When you're done with it, use free_symmetry_group_presentation() * to free the storage. */ extern int sg_get_num_generators(SymmetryGroupPresentation *group); /* * Returns the number of generators in the SymmetryGroupPresentation. */ extern int sg_get_num_relations(SymmetryGroupPresentation *group); /* * Returns the number of relations in the SymmetryGroupPresentation. */ extern int sg_get_num_factors( SymmetryGroupPresentation *group, int which_relation); /* * Returns the number of factors in the specified relation. * For example, the relation a^3 * b^-2 * c^5 has three factors. * The parameter which_relation uses 0-based indexing. */ extern void sg_get_factor( SymmetryGroupPresentation *group, int which_relation, int which_factor, int *generator, int *power); /* * Reports the generator and power of the specified factor in the * specified relation. For example, if relation 1 (i.e. the second * relation) is a^3 * b^-2 * c^5, then passing which_relation = 1 and * which_factor = 2 will cause it to report *generator = 2 and * *power = 5. */ extern void free_symmetry_group_presentation(SymmetryGroupPresentation *group); /* * Frees the storage occupied by a SymmetryGroupPresentation. */ /************************************************************************/ /* */ /* terse_triangulation.c */ /* */ /************************************************************************/ extern TerseTriangulation *tri_to_terse(Triangulation *manifold); extern TerseTriangulation *tri_to_canonical_terse( Triangulation *manifold, Boolean respect_orientation); /* * tri_to_terse() accepts a pointer to a Triangulation, computes * a corresponding TerseTriangulation, and returns a pointer to it. * tri_to_canonical_terse() is similar, but chooses the * TerseTriangulation which is "least" among all possible choices * of base Tetrahedron and base Permutation. */ extern Triangulation *terse_to_tri(TerseTriangulation *tt); /* * Accepts a pointer to a TerseTriangulation, expands it to a full * Triangulation, and returns a pointer to it. */ extern void free_terse_triangulation(TerseTriangulation *tt); /* * Releases the memory used to store a TerseTriangulation. */ /************************************************************************/ /* */ /* tersest_triangulation.c */ /* */ /************************************************************************/ extern void terse_to_tersest( TerseTriangulation *terse, TersestTriangulation tersest); /* * Converts a TerseTriangulation to a TersestTriangulation. */ extern void tersest_to_terse( TersestTriangulation tersest, TerseTriangulation **terse); /* * Converts a TersestTriangulation to a TerseTriangulation. * Allocates space for the result. */ extern void tri_to_tersest( Triangulation *manifold, TersestTriangulation tersest); /* * Composes tri_to_terse() and terse_to_tersest(). */ extern void tersest_to_tri( TersestTriangulation tersest, Triangulation **manifold); /* * Composes tersest_to_terse() and terse_to_tri(). */ /************************************************************************/ /* */ /* triangulations.c */ /* */ /************************************************************************/ extern void data_to_triangulation( TriangulationData *data, Triangulation **manifold_ptr); /* * Uses the TriangulationData (defined in triangulation_io.h) to * construct a Triangulation. Sets *manifold_ptr to point to the * Triangulation, or to NULL if it fails. */ extern void triangulation_to_data( Triangulation *manifold, TriangulationData **data_ptr); /* * Allocates the TriangulationData and writes in the data describing * the manifold. Sets *data_ptr to point to the result. The UI * should call free_triangulation_data() when it's done with the * TriangulationData. */ extern void free_triangulation_data(TriangulationData *data); /* * If the UI lets the kernel allocate a TriangulationData structure * (as in a call to triangulation_to_data()), then the UI should * call free_triangulation_data() to release it. * If the UI allocates its own TriangulationData structure (as in * preparing for a call to data_to_triangulation()), then the UI * should release the structure itself. */ extern void free_triangulation(Triangulation *manifold); /* * If manifold != NULL, frees up the storage associated with a * triangulation structure. * If manifold == NULL, does nothing. */ extern void copy_triangulation(Triangulation *source, Triangulation **destination); /* * Makes a copy of the Triangulation *source. */ /************************************************************************/ /* */ /* two_bridge.c */ /* */ /************************************************************************/ extern void two_bridge( Triangulation *manifold, Boolean *is_two_bridge, long int *p, long int *q); /* * Checks whether *manifold is the (conjectured) canonical triangulation * of a 2-bridge knot or link complement. If it is, sets *is_two_bridge * to TRUE and writes the fraction p/q describing the knot or link into * (*p)/(*q). If it's not, sets *is_two_bridge to FALSE and leaves *p * and *q undefined. */ /************************************************************************/ /* */ /* volume.c */ /* */ /************************************************************************/ extern double volume(Triangulation *manifold, int *precision); /* * Computes and returns the volume of the manifold. * If the pointer "precision" is not NULL, estimates the number * of decimal places of accuracy, and places the result in the * variable *precision. */ #ifdef __cplusplus } #endif #endif regina-4.95/engine/snappea/kernel/solve_equations.c000644 000765 000024 00000027314 12235724562 022366 0ustar00babstaff000000 000000 /* * solve_equations.c * * This file provides the functions * * FuncResult solve_complex_equations(Complex **complex_equations, * int num_rows, int num_columns, Complex *solution); * FuncResult solve_real_equations(double **real_equations, * int num_rows, int num_columns, double *solution); * * which do_Dehn_filling() in hyperbolic_structure.c calls to * solve num_rows linear equations in num_columns variables. Gaussian * elimination with partial pivoting is used. * * The equations are stored as an array of num_rows pointers, each * of which points to an array of (num_columns + 1) entries. The * last entry in each row is the constant on the right hand side of * the equation. * * num_rows is assumed to be greater than or equal to num_columns. * The equations are assumed to have rank exactly equal to num_columns. * Thus, even though there may be more equations than variables, the * equations are assumed to be consistent. * * Even though these routines make no assumption about the origin or * purpose of the equations, their main use is, of course, to find * hyperbolic structures. My hope in including all the equations * (rather than just a linearly independent subset) is that we will * get more accurate solutions, particularly in degenerate or * nearly degenerate situations. * * These functions assume an array of num_columns elements has already * been allocated for the solution. * * Technical detail: While doing the Gaussian elimination, these functions * do not actually compute or write values which are guaranteed to be one * or zero, but they knows where they are. So don't worry that in the end * the matrix does not contain all ones and zeros; the matrix will be a * mess, but the solution will be correct. */ #include "kernel.h" FuncResult solve_complex_equations( Complex **complex_equations, int num_rows, int num_columns, Complex *solution) { /* * The following register variables are used in the n^3 bottleneck. * (See below.) */ register double factor_real, factor_imag; register Complex *row_r, *row_c; register int count; /* * The remaining variables are used in less critical places. */ int r, c, cc, pivot_row = -1; double max_modulus, this_modulus, max_error, error; Complex *temp, factor; /* * Forward elimination. */ for (c = 0; c < num_columns; c++) { /* * Find the pivot row. */ max_modulus = 0.0; for (r = c; r < num_rows; r++) { this_modulus = complex_modulus(complex_equations[r][c]); if (this_modulus > max_modulus) { max_modulus = this_modulus; pivot_row = r; } } if (max_modulus == 0.0) /* In the old snappea, max_modulus */ return func_failed; /* was was never below 1e-100, even */ /* in degenerate cases. */ /* * Swap the pivot row into position. */ temp = complex_equations[c]; complex_equations[c] = complex_equations[pivot_row]; complex_equations[pivot_row] = temp; /* * Multiply the pivot row through by 1.0/(pivot value). */ factor = complex_div(One, complex_equations[c][c]); for (cc = c + 1; cc <= num_columns; cc++) complex_equations[c][cc] = complex_mult( factor, complex_equations[c][cc] ); /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < num_rows; r++) { /* * The following loop is the bottleneck for computing * hyperbolic structures. It is executed n^3 times to solve * an n x n system of equations, and no other n^3 algorithms * are used. For this reason, I've written the loop to * maximize speed at the expense of readability. * * Here's the loop in pseudocode: * for (cc = c + 1; cc <= num_columns; cc++) complex_equations[r][cc] -= complex_equations[r][c] * complex_equations[c][cc] * * Here's a version that will actually run: * for (cc = c + 1; cc <= num_columns; cc++) complex_equations[r][cc] = complex_minus( complex_equations[r][cc], complex_mult( complex_equations[r][c], complex_equations[c][cc] ) ); * * And here's the fancy, built-for-speed version: */ factor_real = - complex_equations[r][c].real; factor_imag = - complex_equations[r][c].imag; if (factor_real || factor_imag) { row_r = complex_equations[r] + c + 1; row_c = complex_equations[c] + c + 1; for (count = num_columns - c; --count >= 0; ) { if (row_c->real || row_c->imag) { row_r->real += factor_real * row_c->real - factor_imag * row_c->imag; row_r->imag += factor_real * row_c->imag + factor_imag * row_c->real; } row_r++; row_c++; } } /* * With all the THINK C compiler's optimization options on, * the fancy version runs 8 times faster than the plain * version. With all THINK C optimization options off, * it runs 9 times faster. The THINK C optimizer increases * the speed of the fancy code by only 6%. */ /* * Yield some time to the window system, and check * whether the user has cancelled this computation. */ if (uLongComputationContinues() == func_cancelled) return func_cancelled; } } /* * Back substitution. */ for (c = num_columns; --c > 0; ) /* Do columns (num_columns - 1) to 1, */ /* but skip column 0. */ for (r = c; --r >= 0; ) /* Do rows (c - 1) to 0. */ complex_equations[r][num_columns] = complex_minus( complex_equations[r][num_columns], complex_mult( complex_equations[r][c], complex_equations[c][num_columns] ) ); /* * Check "extra" rows for consistency. * That is, in each of the last (num_rows - num_columns) rows, * check that the constant on the right hand side is zero. * This will give us a measure of the accuracy of the solution. * I still haven't decided what to do with this number. */ max_error = 0.0; for (r = num_columns; r < num_rows; r++) { error = complex_modulus(complex_equations[r][num_columns]); if (error > max_error) max_error = error; } /* * Record the solution. */ for (r = 0; r < num_columns; r++) solution[r] = complex_equations[r][num_columns]; return func_OK; } FuncResult solve_real_equations( double **real_equations, int num_rows, int num_columns, double *solution) { /* * The following register variables are used in the n^3 bottleneck. * (See below.) */ register double factor, *row_r, *row_c; register int count; /* * The remaining variables are used in less critical places. */ int r, c, cc, pivot_row = -1; double max_abs, this_abs, max_error, error, *temp; /* * Forward elimination. */ for (c = 0; c < num_columns; c++) { /* * Find the pivot row. */ max_abs = 0.0; for (r = c; r < num_rows; r++) { this_abs = fabs(real_equations[r][c]); if (this_abs > max_abs) { max_abs = this_abs; pivot_row = r; } } if (max_abs == 0.0) return func_failed; /* * Swap the pivot row into position. */ temp = real_equations[c]; real_equations[c] = real_equations[pivot_row]; real_equations[pivot_row] = temp; /* * Multiply the pivot row through by 1.0/(pivot value). */ factor = 1.0 / real_equations[c][c]; for (cc = c + 1; cc <= num_columns; cc++) real_equations[c][cc] *= factor; /* * Eliminate the entries in column c which lie below the pivot. */ for (r = c + 1; r < num_rows; r++) { factor = - real_equations[r][c]; /* * The following loop is the bottleneck for computing * hyperbolic structures. It is executed n^3 times to solve * an n x n system of equations, and no other n^3 algorithms * are used. For this reason, I've written the loop to * maximize speed at the expense of readability. * * Here's the loop in its humanly comprehensible form: * if (factor) for (cc = c + 1; cc <= num_columns; cc++) real_equations[r][cc] += factor * real_equations[c][cc]; * * Here's the optimized version of the same thing: */ if (factor) { row_r = real_equations[r] + c + 1; row_c = real_equations[c] + c + 1; for (count = num_columns - c; --count>=0; ) *row_r++ += factor * *row_c++; } /* * Yield some time to the window system, and check * whether the user has cancelled this computation. */ if (uLongComputationContinues() == func_cancelled) return func_cancelled; } } /* * Back substitution. */ for (c = num_columns; --c > 0; ) /* Do columns (num_columns - 1) to 1, */ /* but skip column 0. */ for (r = c; --r >= 0; ) /* Do rows (c - 1) to 0. */ real_equations[r][num_columns] -= real_equations[r][c] * real_equations[c][num_columns]; /* * Check "extra" rows for consistency. * That is, in each of the last (num_rows - num_columns) rows, * check that the constant on the right hand side is zero. * This will give us a measure of the accuracy of the solution. * I still haven't decided what to do with this number. */ max_error = 0.0; for (r = num_columns; r < num_rows; r++) { error = fabs(real_equations[r][num_columns]); if (error > max_error) max_error = error; } /* * Record the solution. */ for (r = 0; r < num_columns; r++) solution[r] = real_equations[r][num_columns]; return func_OK; } regina-4.95/engine/snappea/kernel/subdivide.c000644 000765 000024 00000052606 12235724562 021126 0ustar00babstaff000000 000000 /* * subdivide.c * * This file contains the function * * Triangulation *subdivide(Triangulation *manifold, char *new_name); * * which accepts a Triangulation *manifold, copies it, and * subdivides the copy as described below into a Triangulation * with finite as well as ideal vertices. The original * triangulation is not changed. * * Triangulations produced by subdivide() differ from * ordinary triangulations in that they have finite vertices * as well as ideal vertices. * * At present, only the function fill_cusps() calls subdivide(), * but other kernel functions could use it too, if the need arises. * * Note: subdivide() has a subtle dependence on the implementation * of orient(). The first Tetrahedron on the new Triangulation's * tet list has the correct orientation, and orient() must propogate * that orientation to all the other Tetrahedra. * * The function subdivide() subdivides each Tetrahedron of a * Triangulation as follows. First, a regular neighborhood of * each ideal vertex is sliced off, to form its own new * Tetrahedron. Each such new Tetrahedron will have three finite * vertices as well as the original ideal vertex. The ideal * vertex keeps the same VertexIndex as the original, while * each of the three finite vertices inherits the VertexIndex * of the nearest (other) ideal vertex of the original Tetrahedron. * I wish I could provide a good illustration, but it's hard to * embed 3-D graphics in ASCII files; here's a 2-D illustration * which shows the general idea: * * original * vertex * #0 * * 0 /\ * / \ * / \ * / \ * / \ * 1 /__________\ 2 * / \ * / \ * / \ * / \ * / \ * / \ * 0 /\ /\ 0 * / \ / \ * / \ / \ * / \ / \ * / \ / \ * original /__________\____________/__________\ original * vertex #1 1 2 1 2 vertex #2 * * After removing the four Tetrahedra just described, you are * left with a solid which has four hexagonal faces and four * triangular faces. Please make yourself a sketch of a * truncated tetrahedron to illustrate this. Each hexagonal * face is subdivided into six triangles by coning to the * center: * * original * vertex * #0 * * .. * . . * . . * . . * . . * 1 ____________ 2 * /\ /\ * / \ / \ * / \ / \ * / \ / \ * / \ / \ * 0 /_________3\/__________\ 0 * .\ /\ /. * . \ / \ / . * . \ / \ / . * . \ / \ / . * . \ / \ / . * original . . . . . .\/__________\/. . . . . . original * vertex #1 2 1 vertex #2 * * The center vertex on each face gets the VertexIndex of the * opposite vertex of the original Tetrahedron (which equals the * FaceIndex of the face being subdivided). * Finally, the truncated tetrahedron is subdivided by coning * to the center. This creates 28 Tetrahedron (6 for each of * the four hexagonal faces, plus 1 for each of the four triangular * faces). The vertex at the center of the truncated tetrahedron * cannot be given a canonical VertexIndex common to all incident * Tetrahedra. Instead, each incident Tetrahedron assigns it * the unique index not yet used by that Tetrahedron. * * This canonical scheme for numbering the vertices makes the * calculation of the gluing Permutations very easy. "Internal" * gluings (i.e. between two new Tetrahedra belonging to same old * Tetrahedron) may be readily deduced from the above definitions. * Because the numbering scheme is canonical, all "external" gluings * (i.e. between two new Tetrahedra belonging to different old * Tetrahedra) will be the same as the corresponding gluing of the * original Tetrahedron. (Yes, I realize that the above definitions * of "internal" and "external" aren't quite right in the case of * an original Tetrahedron glued to itself, but I hope you understood * what I said anyhow.) */ #include "kernel.h" /* * If you are not familiar with SnapPea's "Extra" field in * the Tetrahedron data structure, please see the explanation * preceding the Extra typedef in kernel_typedefs.h. * * Subdivide() uses an Extra field in each Tetrahedron of the * old_triangulation to keep track of the 32 corresponding * Tetrahedra in the new_triangulation. Please see the * documentation above for an explanation of the subdivision * algorithm. If you drew a sketch illustrating the subdivision, * it will be helpful in understanding the fields of the Extra struct. */ struct extra { /* * The first four Tetrahedra lopped off in the above * algorithm will be called "outer vertex Tetrahedra". * outer_vertex_tet[i] is a pointer to the outer vertex * Tetrahedron at vertex i of the old Tetrahedron. */ Tetrahedron *outer_vertex_tet[4]; /* * The "inner vertex Tetrahedra" are the remaining * Tetrahedra which are naturally associated to the * ideal vertices of the old Tetrahedron. * inner_vertex_tet[i] is a pointer to the new Tetrahedron * which meets outer_vertex_tet[i] along it's i-th face. */ Tetrahedron *inner_vertex_tet[4]; /* * The "edge Tetrahedra" are the 12 Tetrahedra which have * precisely one edge contained within an edge of the * old Tetrahedron. edge_tet[i][j] is the Tetrahedron * which has a face contained in face i of the old * Tetrahedron, on the side (of face i) opposite vertex j. * edge_tet[i][j] is defined iff i != j. */ Tetrahedron *edge_tet[4][4]; /* * The "face Tetrahedra" are the 12 remaining Tetrahedra. * face_tet[i][j] has a face contained in face i of the * old Tetrahedron, on the side near vertex j (of the old * Tetrahedron). face_tet[i][j] is defined iff i != j. */ Tetrahedron *face_tet[4][4]; }; static void attach_extra(Triangulation *manifold); static void free_extra(Triangulation *manifold); static void create_new_tetrahedra(Triangulation *new_triangulation, Triangulation *old_triangulation); static void allocate_new_tetrahedra(Triangulation *new_triangulation, Triangulation *old_triangulation); static void set_outer_vertex_tets(Tetrahedron *old_tet); static void set_inner_vertex_tets(Tetrahedron *old_tet); static void set_edge_tets(Tetrahedron *old_tet); static void set_face_tets(Tetrahedron *old_tet); static void create_new_cusps(Triangulation *new_triangulation, Triangulation *old_triangulation); static void create_real_cusps(Triangulation *new_triangulation, Triangulation *old_triangulation); Triangulation *subdivide( Triangulation *old_triangulation, char *new_name) { Triangulation *new_triangulation; /* * Allocate storage for the new_triangulation * and initialize it. * * (In spite of what I wrote at the top of this file, * we don't explicitly make a copy of *old_triangulation, * but instead we create *new_triangulation from scratch.) */ new_triangulation = NEW_STRUCT(Triangulation); initialize_triangulation(new_triangulation); new_triangulation->name = NEW_ARRAY(strlen(new_name) + 1, char); strcpy(new_triangulation->name, new_name); new_triangulation->num_tetrahedra = 32 * old_triangulation->num_tetrahedra; new_triangulation->num_cusps = old_triangulation->num_cusps; new_triangulation->num_or_cusps = old_triangulation->num_or_cusps; new_triangulation->num_nonor_cusps = old_triangulation->num_nonor_cusps; /* * Attach an Extra field to each Tetrahedron in the * old_triangulation, to keep track of the corresponding * 32 Tetrahedra in the new_triangulation. */ attach_extra(old_triangulation); /* * Create the new Tetrahedra. * Set fields such as tet->neighbor and tet->gluing, * which can be determined immediately. * Postpone determination of fields such as tet->cusp * and tet->edge_class. */ create_new_tetrahedra(new_triangulation, old_triangulation); /* * Copy the Cusps from the old_triangulation to * the new_triangulation. (Technical note: functions * called by create_new_cusps() rely on the fact that * create_new_tetrahedra() initializes all tet->cusp * fields to NULL.) */ create_new_cusps(new_triangulation, old_triangulation); /* * We're done with the Extra fields, so free them. */ free_extra(old_triangulation); /* * Add the bells and whistles. */ create_edge_classes(new_triangulation); orient_edge_classes(new_triangulation); orient(new_triangulation); /* * Return the new_triangulation. */ return new_triangulation; } static void attach_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Make sure no other routine is using the "extra" * field in the Tetrahedron data structure. */ if (tet->extra != NULL) uFatalError("attach_extra", "filling"); /* * Attach the locally defined struct extra. */ tet->extra = NEW_STRUCT(Extra); } } static void free_extra( Triangulation *manifold) { Tetrahedron *tet; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * Free the struct extra. */ my_free(tet->extra); /* * Set the extra pointer to NULL to let other * modules know we're done with it. */ tet->extra = NULL; } } static void create_new_tetrahedra( Triangulation *new_triangulation, Triangulation *old_triangulation) { Tetrahedron *old_tet; /* * Allocate the memory for all the new Tetrahedra. * We do this before setting any of the fields, so * that the tet->neighbor fields will all have something * to point to. */ allocate_new_tetrahedra(new_triangulation, old_triangulation); /* * For each Tetrahedron in the old_triangulation, we want * to set the various fields of the 32 corresponding Tetrahedra * in the new triangulation. The new Tetrahedra are accessed * through the Extra fields of the old Tetrahedra. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) { set_outer_vertex_tets(old_tet); set_inner_vertex_tets(old_tet); set_edge_tets(old_tet); set_face_tets(old_tet); } } static void allocate_new_tetrahedra( Triangulation *new_triangulation, Triangulation *old_triangulation) { int i, j; Tetrahedron *old_tet, *new_tet; /* * For each Tetrahedron in the old_triangulation, we want * to allocate and initialize the 32 corresponding Tetrahedra * in the new_triangulation. */ /* * IMPORTANT: It's crucial that one of the outer vertex tetrahedra * appears first on new_triangulation's tet list, so that orient() * will preserve the manifold's original orientation. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) { for (i = 0; i < 4; i++) { new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->outer_vertex_tet[i] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) { new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->inner_vertex_tet[i] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { if (i == j) { old_tet->extra->edge_tet[i][j] = NULL; continue; } new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->edge_tet[i][j] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { if (i == j) { old_tet->extra->face_tet[i][j] = NULL; continue; } new_tet = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(new_tet); old_tet->extra->face_tet[i][j] = new_tet; INSERT_BEFORE(new_tet, &new_triangulation->tet_list_end); } } } static void set_outer_vertex_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the four outer_vertex_tets. */ for (i = 0; i < 4; i++) { /* * For notational clarity, let new_tet be a pointer * to the outer_vertex_tet under consideration. */ new_tet = old_tet->extra->outer_vertex_tet[i]; /* * Set the new_tet's four neighbor fields. */ for (j = 0; j < 4; j++) new_tet->neighbor[j] = (i == j) ? old_tet->extra->inner_vertex_tet[i] : old_tet->neighbor[j]->extra->outer_vertex_tet[EVALUATE(old_tet->gluing[j], i)]; /* * Set the new_tet's four gluing fields. */ for (j = 0; j < 4; j++) new_tet->gluing[j] = (i == j) ? IDENTITY_PERMUTATION : old_tet->gluing[j]; /* * Copy the peripheral curves from the old_tet to the ideal * vertex of the new_tet. The peripheral curves of * finite vertices have already been set to zero. */ for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) new_tet->curve[j][k][i][l] = old_tet->curve[j][k][i][l]; } } static void set_inner_vertex_tets( Tetrahedron *old_tet) { int i, j; Tetrahedron *new_tet; /* * Set the fields for each of the four inner_vertex_tets. */ for (i = 0; i < 4; i++) { /* * For notational clarity, let new_tet be a pointer * to the inner_vertex_tet under consideration. */ new_tet = old_tet->extra->inner_vertex_tet[i]; /* * Set the new_tet's four neighbor fields. */ for (j = 0; j < 4; j++) new_tet->neighbor[j] = (i == j) ? old_tet->extra->outer_vertex_tet[i] : old_tet->extra->face_tet[j][i]; /* * Set the new_tet's four gluing fields. */ for (j = 0; j < 4; j++) new_tet->gluing[j] = IDENTITY_PERMUTATION; } } static void set_edge_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the twelve edge_tets. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { /* * Only the case i != j is meaningful. */ if (i == j) continue; /* * For notational clarity, let new_tet be a pointer * to the edge_tet under consideration. */ new_tet = old_tet->extra->edge_tet[i][j]; /* * Let i, j, k and l be the VertexIndices of * new_tet. i and j are already defined as * loop variables. We define k and l here. */ k = remaining_face[i][j]; l = remaining_face[j][i]; /* * Set the new_tet's four neighbor fields. */ new_tet->neighbor[i] = old_tet->extra->edge_tet[j][i]; new_tet->neighbor[j] = old_tet->neighbor[i]->extra->edge_tet[EVALUATE(old_tet->gluing[i], i)][EVALUATE(old_tet->gluing[i], j)]; new_tet->neighbor[k] = old_tet->extra->face_tet[i][k]; new_tet->neighbor[l] = old_tet->extra->face_tet[i][l]; /* * Set the new_tet's four gluing fields. */ new_tet->gluing[i] = CREATE_PERMUTATION(i,j,j,i,k,k,l,l); new_tet->gluing[j] = old_tet->gluing[i]; new_tet->gluing[k] = CREATE_PERMUTATION(i,i,j,k,k,j,l,l); new_tet->gluing[l] = CREATE_PERMUTATION(i,i,j,l,k,k,l,j); } } static void set_face_tets( Tetrahedron *old_tet) { int i, j, k, l; Tetrahedron *new_tet; /* * Set the fields for each of the twelve face_tets. */ for (i = 0; i < 4; i++) for (j = 0; j < 4; j++) { /* * Only the case i != j is meaningful. */ if (i == j) continue; /* * For notational clarity, let new_tet be a pointer * to the face_tet under consideration. */ new_tet = old_tet->extra->face_tet[i][j]; /* * Let i, j, k and l be the VertexIndices of * new_tet. i and j are already defined as * loop variables. We define k and l here. */ k = remaining_face[i][j]; l = remaining_face[j][i]; /* * Set the new_tet's four neighbor fields. */ new_tet->neighbor[i] = old_tet->extra->inner_vertex_tet[j]; new_tet->neighbor[j] = old_tet->neighbor[i]->extra->face_tet[EVALUATE(old_tet->gluing[i], i)][EVALUATE(old_tet->gluing[i], j)]; new_tet->neighbor[k] = old_tet->extra->edge_tet[i][k]; new_tet->neighbor[l] = old_tet->extra->edge_tet[i][l]; /* * Set the new_tet's four gluing fields. */ new_tet->gluing[i] = IDENTITY_PERMUTATION; new_tet->gluing[j] = old_tet->gluing[i]; new_tet->gluing[k] = CREATE_PERMUTATION(i,i,j,k,k,j,l,l); new_tet->gluing[l] = CREATE_PERMUTATION(i,i,j,l,k,k,l,j); } } static void create_new_cusps( Triangulation *new_triangulation, Triangulation *old_triangulation) { /* * The Cusp data structures for the real cusps are * handled separately from the Cusp data structures * for the finite vertices. */ create_real_cusps(new_triangulation, old_triangulation); create_fake_cusps(new_triangulation); } static void create_real_cusps( Triangulation *new_triangulation, Triangulation *old_triangulation) { Cusp *old_cusp, *new_cusp; Tetrahedron *old_tet; int i; /* * The Cusp data structures for the ideal vertices * in the new_triangulation are essentially just * copied from those in the old_triangulation. */ /* * Allocate memory for the new Cusps, copy in the * values from the old cusps, and put them on the * queue. */ for (old_cusp = old_triangulation->cusp_list_begin.next; old_cusp != &old_triangulation->cusp_list_end; old_cusp = old_cusp->next) { new_cusp = NEW_STRUCT(Cusp); *new_cusp = *old_cusp; new_cusp->is_finite = FALSE; INSERT_BEFORE(new_cusp, &new_triangulation->cusp_list_end); old_cusp->matching_cusp = new_cusp; } /* * Set the cusp field for ideal vertices in * the new_triangulation. */ for (old_tet = old_triangulation->tet_list_begin.next; old_tet != &old_triangulation->tet_list_end; old_tet = old_tet->next) for (i = 0; i < 4; i++) old_tet->extra->outer_vertex_tet[i]->cusp[i] = old_tet->cusp[i]->matching_cusp; } regina-4.95/engine/snappea/kernel/symmetry_group.h000644 000765 000024 00000010142 12235724571 022247 0ustar00babstaff000000 000000 /* * symmetry_group.h * * This file defines a general, not necessarily abelian group * for use in computing symmetry groups of manifolds. */ #ifndef _symmetry_group_ #define _symmetry_group_ #include "kernel.h" #include "isometry.h" /* * Symmetries are just Isometries from a manifold to itself. */ typedef Isometry Symmetry; typedef IsometryList SymmetryList; /* * The file SnapPea.h contains the "opaque typedef" * * typedef struct SymmetryGroup SymmetryGroup; * * which lets the UI declare and pass pointers to SymmetryGroups without * actually knowing what they are. This file provides the kernel with * the actual definition. */ /* * Group elements are represented by integers, beginning with 0. * By convention, 0 will always be the identity element. */ struct SymmetryGroup { /* * The order of the group. */ int order; /* * We want to keep the actual Symmetries around * to compute their fixed point sets, their action * on the cusps, etc. */ SymmetryList *symmetry_list; /* * product[][] is the multiplication table for the group. * That is, product[i][j] is the product of elements i and j. * * We compose Symmetries from right to left, so the product BA * is what you get by first doing Symmetry A, then Symmetry B. */ int **product; /* * order_of_element[i] is the order of element i. */ int *order_of_element; /* * inverse[i] is the inverse of element i. */ int *inverse; /* * Is this a cyclic group? * * If so the elements will be ordered as consecutive * powers of a generator. */ Boolean is_cyclic; /* * Is this a dihedral group? * * If so, the elements will be ordered as * I, R, R^2, . . . , R^(n-1), F, FR, . . . , FR^(n-1). */ Boolean is_dihedral; /* * Is this a spherical triangle group? * That is, is it one of the groups * * group (p, q, r) * * (binary) dihedral (2, 2, n) * (binary) tetrahedral (2, 3, 3) * (binary) octahedral (2, 3, 4) * (binary) icosahedral (2, 3, 5) * * If so, p, q and r will record the angles of the triangle * (pi/p, pi/q and pi/r), and is_binary_group will record * whether it's the binary polyhedral group as opposed to * the plain polyhedral group. * * When is_polyhedral is FALSE, p, q, r and is_binary_group * are undefined. */ Boolean is_polyhedral; Boolean is_binary_group; int p, q, r; /* * Is this the symmetric group S5? * (Eventually I'll write a more general treatment of symmetric * and alternating groups, but I want to get this much in place * before the Georgia conference so SnapPea can (I hope) recognize * the symmetry group of the totally symmetric 5-cusp manifold.) */ Boolean is_S5; /* * Is this an abelian group? * * If so, the elements will be ordered in a natural way, * and abelian_description will point to a description * of the group. * * If not, abelian_description will be set to NULL. */ Boolean is_abelian; AbelianGroup *abelian_description; /* * Is this group a nonabelian, nontrivial direct product? * * If so, factor[0] and factor[1] will point to the two * factors. Of course, each factor may itself be a nontrivial * direct product, and so on, leading to a tree structure * for the factorization. * * The symmetry_list field is defined only at the root level, * but all other fields are defined at all nodes. * * If the group is not a nonabelian, nontrivial direct product, * factor[0] and factor[1] will be set to NULL. */ Boolean is_direct_product; SymmetryGroup *factor[2]; }; #endif regina-4.95/engine/snappea/kernel/tables.c000644 000765 000024 00000023505 12235724562 020416 0ustar00babstaff000000 000000 /* * tables.c * * This file provides tables used in working with triangulations. * They are based on the numbering conventions for tetrahedra * described in triangulation.h. * * The tables are * * EdgeIndex edge3[6]; * EdgeIndex edge_between_faces[4][4]; * EdgeIndex edge3_between_faces[4][4]; * EdgeIndex edge_between_vertices[4][4]; * EdgeIndex edge3_between_vertices[4][4]; * FaceIndex one_face_at_edge[6]; * FaceIndex other_face_at_edge[6]; * VertexIndex one_vertex_at_edge[6]; * VertexIndex other_vertex_at_edge[6]; * FaceIndex remaining_face[4][4]; * FaceIndex face_between_edges[6][6]; * Permutation inverse_permutation[256]; * signed char parity[256]; * FaceIndex vt_side[4][3]; * Permutation permutation_by_index[24]; * * Their use is described in detail below. * * The value 9 is used as a filler for undefined table entries. */ #include "kernel.h" /* * edge3[i] is the index of the complex edge parameter associated with * the edge of EdgeIndex i. Opposite edges have equal complex edge * parameters, which are stored only under the lesser EdgeIndex; thus * even though the numbering of the edges runs from 0 to 5, the * numbering of the edge parameters run only from 0 to 2. */ const EdgeIndex edge3[6] = {0, 1, 2, 2, 1, 0}; /* * edge_between_faces[i][j] is the index of the edge lying between * faces i and j. */ const EdgeIndex edge_between_faces[4][4] = {{9, 0, 1, 2}, {0, 9, 3, 4}, {1, 3, 9, 5}, {2, 4, 5, 9}}; /* * edge3_between_faces[i][j] = edge3[ edge_between_faces[i][j] ]. * This table is useful when looking up complex edge parameters. * Cf. edge3[] above. */ const EdgeIndex edge3_between_faces[4][4] = {{9, 0, 1, 2}, {0, 9, 2, 1}, {1, 2, 9, 0}, {2, 1, 0, 9}}; /* * edge_between_vertices[i][j] is the index of the edge lying between * vertices i and j. */ const EdgeIndex edge_between_vertices[4][4] = {{9, 5, 4, 3}, {5, 9, 2, 1}, {4, 2, 9, 0}, {3, 1, 0, 9}}; /* * edge3_between_vertices[i][j] = edge3[ edge_between_vertices[i][j] ]. * This table is useful when looking up complex edge parameters. * Cf. edge3[] above. * Note that edge3_between_vertices[][] and edge3_between_faces[][] * are identical. */ const EdgeIndex edge3_between_vertices[4][4] = {{9, 0, 1, 2}, {0, 9, 2, 1}, {1, 2, 9, 0}, {2, 1, 0, 9}}; /* * one_face_at_edge[i] and other_face_at_edge[i] gives the indices of * the two faces incident to edge i. */ const FaceIndex one_face_at_edge[6] = {0, 0, 0, 1, 1, 2}; const FaceIndex other_face_at_edge[6] = {1, 2, 3, 2, 3, 3}; /* * one_vertex_at_edge[i] and other_vertex_at_edge[i] give the indices of * the two vertices incident to edge i. */ const FaceIndex one_vertex_at_edge[6] = {2, 1, 1, 0, 0, 0}; const FaceIndex other_vertex_at_edge[6] = {3, 3, 2, 3, 2, 1}; /* * Given two faces i and j, remaining_face[i][j] tells you the index * of one of the remaining faces. For a right_handed tetrahedron * (see kernel_typedefs.h for the definition of right_handed) the * faces i, j, and remaining_face[i][j] are arranged in a counterclockwise * order around their common ideal vertex. Thus, for two faces i and j, * remaining_face[i][j] gives one of the remaining faces and * remaining_face[j][i] gives the other. */ const FaceIndex remaining_face[4][4] = {{9, 3, 1, 2}, {2, 9, 3, 0}, {3, 0, 9, 1}, {1, 2, 0, 9}}; /* * face_between_edges[i][j] is the index of the face lying between * (nonopposite) edges i and j. */ const FaceIndex face_between_edges[6][6] = {{9, 0, 0, 1, 1, 9}, {0, 9, 0, 2, 9, 2}, {0, 0, 9, 9, 3, 3}, {1, 2, 9, 9, 1, 2}, {1, 9, 3, 1, 9, 3}, {9, 2, 3, 2, 3, 9}}; /* * inverse_permutation[p] is the inverse of Permutation p. */ const Permutation inverse_permutation[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb1, 0x00, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x00, 0x00, 0xd2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc9, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe1, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; /* * parity[p] is the parity of Permutation p. * * 0 signifies an even permutation * (corresponding to an orientation reversing gluing) * * 1 signifies an odd permutation * (corresponding to an orientation preserving gluing) * * 9 signifies an invalid permutation * * Notes: * (1) The typedef GluingParity relies on 0 and 1 meaning what they do. * (2) The 0 and 1 are reversed relative to the parity[] table in the * old version of snappea. * (3) Use the constants in GluingParity; don't use 0 and 1 directly. */ const signed char parity[256] = { 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 0, 9, 9, 9, 9, 9, 1, 9, 9, 9, 9, 9, 9, 9, 9, 1, 9, 9, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; /* * vt_side[i][j] is the side of the cross sectional triangle at * vertex i which lies between edges j and (j+1)%3, where * the edge numbering is as in edge3_between_faces[][] above. * * An alternate interpretation is that vt_side[v][0], vt_side[v][1] * and vt_side[v][2] are the three faces surrounding vertex v, given * in counterclockwise order relative to the right_handed orientation * of the Tetrahedron. */ const FaceIndex vt_side[4][3] = {{3, 1, 2}, {2, 0, 3}, {1, 3, 0}, {0, 2, 1}}; /* * There are 24 possible Permutations of the set {3, 2, 1, 0}. The table * permutation_by_index[] list them all. E.g. permutation_by_index[2] = 0xD2 * = 3102, which is the permutation taking 3210 to 3102. */ const Permutation permutation_by_index[24] = { 0xE4, 0xE1, 0xD2, 0xD8, 0xC9, 0xC6, 0x93, 0x9C, 0x8D, 0x87, 0xB4, 0xB1, 0x4E, 0x4B, 0x78, 0x72, 0x63, 0x6C, 0x39, 0x36, 0x27, 0x2D, 0x1E, 0x1B}; /* * index_by_permutation[] is the inverse of permutation_by_index[]. * That is, for 0 <= i < 24, index_by_permutation[permutation_by_index[i]] = i. */ const char index_by_permutation[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 23, -1, -1, 22, -1, -1, -1, -1, -1, -1, -1, -1, 20, -1, -1, -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, -1, 19, -1, -1, 18, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, -1, -1, -1, -1, -1, -1, -1, -1, 17, -1, -1, -1, -1, -1, 15, -1, -1, -1, -1, -1, 14, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 9, -1, -1, -1, -1, -1, 8, -1, -1, -1, -1, -1, 6, -1, -1, -1, -1, -1, -1, -1, -1, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 11, -1, -1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 5, -1, -1, 4, -1, -1, -1, -1, -1, -1, -1, -1, 2, -1, -1, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, -1, 1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; regina-4.95/engine/snappea/kernel/tables.h000644 000765 000024 00000002067 12235724571 020423 0ustar00babstaff000000 000000 /* * tables.h * * The following tables are defined and documented in tables.c. * They are globally available within the kernel, but are not * available to the user interface. */ #ifndef _tables_ #define _tables_ #include "kernel_typedefs.h" extern const EdgeIndex edge3[6]; extern const EdgeIndex edge_between_faces[4][4]; extern const EdgeIndex edge3_between_faces[4][4]; extern const EdgeIndex edge_between_vertices[4][4]; extern const EdgeIndex edge3_between_vertices[4][4]; extern const FaceIndex one_face_at_edge[6]; extern const FaceIndex other_face_at_edge[6]; extern const VertexIndex one_vertex_at_edge[6]; extern const VertexIndex other_vertex_at_edge[6]; extern const FaceIndex remaining_face[4][4]; extern const FaceIndex face_between_edges[6][6]; extern const Permutation inverse_permutation[256]; extern const signed char parity[256]; extern const FaceIndex vt_side[4][3]; extern const Permutation permutation_by_index[24]; extern const char index_by_permutation[256]; #endif regina-4.95/engine/snappea/kernel/terse_triangulation.h000644 000765 000024 00000012070 12235724571 023226 0ustar00babstaff000000 000000 /* * terse_triangulation.h * * This data structure describes a Triangulation in a "terse" format. * The data structure is intended to minimize storage requirements, * and contains only the minimum information required to reconstruct * the Triangulation in the full triangulation.h format. * * The data structure allows the option of including the value of the * Chern-Simons invariant (mod 1/2). * * Most often the TerseTriangulation will be used in conjunction with * the TersestTriangulation data format (cf. tersest_triangulation.h), * which represents the TerseTriangulation as a short sequence of * characters, suitable for storage in a file or transmission over * a network. * * Bill Thurston provided the original idea for this data structure. * The implementation has evolved from code written by Martin Hildebrand at * the Geometry Center (then the Geometry Supercomputer Project) in 1988. * * * [The following description of the TerseTriangulation assumes basic * familiarity with the discussion "Gluings of ideal tetrahedra" preceding * the Permutation typedef in kernel_typedefs.h.] * * The TerseTriangulation data structure provides the data for an * algorithm for constructing an ideal triangulation. The algorithm is * as follows. We begin with n ideal tetrahedra, which we are going * to assemble into an ideal triangulation. The vertices of each * tetrahedron are numbered 0, 1, 2 and 3, and the faces are numbered * according to the opposite vertices. The tetrahedra are all identical. * Take one tetrahedron and call it tetrahedron 0. Consider face 0 * of tetrahedron 0. It may glue to some other face of tetrahedron 0, * or it may glue to a different tetrahedron. The first entry of the * Boolean array glues_to_old_tet tells us which is the case. That is, * if glues_to_old_tet[0] is TRUE, then face 0 glues to some other face * of tetrahedron 0; if glues_to_old_tet[0] is FALSE, it glues to a * different tetrahedron. * * In the case that glues_to_old_tet[0] is TRUE, which_old_tet[0] will tell * us which old tetrahedron it glues to (here which_old_tet[0] must * obviously be 0, because tetrahedron 0 is the only tetrahedron we're * working with so far, but in a minute you'll see the full generality of * the which_old_tet array), and which_gluing[0] tells us the gluing * pattern (cf. the discussion "Gluings of ideal tetrahedra" in * kernel_prototypes.h). * * In the case that glues_to_old_tet[0] is FALSE, we attach one of the * as-yet-unused tetrahedra to face 0 of tetrahedron 0. The new * tetrahedron will be called tetrahedron 1, and its vertices will * inherit indices from the vertices of tetrahedron 0 in the canonical * way, by reflection across their common face. (The gluing will be * the identity Permutation.) * * Once we've taken care of face 0 of tetrahedron 0, we move on to * face 1 of tetrahedron 0. If it's already been glued (to face 0), * we do nothing. Otherwise we consult the next entry in the * glues_to_old_tet[] array: if it's TRUE, then the next entries in * the which_old_tet[] and which_gluing[] arrays tell us which tetrahedron * it glues to (maybe tetrahedron 0, or maybe tetrahedron 1), and what * the gluing Permutation is; if it's FALSE, we attach a new tetrahedron * as described above. * * We continue this process for faces 2 and 3 of tetrahedron 0, then * move on to faces 0, 1, 2 and 3 (in that order) of tetrahedron 1, * then tetrahedron 2, etc., until we have constructed the entire * triangulation. Note that this algorithm assumes the triangulation * is connected. * * If there are n tetrahedra, there'll be 4n faces, and (4n)/2 = 2n * decisions as to whether to glue to an old tetrahedron or a new one. * (The last two "decisions" will always be to glue to an old tetrahedron, * but we won't worry about that. The terse string data format does, * however, take this into account.) Precisely (n - 1) of those * decisions will be to add a new tetrahedron, and the remaining (n + 1) * decisions will be to glue to an old tetrahedron. This means that * the glues_to_old_tet[] array must have length 2n, while the * which_old_tet[] and which_gluing[] arrays must have length (n + 1). * * The file SnapPea.h contains the "opaque typedef" * * typedef struct TerseTriangulation TerseTriangulation; * * which lets the UI declare and pass pointers to TerseTriangulations * without actually knowing what they are. This file provides the * kernel with the actual definition. */ #ifndef _terse_triangulation_ #define _terse_triangulation_ #include "kernel.h" struct TerseTriangulation { /* * The first four fields provide the basic data described above. */ int num_tetrahedra; Boolean *glues_to_old_tet; int *which_old_tet; Permutation *which_gluing; /* * Optionally, we may wish to record the Chern-Simons invariant, * since it isn't computable from scratch (at least not in 1993). */ Boolean CS_is_present; double CS_value; }; #endif regina-4.95/engine/snappea/kernel/tersest_triangulation.h000644 000765 000024 00000010013 12235724571 023570 0ustar00babstaff000000 000000 /* * tersest_triangulation.h * * This data structure -- which is really just a formatting convention for * an array of chars -- compresses the information in a TerseTriangulation * to the maximum extent possible. It works only for Triangulations of 7 * or fewer Tetrahedra. It is intended for use in storing libraries of * manifolds, such as the 5-, 6- and 7-tetrahedron censuses. * * Here's how the information from the TerseTriangulation data structure * is stored. The description is for a 7-tetrahedron Triangulation. * With fewer Tetrahedra some bits will be unused, but we always use * the same number of bytes. (The reason we always use the same number * of bytes is so that we can retrieve a manifold from the middle of a long * file without reading the whole file.) Recall that a TerseTriangulation * for n Tetrahedra has a glues_to_old_tet[] array of length 2n, and * which_old_tet[] and which_gluing[] arrays of length n+1. So for * 7 Tetrahedra, glues_to_old_tet[] has length 14, while which_old_tet[] * and which_gluing[] each have length 8. * * num_tetrahedra * * is redundant and is not stored at all. The number of Tetrahedra can * be deduced from the glues_to_old_tet array by keeping track of how * many free faces are available at each stage. * * glues_to_old_tet[] * * is stored in the first two bytes of the TersestTriangulation string. * * glues_to_old_tet[0] is stored in the lowest-order bit of byte 0, * . . . and so on until . . . * glues_to_old_tet[7] is stored in the highest-order bit of byte 0. * * glues_to_old_tet[8] is stored in the lowest-order bit of byte 1, * . . . and so on until . . . * glues_to_old_tet[13] is stored in the sixth bit of byte 1. * * Note that the two highest-order bits in byte 1 remain available for * other purposes. The highest-order bit tells whether a Chern-Simons * invariant is present (cf. below). * * which_old_tet[] * which_gluing[] * * which_old_tet[] and which_gluing[] are stored in tandem. * * which_old_tet[i] is stored in the three highest-order bits * of byte 2+i. which_old_tet[i] will always be an integer * in the range [0, 6], so it fits comfortably in three bits. * * which_gluing[i] is stored in the five lowest-order bits * of byte 2+i. Each Permutation is converted to an integer * in the range [0, 23], which fits comfortably in five bits. * The array index_by_permutation[] in tables.c does the conversion. * * CS_is_present * CS_value * * CS_is_present is stored in the highest-order bit of byte 1. * * if (CS_is_present == TRUE) * bytes 10 through 17 hold the CS_value, encoded as follows. * First add a multiple of 1/2, if necessary, so that the CS_value * lies in the range [-1/4, 1/4) Then mulitply by 2 and add 1/2 * so that it lies in the ragne [0, 1). The CS_value is a * double which on some machines (e.g. the Mac) has an 8-btye * mantissa. In binary, this is a number like 0.100101000..., with * 64 meaningful binary digits after the decimal point (the "binary * point" ?). The first 8 digits are stored in byte 10 of the * TersestTriangulation string, the next 8 digits are stored in * byte 11, and so on until the final 8 meaningful digits are stored * in byte 17. On some machines (e.g. on most UNIX workstations) * doubles have only 6-byte mantissas, in which case we just * zero-fill bytes 16 and 17 of the TersestTriangulation string * (in practice this shouldn't occur, because the libraries of * manifolds will be created on the Mac at full accuracy, and then * only read on less accurate platforms). * * if (CS_is_present == FALSE) * bytes 10 through 17 are unused. */ #ifndef _tersest_triangulation_ #define _tersest_triangulation_ typedef unsigned char TersestTriangulation[18]; #endif regina-4.95/engine/snappea/kernel/tet_shapes.c000644 000765 000024 00000013657 12235724562 021312 0ustar00babstaff000000 000000 /* * tet_shapes.c * * This file contains the following low-level functions for * working with TetShapes. * * void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, * Tetrahedron *tet1, EdgeIndex e1, * Tetrahedron *tet2, EdgeIndex e2) * * Boolean angles_sum_to_zero( Tetrahedron *tet0, EdgeIndex e0, * Tetrahedron *tet1, EdgeIndex e1); * * void compute_remaining_angles(Tetrahedron *tet, EdgeIndex e); * * * add_edge_angles() adds the edge angles at edge e0 of tet0 * to the corresponding angles at edge e1 of tet1, and writes * the results to edge e2 of tet2. It pays careful attention * to the edge_orientations. Note that even though opposite * edges of a Tetrahedron have equal angles, they needn't have * the same edge_orientation, so you should pass an actual * EdgeIndex in the range 0-5, not merely a quasi-equivalent * index in the range 0-2. The edge angle of the sum will be * in the range [(-1/2) pi, (3/2) pi], regardless of the angles * of the summands. * * angles_sum_to_zero() returns TRUE iff one of the angles * (shape[complete]->cwl[ultimate] or shape[filled]->cwl[ultimate]) * at edge e0 of tet0 cancels the corresponding angle at edge e1 * of tet1 (mod 2 pi i). Accounts for edge_orientations. * * compute_remaining_angles() assumes the angle at edge e is * correct, and computes the remaining angles in terms of it. * The EdgeIndex may be given in either the range 0-5 or the * range 0-2. The arguments of the remaining angles will be * in the range [(-1/2) pi, (3/2) pi]. */ #include "kernel.h" /* * CANCELLATION_EPSILON says how close the logs of two complex * numbers must be to be considered equal. */ #define CANCELLATION_EPSILON 1e-4 static void add_tet_shapes( TetShape *ts0, EdgeIndex e30, Orientation eo0, TetShape *ts1, EdgeIndex e31, Orientation eo1, TetShape *ts2, EdgeIndex e32, Orientation eo2); static void add_complex_with_log( ComplexWithLog *cwl0, Orientation eo0, ComplexWithLog *cwl1, Orientation eo1, ComplexWithLog *cwl2, Orientation eo2); static Boolean logs_sum_to_zero( Complex summand0, Orientation eo0, Complex summand1, Orientation eo1); static void compute_cwl(ComplexWithLog cwl[3], EdgeIndex e); static void normalize_angle(double *angle); void add_edge_angles( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1, Tetrahedron *tet2, EdgeIndex e2) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ add_tet_shapes( tet0->shape[i], edge3[e0], tet0->edge_orientation[e0], tet1->shape[i], edge3[e1], tet1->edge_orientation[e1], tet2->shape[i], edge3[e2], tet2->edge_orientation[e2]); } static void add_tet_shapes( TetShape *ts0, EdgeIndex e30, Orientation eo0, TetShape *ts1, EdgeIndex e31, Orientation eo1, TetShape *ts2, EdgeIndex e32, Orientation eo2) { int i; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ add_complex_with_log( &ts0->cwl[i][e30], eo0, &ts1->cwl[i][e31], eo1, &ts2->cwl[i][e32], eo2); } static void add_complex_with_log( ComplexWithLog *cwl0, Orientation eo0, ComplexWithLog *cwl1, Orientation eo1, ComplexWithLog *cwl2, Orientation eo2) { /* * First compute the sum of the logs, then recover * the rectangular form. * * We do all computations relative to the Orientation * of the EdgeClass. So if a particular edge is seen * as left_handed by the EdgeClass, we must negate the * real part of the log of its complex angle. (Recall * that all all TetShapes are stored relative to the * right_handed Orientation of the Tetrahedron.) */ Complex summand0, summand1, sum; summand0 = cwl0->log; if (eo0 == left_handed) summand0.real = - summand0.real; summand1 = cwl1->log; if (eo1 == left_handed) summand1.real = - summand1.real; sum = complex_plus(summand0, summand1); if (eo2 == left_handed) sum.real = - sum.real; normalize_angle(&sum.imag); cwl2->log = sum; cwl2->rect = complex_exp(sum); } Boolean angles_sum_to_zero( Tetrahedron *tet0, EdgeIndex e0, Tetrahedron *tet1, EdgeIndex e1) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ if (logs_sum_to_zero( tet0->shape[i]->cwl[ultimate][edge3[e0]].log, tet0->edge_orientation[e0], tet1->shape[i]->cwl[ultimate][edge3[e1]].log, tet1->edge_orientation[e1])) return TRUE; return FALSE; } static Boolean logs_sum_to_zero( Complex summand0, Orientation eo0, Complex summand1, Orientation eo1) { Complex sum; if (eo0 != eo1) summand1.real = - summand1.real; sum = complex_plus(summand0, summand1); normalize_angle(&sum.imag); return (complex_modulus(sum) < CANCELLATION_EPSILON); } void compute_remaining_angles( Tetrahedron *tet, EdgeIndex e) { int i, j; for (i = 0; i < 2; i++) /* i = complete, filled */ for (j = 0; j < 2; j++) /* j = ultimate, penultimate */ compute_cwl(tet->shape[i]->cwl[j], edge3[e]); } static void compute_cwl( ComplexWithLog cwl[3], EdgeIndex e) { /* * Compute cwl[(e+1)%3] and cwl[(e+2)%3] in terms of cwl[e]. */ int i; for (i = 1; i < 3; i++) { cwl[(e+i)%3].rect = complex_div(One, complex_minus(One, cwl[(e+i-1)%3].rect)); cwl[(e+i)%3].log = complex_log(cwl[(e+i)%3].rect, PI_OVER_2); } } static void normalize_angle( double *angle) { /* * Normalize the angle to lie in the range [(-1/2) pi, (3/2) pi]. */ while (*angle > THREE_PI_OVER_2) *angle -= TWO_PI; while (*angle < - PI_OVER_2) *angle += TWO_PI; } regina-4.95/engine/snappea/kernel/tidy_peripheral_curves.c000644 000765 000024 00000006445 12235724562 023723 0ustar00babstaff000000 000000 /* * tidy_peripheral_curves.c * * * This file provides the function * * void tidy_peripheral_curves(Triangulation *manifold); * * which is used within the kernel to clean up a set of peripheral * curves. * * Functions which alter triangulations maintain a set of peripheral * curves which is technically correct, but suffers two faults. * A minor fault is that the peripheral curves wind around a lot, * and therefore create unnecessarily complicated cusp equations. * The more serious fault is that the curves may evolve trivial loops; * that is, a single meridian or longitude will have several components, * one of which is the correct curve while the others are homotopically * trivial loops. Such trivial loops introduce erroneous multiples * of 2pi into the computed holonomies, and thereby foul up the * computation of hyperbolic structures. * * The function tidy_peripheral_curves() * * (1) makes a copy of the existing peripheral curves, * (2) calls peripheral_curves() to create a nice set of * peripheral curves, and * (3) expresses the original curves as linear combinations * of the nice curves. * * The result is an "efficient" set of curves, with no trivial * loops. */ #include "kernel.h" /* * scratch_curves[0] will store the original peripheral curves. * scratch_curves[1] will store the nice peripheral curves. */ #define original_curves 0 #define nice_curves 1 static void compute_new_curves(Triangulation *manifold); void tidy_peripheral_curves( Triangulation *manifold) { /* * Copy the original peripheral curves to the * scratch_curve[original_curves] fields of the Tetrahedra. */ copy_curves_to_scratch(manifold, original_curves, TRUE); /* * Compute a nice set of peripheral curves. */ peripheral_curves(manifold); /* * Copy the nice peripheral curves to the * scratch_curve[nice_curves] fields of the Tetrahedra. */ copy_curves_to_scratch(manifold, nice_curves, FALSE); /* * Compute the intersection numbers of the original curves * with the nice curves. */ compute_intersection_numbers(manifold); /* * Compute the new curves as linear combinations of the * nice curves. */ compute_new_curves(manifold); } static void compute_new_curves( Triangulation *manifold) { Tetrahedron *tet; int h, i, j, k; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (h = 0; h < 2; h++) /* which curve */ for (i = 0; i < 2; i++) /* which sheet */ for (j = 0; j < 4; j++) /* which vertex */ for (k = 0; k < 4; k++) /* which side of that vertex */ tet->curve[h][i][j][k] = (j == k) ? 0 : - tet->cusp[j]->intersection_number[h][L] * tet->scratch_curve[nice_curves][M][i][j][k] + tet->cusp[j]->intersection_number[h][M] * tet->scratch_curve[nice_curves][L][i][j][k]; } regina-4.95/engine/snappea/kernel/transcendentals.c000644 000765 000024 00000003566 12235724562 022341 0ustar00babstaff000000 000000 /* * transcendentals.c * * What is the value of acos(1.0000000001)? * On the Mac you get the intended answer (0.0), but unix returns NaN. * To guard against this sort of problem, the SnapPea kernel uses * * double safe_acos(double x); * double safe_asin(double x); * double safe_sqrt(double x); * * Incredibly enough, the standard ANSI libraries omit the * inverse hyperbolic trig functions entirely. * * double arcsinh(double x); * double arccosh(double x); * * Many (but not all) standard libraries now provide asinh() and acosh(). * I've changed the names of my homemade versions to arcsinh() and arccosh() * to avoid conflicts. 2000/02/20 JRW */ #include "kernel.h" #define ERROR_EPSILON 1e-3 double safe_acos(double x) { if (x > 1.0) { if (x > 1.0 + ERROR_EPSILON) uFatalError("safe_acos", "transcendentals"); x = 1.0; } if (x < -1.0) { if (x < -(1.0 + ERROR_EPSILON)) uFatalError("safe_acos", "transcendentals"); x = -1.0; } return acos(x); } double safe_asin(double x) { if (x > 1.0) { if (x > 1.0 + ERROR_EPSILON) uFatalError("safe_asin", "transcendentals"); x = 1.0; } if (x < -1.0) { if (x < -(1.0 + ERROR_EPSILON)) uFatalError("safe_asin", "transcendentals"); x = -1.0; } return asin(x); } double safe_sqrt(double x) { if (x < 0.0) { if (x < -ERROR_EPSILON) uFatalError("safe_sqrt", "transcendentals"); x = 0.0; } return sqrt(x); } double arcsinh( double x) { return log(x + sqrt(x*x + 1.0)); } double arccosh( double x) { if (x < 1.0) { if (x < 1.0 - ERROR_EPSILON) uFatalError("arccosh", "transcendentals"); x = 1.0; } return log(x + sqrt(x*x - 1.0)); } regina-4.95/engine/snappea/kernel/triangulation.h000644 000765 000024 00000035671 12235724571 022040 0ustar00babstaff000000 000000 /* * triangulation.h * * This file defines the basic data structure for an ideal * triangulation. SnapPea's various modules communicate with each * other primarily by passing pointers to Triangulations. * * The Triangulation data structure consists of some global information * about the manifold (number of tetrahedra, number of cusps, etc.) * following by doubly linked lists of Tetrahedra, EdgeClasses, and Cusps. * As the triangulation varies dynamically (for example, during the * triangulation simplification algorithm) Tetrahedra, EdgeClasses and * Cusps may be easily inserted and deleted using the INSERT_BEFORE() * and REMOVE_NODE() macros found in kernel_typedefs.h. The Triangulation * data structure contains header and tailer nodes for each doubly linked * list, to avoid having to consider special cases while inserting and * deleting nodes. * * To keep the global structure of this file as clear as possible, * most of the local documentation appears elsewhere. The comment * next to each field says what .c file (if any) contains the * documentation for that field. * * Most fields are maintained globally. That is, you may assume they * contain correct values at all times, and you should update their * values if you change the triangulation. Fields which are used locally * within a single file or module are so indicated. They do not contain * correct values outside that module, and you need not maintain them. * * Note that SnapPea.h (the only header file common to the user interface * and the computational kernel) contains the opaque typedef * * typedef struct Triangulation Triangulation; * * This opaque typedef allows the user interface to declare and pass * pointers to Triangulations, without being able to access a * Triangulation's fields directly. This file provides the kernel with * the actual definition. * * The inclusion of lower-level data structures within higher-level ones * forces the following typedefs to be orangized in a bottom-up fashion, * beginning with the least significant data structure (ComplexWithLog) * and working towards the most significant one (Triangulation). I * therefore recommend that you start reading this file at the bottom * and work your way up. */ #ifndef _triangulation_ #define _triangulation_ #include "SnapPea.h" #include "kernel_typedefs.h" /* * Forward declarations. */ typedef struct ComplexWithLog ComplexWithLog; typedef struct TetShape TetShape; typedef struct Tetrahedron Tetrahedron; typedef struct EdgeClass EdgeClass; typedef struct Cusp Cusp; /* * ComplexWithLog stores a complex edge parameter in both rectangular * and logarithmic form. That is, the log field is always the complex * logarithm of the rect field. The imaginary part of the log varies * continuously during Dehn filling, and is not restricted to any * particular branch of the logarithm. * * The edge parameter is always expressed relative to the * right_handed orientation of the tetrahedron. */ struct ComplexWithLog { Complex rect; Complex log; }; /* * TetShape stores the complex edge parameters at edges 0,1 and 2 of * a given Tetrahedron. (See edge_classes.c for the edge indexing scheme.) * Edges 5, 4 and 3 are opposite 0, 1 and 2, respectively, and therefore * have equal edge parameters. The edge parameters are recorded at the * next-to-the-last as well as the last iteration of Newton's method in * the hyperbolic structures module, to allow the estimation of errors * in various computed quantities (volume, etc.). Warning: the true error * is usually greater than the error between the penultimate and ultimate * iterations of Newton's method. That is, if you switch to a different * triangulation of the same manifold, you'll find the volume, etc. differs * by more than it did between the last two iterations of Newton's method. * The edge parameters at the next-to-the-last iteration are stored as * cwl[penultimate][], while those at the last iteration are cwl[ultimate][]. * * * Note that the Tetrahedron structure (immediately below) contains pointers * to TetShapes, rather than the TetShapes themselves. The disadvantage * of this scheme is that the TetShapes must be allocated and deallocated * explicitly. The advantages are * * (1) Some functions which temporarily require large numbers of Tetrahedra * can get by with less memory if they don't require the TetShapes. * On a Mac, for example, the Tetrahedron structure itself requires * 242 bytes, while the TetShapes require an additional 576 bytes. * This difference can be significant. For example, the function which * computes an ideal triangulation for a partially filled multicusp * manifold will, when applied to a 100-Tetrahedron Triangulation, * temporarily create more than 3000 Tetrahedra. By omitting the * TetShapes, the memory requirement for these Tetrahedra drops * from 2.5 MB to 750 kB. * * (2) It's easy for the low-level retriangulation function (e.g. * the 2-3 and 3-2 moves) to determine whether the Tetrahedra * have shapes associated with them. If the pointers to TetShapes * are NULL, there are no shapes; otherwise there are. * * The TetShape corresponding to the complete (resp. Dehn filled) hyperbolic * structure is stored in the Tetrahedron data structure as tet->shape[complete] * (resp. tet->shape[filled]). By convention, TetShapes will be present iff * tet->solution_type[complete] and tet->solution_type[filled] are something * other than not_attempted. */ struct TetShape { ComplexWithLog cwl[2][3]; }; struct Tetrahedron { Tetrahedron *neighbor[4]; /* kernel_typedefs.h */ Permutation gluing[4]; /* kernel_typedefs.h */ Cusp *cusp[4]; /* the cusp containing each vertex */ int curve[2][2][4][4]; /* peripheral_curves.c */ int scratch_curve[2][2][2][4][4]; /* intersection_numbers.c (local) */ EdgeClass *edge_class[6]; /* edge_classes.c */ Orientation edge_orientation[6];/* edge_classes.c */ TetShape *shape[2]; /* see TetShape and ComplexWithLog above */ ShapeInversion *shape_history[2]; /* kernel_typedefs.h */ int coordinate_system; /* hyperbolic_structure.c (local) */ int index; /* hyperbolic_structure.c (local) */ GeneratorStatus generator_status[4];/* choose_generators.c (local) */ int generator_index[4]; /* choose_generators.c (local) */ GluingParity generator_parity[4];/* choose_generators.c (local) */ Complex corner[4]; /* choose_generators.c (local) */ FaceIndex generator_path; /* choose_generators.c (local) */ VertexCrossSections *cross_section; /* cusp_cross_section.c (local) */ double tilt[4]; /* cusp_cross_section.c (local) */ CanonizeInfo *canonize_info; /* canonize_part_2.c (local) */ Tetrahedron *image; /* symmetry.h (local) */ Permutation map; /* symmetry.h (local) */ Boolean tet_on_curve; /* dual_curves.c (local) */ Boolean face_on_curve[4]; /* dual_curves.c (local) */ CuspNbhdPosition *cusp_nbhd_position;/* cusp_neighborhoods.c (local) */ EdgeIndex parallel_edge; /* normal_surfaces.h (local) */ int num_squares, /* normal_surfaces.h (local) */ num_triangles[4]; /* normal_surfaces.h (local) */ Boolean has_correct_orientation; /* normal_surface_splitting.c (local) */ int flag; /* general purpose integer for local use as necessary */ Extra *extra; /* general purpose pointer for local use as necessary */ /* see Extra typedef in kernel_typedefs.h for details */ Tetrahedron *prev; /* previous tetrahedron on doubly linked list */ Tetrahedron *next; /* next tetrahedron on doubly linked list */ }; struct EdgeClass { int order; /* number of incident edges of tetrahedra */ Tetrahedron *incident_tet; /* one particular incident tetrahedron... */ EdgeIndex incident_edge_index; /* ...and the index of the incident edge */ int num_incident_generators;/* choose_generators.c (local) */ Boolean active_relation; /* choose_generators.c (local) */ Complex *complex_edge_equation; /* gluing_equations.c (used locally) */ double *real_edge_equation_re, /* gluing_equations.c (used locally) */ *real_edge_equation_im; /* gluing_equations.c (used locally) */ Complex edge_angle_sum; /* used locally in hyperbolic structures module */ Complex target_angle_sum; /* used by MC -- force_tet_shapes */ int index; /* used locally for saving Triangulations to disk */ double intercusp_distance; /* cusp_neighborhoods.c (used locally) */ EdgeClass *prev; /* previous EdgeClass on doubly linked list */ EdgeClass *next; /* next EdgeClass on doubly linked list */ }; struct Cusp { CuspTopology topology; /* torus_cusp or Klein_cusp */ Boolean is_complete; /* is the cusp currently unfilled? */ double m, /* Dehn filling coefficient */ l; /* Dehn filling coefficient */ Complex holonomy[2][2]; /* holonomy.c */ Complex target_holonomy; /* used by MC -- force_tet_shapes */ Complex *complex_cusp_equation; /* gluing_equations.c (used locally) */ double *real_cusp_equation_re, /* gluing_equations.c (used locally) */ *real_cusp_equation_im; /* gluing_equations.c (used locally) */ Complex cusp_shape[2]; /* cusp_shapes.c */ int shape_precision[2]; /* cusp_shapes.c */ int index; /* cusp number, as perceived by user */ /* (numbering starts at zero) */ double displacement, /* cusp_neighborhoods.c (used globally) */ displacement_exp, /* cusp_neighborhoods.c (used globally) */ reach, /* cusp_neighborhoods.c (local) */ stopping_displacement; /* cusp_neighborhoods.c (local) */ Cusp *stopper_cusp; /* cusp_neighborhoods.c (local) */ Boolean is_tied; /* cusp_neighborhoods.c (local) */ Complex translation[2], /* cusp_neighborhoods.c (local) */ scratch; /* cusp_neighborhoods.c (local) */ double exp_min_d; /* cusp_neighborhoods.c (local) */ Tetrahedron *basepoint_tet; /* fundamental_group.c (semi-local) */ VertexIndex basepoint_vertex; /* fundamental_group.c (semi-local) */ Orientation basepoint_orientation; /* fundamental_group.c (semi-local) */ int intersection_number[2][2]; /* intersection_numbers.c (local) */ Boolean is_finite; /* finite_vertices.c (used locally) */ /* indices are negative, starting at -1 */ Cusp *matching_cusp; /* subdivide.c, finite_vertices.c, */ /* cover.c, normal_surface_splitting.c */ /* (used locally) */ int euler_characteristic; /* cusps.c (local) */ Cusp *prev; /* previous Cusp on doubly linked list */ Cusp *next; /* next Cusp on doubly linked list */ }; struct Triangulation { char *name; /* name of manifold */ int num_tetrahedra; /* number of tetrahedra */ SolutionType solution_type[2]; /* complete and filled */ Orientability orientability; /* Orientability of manifold */ int num_cusps, /* total number of cusps */ num_or_cusps, /* number of orientable cusps */ num_nonor_cusps; /* number of nonorientable cusps */ int num_generators; /* choose_generators.c (local) */ Boolean CS_value_is_known, /* Chern_Simons.c */ CS_fudge_is_known; /* Chern_Simons.c */ double CS_value[2], /* Chern_Simons.c */ CS_fudge[2]; /* Chern_Simons.c */ double max_reach, /* cusp_neighborhoods.c (local) */ tie_group_reach, /* cusp_neighborhoods.c (local) */ volume; /* cusp_neighborhoods.c (local) */ Tetrahedron tet_list_begin, /* header node for doubly linked list of Tetrahedra */ tet_list_end; /* tailer node for doubly linked list of Tetrahedra */ EdgeClass edge_list_begin,/* header node for doubly linked list of Edges */ edge_list_end; /* tailer node for doubly linked list of Edges */ Cusp cusp_list_begin,/* header node for doubly linked list of Cusps */ cusp_list_end; /* tailer node for doubly linked list of Cusps */ }; #endif regina-4.95/engine/snappea/kernel/triangulation_io.h000644 000765 000024 00000010252 12235724571 022513 0ustar00babstaff000000 000000 /* * triangulation_io.h * * The question of file formats is tricky. Typically an application has a * different file format for each platform it runs on (Mac, Windows, etc.), * perhaps with some support for translating between them. Certainly * the easiest way to write Macintosh SnapPea would have been to use * the THINK Class Library's built-in file handling capabilities: * file i/o would have been trivial to program, and each kind of file * (triangulation, generators, link projection) would have its own * "file type" and icon. However, I chose to use straight ASCII text * files for the following reasons: * * (1) The files can be read and written on any platform. * For example, a Mac user can send a Triangulation file * to a friend using a unix version of SnapPea. * * (2) Human beings can read the files using any text editor. * * (3) Text-only files are best for people doing other programming * projects which use SnapPea's manifolds as input. (Again, * people do this work on a variety of platforms.) * * On the other hand, I think it would be a mistake to hard-code the * SnapPea kernel to read data from unix-style FILEs. When the UI * reads a triangulation file, it passes the data to the kernel using * the data structures defined below. And, of course, when the UI wants * to write a triangulation to a file, it requests the data from the * kernel in this same format. * * Notes: * * (1) This format is similar to that of the Triangulation data structure. * However, the present format is available to the UI, while * the Triangulation data structure is private to the kernel. * * (2) The new file format is not backward compatible to the old * file format, although they are similar. (I couldn't keep * the old file format because it doesn't work properly with * nonorientable manifolds.) SnapPea 2.0 will read (but not write) * the old file format, with the exception that peripheral curves * on nonorientable manifolds are recomputed from scratch. * * (3) If you (or your program) is writing TriangulationData structures * from scratch, note that not all fields are required. The fields * solution_type, volume, and the TetrahedronDatas' filled_shapes * are ignored by data_to_triangulation(), because it recomputes the * hyperbolic structure from scratch. The meridian and longitude * are optional: if you provide them, data_to_triangulation() * will use them; otherwise it provides a default basis. * (In the latter case, of course, you shouldn't specify any * Dehn fillings, because you aren't providing the basis relative * to which they are defined.) * * 96/9/17 If you set both num_or_cusps and num_nonor_cusps to zero, * data_to_triangulation() will create the Cusps for you. It will * also create the peripheral curves. * * 96/9/17 If you specify unknown_orientability, data_to_triangulation() * will attempt to orient the manifold. This will typically change * the indexing of the Tetrahedra's vertices. */ /* * This file (triangulation_io.h) is intended solely for inclusion * in SnapPea.h. It depends on some of the typedefs there. */ typedef struct TriangulationData TriangulationData; typedef struct CuspData CuspData; typedef struct TetrahedronData TetrahedronData; struct TriangulationData { char *name; int num_tetrahedra; SolutionType solution_type; double volume; Orientability orientability; Boolean CS_value_is_known; double CS_value; int num_or_cusps, num_nonor_cusps; CuspData *cusp_data; TetrahedronData *tetrahedron_data; }; struct CuspData { CuspTopology topology; double m, l; }; struct TetrahedronData { /* * Note: gluing[i][j] is the image of j under the i-th gluing permutation. */ int neighbor_index[4]; int gluing[4][4]; int cusp_index[4]; int curve[2][2][4][4]; Complex filled_shape; }; regina-4.95/engine/snappea/kernel/triangulations.c000644 000765 000024 00000100255 12236247215 022201 0ustar00babstaff000000 000000 /* * triangulation.c * * This file contains the following functions which the kernel * provides for the UI: * * void data_to_triangulation( TriangulationData *data, * Triangulation **manifold_ptr); * void triangulation_to_data( TriangulationData **data_ptr, * Triangulation *manifold); * void free_triangulation_data(TriangulationData *data); * void free_triangulation( Triangulation *manifold); * void copy_triangulation( Triangulation *source, * Triangulation **destination); * * Their use is described in SnapPea.h. * * This file also provides the functions * * void initialize_triangulation(Triangulation *manifold); * void initialize_tetrahedron(Tetrahedron *tet); * void initialize_cusp(Cusp *cusp); * void initialize_edge_class(EdgeClass *edge_class); * * which kernel functions use to do generic initializations. Much of * what they do is not really necessary, but it seems like a good idea * to at least write NULL into pointers which have not been set. * * This file also includes * * FuncResult check_Euler_characteristic_of_boundary(Triangulation *manifold); * * which returns func_OK if the Euler characteristic of the total * boundary of the manifold is zero. Otherwise it returns func_failed. * * Furthermore, this file includes * * void number_the_tetrahedra(Triangulation *manifold); * * which sets each Tetrahedron's index field equal to its position in * the linked list. Indices range from 0 to (num_tetrahedra - 1). * * In addition, we have * * void free_tetrahedron(Tetrahedron *tet); * * which frees a Tetrahedron and all attached data structures, but does NOT * remove the Tetrahedron from any doubly linked list it may be on, and * * void clear_shape_history(Tetrahedron *tet); * void copy_shape_history(ShapeInversion *source, ShapeInversion **dest); * void clear_one_shape_history(Tetrahedron *tet, FillingStatus which_history); * * which do what you'd expect (please see the code for details). */ #include "kernel.h" #include /* for sprintf() in check_neighbors_and_gluings() */ static void check_neighbors_and_gluings(Triangulation *manifold); static int count_the_edge_classes(Triangulation *manifold); void data_to_triangulation( TriangulationData *data, Triangulation **manifold_ptr) { /* * We assume the UI has done some basic error checking * on the data, so we don't repeat it here. */ Triangulation *manifold; Tetrahedron **tet_array; Cusp **cusp_array; Boolean cusps_are_given; int i, j, k, l, m; Boolean all_peripheral_curves_are_zero, finite_vertices_are_present; /* * Initialize *manifold_ptr to NULL. * We'll do all our work with manifold, and then copy * manifold to *manifold_ptr at the very end. */ *manifold_ptr = NULL; /* * Allocate and initialize the Triangulation structure. */ manifold = NEW_STRUCT(Triangulation); initialize_triangulation(manifold); /* * Allocate and copy the name. */ manifold->name = NEW_ARRAY(strlen(data->name) + 1, char); strcpy(manifold->name, data->name); /* * Set up the global information. * * The hyperbolic structure is included in the file only for * human readers; here we recompute it from scratch. */ manifold->num_tetrahedra = data->num_tetrahedra; manifold->solution_type[complete] = not_attempted; manifold->solution_type[ filled ] = not_attempted; manifold->orientability = data->orientability; manifold->num_or_cusps = data->num_or_cusps; manifold->num_nonor_cusps = data->num_nonor_cusps; manifold->num_cusps = manifold->num_or_cusps + manifold->num_nonor_cusps; /* * Allocate the Tetrahedra. * Keep pointers to them on a temporary array, so we can * find them by their indices. */ tet_array = NEW_ARRAY(manifold->num_tetrahedra, Tetrahedron *); for (i = 0; i < manifold->num_tetrahedra; i++) { tet_array[i] = NEW_STRUCT(Tetrahedron); initialize_tetrahedron(tet_array[i]); INSERT_BEFORE(tet_array[i], &manifold->tet_list_end); } /* * If num_or_cusps or num_nonor_cusps is nonzero, allocate the Cusps. * Keep pointers to them on temporary arrays, so we can find them * by their indices. * Otherwise we will create arbitrary Cusps later. */ cusps_are_given = (data->num_or_cusps != 0) || (data->num_nonor_cusps != 0); if (cusps_are_given == TRUE) { cusp_array = NEW_ARRAY(manifold->num_cusps, Cusp *); for (i = 0; i < manifold->num_cusps; i++) { cusp_array[i] = NEW_STRUCT(Cusp); initialize_cusp(cusp_array[i]); INSERT_BEFORE(cusp_array[i], &manifold->cusp_list_end); } } else cusp_array = NULL; /* * Set up the Tetrahedra. */ all_peripheral_curves_are_zero = TRUE; finite_vertices_are_present = FALSE; for (i = 0; i < manifold->num_tetrahedra; i++) { for (j = 0; j < 4; j++) tet_array[i]->neighbor[j] = tet_array[data->tetrahedron_data[i].neighbor_index[j]]; for (j = 0; j < 4; j++) tet_array[i]->gluing[j] = CREATE_PERMUTATION( 0, data->tetrahedron_data[i].gluing[j][0], 1, data->tetrahedron_data[i].gluing[j][1], 2, data->tetrahedron_data[i].gluing[j][2], 3, data->tetrahedron_data[i].gluing[j][3]); if (cusps_are_given == TRUE) { for (j = 0; j < 4; j++) { if (data->tetrahedron_data[i].cusp_index[j] >= 0) /* assign a real cusp */ tet_array[i]->cusp[j] = cusp_array[data->tetrahedron_data[i].cusp_index[j]]; else { /* mark a finite vertex with NULL */ tet_array[i]->cusp[j] = NULL; finite_vertices_are_present = TRUE; } } for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) for (m = 0; m < 4; m++) { tet_array[i]->curve[j][k][l][m] = data->tetrahedron_data[i].curve[j][k][l][m]; if (data->tetrahedron_data[i].curve[j][k][l][m] != 0) all_peripheral_curves_are_zero = FALSE; } } } /* * 97/12/8 Check that the neighbors and gluings are consistent. * For SnapPea's own files this isn't necessary, but it's a big * help for people who write files by hand. It catches the most * obvious errors and provides a useful diagnosis (as opposed to, * say, having the program hang when inconsistent neighbors and/or * gluings send create_edge_classes() into an infinite loop). * Even in the typical case of reading SnapPea's own files, * check_neighbors_and_gluings() is very quick. */ check_neighbors_and_gluings(manifold); /* * Set up the EdgeClasses. */ create_edge_classes(manifold); orient_edge_classes(manifold); /* * If the Cusps were specified explicitly, copy in the data. * Otherwise create arbitrary Cusps now. */ if (cusps_are_given == TRUE) { for (i = 0; i < manifold->num_cusps; i++) { cusp_array[i]->topology = data->cusp_data[i].topology; cusp_array[i]->m = data->cusp_data[i].m; cusp_array[i]->l = data->cusp_data[i].l; cusp_array[i]->is_complete = (data->cusp_data[i].m == 0.0 && data->cusp_data[i].l == 0.0); cusp_array[i]->index = i; } /* * If finite vertices are present they will be marked with NULL. * Assign Cusp structures. */ if (finite_vertices_are_present == TRUE) create_fake_cusps(manifold); } else { create_cusps(manifold); finite_vertices_are_present = mark_fake_cusps(manifold); } /* * Provide peripheral curves if necessary. * This automatically records the CuspTopologies. */ peripheral_curves_as_needed(manifold); /* * Typically the manifold's orientability will already be known, * but if it isn't, try to orient it now. */ if (manifold->orientability == unknown_orientability) { orient(manifold); if (manifold->orientability == oriented_manifold) { fix_peripheral_orientations(manifold); } } /* * If the given triangulation includes finite vertices, remove them. */ if (finite_vertices_are_present == TRUE) remove_finite_vertices(manifold); /* * Count the Cusps if necessary, noting how many have each topology. */ if (cusps_are_given == FALSE) count_cusps(manifold); /* * Free the temporary arrays. */ my_free(tet_array); if (cusp_array != NULL) my_free(cusp_array); /* * Compute the complete and filled hyperbolic structures. * * (The Dehn fillings should be nontrivial only if the data * provided the peripheral curves.) * * Removed 2013/10/15 by NMD. * * find_complete_hyperbolic_structure(manifold); * do_Dehn_filling(manifold); */ /* * If we provided the basis and the manifold is hyperbolic, * replace it with a shortest basis. */ if (all_peripheral_curves_are_zero == TRUE && ( manifold->solution_type[complete] == geometric_solution || manifold->solution_type[complete] == nongeometric_solution)) install_shortest_bases(manifold); /* * If the Chern-Simons invariant is present, compute the fudge factor. * Then recompute the value from the fudge factor, to restore the * uncertainty between the ultimate and penultimate values. */ manifold->CS_value_is_known = data->CS_value_is_known; manifold->CS_value[ultimate] = data->CS_value; manifold->CS_value[penultimate] = data->CS_value; compute_CS_fudge_from_value(manifold); compute_CS_value_from_fudge(manifold); /* * Done. */ *manifold_ptr = manifold; } static void check_neighbors_and_gluings( Triangulation *manifold) { Tetrahedron *tet, *nbr; FaceIndex f, nbr_f; Permutation this_gluing; char scratch[256]; number_the_tetrahedra(manifold); for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) for (f = 0; f < 4; f++) { this_gluing = tet->gluing[f]; nbr = tet->neighbor[f]; nbr_f = EVALUATE(this_gluing, f); if (nbr->neighbor[nbr_f] != tet) { sprintf(scratch, "inconsistent neighbor data, tet %d face %d to tet %d face %d", tet->index, f, nbr->index, nbr_f); uAcknowledge(scratch); uFatalError("check_neighbors_and_gluings", "triangulations"); } if (nbr->gluing[nbr_f] != inverse_permutation[this_gluing]) { sprintf(scratch, "inconsistent gluing data, tet %d face %d to tet %d face %d", tet->index, f, nbr->index, nbr_f); uAcknowledge(scratch); uFatalError("check_neighbors_and_gluings", "triangulations"); } } } void triangulation_to_data( Triangulation *manifold, TriangulationData **data_ptr) { /* * Allocate the TriangulationData and write in the data describing * the manifold. Set *data_ptr to point to the result. The UI * should call free_triangulation_data() when it's done with the * TriangulationData. */ TriangulationData *data; Cusp *cusp; Tetrahedron *tet; int i, j, k, l, m; *data_ptr = NULL; data = NEW_STRUCT(TriangulationData); if (manifold->name != NULL) { data->name = NEW_ARRAY(strlen(manifold->name) + 1, char); strcpy(data->name, manifold->name); } else data->name = NULL; data->num_tetrahedra = manifold->num_tetrahedra; data->solution_type = manifold->solution_type[filled]; data->volume = volume(manifold, NULL); data->orientability = manifold->orientability; data->CS_value_is_known = manifold->CS_value_is_known; data->num_or_cusps = manifold->num_or_cusps; data->num_nonor_cusps = manifold->num_nonor_cusps; if (manifold->CS_value_is_known == TRUE) data->CS_value = manifold->CS_value[ultimate]; data->cusp_data = NEW_ARRAY(manifold->num_cusps, CuspData); for (i = 0; i < manifold->num_cusps; i++) { cusp = find_cusp(manifold, i); data->cusp_data[i].topology = cusp->topology; data->cusp_data[i].m = cusp->m; data->cusp_data[i].l = cusp->l; } number_the_tetrahedra(manifold); data->tetrahedron_data = NEW_ARRAY(manifold->num_tetrahedra, TetrahedronData); for (tet = manifold->tet_list_begin.next, i = 0; tet != &manifold->tet_list_end; tet = tet->next, i++) { for (j = 0; j < 4; j++) data->tetrahedron_data[i].neighbor_index[j] = tet->neighbor[j]->index; for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) data->tetrahedron_data[i].gluing[j][k] = EVALUATE(tet->gluing[j], k); /* * All negative cusp indices (for finite vertices) map to -1. * This could be changed if desired, but at the moment it's * consistent with TriagulationFileFormat. */ for (j = 0; j < 4; j++) data->tetrahedron_data[i].cusp_index[j] = ((tet->cusp[j]->index >= 0) ? tet->cusp[j]->index : -1); for (j = 0; j < 2; j++) for (k = 0; k < 2; k++) for (l = 0; l < 4; l++) for (m = 0; m < 4; m++) data->tetrahedron_data[i].curve[j][k][l][m] = tet->curve[j][k][l][m]; data->tetrahedron_data[i].filled_shape = (tet->shape[filled] != NULL) ? tet->shape[filled]->cwl[ultimate][0].rect : Zero; } *data_ptr = data; } void free_triangulation_data( TriangulationData *data) { /* * If the kernel allocates a TriangulationData structure, * the kernel must free it. */ if (data != NULL) { if (data->name != NULL) my_free(data->name); if (data->cusp_data != NULL) my_free(data->cusp_data); if (data->tetrahedron_data != NULL) my_free(data->tetrahedron_data); my_free(data); } } void free_triangulation( Triangulation *manifold) { Tetrahedron *dead_tet; EdgeClass *dead_edge; Cusp *dead_cusp; if (manifold != NULL) { if (manifold->name != NULL) my_free(manifold->name); while (manifold->tet_list_begin.next != &manifold->tet_list_end) { dead_tet = manifold->tet_list_begin.next; REMOVE_NODE(dead_tet); free_tetrahedron(dead_tet); } while (manifold->edge_list_begin.next != &manifold->edge_list_end) { dead_edge = manifold->edge_list_begin.next; REMOVE_NODE(dead_edge); my_free(dead_edge); } while (manifold->cusp_list_begin.next != &manifold->cusp_list_end) { dead_cusp = manifold->cusp_list_begin.next; REMOVE_NODE(dead_cusp); my_free(dead_cusp); } my_free(manifold); } } void free_tetrahedron( Tetrahedron *tet) { /* * This function does NOT remove the Tetrahedron from * any doubly linked list it may be on, but does remove * all data structures attached to the Tetrahedron. */ int i; for (i = 0; i < 2; i++) /* i = complete, filled */ if (tet->shape[i] != NULL) my_free(tet->shape[i]); clear_shape_history(tet); if (tet->cross_section != NULL) my_free(tet->cross_section); if (tet->canonize_info != NULL) my_free(tet->canonize_info); if (tet->cusp_nbhd_position != NULL) my_free(tet->cusp_nbhd_position); if (tet->extra != NULL) my_free(tet->extra); my_free(tet); } void clear_shape_history( Tetrahedron *tet) { int i; for (i = 0; i < 2; i++) /* i = complete, filled */ clear_one_shape_history(tet, i); } void clear_one_shape_history( Tetrahedron *tet, FillingStatus which_history) /* filled or complete */ { ShapeInversion *dead_shape_inversion; while (tet->shape_history[which_history] != NULL) { dead_shape_inversion = tet->shape_history[which_history]; tet->shape_history[which_history] = tet->shape_history[which_history]->next; my_free(dead_shape_inversion); } } void copy_triangulation( Triangulation *source, Triangulation **destination_ptr) { Triangulation *destination; Tetrahedron **new_tet; EdgeClass **new_edge; Cusp **new_cusp; Tetrahedron *tet; EdgeClass *edge; Cusp *cusp; int num_edge_classes, min_cusp_index, max_cusp_index, num_potential_cusps, i, j; /* * Allocate space for the new Triangulation. */ *destination_ptr = NEW_STRUCT(Triangulation); /* * Give it a local name. */ destination = *destination_ptr; /* * Copy the global information. * In a moment we'll overwrite the fields involving pointers. */ *destination = *source; /* * Allocate space for the name, and copy it it. */ destination->name = NEW_ARRAY(strlen(source->name) + 1, char); strcpy(destination->name, source->name); /* * Initialize the doubly linked lists. */ destination->tet_list_begin.prev = NULL; destination->tet_list_begin.next = &destination->tet_list_end; destination->tet_list_end.prev = &destination->tet_list_begin; destination->tet_list_end.next = NULL; destination->edge_list_begin.prev = NULL; destination->edge_list_begin.next = &destination->edge_list_end; destination->edge_list_end.prev = &destination->edge_list_begin; destination->edge_list_end.next = NULL; destination->cusp_list_begin.prev = NULL; destination->cusp_list_begin.next = &destination->cusp_list_end; destination->cusp_list_end.prev = &destination->cusp_list_begin; destination->cusp_list_end.next = NULL; /* * Assign consecutive indices to source's Tetrahedra and EdgeClasses. * The Cusps will already be numbered. * * While we're at it, count the EdgeClasses. * If no finite vertices are present, the number of EdgeClasses * will equal the number of Tetrahedra. */ number_the_tetrahedra(source); number_the_edge_classes(source); num_edge_classes = count_the_edge_classes(source); /* * Find the largest and smallest Cusp indices. */ min_cusp_index = source->cusp_list_begin.next->index; max_cusp_index = source->cusp_list_begin.next->index; for ( cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) { if (cusp->index < min_cusp_index) min_cusp_index = cusp->index; if (cusp->index > max_cusp_index) max_cusp_index = cusp->index; } num_potential_cusps = max_cusp_index - min_cusp_index + 1; /* * Allocate the new Tetrahedra, EdgeClasses and Cusps. * For the Cusps we want to allow for the possibility * that there'll be gaps in the indexing scheme. */ new_tet = NEW_ARRAY(source->num_tetrahedra, Tetrahedron *); for (i = 0; i < source->num_tetrahedra; i++) new_tet[i] = NEW_STRUCT(Tetrahedron); new_edge = NEW_ARRAY(num_edge_classes, EdgeClass *); for (i = 0; i < num_edge_classes; i++) new_edge[i] = NEW_STRUCT(EdgeClass); new_cusp = NEW_ARRAY(num_potential_cusps, Cusp *); for (i = 0; i < num_potential_cusps; i++) new_cusp[i] = NULL; for (cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) new_cusp[cusp->index - min_cusp_index] = NEW_STRUCT(Cusp); /* * Copy the fields of each Tetrahedron in source * to the corresponding fields in new_tet[i]. */ for (tet = source->tet_list_begin.next, i = 0; tet != &source->tet_list_end; tet = tet->next, i++) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_tet[i] = *tet; for (j = 0; j < 4; j++) { new_tet[i]->neighbor[j] = new_tet[tet->neighbor[j]->index]; new_tet[i]->gluing[j] = tet->gluing[j]; new_tet[i]->cusp[j] = new_cusp[tet->cusp[j]->index - min_cusp_index]; } for (j = 0; j < 6; j++) new_tet[i]->edge_class[j] = new_edge[tet->edge_class[j]->index]; for (j = 0; j < 2; j++) /* j = complete, filled */ if (tet->shape[j] != NULL) { new_tet[i]->shape[j] = NEW_STRUCT(TetShape); *new_tet[i]->shape[j] = *tet->shape[j]; } for (j = 0; j < 2; j++) /* j = complete, filled */ copy_shape_history(tet->shape_history[j], &new_tet[i]->shape_history[j]); if (tet->cusp_nbhd_position != NULL) { new_tet[i]->cusp_nbhd_position = NEW_STRUCT(CuspNbhdPosition); *new_tet[i]->cusp_nbhd_position = *tet->cusp_nbhd_position; } /* * Just to be safe. */ new_tet[i]->cross_section = NULL; new_tet[i]->canonize_info = NULL; new_tet[i]->extra = NULL; INSERT_BEFORE(new_tet[i], &destination->tet_list_end); } /* * Copy the fields of each EdgeClass in source * to the corresponding fields in new_edge[i]. */ for (edge = source->edge_list_begin.next, i = 0; edge != &source->edge_list_end; edge = edge->next, i++) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_edge[i] = *edge; new_edge[i]->incident_tet = new_tet[edge->incident_tet->index]; INSERT_BEFORE(new_edge[i], &destination->edge_list_end); } /* * Copy the fields of each Cusp in source * to the corresponding fields in new_cusp[i]. */ for (cusp = source->cusp_list_begin.next; cusp != &source->cusp_list_end; cusp = cusp->next) { /* * Copy all fields, * then overwrite the ones involving pointers. */ *new_cusp[cusp->index - min_cusp_index] = *cusp; INSERT_BEFORE(new_cusp[cusp->index - min_cusp_index], &destination->cusp_list_end); } /* * Free the arrays of pointers. */ my_free(new_tet); my_free(new_edge); my_free(new_cusp); } void copy_shape_history( ShapeInversion *source, ShapeInversion **dest) { while (source != NULL) { *dest = NEW_STRUCT(ShapeInversion); (*dest)->wide_angle = source->wide_angle; source = source->next; dest = &(*dest)->next; } *dest = NULL; } void initialize_triangulation( Triangulation *manifold) { manifold->name = NULL; manifold->num_tetrahedra = 0; manifold->solution_type[complete] = not_attempted; manifold->solution_type[filled] = not_attempted; manifold->orientability = unknown_orientability; manifold->num_cusps = 0; manifold->num_or_cusps = 0; manifold->num_nonor_cusps = 0; manifold->num_generators = 0; manifold->CS_value_is_known = FALSE; manifold->CS_fudge_is_known = FALSE; manifold->CS_value[ultimate] = 0.0; manifold->CS_value[penultimate] = 0.0; manifold->CS_fudge[ultimate] = 0.0; manifold->CS_fudge[penultimate] = 0.0; initialize_tetrahedron(&manifold->tet_list_begin); initialize_tetrahedron(&manifold->tet_list_end); manifold->tet_list_begin.prev = NULL; manifold->tet_list_begin.next = &manifold->tet_list_end; manifold->tet_list_end.prev = &manifold->tet_list_begin; manifold->tet_list_end.next = NULL; initialize_edge_class(&manifold->edge_list_begin); initialize_edge_class(&manifold->edge_list_end); manifold->edge_list_begin.prev = NULL; manifold->edge_list_begin.next = &manifold->edge_list_end; manifold->edge_list_end.prev = &manifold->edge_list_begin; manifold->edge_list_end.next = NULL; initialize_cusp(&manifold->cusp_list_begin); initialize_cusp(&manifold->cusp_list_end); manifold->cusp_list_begin.prev = NULL; manifold->cusp_list_begin.next = &manifold->cusp_list_end; manifold->cusp_list_end.prev = &manifold->cusp_list_begin; manifold->cusp_list_end.next = NULL; } void initialize_tetrahedron( Tetrahedron *tet) { int h, i, j, k; for (i = 0; i < 4; i++) { tet->neighbor[i] = NULL; tet->gluing[i] = 0; tet->cusp[i] = NULL; tet->generator_status[i] = unassigned_generator; tet->generator_index[i] = -1; tet->generator_parity[i] = -1; tet->corner[i] = Zero; tet->tilt[i] = -1.0e17; } for (h = 0; h < 2; h++) for (i = 0; i < 2; i++) for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) tet->curve[h][i][j][k] = 0; for (i = 0; i < 6; i++) { tet->edge_class[i] = NULL; tet->edge_orientation[i] = -1; } for (i = 0; i < 2; i++) { tet->shape[i] = NULL; tet->shape_history[i] = NULL; } tet->generator_path = -2; tet->cross_section = NULL; tet->canonize_info = NULL; tet->cusp_nbhd_position = NULL; tet->extra = NULL; tet->prev = NULL; tet->next = NULL; } void initialize_cusp( Cusp *cusp) { cusp->topology = unknown_topology; cusp->is_complete = TRUE; cusp->m = 0.0; cusp->l = 0.0; cusp->holonomy[ ultimate][M] = Zero; cusp->holonomy[ ultimate][L] = Zero; cusp->holonomy[penultimate][M] = Zero; cusp->holonomy[penultimate][L] = Zero; cusp->target_holonomy = TwoPiI; cusp->complex_cusp_equation = NULL; cusp->real_cusp_equation_re = NULL; cusp->real_cusp_equation_im = NULL; cusp->cusp_shape[initial] = Zero; cusp->cusp_shape[current] = Zero; cusp->shape_precision[initial] = 0; cusp->shape_precision[current] = 0; cusp->index = 255; cusp->displacement = 0.0; cusp->displacement_exp = 1.0; cusp->is_finite = FALSE; cusp->matching_cusp = NULL; cusp->prev = NULL; cusp->next = NULL; } void initialize_edge_class( EdgeClass *edge_class) { edge_class->order = 0; edge_class->incident_tet = NULL; edge_class->incident_edge_index = -1; edge_class->num_incident_generators = -1; edge_class->complex_edge_equation = NULL; edge_class->real_edge_equation_re = NULL; edge_class->real_edge_equation_im = NULL; edge_class->target_angle_sum = TwoPiI; edge_class->prev = NULL; edge_class->next = NULL; } FuncResult check_Euler_characteristic_of_boundary( Triangulation *manifold) { int num_edges; EdgeClass *edge; /* * check_Euler_characteristic_of_boundary() returns * func_OK if the Euler characteristic of the total * boundary is zero, and returns func_failed otherwise. * Note that (so far) all functions which call * check_Euler_characteristic_of_boundary() are testing * whether curves on the (intended) boundary components * have been pinched off. In these cases the Euler * characteristic of the affected boundary component * will increase, but never decrease. So knowing that * the Euler characteristic of the total boundary is * zero implies that the Euler characteristic of each * component is also zero. * * Checking the Euler characteristic of the boundary * is trivially easy -- you just check whether the * number of EdgeClasses in the manifold equals the * number of Tetrahedra. Here's the proof: * * Let v, e and f be the number of vertices, edges, and * faces in the triangulation of the boundary. * Let E, F and T be the number of edges, faces and * tetrahedra in the ideal triangulation of the * manifold. * * v = 2E * e = 3F = 6T * f = 4T * * v - e + f = 2E - 6T + 4T = 2(E - T) * * So the boundary topology will be correct iff E == T. */ /* * Count the number of edge classes. */ num_edges = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) num_edges++; /* * Compare the number of edges to the number of tetrahedra. */ if (num_edges != manifold->num_tetrahedra) return func_failed; else return func_OK; } /* * number_the_tetrahedra() fills in the index field of the Tetrahedra * according to their order in the manifold's doubly-linked list. * Indices range from 0 to (num_tetrahedra - 1). */ void number_the_tetrahedra( Triangulation *manifold) { Tetrahedron *tet; int count; count = 0; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) tet->index = count++; } /* * number_the_edge_classes() fills in the index field of the EdgeClasses * according to their order in the manifold's doubly-linked list. * Indices range from 0 to (number of EdgeClasses) - 1. */ void number_the_edge_classes( Triangulation *manifold) { EdgeClass *edge; int count; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) edge->index = count++; } static int count_the_edge_classes( Triangulation *manifold) { EdgeClass *edge; int count; count = 0; for (edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) count++; return count; } /* * compose_permutations() returns the composition of two permutations. * Permutations are composed right-to-left: the composition p1 o p0 * is what you get by first doing p0, then p1. */ Permutation compose_permutations( Permutation p1, Permutation p0) { Permutation result; int i; result = 0; for (i = 4; --i >= 0; ) { result <<= 2; result += EVALUATE(p1, EVALUATE(p0, i)); } return result; } regina-4.95/engine/snappea/kernel/unix_file_io.c000644 000765 000024 00000052652 12236247215 021616 0ustar00babstaff000000 000000 /* * unix_file_io.c * * This hacked together file allows unix-style programs * to read and save Triangulations. */ #include #include #include #include "unix_file_io.h" #define READ_OLD_FILE_FORMAT 0 #define FALSE 0 #define TRUE 1 /* * gcc complains about the * * use of `l' length character with `f' type character * * in fprintf() calls. Presumably it considers the 'l' unnecessary * because even floats would undergo default promotion to doubles * in the function call (see section A7.3.2 in Appendix A of K&R 2nd ed.). * Therefore I've changed "%lf" to "%f" in all fprintf() calls. * If this makes trouble on your system, change it back, and please * let me know (weeks@northnet.org). */ /* Modified 2010/09/20 by Marc Culler to avoid quadratic time behavior * caused by the fact that sscanf calls strlen. */ /* Size for our temporary string buffers. */ #define SBSIZE 256 /* Copy up to the specified number of words of the string str into the * specified buffer, which must hold at least SBSIZE chars. Return * the number of chars copied. This assumes that any line in a * triangulation file will have length < SBSIZE. */ #define whitespace(x) (x=='\040'||x=='\f'||x=='\n'||x=='\r'||x=='\t'||x=='\v') static int read_head(char *headbuf, char *str, int words) { char *ptr = headbuf; int n; for (n=0; n < words; n++) { /* copy whitespace */ while ( whitespace(*str) && ptr-headbuf < SBSIZE-1 ) *ptr++ = *str++; /* copy non-whitespace */ while ( *str != '\0' && !whitespace(*str) && ptr-headbuf < SBSIZE-1) *ptr++ = *str++; if ( *str == '\0' ) break; } /* add trailing null char */ *ptr = '\0'; return (ptr - headbuf); } /* Modified 04/23/09 by Marc Culler to allow reading a triangulation * from a string. */ static TriangulationData *ReadNewFileFormat(char *buffer); static void WriteNewFileFormat(FILE *fp, TriangulationData *data); static char *StringNewFileFormat(TriangulationData *data); #if READ_OLD_FILE_FORMAT extern FuncResult read_old_manifold(FILE *fp, Triangulation **manifold); #endif /* Modified 2010/7/27 by NMD to allow for Classic Mac OS and Windows line endings, as well Unix ones */ static Boolean is_eol_char(char *buffer); Boolean is_eol_char(char *buffer){ return (*buffer == '\n') || (*buffer == '\r'); } /* Modified 04/24/09 by Marc Culler to allow extracting a triangulation from a C string. */ Triangulation *read_triangulation_from_string( char *file_data) { TriangulationData *theTriangulationData; Triangulation *manifold; if ( strncmp("% Triangulation", file_data, 15) != 0) uFatalError("read_triangulation_from_string", "unix file io"); theTriangulationData = ReadNewFileFormat(file_data); data_to_triangulation(theTriangulationData, &manifold); free(theTriangulationData->name); free(theTriangulationData->cusp_data); free(theTriangulationData->tetrahedron_data); free(theTriangulationData); return manifold; } Triangulation *read_triangulation( char *file_name) { FILE *fp; Boolean theNewFormat; Triangulation *manifold; char firstline[100]; /* * If the file_name is nonempty, read the file. * If the file_name is empty, read from stdin. */ if (strlen(file_name) > 0) { fp = fopen(file_name, "r"); if (fp == NULL) return NULL; /* * Take a peek at the first line to see whether this is * the new file format or the old one. */ theNewFormat = (getc(fp) == '%'); rewind(fp); } else { fp = stdin; theNewFormat = TRUE; /* read only the new format from stdin */ } if (theNewFormat == TRUE) { TriangulationData *theTriangulationData; long filesize; char *buffer; if ( fseek(fp, 0, SEEK_END) != 0 || ( filesize = ftell(fp) ) == -1 || fseek(fp, 0, SEEK_SET) != 0 ) uFatalError("read_triangulation", "unix file io"); buffer = malloc(filesize + 1); if ( buffer == NULL) uFatalError("read_triangulation", "unix file io"); if ( fread(buffer, filesize, 1, fp) != 1 ) uFatalError("read_triangulation", "unix file io"); buffer[filesize] = '\0'; theTriangulationData = ReadNewFileFormat(buffer); free(buffer); data_to_triangulation(theTriangulationData, &manifold); free(theTriangulationData->name); free(theTriangulationData->cusp_data); free(theTriangulationData->tetrahedron_data); free(theTriangulationData); } else { #if READ_OLD_FILE_FORMAT read_old_manifold(fp, &manifold); #else fprintf(stderr, "The manifold is in the old file format.\n"); fprintf(stderr, "I recommend converting it to the new format.\n"); fprintf(stderr, "If absolutely necessary, I can provide code for reading the old format.\n"); fprintf(stderr, "Questions? Contact me at weeks@northnet.org.\n"); uFatalError("read_triangulation", "unix file io"); #endif } if (fp != stdin) fclose(fp); return manifold; } static TriangulationData *ReadNewFileFormat( char *buffer) { char *ptr; char theScratchString[SBSIZE]; char HeadBuffer[SBSIZE]; int count; TriangulationData *theTriangulationData; int theTotalNumCusps, i, j, k, v, f; size_t size = 0; /* * Read and ignore the header (% Triangulation). */ while (!is_eol_char(buffer)) buffer++; /* * Allocate the TriangulationData. */ theTriangulationData = (TriangulationData *) malloc(sizeof(TriangulationData)); if (theTriangulationData == NULL) uFatalError("ReadNewFileFormat 1", "unix file io"); theTriangulationData->name = NULL; theTriangulationData->cusp_data = NULL; theTriangulationData->tetrahedron_data = NULL; /* * Allocate and read the name of the manifold. * The name will be on the first nonempty line. */ while (is_eol_char(buffer)) buffer++; i = 0; ptr = NULL; while (!is_eol_char(buffer)){ if (i + 2 > size){ size += 100; ptr = (char *) realloc(ptr, size); if (ptr == NULL){ /* We could have saved the old pointer, to free it here. * But why bother? We are toast anyway. */ uFatalError("ReadNewFileFormat 2", "unix file io"); return NULL; } } ptr[i++] = *buffer++; } ptr[i] = 0; theTriangulationData->name = ptr; /* * Read the filled solution type. */ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%s", theScratchString); if (strcmp(theScratchString, "not_attempted") == 0) theTriangulationData->solution_type = not_attempted; else if (strcmp(theScratchString, "geometric_solution") == 0) theTriangulationData->solution_type = geometric_solution; else if (strcmp(theScratchString, "nongeometric_solution") == 0) theTriangulationData->solution_type = nongeometric_solution; else if (strcmp(theScratchString, "flat_solution") == 0) theTriangulationData->solution_type = flat_solution; else if (strcmp(theScratchString, "degenerate_solution") == 0) theTriangulationData->solution_type = degenerate_solution; else if (strcmp(theScratchString, "other_solution") == 0) theTriangulationData->solution_type = other_solution; else if (strcmp(theScratchString, "no_solution") == 0) theTriangulationData->solution_type = no_solution; else uFatalError("ReadNewFileFormat 3", "unix file io"); /* * Read the volume. */ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%lf", &theTriangulationData->volume); /* * Read the orientability. */ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%s", theScratchString); if (strcmp(theScratchString, "oriented_manifold") == 0) theTriangulationData->orientability = oriented_manifold; else if (strcmp(theScratchString, "nonorientable_manifold") == 0) theTriangulationData->orientability = nonorientable_manifold; else if (strcmp(theScratchString, "unknown_orientability") == 0) theTriangulationData->orientability = unknown_orientability; else uFatalError("ReadNewFileFormat 4", "unix file io"); /* * Read the Chern-Simons invariant, if present. */ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%s", theScratchString); if (strcmp(theScratchString, "CS_known") == 0) theTriangulationData->CS_value_is_known = TRUE; else if (strcmp(theScratchString, "CS_unknown") == 0) theTriangulationData->CS_value_is_known = FALSE; else uFatalError("ReadNewFileFormat 5", "unix file io"); if (theTriangulationData->CS_value_is_known == TRUE) { buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%lf", &theTriangulationData->CS_value); } else theTriangulationData->CS_value = 0.0; /* * Read the number of cusps, allocate an array for the cusp data, * and read the cusp data. */ buffer += read_head(HeadBuffer, buffer, 2); sscanf(HeadBuffer, "%d%d", &theTriangulationData->num_or_cusps, &theTriangulationData->num_nonor_cusps); theTotalNumCusps = theTriangulationData->num_or_cusps + theTriangulationData->num_nonor_cusps; theTriangulationData->cusp_data = (CuspData *) malloc(theTotalNumCusps * sizeof(CuspData)); if (theTriangulationData->cusp_data == NULL) uFatalError("ReadNewFileFormat 6", "unix file io"); for (i = 0; i < theTotalNumCusps; i++) { buffer += read_head(HeadBuffer, buffer, 3); if (sscanf(HeadBuffer, "%s%lf%lf", theScratchString, &theTriangulationData->cusp_data[i].m, &theTriangulationData->cusp_data[i].l) != 3) uFatalError("ReadNewFileFormat 7", "unix file io"); switch (theScratchString[0]) { case 't': case 'T': theTriangulationData->cusp_data[i].topology = torus_cusp; break; case 'k': case 'K': theTriangulationData->cusp_data[i].topology = Klein_cusp; break; default: uFatalError("ReadNewFileFormat 8", "unix file io"); } } /* * Read the number of tetrahedra, allocate an array for the * tetrahedron data, and read the tetrahedron data. */ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%d", &theTriangulationData->num_tetrahedra); theTriangulationData->tetrahedron_data = (TetrahedronData *) malloc(theTriangulationData->num_tetrahedra * sizeof(TetrahedronData)); if (theTriangulationData->tetrahedron_data == NULL) uFatalError("ReadNewFileFormat 9", "unix file io"); for (i = 0; i < theTriangulationData->num_tetrahedra; i++) { /* * Read the neighbor indices. */ for (j = 0; j < 4; j++) { buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%d", &theTriangulationData->tetrahedron_data[i].neighbor_index[j]); if (theTriangulationData->tetrahedron_data[i].neighbor_index[j] < 0 || theTriangulationData->tetrahedron_data[i].neighbor_index[j] >= theTriangulationData->num_tetrahedra) uFatalError("ReadNewFileFormat 10", "unix file io"); } /* * Read the gluings. */ /* This assumes that the gluings are groups of 4 digits with no whitespace between, but with whitespace between groups. */ buffer += read_head(HeadBuffer, buffer, 4); ptr = HeadBuffer; for (j = 0; j < 4; j++) for (k = 0; k < 4; k++) { sscanf(ptr, "%1d%n", &theTriangulationData->tetrahedron_data[i].gluing[j][k], &count); ptr += count; if (theTriangulationData->tetrahedron_data[i].gluing[j][k] < 0 || theTriangulationData->tetrahedron_data[i].gluing[j][k] > 3) uFatalError("ReadNewFileFormat 11", "unix file io"); } /* * Read the cusp indices. * * 99/06/04 Allow an index of -1 on "cusps" that are * really finite vertices. */ for (j = 0; j < 4; j++) { buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%d", &theTriangulationData->tetrahedron_data[i].cusp_index[j]); if (theTriangulationData->tetrahedron_data[i].cusp_index[j] < -1 || theTriangulationData->tetrahedron_data[i].cusp_index[j] >= theTotalNumCusps) uFatalError("ReadNewFileFormat 12", "unix file io"); } /* * Read the peripheral curves. */ for (j = 0; j < 2; j++) /* meridian, longitude */ for (k = 0; k < 2; k++) /* righthanded, lefthanded */ for (v = 0; v < 4; v++) for (f = 0; f < 4; f++){ buffer += read_head(HeadBuffer, buffer, 1); sscanf(HeadBuffer, "%d", &theTriangulationData->tetrahedron_data[i].curve[j][k][v][f]); } /* * Read the filled shape (which the kernel ignores). */ buffer += read_head(HeadBuffer, buffer, 2); sscanf(HeadBuffer, "%lf%lf", &theTriangulationData->tetrahedron_data[i].filled_shape.real, &theTriangulationData->tetrahedron_data[i].filled_shape.imag); } return theTriangulationData; } void write_triangulation( Triangulation *manifold, char *file_name) { TriangulationData *theTriangulationData; FILE *fp; /* * If the file_name is nonempty, write the file. * If the file_name is empty, write to stdout. */ if (strlen(file_name) > 0) { fp = fopen(file_name, "w"); if (fp == NULL) { printf("couldn't open %s\n", file_name); return; } } else fp = stdout; triangulation_to_data(manifold, &theTriangulationData); WriteNewFileFormat(fp, theTriangulationData); free_triangulation_data(theTriangulationData); if (fp != stdout) fclose(fp); } static void WriteNewFileFormat( FILE *fp, TriangulationData *data) { int i, j, k, v, f; fprintf(fp, "%% Triangulation\n"); if (data->name != NULL) fprintf(fp, "%s\n", data->name); else fprintf(fp, "untitled"); switch (data->solution_type) { case not_attempted: fprintf(fp, "not_attempted"); break; case geometric_solution: fprintf(fp, "geometric_solution"); break; case nongeometric_solution: fprintf(fp, "nongeometric_solution"); break; case flat_solution: fprintf(fp, "flat_solution"); break; case degenerate_solution: fprintf(fp, "degenerate_solution"); break; case other_solution: fprintf(fp, "other_solution"); break; case no_solution: fprintf(fp, "no_solution"); break; } if (data->solution_type != not_attempted) fprintf(fp, " %.8f\n", data->volume); else fprintf(fp, " %.1f\n", 0.0); switch (data->orientability) { case oriented_manifold: fprintf(fp, "oriented_manifold\n"); break; case nonorientable_manifold: fprintf(fp, "nonorientable_manifold\n"); break; } if (data->CS_value_is_known == TRUE) fprintf(fp, "CS_known %.16f\n", data->CS_value); else fprintf(fp, "CS_unknown\n"); fprintf(fp, "\n%d %d\n", data->num_or_cusps, data->num_nonor_cusps); for (i = 0; i < data->num_or_cusps + data->num_nonor_cusps; i++) fprintf(fp, " %s %16.12f %16.12f\n", (data->cusp_data[i].topology == torus_cusp) ? "torus" : "Klein", data->cusp_data[i].m, data->cusp_data[i].l); fprintf(fp, "\n"); fprintf(fp, "%d\n", data->num_tetrahedra); for (i = 0; i < data->num_tetrahedra; i++) { for (j = 0; j < 4; j++) fprintf(fp, "%4d ", data->tetrahedron_data[i].neighbor_index[j]); fprintf(fp, "\n"); for (j = 0; j < 4; j++) { fprintf(fp, " "); for (k = 0; k < 4; k++) fprintf(fp, "%d", data->tetrahedron_data[i].gluing[j][k]); } fprintf(fp, "\n"); for (j = 0; j < 4; j++) fprintf(fp, "%4d ", data->tetrahedron_data[i].cusp_index[j]); fprintf(fp, "\n"); for (j = 0; j < 2; j++) /* meridian, longitude */ for (k = 0; k < 2; k++) /* righthanded, lefthanded */ { for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) fprintf(fp, " %2d", data->tetrahedron_data[i].curve[j][k][v][f]); fprintf(fp, "\n"); } if (data->solution_type != not_attempted) fprintf(fp, "%16.12f %16.12f\n\n", data->tetrahedron_data[i].filled_shape.real, data->tetrahedron_data[i].filled_shape.imag); else fprintf(fp, "%3.1f %3.1f\n\n", 0.0, 0.0); } } /* Added by Marc Culler 2010-12-17 to allow writing a triangulation * to a string. Memory is malloc'ed for the string. Caller must free. */ char *string_triangulation( Triangulation *manifold) { TriangulationData *theTriangulationData; char *result; triangulation_to_data(manifold, &theTriangulationData); result = StringNewFileFormat(theTriangulationData); free_triangulation_data(theTriangulationData); return result; } static char *StringNewFileFormat( TriangulationData *data) { int i, j, k, v, f, size; char *buffer; char *p; size = 100*(10 + data->num_or_cusps + data->num_nonor_cusps + 8*data->num_tetrahedra); buffer = malloc(size); if ( buffer == NULL) uFatalError("StringNewFileFormat", "unix file io"); p = buffer; p += sprintf(p, "%% Triangulation\n"); if (data->name != NULL) p += sprintf(p, "%s\n", data->name); else p += sprintf(p, "untitled\n"); switch (data->solution_type) { case not_attempted: p += sprintf(p, "not_attempted"); break; case geometric_solution: p += sprintf(p, "geometric_solution"); break; case nongeometric_solution: p += sprintf(p, "nongeometric_solution"); break; case flat_solution: p += sprintf(p, "flat_solution"); break; case degenerate_solution: p += sprintf(p, "degenerate_solution"); break; case other_solution: p += sprintf(p, "other_solution"); break; case no_solution: p += sprintf(p, "no_solution"); break; } if (data->solution_type != not_attempted) p += sprintf(p, " %.8f\n", data->volume); else p += sprintf(p, " %.1f\n", 0.0); switch (data->orientability) { case oriented_manifold: p += sprintf(p, "oriented_manifold\n"); break; case nonorientable_manifold: p += sprintf(p, "nonorientable_manifold\n"); break; } if (data->CS_value_is_known == TRUE) p += sprintf(p, "CS_known %.16f\n", data->CS_value); else p += sprintf(p, "CS_unknown\n"); p += sprintf(p, "\n%d %d\n", data->num_or_cusps, data->num_nonor_cusps); for (i = 0; i < data->num_or_cusps + data->num_nonor_cusps; i++) p += sprintf(p, " %s %16.12f %16.12f\n", (data->cusp_data[i].topology == torus_cusp) ? "torus" : "Klein", data->cusp_data[i].m, data->cusp_data[i].l); p += sprintf(p, "\n"); p += sprintf(p, "%d\n", data->num_tetrahedra); for (i = 0; i < data->num_tetrahedra; i++) { for (j = 0; j < 4; j++) p += sprintf(p, "%4d ", data->tetrahedron_data[i].neighbor_index[j]); p += sprintf(p, "\n"); for (j = 0; j < 4; j++) { p += sprintf(p, " "); for (k = 0; k < 4; k++) p += sprintf(p, "%d", data->tetrahedron_data[i].gluing[j][k]); } p += sprintf(p, "\n"); for (j = 0; j < 4; j++) p += sprintf(p, "%4d ", data->tetrahedron_data[i].cusp_index[j]); p += sprintf(p, "\n"); for (j = 0; j < 2; j++) /* meridian, longitude */ for (k = 0; k < 2; k++) /* righthanded, lefthanded */ { for (v = 0; v < 4; v++) for (f = 0; f < 4; f++) p += sprintf(p, " %2d", data->tetrahedron_data[i].curve[j][k][v][f]); p += sprintf(p, "\n"); } if (data->solution_type != not_attempted) p += sprintf(p, "%16.12f %16.12f\n\n", data->tetrahedron_data[i].filled_shape.real, data->tetrahedron_data[i].filled_shape.imag); else p += sprintf(p, "%3.1f %3.1f\n\n", 0.0, 0.0); } return buffer; } regina-4.95/engine/snappea/kernel/unix_file_io.h000644 000765 000024 00000000664 12235724612 021617 0ustar00babstaff000000 000000 /* * unix_file_io.h * * These three functions allow unix-style programs * to read and save Triangulations. */ #include "SnapPea.h" extern Triangulation *read_triangulation(char *file_name); extern Triangulation *read_triangulation_from_string(char *file_data); extern void write_triangulation(Triangulation *manifold, char *file_name); extern char *string_triangulation(Triangulation *manifold); regina-4.95/engine/snappea/kernel/update_shapes.c000644 000765 000024 00000022671 12235724562 021774 0ustar00babstaff000000 000000 /* * update_shapes.c * * This file provides the function * * void update_shapes(Triangulation *manifold, Complex *delta); * * which is called by do_Dehn_filling() in hyperbolic_structure.c. * update_shapes() updates the shapes of the tetrahedra in *manifold * by the amounts specified in the array delta. If necessary, delta * is first scaled so that no delta[i].real or delta[i].imag exceeds * the limit specified by the constant allowable_change (see below). * * The entries in delta are interpreted relative to the coordinate system * given by the coordinate_system field of each Tetrahedron, and the * indexing of delta is assumed to correspond to the index field of each * tetrahedron. */ /* * The allowable_change constant specifies the maximum amount * the log of the complex edge parameter may change. * * allowable_change.real is the maximum allowable change in * the log of its modulus, and * * allowable_change.imag is the maximum allowable change in * its argument. * * If necessary, all the delta[i] are scaled by a constant (between * zero and one) so that no delta[i] exceeds the allowable change. * * * Setting allowable_change. * * A small value for allowable_change makes Newton's method slow, * but reliable. A larger value speeds it up, but increases the risk * of winding up on some funny branch of the solution space. * The values of allowable_change.real and allowable_change.imag * must not exceed 0.5, for the following reasons. * * (1) Because choose_coordinate_system() is called at the start of * each iteration of Newton's method, we know that the current * value of the edge parameter (relative to the chosen coordinate * system) satisfies |z-1| >= 1 and Re(z) <= 0.5 (see the comment * preceding choose_coordinate_system() in hyperbolic_structure.c). * Therefore if allowable_change.imag is less than pi/6 = 0.52..., * the parameter z cannot go more than half way to the singularity * at 1. If allowable_change.real is less than log(2) = 0.69..., * then z cannot go more than half way to the singularity at 0, nor * can it go "more than half way to infinity", in the sense that * its modulus cannot increase by more than a factor of two. * * (2) The code which maintains the shape_history assumes that when a * Tetrahedron's shape changes, the edge parameter given by * coordinate_system is the one passing through pi (mod 2 pi), and * the other two edge parameters are passing through 0 (mod 2 pi). * This assumption relies on the fact that allowable_change.imag * is less than pi/6 = 0.52... . */ #include "kernel.h" /* * The entries in allowable_change must not exceed 0.5. * See explanation above. */ static const Complex allowable_change = {0.5, 0.5}; static void scale_delta(Triangulation *manifold, Complex *delta); static void recompute_shapes(Triangulation *manifold, Complex *delta); void update_shapes( Triangulation *manifold, Complex *delta) { scale_delta(manifold, delta); recompute_shapes(manifold, delta); } static void scale_delta( Triangulation *manifold, Complex *delta) { int i; Complex max; double scaled_max, factor; /* * Find the maximum values of delta[i].real and delta[i].imag. */ max = Zero; for (i = 0; i < manifold->num_tetrahedra; i++) { if ( fabs(delta[i].real) > max.real ) max.real = fabs(delta[i].real); if ( fabs(delta[i].imag) > max.imag ) max.imag = fabs(delta[i].imag); } /* * Scale the solution if necessary. */ scaled_max = MAX( max.real/allowable_change.real, max.imag/allowable_change.imag ); if (scaled_max > 1.0) { factor = 1.0 / scaled_max; for (i = 0; i < manifold->num_tetrahedra; i++) delta[i] = complex_real_mult(factor, delta[i]); } } static void recompute_shapes( Triangulation *manifold, Complex *delta) { Tetrahedron *tet; int i, c[3]; Complex log_z, z[3], old_z, new_z; ShapeInversion *dead_shape_inversion, *new_shape_inversion; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) { /* * The array c[] is used to index the coordinate systems. * For example, if tet->coordinate_system is 1, then * c[0] = 1 (the current coordinate system), c[1] = 2 (the next * one), and c[2] = 0 (the one after that, cyclically speaking). */ for (i = 0; i < 3; i++) c[i] = (tet->coordinate_system + i) % 3; /* * Find the new value of log(z) in the primary coordinate system. */ log_z = complex_plus( tet->shape[filled]->cwl[ultimate][c[0]].log, /* old log_z */ delta[tet->index] /* change in log_z */ ); /* * Compute the new edge parameters in rectangular form. * Use z1 = 1/(1 - z0), etc. */ z[c[0]] = complex_exp(log_z); z[c[1]] = complex_div( One, complex_minus(One, z[c[0]]) ); z[c[2]] = complex_div( One, complex_minus(One, z[c[1]]) ); /* * Note the old z[0] and the new z[0]. * * If the Tetrahedron has experienced a ShapeInversion, update * its shape_history. * * Note that this approach is completely robust with respect * to roundoff errors. A Tetrahedron is considered positively * oriented if its shape has z.imag >= 0.0, and negatively * oriented if its shape has z.imag < 0.0. (It doesn't matter * whether z.imag == 0.0 is considered positively or negatively * oriented, just so we make a convention and use it * consistently.) Also, whether a Tetrahedron is perceived * as positively or negatively oriented is independent of its * coordinate_system: the above computation of z[c[0]], z[c[1]], * and z[c[2]] insures that the imaginary parts of all three will * have the same sign (-, 0, +), regardless of roundoff errors. */ old_z = tet->shape[filled]->cwl[ultimate][0].rect; new_z = z[0]; if ((old_z.imag >= 0.0) != (new_z.imag >= 0.0)) { /* * The Tetrahedron has undergone a ShapeInversion. * Because old_z is in the region |z-1| >= 1 and Re(z) <= 0.5 * (see the comment preceding choose_coordinate_system() in * hyperbolic_structure.c) and allowable_change.imag <= 0.5 < pi/6, * it follows that the edge parameter coordinate_system * passed through pi (mod 2 pi), and the other two edge * parameters passed through 0 (mod 2 pi). That is, the * ShapeInversion we are adding to the stack will have * wide_angle = coordinate_system. * * If the last item on the shape_history stack also has its * wide_angle field equal to the present coordinate_system, * then we remove it, because it cancels with the present * ShapeInversion. Otherwise we add the new ShapeInversion * to the stack. */ /* * If there's a nonempty shape_history stack and the last * ShapeInversion has wide_angle == coordinate_system, then * remove it. It cancels with the ShapeInversion we were * about to put on the stack. */ if (tet->shape_history[filled] != NULL && tet->shape_history[filled]->wide_angle == tet->coordinate_system) { dead_shape_inversion = tet->shape_history[filled]; tet->shape_history[filled] = tet->shape_history[filled]->next; my_free(dead_shape_inversion); } /* * Otherwise add the new ShapeInversion to the stack. */ else { new_shape_inversion = NEW_STRUCT(ShapeInversion); new_shape_inversion->wide_angle = tet->coordinate_system; new_shape_inversion->next = tet->shape_history[filled]; tet->shape_history[filled] = new_shape_inversion; } } /* * For each of the three complex edge parameters . . . */ for (i = 0; i < 3; i++) { /* * Copy the ultimate shape to the penultimate. */ tet->shape[filled]->cwl[penultimate][i] = tet->shape[filled]->cwl[ultimate][i]; /* * Copy in the new ultimate shape in rectangular form. */ tet->shape[filled]->cwl[ultimate][i].rect = z[i]; /* * Compute the log, using the argument of the previous log * to choose the branch (for analytic continuation). */ tet->shape[filled]->cwl[ultimate][i].log = complex_log( tet->shape[filled]->cwl[ultimate][i].rect, tet->shape[filled]->cwl[penultimate][i].log.imag ); } } } regina-4.95/engine/snappea/kernel/volume.c000644 000765 000024 00000020666 12235724562 020460 0ustar00babstaff000000 000000 /* * volume.c * * This file contains the function * * double volume(Triangulation *manifold, int *precision); * * which the kernel provides for the UI. It computes and returns * the volume of the manifold. If the pointer "precision" is not NULL, * volume() estimates the number of decimal places of accuracy, and * places the result in the variable *precision. The error estimate is * the difference in the computed volumes at the last and next-to-the-last * iterations of Newton's method (cf. hyperbolic_structures.c). * * 94/11/30 JRW This file now contains the function * * double birectangular_tetrahedron_volume( O31Vector a, * O31Vector b, * O31Vector c, * O31Vector d); * * as well, for use within the kernel. */ #include "kernel.h" static double Lobachevsky(double theta); double volume( Triangulation *manifold, int *precision) { int i, j; double vol[2]; /* vol[ultimate/penultimate] */ Tetrahedron *tet; for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ vol[i] = 0.0; for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next) if (tet->shape[filled] != NULL) for (i = 0; i < 2; i++) /* i = ultimate, penultimate */ for (j = 0; j < 3; j++) vol[i] += Lobachevsky(tet->shape[filled]->cwl[i][j].log.imag); if (precision != NULL) *precision = decimal_places_of_accuracy(vol[ultimate], vol[penultimate]); return vol[ultimate]; } /* * The Lobachevsky() function is based on the formula in * Milnor's article "Hyperbolic geometry: The first 150 years", * Bulletin of the American Mathematical Society, volume 6, number 1, * January 1982, pp. 9-24. The actual formula appears about 2/3 of * the way down page 18. */ static double Lobachevsky(double theta) { double term, sum, product, theta_over_pi_squared; const double *lobcoefptr; const static double lobcoef[30] = { 5.4831135561607547882413838888201e-1, 1.0823232337111381915160036965412e-1, 4.8444907713545197129262758561472e-2, 2.7891037672165120538296812180796e-2, 1.8199901365960328824311744707278e-2, 1.2823667776324462157674846128817e-2, 9.5243928393815114745643670962394e-3, 7.3530535460250636167039159668208e-3, 5.8479755397266959054962366178048e-3, 4.7619093045811136799814912001842e-3, 3.9525701124525799515137944808229e-3, 3.3333335320272968375315987081340e-3, 2.8490028914574211634331659107080e-3, 2.4630541963678177950454607261557e-3, 2.1505376364114568439132649094016e-3, 1.8939393943803620897114593734851e-3, 1.6806722690053911275277764855335e-3, 1.5015015015233512340706336099638e-3, 1.3495276653220485553945730785293e-3, 1.2195121951230603594927151084491e-3, 1.1074197120711266596728487987281e-3, 1.0101010101010675186059356321779e-3, 9.2506938020352840967144128733280e-4, 8.5034013605442478972252664720549e-4, 7.8431372549019677504189889653457e-4, 7.2568940493468811469129541350086e-4, 6.7340067340067343805464730535677e-4, 6.2656641604010025932192218654462e-4, 5.8445353594389246257711686275038e-4, 5.4644808743169398954500641421420e-4}; /* * As long as DBL_EPSILON > 5e-22 there will be enough lobcoefs * for the series. If DBL_EPSILON is smaller than this you'll * need to add more coefficients to the list. */ #if (DBL_DIG > 19) You need to check DBL_EPSILON. If it is less than 5e-22 you will need to provide more lobcoefs. #endif /* * Milnor (Lemma 1, p. 17) shows that the Lobachevsky function is * periodic with period pi. Put theta in the range [-pi/2, +pi/2]. */ while (theta > PI_OVER_2) theta -= PI; while (theta < -PI_OVER_2) theta += PI; /* * Milnor (Lemma 1, p. 17) also shows that the Lobachevsky function * is an odd function, so we can further restrict theta to the * range [0, pi/2]. */ if (theta < 0.0) return -Lobachevsky(-theta); /* * Handle theta == 0.0 specially, to avoid encountering * log(0.0) later on. */ if (theta == 0.0) return 0.0; theta_over_pi_squared = (theta/PI)*(theta/PI); sum = 0.0; product = 1.0; lobcoefptr = lobcoef; do { product *= theta_over_pi_squared; term = *lobcoefptr++ * product; sum += term; } while (term > DBL_EPSILON); return theta*(1.0 - log(2*theta) + sum); } double birectangular_tetrahedron_volume( O31Vector a, O31Vector b, O31Vector c, O31Vector d) { /* * Compute the volume of the birectangular tetrahedron with vertices * a, b, c and d, using the method found in section 4.3 of * * E. B. Vinberg, Ob'emy neevklidovykh mnogogrannikov, * Uspekhi Matematicheskix Nauk, May(?) 1993, 17-46. * * Our a, b, c and d correspond to Vinberg's A, B, C and D, as shown * in his Figure 9. We need to compute the dual basis {aa, bb, cc, dd} * defined by = 1, = = = 0, etc. * Let m be the matrix whose rows are the vectors {a, b, c, d}, but * with the entries in the first column negated to account for the * indefinite inner product, and let mm be the matrix whose columns * are the vectors {aa, bb, cc, dd}. Then (m)(mm) = identity, by the * definition of the dual basis. */ GL4RMatrix m, mm; O31Vector aa, bb, cc, dd; double alpha, beta, gamma, delta, big_delta, tetrahedron_volume; int i; /* * Set up the matrix m. */ for (i = 0; i < 4; i++) { m[0][i] = a[i]; m[1][i] = b[i]; m[2][i] = c[i]; m[3][i] = d[i]; } for (i = 0; i < 4; i++) m[i][0] = - m[i][0]; /* * The matrix mm will be the inverse of m, as explained above. * When m is singular, the birectangular tetrahedron's volume is zero. */ if (gl4R_invert(m, mm) != func_OK) return 0.0; /* * Read the dual basis {aa, bb, cc, dd} from mm. */ for (i = 0; i < 4; i++) { aa[i] = mm[i][0]; bb[i] = mm[i][1]; cc[i] = mm[i][2]; dd[i] = mm[i][3]; } /* * Any pair of dual vectors lies in a positive definite 2-plane * in E^(3,1). Normalize them to have length one, so we can use * their dot products to compute the dihedral angles. */ o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(aa,aa) ), aa, aa); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(bb,bb) ), bb, bb); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(cc,cc) ), cc, cc); o31_constant_times_vector( 1.0 / safe_sqrt ( o31_inner_product(dd,dd) ), dd, dd); /* * Compute the angles alpha, beta and gamma, as shown in * Vinberg's Figure 9. */ alpha = PI - safe_acos(o31_inner_product(aa, bb)); beta = PI - safe_acos(o31_inner_product(bb, cc)); gamma = PI - safe_acos(o31_inner_product(cc, dd)); /* * Compute big_delta and delta, as in * Vinberg's Sections 4.2 and 4.3. */ big_delta = sin(alpha) * sin(alpha) * sin(gamma) * sin(gamma) - cos(beta) * cos(beta); if (big_delta >= 0.0) uFatalError("birectangular_tetrahedron_volume", "volume"); delta = atan( safe_sqrt( - big_delta) / (cos(alpha) * cos(gamma)) ); tetrahedron_volume = 0.25 * ( Lobachevsky(alpha + delta) - Lobachevsky(alpha - delta) + Lobachevsky(gamma + delta) - Lobachevsky(gamma - delta) - Lobachevsky(PI_OVER_2 - beta + delta) + Lobachevsky(PI_OVER_2 - beta - delta) + 2.0 * Lobachevsky(PI_OVER_2 - delta) ); return tetrahedron_volume; } regina-4.95/engine/snappea/kernel/winged_edge.h000644 000765 000024 00000056622 12235724571 021420 0ustar00babstaff000000 000000 /* * winged_edge.h * * This file defines the usual "winged edge" data structure for * representing a convex polyhedron, along with some extra * fields describing the polyhedron's position in the projective * model of hyperbolic 3-space. (The "projective model" is * the Minkowski space model projected onto the hyperplane * x[0] == 1.) * * This file is intended solely for #inclusion in SnapPea.h. * It needs some of the typedefs which occur there. */ #ifndef _winged_edge_ #define _winged_edge_ #define INFINITE_RADIUS 1e20 #define INFINITE_DISTANCE 1e20 #define INFINITE_LENGTH 1e20 /* * The values of the following two enums are used to index the * static arrays in make_cube() in Dirichlet_construction.c, * so their values shouldn't be changed. (They are also toggled in * Dirichlet_extras.c Dirichlet_conversion.c with the "not" operator '!'.) */ typedef int WEEdgeEnd; enum { tail = 0, tip = 1 }; typedef int WEEdgeSide; enum { left = 0, right = 1 }; /* * The WEEdge structure keeps pointers to Tetrahedra for local use * in Dirichlet_conversion.c. The internal structure of a Tetrahedron * is private to the kernel, so we must include an "opaque typedef" here. * (Indeed even the existence of the Tetrahedron structure is private to * the kernel, so we are "cheating" a bit even by including the typedef.) */ typedef struct Tetrahedron TetrahedronSneak; /* * Forward declarations. */ typedef struct WEVertex WEVertex; typedef struct WEEdge WEEdge; typedef struct WEFace WEFace; typedef struct WEVertexClass WEVertexClass; typedef struct WEEdgeClass WEEdgeClass; typedef struct WEFaceClass WEFaceClass; typedef struct WEPolyhedron WEPolyhedron; struct WEVertex { /* * The vector x gives the position of the WEVertex in the * projective model of hyperbolic 3-space. The projective * model is viewed as a subset of the Minkowski space model, * so x is a 4-element vector with x[0] == 1.0. */ O31Vector x; /* * The vector xx[] is an extra copy of x[] for local use in * the user interface. Even as the polyhedron spins, the UI * should not modify x[], but should write the coordinates * of the rotated vertices into xx[] instead. There are two * reasons for this: * * (1) This scheme avoids cumulative roundoff error in the * coordinates, which could in principle deform the * polyhedron. One can argue that on a 680x0 Mac, which * has 8-byte mantissas, a polyhedron could spin for a * million years before enough error would accumulate * to make a 1-pixel difference on the screen. However, * on an Iris, which uses floats instead of doubles, the * roundoff error is a real issue. In any case, good * programming style dictates that a routine for displaying * a polyhedron should not alter the polyhedron it's * displaying. * * (2) If the user changes the Dehn filling coefficients * slightly, say from (7,1) to (8,1), the change in the * polyhedron will be small, and we'd like the new polyhedron * to appear in roughly the same position as the old one, * so the continuity is visible. For this to occur, x[] * must always contain the polyhedron's initial position, * not its rotated position. */ O31Vector xx; /* * The distance from the vertex to the origin. If the vertex is ideal, * dist is set to INFINITE_DISTANCE. Even though just the distance is * given here, the UI may want to display cosh(dist) as well. */ double dist; /* * Is this an ideal vertex? */ Boolean ideal; /* * The solid angle at this vertex of the Dirichlet domain. */ double solid_angle; /* * The Dirichlet domain's face pairings group the vertices * into vertex classes. */ WEVertexClass *v_class; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the vertex is visible to the user. */ Boolean visible; /* * The distance_to_plane field is used locally within * Dirichlet_construction.c to record the inner product * of the WEVertex's location x[] with an arbitrary but * fixed normal vector to the hyperplane currently under * consideration. Thus, distance_to_plane is proportional * to the Euclidean distance in the projective model from * the point (x[1], x[2], x[3]) to the intersection of the * hyperplane with the projective model (at x[0] == 1.0). * * The which_side_of_plane field is +1, 0 or -1 according * to whether, after accounting for possible roundoff error, * distance_to_plane is positive, zero or negative, respectively. */ double distance_to_plane; int which_side_of_plane; /* * The zero_order field is used locally in check_topology_of_cut() * to verify that precisely zero or two 0-edges are incident to * each 0-vertex. */ int zero_order; /* * The WEVertices are kept on a doubly-linked list. */ WEVertex *prev, *next; }; struct WEEdge { /* * v[tail] and v[tip] are the vertices incident to the * tail and tip, respectively, of the directed WEEdge. */ WEVertex *v[2]; /* * e[tail][left] is the WEEdge incident to both v[tail] and f[left]. * e[tail][right] is the WEEdge incident to both v[tail] and f[right]. * e[tip ][left] is the WEEdge incident to both v[tip ] and f[left]. * e[tip ][right] is the WEEdge incident to both v[tip ] and f[right]. */ WEEdge *e[2][2]; /* * f[left] and f[right] are the faces incident to the * left and right sides, respectively, of the directed WEEdge. */ WEFace *f[2]; /* * The dihedral angle between edge->f[left] and edge->f[right]. */ double dihedral_angle; /* * dist_line_to_origin is the distance to the origin from the line * containing the edge. * * dist_edge_to_origin is the distance to the origin from the edge * itself. Usually this will be the same as dist_line_to_origin, * but occasionally the minimum distance from the line to the origin * will be realized at a point not on the edge itself. In the latter * case the minimum distance from the origin to the edge itself will * be realized at an endpoint. * * Note: The UI may want to display the squared hyperbolic cosines * of the above distances, as well as the distances themselves. * * closest_point_on_line and closest_point_on_edge record the points * at which the above minima are realized. (At present the Mac UI * ignores these, but eventually we may want to display them to the * user upon request.) */ double dist_line_to_origin, dist_edge_to_origin; O31Vector closest_point_on_line, closest_point_on_edge; /* * How long is this edge? * If it's infinite, the length is set to INFINITE_LENGTH. */ double length; /* * The Dirichlet domain's face pairings group the edges * into edge classes. */ WEEdgeClass *e_class; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the edge is visible to the user. */ Boolean visible; /* * The Dirichlet domain's face identifications determine which sets * of edges are identified to single edges in the manifold itself. * For each edge on the Dirichlet domain, * * edge->neighbor[left] tells the WEEdge to which the given edge * is mapped by edge->f[left]->group_element, * * edge->preserves_sides[left] tell whether the left side of the * given edge maps to the left side of the image, * * edge->preserves_direction[left] tells whether the mapping * preserves the direction of the edge, and * * edge->preserves_orientation[left] tells whether the mapping * preserves orientation, * * and similarly for edge->neighbor[right], etc. * * The preserves_sides, preserves_direction and preserves_orientation * fields are intentionally redundant. Any two determine the third. * * If the singular set is empty or consists of disjoint circles (as will * always be the case for Dehn fillings on cusped manifolds), then the * function Dirichlet_bells_and_whistles() will redirect WEEdges as * necessary so that the preserves_direction[] fields are all TRUE. * Even for orbifolds with more complicated singular sets, it will * give consistent directions to the edges whenever possible. */ WEEdge *neighbor[2]; Boolean preserves_sides[2], preserves_direction[2], preserves_orientation[2]; /* * The tet[][] fields are used locally in Dirichlet_conversion.c * to construct a Triangulation for the manifold represented by * the Dirichlet domain. Otherwise they may be ignored. * The four Tetrahedra incident to this WEEdge are tet[tail][left], * tet[tail][right], tet[tip][left], and tet[tip][right]. */ TetrahedronSneak *tet[2][2]; /* * The WEEdges are kept on a doubly-linked list. */ WEEdge *prev, *next; }; struct WEFace { /* * some_edge is an arbitrary WEEdge incident to the given WEFace. */ WEEdge *some_edge; /* * mate is the WEFace which is identified to this face under * the action of the covering transformation group. */ WEFace *mate; /* * group_element is the O(3,1) matrix which takes this face's mate * to this face. In other words, this face lies in the plane which * passes orthogonally through the midpoint of the segment connecting * the origin to its (the origin's) image under the group_element. */ O31Matrix *group_element; /* * The distance from the face plane to the origin. The point of * closest approach may or may not lie on the face itself. */ double dist; O31Vector closest_point; /* * The to_be_removed field is used locally in install_new_face() in * Dirichlet_construction.c to record which WEFaces are to be removed. */ Boolean to_be_removed; /* * The clean field is used locally in check_faces() in * Dirichlet_construction.c to record which WEFaces are known to be * subsets of their mates under the action of the group_element. */ Boolean clean; /* * The copied field is used locally in rewrite_gen_list() and * (independently) in poly_to_current_list() in Dirichlet_construction.c * to record which WEFaces have had their group_elements copied to the * MatrixPairList. */ Boolean copied; /* * The matched field show that the WEEdges incident to this face have * been matched with their neighbors incident to face->mate. */ Boolean matched; /* * The visible field is used while displaying the WEPolyhedron to * keep track of whether the face is visible to the user. */ Boolean visible; /* * How many sides does this face have? */ int num_sides; /* * The face and its mate are assigned to the same WEFaceClass. * In the case of an orbifold, a face may be its own mate, in which * case the WEFaceClass will have only one element. */ WEFaceClass *f_class; /* * The WEFaces are kept on a doubly-linked list. */ WEFace *prev, *next; }; /* * The Dirichlet domain's face pairings identify the WEVertices, WEEdges * and WEFaces into WEVertexClasses, WEEdgeClasses and WEFaceClasses. * Each equivalence class is assigned an index. (The indices are * assigned consecutively, beginning at zero.) The index is used * to define a "hue", which is the suggested hue for that cell. * The function index_to_hue() in index_to_hue.c assigns hues in such * a way that the low-numbered cells' hues are all easily distinguishable * from one another. This is especially important for the WEFaceClasses. * If there are a large number of faces we can't possibly hope that all * the hues will be easily distinguishable, but we do want the hues of * the largest faces (which are closest to the origin and have the lowest * indices) to be easily distinguishable. */ struct WEVertexClass { /* * At present the WEVertexClasses are listed in arbitrary order, * but if necessary they could easily be sorted to provide * some control over their hues, as is done for WEFaceClasses. */ int index; double hue; int num_elements; /* * The total solid angle surrounding this vertex * (4pi for a manifold, 4pi/n for an orbifold). */ double solid_angle; /* * The "n" in the preceding 4pi/n is recorded as the singularity_order. * (For ideal vertices, n is set to zero.) */ int singularity_order; /* * Is this an ideal vertex class? */ Boolean ideal; /* * All the vertices in the vertex class should be the same distance from * the origin. Dirichlet_extras.c checks that the distances are indeed * approximately equal, and records their average here. */ double dist; /* * min_dist and max_dist are used locally in vertex_distances() * in Dirichlet_extras.c to check that the dist values of the * constituent vertices are consistent. */ double min_dist, max_dist; /* * belongs_to_region and is_3_ball are used locally in * compute_spine_radius() in Dirichlet_extras.c. * belongs_to_region keeps track of how various regions * have been united. is_3_ball records which such unified * regions are topologically 3-balls. */ WEVertexClass *belongs_to_region; Boolean is_3_ball; /* * The WEVertexClasses are kept on a doubly-linked list. */ WEVertexClass *prev, *next; }; struct WEEdgeClass { /* * At present the WEEdgeClasses are listed in arbitrary order, * but if necessary they could easily be sorted to provide * some control over their hues, as is done for WEFaceClasses. */ int index; double hue; int num_elements; /* * The total dihedral angle surrounding this edge * (2pi for a manifold, 2pi/n for an orbifold). */ double dihedral_angle; /* * The "n" in the preceding 2pi/n is recorded as the singularity_order. */ int singularity_order; /* * All the edges in the edge class should be the same distance from * the origin. Dirichlet_extras.c checks that the distances are indeed * approximately equal, and records their average here. */ double dist_line_to_origin, dist_edge_to_origin; /* * How long is the identified edge? * If it's infinite, the length is set to INFINITE_LENGTH. */ double length; /* * Performing the face identifications on the Dirichlet domain gives * a manifold or orbifold. The link of the midpoint of an edge will * be a 2-orbifold. */ Orbifold2 link; /* * min_line_dist and max_line_dist are used locally in edge_distances() * in Dirichlet_extras.c to check that the dist_line_to_origin values * of the constituent edges are consistent. */ double min_line_dist, max_line_dist; /* * min_length and max_length are used locally in edge_lengths() * in Dirichlet_extras.c to check that the length values * of the constituent edges are consistent. */ double min_length, max_length; /* * removed is used locally in compute_spine_radius() in Dirichlet_extras.c * to keep track of which 2-cells in the dual spine have been removed. */ Boolean removed; /* * The WEEdgeClasses are kept on a doubly-linked list. */ WEEdgeClass *prev, *next; }; struct WEFaceClass { /* * Indices are assigned to face classes in order of increasing * distance from the origin. The closest face class gets index 0, * the next closest gets index 1, etc. (The distance is actually the * distance from the origin to the plane containing the face, whether * or not the face happens to include the point where the plane is * closest to the origin.) * * Lemma. A face and its mate are the same distance from the origin. * * Proof. The face plane is midway between the origin and the * origin's image under the group_element. d(g^-1(origin), origin) * = d(origin, g(origin)). Q.E.D. * * The function index_to_hue() in index_to_hue.c insures that * the largest faces have easily distinguishable colors. For example, * a (37,1) Dehn surgery on the figure eight knot yields a Dirichlet * domain with a few large faces and many tiny ones. We wouldn't want * to color the large faces with, say, twelve different shades of blue. * The index-to-hue conversion scheme spreads their hues evenly through * the spectrum. */ int index; double hue; /* * Typically a WEFaceClass will have two elements, but if a face is * glued to itself (in an orbifold), then the WEFaceClass will have * only one element. */ int num_elements; /* * The distance from the face plane to the origin. The point of * closest approach may or may not lie on the face itself. */ double dist; /* * Is the gluing orientation_reversing or orientation_preserving? */ MatrixParity parity; /* * The WEFaceClasses are kept on a doubly-linked list. */ WEFaceClass *prev, *next; }; struct WEPolyhedron { int num_vertices, num_edges, num_faces; int num_finite_vertices, num_ideal_vertices; int num_vertex_classes, num_edge_classes, num_face_classes; int num_finite_vertex_classes, num_ideal_vertex_classes; /* * Because matrices in O(3,1) tend to accumulate roundoff error, it's * hard to get a good bound on the accuracy of the computed volume. * Nevertheless, the kernel computes the best value it can, with the * hope that it will aid the user in recognizing manifolds defined * by a set of generators. (The volume of a manifold defined by * Dehn filling a Triangulation can be computed directly to great * accuracy, using the kernel's volume() function.) */ double approximate_volume; /* * The inradius is the radius of the largest sphere (centered at the * basepoint) which can be inscribed in the Dirichlet domain. * The outradius is the radius of the smallest sphere (centered at the * basepoint) which can be circumscribed about the Dirichlet domain. * The outradius will be infinite for cusped manifolds, in which * case it's set to INFINITE_RADIUS. */ double inradius, outradius; /* * spine_radius is the infimum of the radii (measured from the origin) * of all spines dual to the Dirichlet domain. compute_spine_radius() * in Dirichlet_extras.c shows that spine_radius equals the maximum * of dist_edge_to_origin over all edge classes. The spine_radius * plays an essential role in the length spectrum routines. * * Note: In practice compute_spine_radius() removes selected 2-cells * from the spine to reduce its radius, and thereby reduce the time * required to compute length spectra. Please see compute_spine_radius() * in Dirichlet_extras.c for details. */ double spine_radius; /* * Each face pairing isometry is an element of SO(3,1), so the inner * products of its i-th column with its j-th column should be -1 (if * i = j = 0), +1 (if i = j != 0) or 0 (if i != j). The greatest * deviation from these values (over all faces) is recorded in the * deviation field. */ double deviation; /* * The geometric Euler characteristic of the quotient orbifold (i.e. the * orbifold obtained by doing the face identifications) is computed as * * c[0] - c[1] + c[2] - c[3] * * where * * c[0] = the sum of the solid angles at the vertices divided by 4 pi, * * c[1] = the sum of the dihedral angles at the edges divided by 2 pi, * * c[2] = half the number of faces of the Dirichlet domain, * * c[3] = the number of 3-cells, which is always one. * * This corresponds to the definition of the Euler characteristic of an * orbifold, as explained in Chapter 5 of the 1991 version of Thurston's * notes. It should, in theory, always come out to zero. But since * we compute it using floating point approximations to the solid and * dihedral angles, it provides a measure of the numerical inaccuracies * in the computation. */ double geometric_Euler_characteristic; /* * vertex_epsilon is used in the construction of the Dirichlet domain. * If the squared distance from a vertex to a hyperplane is within * vertex_epsilon of zero, the vertex is assumed to lie on the hyperplane. * If vertex_epsilon is too large, we won't be able to resolve the small * faces which occur in high order Dehn fillings. If vertex_epsilon is * too small, we'll get spurious Dirichlet plane intersections for * manifolds with simple, symmetrical, non-general-position covering * transformation groups. */ double vertex_epsilon; /* * The following dummy nodes serve as the beginnings and ends of the * doubly-linked lists of vertices, edges and faces. */ WEVertex vertex_list_begin, vertex_list_end; WEEdge edge_list_begin, edge_list_end; WEFace face_list_begin, face_list_end; /* * The following dummy nodes serve as the beginnings and ends of the * doubly-linked lists of vertex classes, edge classes and face classes. */ WEVertexClass vertex_class_begin, vertex_class_end; WEEdgeClass edge_class_begin, edge_class_end; WEFaceClass face_class_begin, face_class_end; }; #endif regina-4.95/engine/snappea/nsnappeatriangulation.cpp000644 000765 000024 00000026672 12236247215 022636 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "maths/nmatrixint.h" #include "snappea/nsnappeatriangulation.h" #include "snappea/kernel/kernel_prototypes.h" #include "snappea/kernel/triangulation.h" #include "snappea/snappy/SnapPy.h" #include "triangulation/ntriangulation.h" #include "utilities/nthread.h" #ifdef __cplusplus extern "C" { #endif #include "snappea/kernel/unix_file_io.h" #ifdef __cplusplus } #endif namespace regina { namespace { /** * A mutex to protect kernelMessages. */ static NMutex snapMutex; } NSnapPeaTriangulation::NSnapPeaTriangulation(const NSnapPeaTriangulation& tri) : ShareableObject() { if (tri.snappeaData) ::copy_triangulation(tri.snappeaData, &snappeaData); else snappeaData = 0; } NSnapPeaTriangulation::NSnapPeaTriangulation(const NTriangulation& tri, bool allowClosed) { snappeaData = reginaToSnapPea(tri, allowClosed); if (snappeaData) { ::find_complete_hyperbolic_structure(snappeaData); } } NSnapPeaTriangulation::~NSnapPeaTriangulation() { ::free_triangulation(snappeaData); } NSnapPeaTriangulation::SolutionType NSnapPeaTriangulation::solutionType() const { if (! snappeaData) return NSnapPeaTriangulation::not_attempted; return static_cast(::get_complete_solution_type(snappeaData)); } double NSnapPeaTriangulation::volume() const { if (! snappeaData) return 0; return ::volume(snappeaData, 0); } double NSnapPeaTriangulation::volume(int& precision) const { if (! snappeaData) return 0; return ::volume(snappeaData, &precision); } NTriangulation* NSnapPeaTriangulation::canonize() const { if (! snappeaData) return 0; ::Triangulation* tmp; ::copy_triangulation(snappeaData, &tmp); if (::canonize(tmp) != ::func_OK) { ::free_triangulation(tmp); return 0; } NTriangulation* ans = snapPeaToRegina(tmp); ::free_triangulation(tmp); return ans; } void NSnapPeaTriangulation::randomize() { if (! snappeaData) return; ::randomize_triangulation(snappeaData); ::find_complete_hyperbolic_structure(snappeaData); } /** * Written by William Pettersson, 2011. */ NMatrixInt* NSnapPeaTriangulation::slopeEquations() const { int i,j; if (! snappeaData) return 0; NMatrixInt* matrix = new NMatrixInt(2*snappeaData->num_cusps, 3*snappeaData->num_tetrahedra); for(i=0; i< snappeaData->num_cusps; i++) { int numRows; // SnapPea returns "a b c" for each tetrahedron, where the // derivative of the holonomy of meridians and longitudes is given as // a log (z_0) + b log ( 1/(1-z_0)) + c log ((z_0 - 1)/z_0) + ... = 0 // // The equation for slopes in terms of quads of types q, q' and q'' // becomes // nu = (b-c)q + (c-a)q' + (a-b)q'' // // See Lemma 4.2 in "Degenerations of ideal hyperbolic triangulations", // Stephan Tillmann, Mathematische Zeitschrift, // DOI: 10.1007/s00209-011-0958-8. // int *equations = ::get_cusp_equation(snappeaData, i, 1, 0, &numRows); for(j=0; j< snappeaData->num_tetrahedra; j++) { matrix->entry(2*i,3*j) = equations[3*j+1] - equations[3*j+2]; matrix->entry(2*i,3*j+1) = equations[3*j+2] - equations[3*j]; matrix->entry(2*i,3*j+2) = equations[3*j] - equations[3*j+1]; } ::free_cusp_equation(equations); equations = ::get_cusp_equation(snappeaData, i, 0, 1, &numRows); for(j=0; j< snappeaData->num_tetrahedra; j++) { matrix->entry(2*i+1,3*j) = equations[3*j+1] - equations[3*j+2]; matrix->entry(2*i+1,3*j+1) = equations[3*j+2] - equations[3*j]; matrix->entry(2*i+1,3*j+2) = equations[3*j] - equations[3*j+1]; } ::free_cusp_equation(equations); } return matrix; } /** * Written by William Pettersson, 2011. */ bool NSnapPeaTriangulation::verifyTriangulation(const NTriangulation& tri) const { if (! snappeaData) return false; ::TriangulationData *data; ::triangulation_to_data(snappeaData, &data); int tet, face, i; if (data->num_tetrahedra != tri.getNumberOfTetrahedra()) { free_triangulation_data(data); return false; } NTriangulation::TetrahedronIterator it = tri.getTetrahedra().begin(); for (tet = 0; tet < data->num_tetrahedra; tet++) { for (face = 0; face < 4; face++) { if (data->tetrahedron_data[tet].neighbor_index[face] != tri.tetrahedronIndex((*it)->adjacentTetrahedron(face))) { free_triangulation_data(data); return false; } for (i = 0; i < 4; i++) if (data->tetrahedron_data[tet].gluing[face][i] != (*it)->adjacentGluing(face)[i]) { free_triangulation_data(data); return false; } } it++; } free_triangulation_data(data); return true; } void NSnapPeaTriangulation::saveAsSnapPea(const char* filename) const { if (snappeaData) write_triangulation(snappeaData, const_cast(filename)); } void NSnapPeaTriangulation::writeTextShort(std::ostream& out) const { if (snappeaData) { out << "SnapPea triangulation with " << snappeaData->num_tetrahedra << " tetrahedra."; } else { out << "Null SnapPea triangulation"; } } ::Triangulation* NSnapPeaTriangulation::reginaToSnapPea( const NTriangulation& tri, bool allowClosed) { // Make sure SnapPea is likely to be comfortable with it. if (tri.getNumberOfTetrahedra() == 0) return 0; if (tri.hasBoundaryTriangles()) return 0; if (! tri.isConnected()) return 0; if (! tri.isValid()) return 0; if (! tri.isStandard()) return 0; if (tri.isIdeal()) { // If it's ideal, make sure every vertex is ideal. if (tri.getNumberOfBoundaryComponents() < tri.getNumberOfVertices()) return 0; } else { // No boundary triangles, not ideal.. must be closed. if (! allowClosed) return 0; // If closed is okay, at least make sure it's one-vertex. if (1 != tri.getNumberOfVertices()) return 0; } if (tri.getNumberOfTetrahedra() >= INT_MAX) return 0; ::TriangulationData data; data.name = strdup(tri.getPacketLabel().c_str()); data.num_tetrahedra = static_cast(tri.getNumberOfTetrahedra()); // Fields recalculated by SnapPea: data.solution_type = ::not_attempted; data.volume = 0; data.orientability = ::unknown_orientability; data.CS_value_is_known = false; data.CS_value = 0; data.num_or_cusps = 0; data.num_nonor_cusps = 0; data.cusp_data = 0; data.tetrahedron_data = new ::TetrahedronData[data.num_tetrahedra]; int tet, face, i, j, k, l; NTriangulation::TetrahedronIterator it = tri.getTetrahedra().begin(); for (tet = 0; tet < data.num_tetrahedra; tet++) { for (face = 0; face < 4; face++) { data.tetrahedron_data[tet].neighbor_index[face] = static_cast( tri.tetrahedronIndex((*it)->adjacentTetrahedron(face))); for (i = 0; i < 4; i++) data.tetrahedron_data[tet].gluing[face][i] = (*it)->adjacentGluing(face)[i]; } // Other fields are recalculated by SnapPea. for (i = 0; i < 4; i++) data.tetrahedron_data[tet].cusp_index[i] = -1; for (i = 0; i < 2; i++) for (j = 0; j < 2; j++) for (k = 0; k < 4; k++) for (l = 0; l < 4; l++) data.tetrahedron_data[tet].curve[i][j][k][l] = 0; data.tetrahedron_data[tet].filled_shape.real = 0; data.tetrahedron_data[tet].filled_shape.imag = 0; it++; } ::Triangulation* ans; ::data_to_triangulation(&data, &ans); delete[] data.tetrahedron_data; free(data.name); return ans; } bool NSnapPeaTriangulation::kernelMessagesEnabled() { NMutex::MutexLock ml(snapMutex); return kernelMessages; } void NSnapPeaTriangulation::enableKernelMessages(bool enabled) { NMutex::MutexLock ml(snapMutex); kernelMessages = enabled; } void NSnapPeaTriangulation::disableKernelMessages() { NMutex::MutexLock ml(snapMutex); kernelMessages = false; } NTriangulation* NSnapPeaTriangulation::snapPeaToRegina(::Triangulation* tri) { if (! tri) return 0; ::TriangulationData* data; ::triangulation_to_data(tri, &data); NTriangulation* ans = new NTriangulation(); ans->setPacketLabel(data->name); NTetrahedron** tet = new NTetrahedron*[data->num_tetrahedra]; int i, j; for (i = 0; i < data->num_tetrahedra; ++i) tet[i] = ans->newTetrahedron(); for (i = 0; i < data->num_tetrahedra; ++i) for (j = 0; j < 4; ++j) if (! tet[i]->adjacentTetrahedron(j)) tet[i]->joinTo(j, tet[data->tetrahedron_data[i].neighbor_index[j]], NPerm4(data->tetrahedron_data[i].gluing[j])); delete[] tet; ::free_triangulation_data(data); return ans; } } // namespace regina regina-4.95/engine/snappea/nsnappeatriangulation.h000644 000765 000024 00000056336 12236247215 022303 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file snappea/nsnappeatriangulation.h * \brief Allows Regina triangulations to interact with the SnapPea kernel. */ #ifndef __NSNAPPEATRIANGULATION_H #ifndef __DOXYGEN #define __NSNAPPEATRIANGULATION_H #endif #include "regina-config.h" // For EXCLUDE_SNAPPEA #include "regina-core.h" #include "shareableobject.h" // Forward declaration of SnapPea structures. struct Triangulation; namespace regina { class NMatrixInt; class NTriangulation; /** * \weakgroup triangulation * @{ */ #ifndef EXCLUDE_SNAPPEA /** * Offers direct access to the SnapPea kernel from within Regina. * * An object of this class represents a 3-manifold triangulation, stored in * SnapPea's internal format. Such an object may be constructed by cloning * either a standard Regina triangulation or another SnapPea triangulation. * * Note that not all Regina triangulations can be represented in SnapPea * format, since Regina works with more general kinds of triangulation * than SnapPea does. You should always call isNull() to test whether any * Regina-to-SnapPea conversion was successful. * * This class is designed to act as the sole conduit between the Regina * calculation engine and the SnapPea kernel. Regina code should not * interact with the SnapPea kernel other than through this class. * * Regina uses the variant of the SnapPea kernel that is shipped with * SnapPy, as well as some additional code written explicitly for SnapPy. * The SnapPea kernel was written by Jeff Weeks, and SnapPy was written by * Marc Culler, Nathan Dunfield, and others. SnapPy and the corresponding * SnapPea kernel are distributed under the terms of the GNU General * Public License, version 2 or any later version, as published by the * Free Software Foundation. * * See http://snappy.computop.org/ for further information on * SnapPea and its successor SnapPy. * * \testpart */ class REGINA_API NSnapPeaTriangulation : public ShareableObject { public: /** * Describes the different types of solution that can be found when * solving for a complete hyperbolic structure. * * Although this enumeration is identical to SnapPea's own * SolutionType, it is declared again in this class because Regina * code should not in general be interacting directly with the * SnapPea kernel. Values may be freely converted between the * two enumeration types by simple assignment and/or typecasting. * * \warning This enumeration must always be kept in sync with * SnapPea's own SolutionType enumeration. */ typedef enum { not_attempted, /**< A solution has not been attempted. */ geometric_solution, /**< All tetrahedra are either positively oriented or flat, though the entire solution is not flat and no tetrahedra are degenerate. */ nongeometric_solution, /**< The volume is positive, but some tetrahedra are negatively oriented. */ flat_solution, /**< All tetrahedra are flat, but none have shape 0, 1 or infinity. */ degenerate_solution, /**< At least one tetrahedron has shape 0, 1 or infinity. */ other_solution, /**< The volume is zero or negative, but the solution is neither flat nor degenerate. */ no_solution /**< The gluing equations could not be solved. */ } SolutionType; private: ::Triangulation* snappeaData; /**< The triangulation stored in SnapPea's native format. */ static bool kernelMessages; /**< Should the SnapPea kernel write diagnostic messages to standard output? */ public: /** * Creates a SnapPea clone of the given SnapPea triangulation. * This SnapPea triangulation stands independent of \a tri, * so this triangulation will not be affected if \a tri is later * changed or destroyed. * * If \a tri is a null triangulation then this will be a null * triangulation also. See isNull() for further details. * * Note that the tetrahedron and vertex numbers might be changed * in the new SnapPea triangulation. * * @param tri the SnapPea triangulation to clone. */ NSnapPeaTriangulation(const NSnapPeaTriangulation& tri); /** * Creates a SnapPea clone of the given Regina triangulation. * This SnapPea triangulation stands independent of \a tri, * so this triangulation will not be affected if \a tri is later * changed or destroyed. * * Note that, since Regina works with more general kinds of * trianguations than SnapPea, not all Regina triangulations can be * represented in SnapPea format. If the conversion is * unsuccessful, this will be marked as a null triangulation. * You should always test isNull() to determine whether the * conversion was successful. * * If the conversion is successful, this constructor will immediately * ask SnapPea to try to find a complete hyperbolic structure. * * SnapPea is designed primarily to work with ideal * triangulations only. Passing closed triangulations can * occasionally cause the SnapPea kernel to raise a fatal error, * which in turn will crash the entire program. Thus by default, * closed triangulations are never converted (a null SnapPea * triangulation will be created instead). See the optional * argument \a allowClosed for how to change this behaviour. * * Note also that the tetrahedron and vertex numbers might be changed * in the new SnapPea triangulation. In particular, if the * given triangulation is orientable but not oriented, then you * should expect these numbers to change. * * \warning Passing \a allowClosed as \c true can occasionally * cause the program to crash! See the notes above for details. * * @param tri the Regina triangulation to clone. * @param allowClosed \c true if closed triangulations should be * considered, or \c false if all closed triangulations should give * null SnapPea data (the default). See above for details. */ NSnapPeaTriangulation(const NTriangulation& tri, bool allowClosed = false); /** * Destroys this triangulation. All internal SnapPea data will * also be destroyed. */ ~NSnapPeaTriangulation(); /** * Determines whether this triangulation contains valid SnapPea * data. * * A null SnapPea triangulation can occur when converting unusual * types of Regina triangulation into SnapPea format, since * Regina is written to deal with more general types of triangulations * than SnapPea. * * @return \c true if this is a null triangulation, or \c false * if this triangulation contains valid SnapPea data. */ bool isNull() const; /** * Returns the type of solution found when solving for a complete * hyperbolic structure. * * Note that SnapPea distinguishes between a complete hyperbolic * structure and a Dehn filled hyperbolic structure. At the present * time Regina does not concern itself with Dehn fillings, so only * the complete solution type is offered here. * * @return the solution type. */ SolutionType solutionType() const; /** * Computes the volume of the underlying 3-manifold. * * @return the volume of the underlying 3-manifold, or 0 if this * is a null triangulation. */ double volume() const; /** * Computes the volume of the underlying 3-manifold and * estimates the accuracy of the answer. * * @param precision used to return an estimate of the number of * decimal places of accuracy in the calculated volume. * * \ifacespython The \a precision argument is not present. * Instead, two routines are offered. The routine \a volume() * takes no arguments and returns the volume only, whereas the * routine \a volumeWithPrecision() takes no arguments and * returns a (\a volume, \a precision) tuple. * * @return the volume of the underlying 3-manifold, or 0 if this * is a null triangulation. */ double volume(int& precision) const; /** * Returns a matrix for computing boundary slopes of * spun-normal surfaces at the cusps of the triangulation. This * matrix includes a pair of rows for each cusp in the triangulation: * one row for determining the algebraic intersection number * with the meridian, and one row for determining the algebraic * intersection number with the longitude. * If the triangulation has more than one cusp, these pairs are * ordered by vertex number in the triangulation. Within each * pair, the meridian row always appears before the longitude row. * * This matrix is constructed so that, if \a M and \a L are the * rows for the meridian and longitude at some cusp, then for * any spun-normal surface with quadrilateral coordinates * \a q, the boundary curves have algebraic intersection number * M.q with the meridian and L.q with the longitude. * Equivalently, the boundary curves pass L.q times around the * meridian and -M.q times around the longitude. * To compute these slopes directly from a normal surface, see * NNormalSurface::boundarySlopes(). * * This code makes use of the \e SnapPy kernel, and the choice * of meridian and longitude on each cusp follows \e SnapPy's * conventions. In particular, we use the orientations for * meridian and longitude from \e SnapPy. The orientations of the * boundary curves of a spun-normal surface are chosen so * that \e if meridian and longitude are a positive basis as * vieved from the cusp, then as one travels along an oriented * boundary curve, the spun-normal surface spirals into the cusp * to one's right and down into the manifold to one's left. * * \pre All vertex links in this triangulation must be tori. * * @author William Pettersson and Stephan Tillmann * * @return a newly allocated matrix with (2 * \a number_of_cusps) rows * and (3 * \a number_of_tetrahedron) columns as described above, * or 0 if this is a null triangulation. */ NMatrixInt* slopeEquations() const; /** * Constructs the canonical retriangulation of the canonical * cell decomposition. * * The canonical cell decomposition is the one described in * "Convex hulls and isometries of cusped hyperbolic 3-manifolds", * Jeffrey R. Weeks, Topology Appl. 52 (1993), 127-149. * The canoical retriangulation is defined as follows: If the * canonical cell decomposition is already a triangulation then * we leave it untouched. Otherwise: (i) within each 3-cell of * the original complex we introduce a new internal (finite) * vertex and cone the 3-cell boundary to this new vertex, and * (ii) for each 2-cell of the original complex we replace the * two new cones on either side with a ring of tetrahedra surrounding * a new edge that connects the two new vertices on either side. * See canonize_part_2.c in the SnapPea source code for details. * * The resulting triangulation will be newly allocated, and it * is the responsibility of the caller of this routine to destroy it. * * If for any reason either Regina or SnapPea are unable to * construct the canonical retriangulation of the canonical cell * decomposition, this routine will return 0. * * \warning This matches the triangulation produced by SnapPea's * version of canonize(). However, it does not match the * triangulation produced by SnapPy's version of canonize(). * This is because SnapPy returns an arbitrary simplicial subdivision * of the canonical cell decomposition, whereas SnapPea follows * this with a canonical retriangulation. * * \pre This is an ideal triangulation, not a closed triangulation. * * @return the canonical triangulation of the canonical cell * decomposition, or 0 if this could not be constructed. */ NTriangulation* canonize() const; /** * Asks SnapPea to randomly retriangulate its internal * representation of the manifold. This can help when SnapPea * is having difficulty finding a complete hyperbolic structure. * * This routine uses SnapPea's own retriangulation code. It is highly * likely that, after calling randomize(), the internal SnapPea * representation of the manifold will \e not match the original * triangulation passed from Regina. In other words, calling * verifyTriangulation() with your original Regina triangulation * will most likely return \c false. * * After randomizing, this routine will immediately ask SnapPea * to try to find a complete hyperbolic structure. * * If this is a null SnapPea triangulation, this routine does nothing. */ void randomize(); /** * Verifies that the tetrahedron face gluings from this SnapPea * triangulation match the given Regina triangulation precisely. * * This is useful if you are not sure whether SnapPea will relabel * and/or retriangulate. * * This routine is equivalent to testing whether the given * triangulation is identical to the triangulation returned by * toRegina(). * * @param triangulation the triangulation to compare with this * SnapPea triangulation. * @return \c true if the tetrahedron face gluings match precisely, or * \c false if the face gluings do not match or if this is a * null triangulation. */ bool verifyTriangulation(const NTriangulation& triangulation) const; /** * Creates a new Regina triangulation that mirrors the internal * SnapPea structure. * * Note that this might be different from the original triangulation * that was passed into the NSnapPeaTriangulation constructor, since * the SnapPea kernel can sometimes relabel tetrahedra and/or * retriangulate the manifold. * * The resulting triangulation will be newly created, and it is * the responsibility of the caller of this routine to * eventually delete it. * * @return a new Regina triangulation, or \c null if this is a * null SnapPea triangulation (see isNull()). */ NTriangulation* toRegina() const; /** * Dumps the underlying SnapPea data to standard output. * * This routine should be regarded primarily as a diagnostic tool * for investigating how the SnapPea kernel has modified and/or * analysed a triangulation. * * Note that the SnapPea data is written using C-style output * (i.e., using the \a stdout file pointer), which may or may not * cause unexpected behaviour when used in conjunction with * \a std::cout. * * If this triangulation does not contain any valid SnapPea * data, this routine will do nothing. See isNull() for further * details. */ void dump() const; /** * Saves the underlying triangulation as a native SnapPea file. * Like dump(), this routine is provided primarily as a diagnostic * tool. * * For a general export-to-SnapPea method, users are referred to * regina::writeSnapPea() instead, which avoids the internal SnapPea * conversion entirely and simply writes Regina's native * triangulation data in SnapPea's text format. * * Passing an empty string as the filename will cause the * SnapPea data to be written to standard output, just like * dump(). See the dump() documentation for caveats when * combining the C-style output of this routine with \a std::cout. * * If this triangulation does not contain any valid SnapPea * data, this routine will do nothing. See isNull() for further * details. * * @param filename the name of the SnapPea file to write. */ void saveAsSnapPea(const char* filename) const; virtual void writeTextShort(std::ostream& out) const; /** * Returns whether or not the SnapPea kernel writes diagnostic * messages to standard output. * * By default such diagnostic messages are disabled. To enable * them, call enableKernelMessages(). * * This routine (which interacts with static data) is thread-safe. * * @return \c true if and only if diagonstic messages are enabled. */ static bool kernelMessagesEnabled(); /** * Configures whether or not the SnapPea kernel should write * diagnostic messages to standard output. * * By default such diagnostic messages are disabled. * * This routine (which interacts with static data) is thread-safe. * * @param enabled \c true if diagnostic messages should be * enabled, or \c false otherwise. */ static void enableKernelMessages(bool enabled = true); /** * Specifies that the SnapPea kernel should not write diagnostic * messages to standard output. * * Calling this routine is equivalent to calling * enableKernelMessages(false). * * Note that diagnostic messages are already disabled by default. * * This routine (which interacts with static data) is thread-safe. */ static void disableKernelMessages(); private: /** * Creates a new raw SnapPea structure mirroring the given Regina * triangulation. * * Note that the tetrahedron and vertex numbers might be changed * in the new SnapPea triangulation. * * The resulting structure should be destroyed by calling * free_triangulation() in the SnapPea kernel. * * Note that not all Regina triangulations can be successfully * converted into SnapPea triangulations. If the conversion is * unsuccessful, 0 will be returned. * * \warning Passing \a allowClosed as \c true can occasionally * cause the program to crash! This is because SnapPea is * primarily designed to work with ideal triangulations only. See * the NSnapPeaTriangulation constructor notes for further details. * * @param tri the Regina triangulation to clone. * @param allowClosed \c true if closed triangulations should be * considered, or \c false if all closed triangulations should * return null. See the NSnapPeaTriangulation constructor notes * for details. * @return a corresponding SnapPea structure, or 0 if the * conversion was unsuccessful. */ static ::Triangulation* reginaToSnapPea(const NTriangulation& tri, bool allowClosed); /** * Creates a new native Regina triangulation that mirrors the * given raw SnapPea triangulation. * * The resulting triangulation will be newly allocated, and it * is the responsibility of the caller of this routine to destroy it. * * @param tri the SnapPea triangulation to clone. * @return a corresponding Regina triangulation, or 0 if * \a tri is a null pointer. */ static NTriangulation* snapPeaToRegina(::Triangulation* tri); }; /*@}*/ // Inline functions for NSnapPeaTriangulation inline bool NSnapPeaTriangulation::isNull() const { return (snappeaData == 0); } inline NTriangulation* NSnapPeaTriangulation::toRegina() const { return snapPeaToRegina(snappeaData); } inline void NSnapPeaTriangulation::dump() const { saveAsSnapPea(""); } #endif // EXCLUDE_SNAPPEA } // namespace regina #endif regina-4.95/engine/snappea/snappy/CMakeLists.txt000644 000765 000024 00000000542 12234011536 021553 0ustar00babstaff000000 000000 # Files to compile SET ( FILES snappy_gluing_equations ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} snappea/snappy/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET(SOURCES ${SOURCES} PARENT_SCOPE) # Snappy headers should not be shipped: these are for internal use only. regina-4.95/engine/snappea/snappy/README.txt000644 000765 000024 00000001154 12234011536 020511 0ustar00babstaff000000 000000 Code from SnapPy ---------------- This directory contains additional code from SnapPy that is not included in the SnapPea kernel. This code is currently synced with SnapPy version 1.8.0 (19 May 2013). SnapPy is distributed under the terms of the GNU General Public License, version 2 or any later version, as published by the Free Software Foundation. The full text of this license can be found in the file LICENSE.txt in Regina's top-level source directory. M. Culler, N. M. Dunfield, and J. R. Weeks. SnapPy, a computer program for studying the geometry and topology of 3-manifolds, http://snappy.computop.org . regina-4.95/engine/snappea/snappy/regina.patch000644 000765 000024 00000000624 12234011536 021302 0ustar00babstaff000000 000000 --- a/engine/snappea/snappy/snappy_gluing_equations.c +++ b/engine/snappea/snappy/snappy_gluing_equations.c @@ -38,7 +38,7 @@ int** get_gluing_equations(Triangulation *manifold, int* num_rows, int* num_cols) { - int *eqn, i, T, num_edges, num_eqns, eqn_index; + int *eqn, i, T, num_eqns, eqn_index; int **eqns; EdgeClass *edge; PositionedTet ptet0, ptet; regina-4.95/engine/snappea/snappy/SnapPy.h000644 000765 000024 00000000606 12234011536 020377 0ustar00babstaff000000 000000 /** * Headers for additional SnapPy code that is not shipped with the * SnapPea kernel. */ #ifndef _SnapPy_ #define _SnapPy_ #include "../kernel/SnapPea.h" #ifdef __cplusplus extern "C" { #endif extern int* get_cusp_equation(Triangulation* manifold, int cusp_num, int m, int l, int* numRows); extern void free_cusp_equation(int *equation); #ifdef __cplusplus } #endif #endif regina-4.95/engine/snappea/snappy/snappy_gluing_equations.c000644 000765 000024 00000006661 12234011536 024136 0ustar00babstaff000000 000000 #include "../kernel/kernel.h" /* * This file was originally named "gluing_equations.c" when distributed with * SnapPy, but has been renamed here to avoid conflict. * * SnapPy, and this file as a result, is licensed under the GNU General Public * License. * * M. Culler, N. M. Dunfield, and J. R. Weeks. * SnapPy, a computer program for studying the geometry and * topology of 3-manifolds, http://snappy.computop.org * * */ /* Returns a matrix with rows of the form a b c d e f ... which means a*log(z0) + b*log(1/(1-z0)) + c*log((z0-1)/z) + d*log(z1) +... = 2 pi i for an edge equation, and (same) = 1 for a cusp equation. In terms of the tetrahedra, a is the invariant of the edge (2,3), b the invariant of the edge (0,2) and c is the invariant of the edge (1,2). See kernel_code/edge_classes.c for a detailed account of the convention. */ int** get_gluing_equations(Triangulation *manifold, int* num_rows, int* num_cols) { int *eqn, i, T, num_eqns, eqn_index; int **eqns; EdgeClass *edge; PositionedTet ptet0, ptet; T = manifold -> num_tetrahedra; num_eqns = 0; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) num_eqns++; eqns = NEW_ARRAY(num_eqns, int*); for (i = 0; i < num_eqns; i ++) eqns[i] = NEW_ARRAY(3*T, int); /* * Build edge equations. */ eqn_index = 0; for ( edge = manifold->edge_list_begin.next; edge != &manifold->edge_list_end; edge = edge->next) { eqn = eqns[eqn_index]; for (i = 0; i < 3 * T; i++) eqn[i] = 0; set_left_edge(edge, &ptet0); ptet = ptet0; do{ eqn[3*ptet.tet->index + edge3_between_faces[ptet.near_face][ptet.left_face]]++; veer_left(&ptet); } while (same_positioned_tet(&ptet, &ptet0) == FALSE); eqn_index++; } *num_rows = num_eqns; *num_cols = 3*T; return eqns; } void free_gluing_equations(int** equations, int num_rows){ int i; for (i = 0; i < num_rows; i++) my_free(equations[i]); my_free(equations); } /* computes the cusp equation the curve (merid)^m (long)^l * in cusp cusp_num. See get_gluing_equations for the return * convention. */ int* get_cusp_equation(Triangulation* manifold, int cusp_num, int m, int l, int* num_rows) { int *eqn, i, coef[2], T; Tetrahedron *tet; VertexIndex v; Cusp *cusp; FaceIndex f, ff; PeripheralCurve c; /* initialize variables */ coef[0] = m; coef[1] = l; T = manifold -> num_tetrahedra; eqn = NEW_ARRAY(3 * T, int); for (i = 0; i < 3 * T; i++) eqn[i] = 0; /* find right cusp */ cusp = manifold->cusp_list_begin.next; for (i = 0; i < cusp_num; i++ ) cusp = cusp->next; /* compute equation */ for (tet = manifold->tet_list_begin.next; tet != &manifold->tet_list_end; tet = tet->next){ for (v = 0; v < 4; v++){ if (tet->cusp[v] != cusp) continue; for (f = 0; f < 4; f++){ if (f == v) continue; ff = remaining_face[v][f]; for (c = 0; c < 2; c++) /* c = M, L */ eqn[3*tet->index + edge3_between_faces[f][ff]] += coef[c] *FLOW(tet->curve[c][right_handed][v][f], tet->curve[c][right_handed][v][ff]); } } } *num_rows = 3*T; return eqn; } void free_cusp_equation(int* equation){ my_free(equation); } regina-4.95/engine/snappea/uimessages.cpp000644 000765 000024 00000007642 12236247215 020371 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "snappea/nsnappeatriangulation.h" #include "snappea/kernel/SnapPea.h" bool regina::NSnapPeaTriangulation::kernelMessages = false; /** * Supply bare-bones UI messaging functions for the SnapPea kernel to use. * * See snappea/kernel/SnapPea.h for details on what each function should do. */ #ifdef __cplusplus extern "C" { #endif void uAcknowledge(const char *message) { if (regina::NSnapPeaTriangulation::kernelMessagesEnabled()) std::cout << message << std::endl; } int uQuery(const char *message, const int num_responses, const char *responses[], const int default_response) { if (regina::NSnapPeaTriangulation::kernelMessagesEnabled()) { std::cout << message << std::endl; for (int i = 0; i < num_responses; i++) { std::cout << i << ". " << responses[i] << std::endl; } std::cout << "Responding with default (" << default_response << ')' << std::endl; } return default_response; } void uFatalError(const char *function, const char *file) { std::cerr << "FATAL ERROR: " << file << ", " << function << std::endl; exit(1); } void uAbortMemoryFull(void) { std::cerr << "FATAL ERROR: Available memory has been exhausted." << std::endl; exit(1); } void uPrepareMemFullMessage() { // Do nothing for now. } void uLongComputationBegins(char *message, Boolean /* is_abortable */) { if (regina::NSnapPeaTriangulation::kernelMessagesEnabled()) std::cout << message << std::endl; } FuncResult uLongComputationContinues() { return func_OK; } void uLongComputationEnds() { } #ifdef __cplusplus } // extern "C" #endif regina-4.95/engine/split/CMakeLists.txt000644 000765 000024 00000000751 12234011536 017747 0ustar00babstaff000000 000000 # split # Files to compile SET ( FILES nsigcensus nsigisomorphism nsignature ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} split/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES nsigcensus.h nsigisomorphism.h nsignature.h DESTINATION ${INCLUDEDIR}/split COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/split/nsigcensus.cpp000644 000765 000024 00000022372 12234011536 020077 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "split/nsigcensus.h" #include "utilities/memutils.h" namespace regina { unsigned long formSigCensus(unsigned order, UseSignature use, void* useArgs) { NSigCensus census(order, use, useArgs); census.run(0); return census.totalFound; } void* NSigCensus::run(void*) { // Initialisations. sig.nCycles = 0; sig.nCycleGroups = 0; nextLabel = 0; std::fill(used, used + sig.order, 0); totalFound = 0; // Try creating a first cycle. extendAutomorphisms(); for (unsigned i = 2 * sig.order; i > 0; i--) tryCycle(i, true, 0); clearTopAutomorphisms(); return 0; } void NSigCensus::clearTopAutomorphisms() { if (! automorph[sig.nCycleGroups].empty()) { for_each(automorph[sig.nCycleGroups].begin(), automorph[sig.nCycleGroups].end(), FuncDelete()); automorph[sig.nCycleGroups].clear(); } } bool NSigCensus::extendAutomorphisms() { if (sig.nCycleGroups == 0) { automorph[0].push_back(new NSigPartialIsomorphism(1)); automorph[0].push_back(new NSigPartialIsomorphism(-1)); return true; } NSigPartialIsomorphism* iso; unsigned firstLabel; int result; unsigned i; std::list::const_iterator it; for (it = automorph[sig.nCycleGroups - 1].begin(); it != automorph[sig.nCycleGroups - 1].end(); it++) { // Try extending this automorphism. iso = new NSigPartialIsomorphism(**it, nextLabel, sig.nCycles); firstLabel = (*it)->nLabels; if (firstLabel == nextLabel) { iso->makeCanonical(sig, sig.nCycleGroups - 1); result = iso->compareWith(sig, 0, sig.nCycleGroups - 1); if (result == 0) automorph[sig.nCycleGroups].push_back(iso); else { delete iso; if (result < 0) return false; } } else { for (i = firstLabel; i < nextLabel; i++) iso->labelImage[i] = i; do { iso->makeCanonical(sig, sig.nCycleGroups - 1); result = iso->compareWith(sig, 0, sig.nCycleGroups - 1); if (result < 0) { delete iso; return false; } else if (result == 0) automorph[sig.nCycleGroups].push_back( new NSigPartialIsomorphism(*iso)); } while (std::next_permutation(iso->labelImage + firstLabel, iso->labelImage + nextLabel)); delete iso; } } return true; } void NSigCensus::tryCycle(unsigned cycleLen, bool newCycleGroup, unsigned startPos) { // Are we finished? if (startPos == 2 * sig.order) { totalFound++; use(sig, automorph[sig.nCycleGroups], useArgs); return; } // Prepare the signature for the forthcoming cycle. sig.nCycles++; if (newCycleGroup) sig.nCycleGroups++; // Insert the cycleStart sentinel. unsigned endPos = startPos + cycleLen; sig.cycleStart[sig.nCycles] = endPos; // We won't insert the cycleGroupStart sentinel until we know where // the group will finish. // Generate all possibilities for this cycle. unsigned tryPos = startPos; sig.label[tryPos] = 0; unsigned lowerBnd, upperBnd; bool avoid; unsigned i; while(true) { if (tryPos == endPos) { // Found a complete cycle. avoid = false; if (startPos == 0 && used[sig.label[startPos]] == 2) { // We run the risk of having a cycle that could be // made lexicographically smaller simply by rotating it. i = 1; while (sig.label[startPos + i] != sig.label[startPos]) i++; if (NSignature::cycleCmp(sig, sig.nCycles - 1, 0, 1, 0, sig, sig.nCycles - 1, i, 1, 0) > 0) avoid = true; } if (! avoid) { if (endPos == 2 * sig.order) { // Found a complete cycle set. sig.cycleGroupStart[sig.nCycleGroups] = sig.nCycles; if (extendAutomorphisms()) tryCycle(0, true, endPos); clearTopAutomorphisms(); } else { // Move on to create the next cycle. // The next cycle will have length i. if (endPos + cycleLen <= 2 * sig.order) tryCycle(cycleLen, false, endPos); sig.cycleGroupStart[sig.nCycleGroups] = sig.nCycles; if (extendAutomorphisms()) for (i = (endPos + cycleLen - 1 <= 2 * sig.order ? cycleLen - 1 : 2 * sig.order - endPos); i > 0; i--) tryCycle(i, true, endPos); clearTopAutomorphisms(); } } // Step back again. tryPos--; used[sig.label[tryPos]]--; if (sig.label[tryPos] == nextLabel - 1 && used[sig.label[tryPos]] == 0) nextLabel--; sig.label[tryPos]++; } else { // Find the next viable possibility for this position. if (tryPos == startPos) { if (newCycleGroup) lowerBnd = 0; else lowerBnd = sig.label[startPos - cycleLen]; upperBnd = (startPos == 0 ? 1 : nextLabel); } else { lowerBnd = (startPos == 0 ? sig.label[startPos] : sig.label[startPos] + 1); upperBnd = nextLabel + 1; } if (upperBnd >= sig.order) upperBnd = sig.order; if (sig.label[tryPos] < lowerBnd) sig.label[tryPos] = lowerBnd; while (sig.label[tryPos] < upperBnd) { if (used[sig.label[tryPos]] < 2) break; sig.label[tryPos]++; } if (sig.label[tryPos] >= upperBnd) { // We've run out of ideas for this position. // Step back and undo the previous position. if (tryPos == startPos) break; tryPos--; used[sig.label[tryPos]]--; if (sig.label[tryPos] == nextLabel - 1 && used[sig.label[tryPos]] == 0) nextLabel--; sig.label[tryPos]++; } else { // We've found a value to try. used[sig.label[tryPos]]++; if (sig.label[tryPos] == nextLabel) nextLabel++; tryPos++; sig.label[tryPos] = 0; } } } sig.nCycles--; if (newCycleGroup) sig.nCycleGroups--; } } // namespace regina regina-4.95/engine/split/nsigcensus.h000644 000765 000024 00000021126 12234011536 017540 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file split/nsigcensus.h * \brief Deals with forming a census of splitting surface signatures. */ #ifndef __NSIGCENSUS_H #ifndef __DOXYGEN #define __NSIGCENSUS_H #endif #include #include "regina-core.h" #include "split/nsignature.h" #include "split/nsigisomorphism.h" namespace regina { /** * \weakgroup split * @{ */ /** * A list of partial isomorphisms on splitting surface signatures. */ typedef std::list NSigIsoList; /** * A routine used to do arbitrary processing upon a splitting surface * signature and its automorphisms. Such routines are used to process * signatures found when running a signature census. * * The first parameter passed should be a splitting surface signature. * The second parameter should be a list of all automorphisms of this signature. * The third parameter may contain arbitrary data as passed to * formSigCensus(). */ typedef void (*UseSignature)(const NSignature&, const NSigIsoList&, void *); /** * Forms a census of all splitting surface signatures of the given order. * The order of a signature is the number of quads in the corresponding * splitting surface. * * Each signature will be produced precisely once up to equivalence. * Signatures are considered equivalent if they are related by some * combination of: * - relabelling symbols; * - rotating an individual cycle; * - inverting an individual cycle (i.e., reversing the cycle and * changing the case of each symbol in the cycle); * - reversing all cycles without changing the case of any symbols. * * Each signature produced will have its cycles ordered by decreasing * length. Each cycle will have at least half of its symbols lower-case. * * For each signature that is generated, routine \a use (as passed to this * function) will be called with that signature and its automorphisms as * arguments. * * \warning Currently upper-case symbols in signatures are not supported * by this routine; only signatures whose symbols are all lower-case will * be produced. * * \todo \feature Add support for symbols of differing case. * * \ifacespython Not present. * * @param order the order of signatures to generate. * @param use the function to call upon each signature that is found. * The first parameter passed to this function will be a splitting * surface signature. The second parameter will be a list of all its * automorphisms. The third parameter will be parameter \a useArgs as * was passed to this routine. * @param useArgs the pointer to pass as the final parameter for the * function \a use which will be called upon each signature found. * @return the total number of non-equivalent signatures that were found. */ REGINA_API unsigned long formSigCensus(unsigned order, UseSignature use, void* useArgs = 0); /** * A utility class used by formSigCensus(). Other routines should never * refer to this class directly. It is used to store temporary * information when forming the census. * * \ifacespython Not present. */ class REGINA_API NSigCensus { private: NSignature sig; /**< The signature being constructed. */ unsigned nextLabel; /**< The first symbol that has not yet been used. */ unsigned* used; /**< The number of times each symbol has been used so far. */ NSigIsoList* automorph; /**< List automorph[k] represents all automorphisms of the first \a k cycle groups of the partially formed signature. */ UseSignature use; /**< The argument passed to formSigCensus(). */ void* useArgs; /**< The argument passed to formSigCensus(). */ unsigned long totalFound; /**< The total number of signatures found so far. */ public: /** * Deallocates any memory used specifically by this structure. */ ~NSigCensus(); /** * Runs a complete signature census generation. At most one * copy of this routine should be running at any given time for * a particular NSigCensus. * * @param param this parameter is ignored. * @return 0. */ void* run(void* param); private: /** * Creates a new structure to form a signature census. * All parameters not explained are taken directly from * formSigCensus(). * * \pre order is at least 1. */ NSigCensus(unsigned order, UseSignature newUse, void* newUseArgs); /** * Empty the list automorph[sig.nCycleGroups] and * destroy the corresponding partial isomorphisms. */ void clearTopAutomorphisms(); /** * Extend the automorphisms in list * automorph[sig.nCycleGroups - 1] to form the * automorphisms in list automorph[sig.nCycleGroups]. * * If in the processing of extending these automorphisms it is * discovered that the partial signature sig is not in * canonical form, \c false will be returned and the contents of * list automorph[sig.nCycleGroups] will be undefined. * * @return \c true if and only if it was confirmed during * processing that the partial signature sig is in * canonical form. */ bool extendAutomorphisms(); /** * Extends the partial signature created so far to add a new * cycle. * * @param cycleLen the length of the new cycle to add. * @param newCycleGroup \c true if and only if the new cycle * begins a new cycle group. * @param startPos the position within the list of symbols * that make up the signature at which the new cycle will begin. */ void tryCycle(unsigned cycleLen, bool newCycleGroup, unsigned startPos); friend unsigned long formSigCensus(unsigned order, UseSignature use, void* useArgs); }; /*@}*/ // Inline functions for NSigCensus inline NSigCensus::NSigCensus(unsigned order, UseSignature newUse, void* newUseArgs) : sig(order), used(new unsigned[order]), automorph(new NSigIsoList[order + 2]), use(newUse), useArgs(newUseArgs) { } inline NSigCensus::~NSigCensus() { delete[] used; delete[] automorph; } } // namespace regina #endif regina-4.95/engine/split/nsigisomorphism.cpp000644 000765 000024 00000015046 12234011536 021150 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "split/nsigisomorphism.h" namespace regina { NSigPartialIsomorphism::NSigPartialIsomorphism( const NSigPartialIsomorphism& iso) : nLabels(iso.nLabels), nCycles(iso.nCycles), labelImage(iso.nLabels ? new unsigned[iso.nLabels] : 0), cyclePreImage(iso.nCycles ? new unsigned[iso.nCycles] : 0), cycleStart(iso.nCycles ? new unsigned[iso.nCycles] : 0), dir(iso.dir) { if (iso.nLabels) std::copy(iso.labelImage, iso.labelImage + iso.nLabels, labelImage); if (iso.nCycles) { std::copy(iso.cyclePreImage, iso.cyclePreImage + iso.nCycles, cyclePreImage); std::copy(iso.cycleStart, iso.cycleStart + iso.nCycles, cycleStart); } } NSigPartialIsomorphism::NSigPartialIsomorphism( const NSigPartialIsomorphism& base, unsigned newLabels, unsigned newCycles) : nLabels(newLabels), nCycles(newCycles), labelImage(newLabels ? new unsigned[newLabels] : 0), cyclePreImage(newCycles ? new unsigned[newCycles] : 0), cycleStart(newCycles ? new unsigned[newCycles] : 0), dir(base.dir) { if (base.nLabels) std::copy(base.labelImage, base.labelImage + base.nLabels, labelImage); if (base.nCycles) { std::copy(base.cyclePreImage, base.cyclePreImage + base.nCycles, cyclePreImage); std::copy(base.cycleStart, base.cycleStart + base.nCycles, cycleStart); } } void NSigPartialIsomorphism::makeCanonical(const NSignature& sig, unsigned fromCycleGroup) { unsigned fromCycle, toCycle; unsigned c, i; unsigned cycleLen; unsigned start1, start2; // Deal with each cycle group separately. for ( ; sig.cycleGroupStart[fromCycleGroup] < nCycles; fromCycleGroup++) { fromCycle = sig.cycleGroupStart[fromCycleGroup]; toCycle = sig.cycleGroupStart[fromCycleGroup + 1]; if (toCycle > nCycles) toCycle = nCycles; if (fromCycle >= toCycle) continue; // Determine where each cycle should start. cycleLen = sig.cycleStart[fromCycle + 1] - sig.cycleStart[fromCycle]; for (c = fromCycle; c < toCycle; c++) { start1 = start2 = cycleLen; for (i = 0; i < cycleLen; i++) if (start1 == cycleLen || labelImage[sig.label[sig.cycleStart[c] + i]] < labelImage[sig.label[sig.cycleStart[c] + start1]]) { start1 = i; start2 = cycleLen; } else if (labelImage[sig.label[sig.cycleStart[c] + i]] == labelImage[sig.label[sig.cycleStart[c] + start1]]) start2 = i; if (start2 == cycleLen) cycleStart[c] = start1; else { // Two possible starting points; we must choose between them. if (NSignature::cycleCmp(sig, c, start1, dir, labelImage, sig, c, start2, dir, labelImage) <= 0) cycleStart[c] = start1; else cycleStart[c] = start2; } } // At this point we now know where each cycle starts under the new // labelling. It's now time to determine in which order the cycles // should be presented. for (c = fromCycle; c < toCycle; c++) cyclePreImage[c] = c; std::sort(cyclePreImage + fromCycle, cyclePreImage + toCycle, ShorterCycle(sig, *this)); } } int NSigPartialIsomorphism::compareWith(const NSignature& sig, const NSigPartialIsomorphism* other, unsigned fromCycleGroup) const { int result; for (unsigned c = sig.cycleGroupStart[fromCycleGroup]; c < nCycles; c++) { if (other) result = NSignature::cycleCmp(sig, cyclePreImage[c], cycleStart[cyclePreImage[c]], dir, labelImage, sig, other->cyclePreImage[c], other->cycleStart[other->cyclePreImage[c]], other->dir, other->labelImage); else result = NSignature::cycleCmp(sig, cyclePreImage[c], cycleStart[cyclePreImage[c]], dir, labelImage, sig, c, 0, 1, 0); if (result < 0) return -1; if (result > 0) return 1; } return 0; } } // namespace regina regina-4.95/engine/split/nsigisomorphism.h000644 000765 000024 00000027241 12234011536 020615 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file split/nsigisomorphism.h * \brief Deals with full and partial isomorphisms of splitting surface * signatures. */ #ifndef __NSIGISOMORPHISM_H #ifndef __DOXYGEN #define __NSIGISOMORPHISM_H #endif #include "regina-core.h" #include "split/nsignature.h" namespace regina { /** * \weakgroup split * @{ */ /** * Represents a partial isomorphism between two splitting surface * signatures. See class NSignature for details on splitting surface * signatures. * * The two signatures related by this partial isomorphism must have the * same cycle structure, i.e., the same number of cycle groups and the * same cycle length and number of cycles within each cycle group. * * The partial isomorphism maps symbols to symbols and cycles to cycles, * with the option of rotating some cycles and/or reversing all cycles in * the process. * Cycles within the kth cycle group of the source signature * must map to cycles within the kth cycle group of the * destination signature. * * A \e partial isomorphism is only required to map the cycles * and symbols found in the first g cycle groups of the * source isomorphism (for some g). If only a subset of symbols * are mapped, that subset must be symbols 0,1,...,k for some k. * * \ifacespython Not present. */ class REGINA_API NSigPartialIsomorphism { private: unsigned nLabels; /**< The number of symbols whose images are defined. */ unsigned nCycles; /**< The number of cycles whose images are defined. */ unsigned* labelImage; /**< Stores the image of each symbol. */ unsigned* cyclePreImage; /**< Stores the preimage of each cycle. */ unsigned* cycleStart; /**< Allows a cycle to be rotated: cycleStart[k] is the position in original cycle \a k where the image cycle begins. */ int dir; /**< Positive if all cycles keep their original direction, negative if all cycles are reversed. */ public: /** * Creates a new partial isomorphism that maps no cycles or * symbols. This empty isomorphism is designed to be extended * at some later point. * * @param newDir positive if this isomorphism specifies that all * cycles are reversed, or negative if this isomorphism specifies * that all cycles keep their original direction. */ NSigPartialIsomorphism(int newDir); /** * Creates a new partial isomorphism that is a clone of the given * partial isomorphism. * * @param iso the partial isomorphism to clone. */ NSigPartialIsomorphism(const NSigPartialIsomorphism& iso); /** * Destroys this partial isomorphism. */ ~NSigPartialIsomorphism(); /** * Rearranges the cycle images so that this isomorphism when * applied to the given signature produces a new signature that * is in canonical form. * * The result of this routine is dependent upon the symbol map * defined by this isomorphism (this symbol map will not be changed). * * @param sig the signature to which this isomorphism will be applied. * @param fromCycleGroup the first cycle group whose images may * be rearranged. If it is already known that the cycle images for * the first \a k cycle groups are correct, \a k should be passed * in this parameter. This parameter should not exceed the * number of cycle groups whose cycles are mapped by this partial * isomorphism. */ void makeCanonical(const NSignature& sig, unsigned fromCycleGroup = 0); /** * Lexicographically compares the results of applying this and * the given isomorphism to the given signature. * * Comparisons are done on a cycle-by-cycle basis; comparisons * within a cycle are done as described by NSignature::cycleCmp(). * Comparison will not proceed beyond the cycles mapped by this * partial isomorphism. * * \pre the given partial isomorphism maps at least as many * cycles and symbols as this partial isomorphism. * * @param sig the signature to which both this and the given * isomorphism will be applied. * @param other the isomorphism to compare with this isomorphism. * @param fromCycleGroup the first cycle group whose images should * be examined. If it is already known that the cycle images for * the first \a k cycle groups are identical under both * isomorphisms, \a k should be passed in this parameter. * This parameter should not exceed the number of cycle groups * whose cycles are mapped by this partial isomorphism. * * @return -1, 1 or 0 if the image of the given signature under * this isomorphism is lexicographically less than, greater than * or equal to its image under the given isomorphism respectively. */ int compareWith(const NSignature& sig, const NSigPartialIsomorphism* other, unsigned fromCycleGroup = 0) const; private: /** * Creates a new partial isomorphism that is an extension of the * given partial isomorphism. * * The portion of the new isomorphism matching the given isomorphism * will be initialised; the remainder of the new isomorphism will * remain uninitialised. * * @param base the partial isomorphism to be extended. * @param newLabels the number of symbols that the new * isomorphism will map; this must be at least as large as the * number of symbols mapped by the given isomorphism. * @param newCycles the number of cycles that the new * isomorphism will map; this must be at least as large as the * number of cycles mapped by the given isomorphism. */ NSigPartialIsomorphism(const NSigPartialIsomorphism& base, unsigned newLabels, unsigned newCycles); /** * A comparison function for use with the Standard Template * Library. * * This function determines whether the image of one cycle is * less than the image of another under the given fixed isomorphism * when applied to the given fixed signature. Cycle comparison is * done using NSignature::cycleCmp(). * * It is irrelevant which cycle is mapped to appear before the other * in the sequence of cycles belonging to the image signature. */ struct ShorterCycle { const NSignature& sig; /**< The signature containing the cycles to examine. */ const NSigPartialIsomorphism& iso; /**< The isomorphism to apply to the cycles before they are compared. */ /** * Creates a new comparison function. * * @param newSig the signature containing the cycles that * this function will examine. * @param newIso the partial isomorphism to apply to the cycles * before they are compared. */ ShorterCycle(const NSignature& newSig, const NSigPartialIsomorphism& newIso); /** * Determines whether the image of one cycle is * lexicographically less than the image of another. * See the class notes for further details on how this * comparison is done. * * @param cycle1 the index of the first cycle to examine; * this must be less than the total number of cycles mapped * by the isomorphism concerned and less than the total number * of cycles in the signature concerned. * @param cycle2 the index of the second cycle to examine; * this must be less than the total number of cycles mapped * by the isomorphism concerned and less than the total number * of cycles in the signature concerned. * @return \c true if and only if the image of the first * cycle is less than the image of the second cycle. */ bool operator () (unsigned cycle1, unsigned cycle2) const; }; friend struct NSigPartialIsomorphism::ShorterCycle; friend class regina::NSigCensus; }; /*@}*/ // Inline functions for NSigPartialIsomorphism inline NSigPartialIsomorphism::NSigPartialIsomorphism(int newDir) : nLabels(0), nCycles(0), labelImage(0), cyclePreImage(0), cycleStart(0), dir(newDir) { } inline NSigPartialIsomorphism::~NSigPartialIsomorphism() { if (labelImage) delete[] labelImage; if (cyclePreImage) delete[] cyclePreImage; if (cycleStart) delete[] cycleStart; } inline NSigPartialIsomorphism::ShorterCycle::ShorterCycle( const NSignature& newSig, const NSigPartialIsomorphism& newIso) : sig(newSig), iso(newIso) { } inline bool NSigPartialIsomorphism::ShorterCycle::operator () (unsigned cycle1, unsigned cycle2) const { return (NSignature::cycleCmp(sig, cycle1, iso.cycleStart[cycle1], iso.dir, iso.labelImage, sig, cycle2, iso.cycleStart[cycle2], iso.dir, iso.labelImage) < 0); } } // namespace regina #endif regina-4.95/engine/split/nsignature.cpp000644 000765 000024 00000023703 12234011536 020074 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "split/nsignature.h" #include "triangulation/ntriangulation.h" #include "utilities/memutils.h" namespace regina { namespace { NPerm4 exitFace(bool firstOccurrence, bool lowerCase) { if (firstOccurrence) { if (lowerCase) return NPerm4(2,3,1,0); else return NPerm4(2,3,0,1); } else { if (lowerCase) return NPerm4(0,1,3,2); else return NPerm4(0,1,2,3); } } } NSignature::NSignature(const NSignature& sig) : ShareableObject(), order(sig.order), label(new unsigned[2 * sig.order]), labelInv(new bool[2 * sig.order]), nCycles(sig.nCycles), cycleStart(new unsigned[sig.nCycles + 1]), nCycleGroups(sig.nCycleGroups), cycleGroupStart(new unsigned[sig.nCycleGroups + 1]) { std::copy(sig.label, sig.label + 2 * sig.order, label); std::copy(sig.labelInv, sig.labelInv + 2 * sig.order, labelInv); std::copy(sig.cycleStart, sig.cycleStart + sig.nCycles + 1, cycleStart); std::copy(sig.cycleGroupStart, sig.cycleGroupStart + sig.nCycleGroups + 1, cycleGroupStart); } NSignature* NSignature::parse(const std::string& str) { // See if the string looks correctly formed. // Note that we're not yet counting the individual frequency of each // letter, just the overall number of letters. // Cycles are assumed to be separated by any non-space // non-alphabetic characters. unsigned nAlpha = 0; int largestLetter = -1; size_t len = str.length(); size_t pos; for (pos = 0; pos < len; pos++) // Avoid isalpha(), etc. and be explicit, in case the signature // string contains international characters. if (str[pos] >= 'A' && str[pos] <= 'Z') { nAlpha++; if (largestLetter < str[pos] - 'A') largestLetter = str[pos] - 'A'; } else if (str[pos] >= 'a' && str[pos] <= 'z') { nAlpha++; if (largestLetter < str[pos] - 'a') largestLetter = str[pos] - 'a'; } if (static_cast(nAlpha) != 2 * (largestLetter + 1)) return 0; if (nAlpha == 0) return 0; // Looks fine so far. // Build the signature and cycle structure (but not cycle groups yet). unsigned order = largestLetter + 1; unsigned* label = new unsigned[nAlpha]; bool* labelInv = new bool[nAlpha]; unsigned nCycles = 0; unsigned* cycleStart = new unsigned[nAlpha + 1]; cycleStart[0] = 0; unsigned* freq = new unsigned[order]; std::fill(freq, freq + order, 0); unsigned whichPos = 0; /* Position in the signature, as opposed to position in the string. */ unsigned letterIndex; for (pos = 0; pos < len; pos++) { if (isspace(str[pos])) continue; if (! ((str[pos] >= 'A' && str[pos] <= 'Z') || (str[pos] >= 'a' && str[pos] <= 'z'))) { if (cycleStart[nCycles] < whichPos) { // We've just ended a cycle. nCycles++; cycleStart[nCycles] = whichPos; } } else { if (str[pos] >= 'A' && str[pos] <= 'Z') letterIndex = str[pos] - 'A'; else letterIndex = str[pos] - 'a'; freq[letterIndex]++; if (freq[letterIndex] > 2) { // We've seen this letter a third time! delete[] label; delete[] labelInv; delete[] cycleStart; delete[] freq; return 0; } label[whichPos] = letterIndex; labelInv[whichPos] = (str[pos] >= 'A' && str[pos] <= 'Z'); whichPos++; } } delete[] freq; if (cycleStart[nCycles] < whichPos) { nCycles++; cycleStart[nCycles] = whichPos; } // We now have a valid signature! NSignature* sig = new NSignature(); sig->order = order; sig->label = label; sig->labelInv = labelInv; sig->nCycles = nCycles; sig->cycleStart = cycleStart; // Fill in the rest of the data members. sig->nCycleGroups = 0; sig->cycleGroupStart = new unsigned[nCycles]; for (pos = 0; pos < nCycles; pos++) if (pos == 0 || sig->cycleStart[pos + 1] - sig->cycleStart[pos] != sig->cycleStart[pos] - sig->cycleStart[pos - 1]) { // New cycle group. sig->cycleGroupStart[sig->nCycleGroups] = static_cast(pos); sig->nCycleGroups++; } return sig; } NTriangulation* NSignature::triangulate() const { unsigned sigLen = 2 * order; NTriangulation* tri = new NTriangulation(); // Create a new set of tetrahedra. // Tetrahedron vertices will be: // bottom left -> top right: 0 -> 1 // bottom right -> top left: 2 -> 3 NTetrahedron** tet = new NTetrahedron*[order]; unsigned pos; for (pos = 0; pos < order; pos++) tet[pos] = tri->newTetrahedron(); // Store the first occurrence of each symbol. unsigned* first = new unsigned[order]; std::fill(first, first + order, sigLen); for (pos = 0; pos < sigLen; pos++) if (first[label[pos]] == sigLen) first[label[pos]] = pos; // Make the face gluings. unsigned currCycle = 0; unsigned adjPos; NPerm4 myFacePerm, yourFacePerm; for (pos = 0; pos < sigLen; pos++) { if (cycleStart[currCycle + 1] == pos + 1) { adjPos = cycleStart[currCycle]; currCycle++; } else adjPos = pos + 1; myFacePerm = exitFace(first[label[pos]] == pos, ! labelInv[pos]); yourFacePerm = exitFace(first[label[adjPos]] == adjPos, labelInv[adjPos]); tet[label[pos]]->joinTo(myFacePerm[3], tet[label[adjPos]], yourFacePerm * myFacePerm.inverse()); } // Clean up. delete[] first; delete[] tet; return tri; } int NSignature::cycleCmp(const NSignature& sig1, unsigned cycle1, unsigned start1, int dir1, unsigned* relabel1, const NSignature& sig2, unsigned cycle2, unsigned start2, int dir2, unsigned* relabel2) { unsigned len = sig1.cycleStart[cycle1 + 1] - sig1.cycleStart[cycle1]; unsigned* arr1 = sig1.label + sig1.cycleStart[cycle1]; unsigned* arr2 = sig2.label + sig2.cycleStart[cycle2]; unsigned pos1 = start1; unsigned pos2 = start2; for (unsigned i = 0; i < len; i++) { if ((relabel1 ? relabel1[arr1[pos1]] : arr1[pos1]) < (relabel2 ? relabel2[arr2[pos2]] : arr2[pos2])) return -1; if ((relabel1 ? relabel1[arr1[pos1]] : arr1[pos1]) > (relabel2 ? relabel2[arr2[pos2]] : arr2[pos2])) return 1; if (dir1 > 0) { pos1++; if (pos1 == len) pos1 = 0; } else { if (pos1 == 0) pos1 = len - 1; else pos1--; } if (dir2 > 0) { pos2++; if (pos2 == len) pos2 = 0; } else { if (pos2 == 0) pos2 = len - 1; else pos2--; } } return 0; } void NSignature::writeCycles(std::ostream& out, const std::string& cycleOpen, const std::string& cycleClose, const std::string& cycleJoin) const { out << cycleOpen; unsigned cycle = 0; for (unsigned pos = 0; pos < 2 * order; pos++) { if (cycleStart[cycle] == pos) { if (cycle > 0) out << cycleClose << cycleJoin << cycleOpen; cycle++; } out << char((labelInv[pos] ? 'A' : 'a') + label[pos]); } out << cycleClose; } } // namespace regina regina-4.95/engine/split/nsignature.h000644 000765 000024 00000031024 12234011536 017534 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file split/nsignature.h * \brief Deals with signatures of splitting surfaces. */ #ifndef __NSIGNATURE_H #ifndef __DOXYGEN #define __NSIGNATURE_H #endif #include "regina-core.h" #include "shareableobject.h" namespace regina { class NSigCensus; class NSigPartialIsomorphism; class NTriangulation; /** * \addtogroup split Splitting Surfaces * Splitting surfaces in triangulations. * @{ */ /** * Represents a signature of a splitting surface in a closed 3-manifold * triangulation. * * A splitting surface is (for these purposes) a compact normal surface * consisting of precisely one quad per tetrahedron and no other normal * (or almost normal) discs. * * A signature of order n is a string consisting of 2n * letters arranged into cycles, where n is the number of quads in the * splitting surface. From a signature, the corresponding splitting * surface and then the entire 3-manifold triangulation can be recreated. * * A signature of order n uses the first n letters of the * alphabet, each precisely twice. Case is important; the meaning of a * letter changes according to whether it appears in upper-case or * lower-case. * * Each letter represents an individual quadrilateral (the two * occurrences of the letter representing the quadrilateral's two sides). * Each cycle represents a chain of quadrilaterals joined together in the * splitting surface. The case of a letter represents in which direction * a quadrilateral is traversed within a cycle. * * Cycles are arranged into cycle groups, where a cycle group * consists of a series of consecutive cycles all of the same length. * * An example of a signature is (abc)(a)(b)(c). This signature * is of order 3 and contains two cycle groups, the first being * (abc) and the second being (a)(b)(c). * * A signature cannot represent a splitting surface with more than 26 * quadrilaterals. * * For further details on splitting surfaces and their signatures, consult * Minimal triangulations and normal surfaces, Burton, PhD thesis, * available from the Regina website. */ class REGINA_API NSignature : public ShareableObject { private: unsigned order; /**< The number of quads in this splitting surface. */ unsigned* label; /**< The 2n letters making up this signature from start to finish; letters A,B,... are represented by integers 0,1,... . */ bool* labelInv; /**< labelInv[i] stores the case of the letter corresponding to label[i]. In this case \c false represents lower-case and \c true represents upper-case. */ unsigned nCycles; /**< The number of cycles in this signature. */ unsigned* cycleStart; /**< The starting position of each cycle; an additional element is appended to the end of this array storing the length of the entire signature. */ unsigned nCycleGroups; /**< The number of cycle groups in this signature. */ unsigned* cycleGroupStart; /**< The starting cycle for each cycle group; an additional element is appended to the end of this array storing the total number of cycles. */ public: /** * Creates a new signature that is a clone of the given signature. * * @param sig the signature to clone. */ NSignature(const NSignature& sig); /** * Destroys this signature. */ virtual ~NSignature(); /** * Returns the order of this signature. The order is the number * of quads in the corresponding splitting surface. * * @return the order of this signature. */ unsigned getOrder() const; /** * Parses the given signature string. * * Punctuation characters in the given string will be interpreted * as separating cycles. All whitespace will be ignored. * * Examples of valid signatures are "(ab)(bC)(Ca)" and * "AAb-bc-C". See the class notes for further details * on what constitutes a valid signature. * * \pre The given string contains at least one letter. * * @param sig a string representation of a splitting surface * signature. * @return a corresponding newly created signature, or 0 if the * given string was invalid. */ static NSignature* parse(const std::string& sig); /** * Returns a newly created 3-manifold triangulation corresponding to * this splitting surface signature. * * @return the corresponding triangulation. */ NTriangulation* triangulate() const; /** * Lexicographically compares the results of transformations upon * two given cycles. Even if transformations are specified, the * underlying signatures will not be changed. * * This comparison is \e not case-sensitive. * * \pre The two specified cycles have the same length. * * \ifacespython Not present. * * @param sig1 the signature containing the first cycle to examine. * @param cycle1 specifies which cycle to examine in signature * \a sig1. This must be less than the total number of cycles in * \a sig1. * @param start1 allows the first cycle to be transformed by * rotation; this parameter is the new starting position of the first * cycle. This must be between 0 and * sig1.getCycleLength(cycle1)-1 inclusive. * @param dir1 allows the first cycle to be transformed by * reversal; this parameter must be positive to use an unreversed * cycle or negative to use a reversed cycle. * @param relabel1 allows the first cycle to be transformed by * relabelling; this parameter must be an array of size at least * sig1.getOrder() mapping old labels 0,1,... * (representing letters A,B,...) to new labels (which must also be * 0,1,..., possibly in a different order). This parameter may * be 0 if no relabelling is to be used. * * @param sig2 the signature containing the second cycle to examine. * @param cycle2 specifies which cycle to examine in signature * \a sig2. This must be less than the total number of cycles in * \a sig2. * @param start2 allows the second cycle to be transformed by * rotation; this parameter is the new starting position of the * second cycle. This must be between 0 and * sig2.getCycleLength(cycle2)-1 inclusive. * @param dir2 allows the second cycle to be transformed by * reversal; this parameter must be positive to use an unreversed * cycle or negative to use a reversed cycle. * @param relabel2 allows the second cycle to be transformed by * relabelling; this parameter must be an array of size at least * sig2.getOrder() mapping old labels 0,1,... * (representing letters A,B,...) to new labels (which must also be * 0,1,..., possibly in a different order). This parameter may * be 0 if no relabelling is to be used. * * @return -1, 1 or 0 if the transformed first cycle is * lexicographically less than, greater than or equal to the * transformed second cycle respectively. */ static int cycleCmp(const NSignature& sig1, unsigned cycle1, unsigned start1, int dir1, unsigned* relabel1, const NSignature& sig2, unsigned cycle2, unsigned start2, int dir2, unsigned* relabel2); /** * Writes a string representation of this signature to the given * output stream. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @param cycleOpen the text to write at the beginning of a cycle * (such as "("). * @param cycleClose the text to write at the end of a cycle * (such as ")"). * @param cycleJoin the text to write between two cycles. */ void writeCycles(std::ostream& out, const std::string& cycleOpen, const std::string& cycleClose, const std::string& cycleJoin) const; virtual void writeTextShort(std::ostream& out) const; private: /** * Creates a new completely uninitialised signature. * * \warning The internal arrays \e must be created before this * signature is destroyed! */ NSignature(); /** * Creates a new signature of the given order. All internal * arrays will be created but not initialised. * * The first elements of the \a cycleStart and \a cycleGroupStart * arrays will be set to 0. * * The newly created signature can be used as a partial * signature containing no cycles. * * @param newOrder the order of the new signature; this must be * strictly positive. */ NSignature(unsigned newOrder); friend class regina::NSigPartialIsomorphism; friend class regina::NSigCensus; }; /*@}*/ // Inline functions for NSignature inline NSignature::NSignature() { } inline NSignature::NSignature(unsigned newOrder) : order(newOrder), label(new unsigned[2 * newOrder]), labelInv(new bool[2 * newOrder]), nCycles(0), cycleStart(new unsigned[2 * newOrder + 1]), nCycleGroups(0), cycleGroupStart(new unsigned[2 * newOrder + 1]) { // Insert sentinels. cycleStart[0] = cycleGroupStart[0] = 0; } inline NSignature::~NSignature() { delete[] label; delete[] labelInv; delete[] cycleStart; delete[] cycleGroupStart; } inline unsigned NSignature::getOrder() const { return order; } inline void NSignature::writeTextShort(std::ostream& out) const { writeCycles(out, "(", ")", ""); } } // namespace regina #endif regina-4.95/engine/subcomplex/CMakeLists.txt000644 000765 000024 00000002757 12234011536 021005 0ustar00babstaff000000 000000 # subcomplex # Files to compile SET ( FILES naugtrisolidtorus nblockedsfs nblockedsfsloop nblockedsfspair nblockedsfstriple nl31pillow nlayeredchain nlayeredchainpair nlayeredlensspace nlayeredloop nlayeredsolidtorus nlayeredsurfacebundle nlayering npillowtwosphere npluggedtorusbundle nplugtrisolidtorus nsatannulus nsatblock nsatblockstarter nsatblocktypes nsatregion nsnappeacensustri nsnappedball nsnappedtwosphere nspiralsolidtorus nstandardtri ntrisolidtorus ntrivialtri ntxicore ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} subcomplex/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES naugtrisolidtorus.h nblockedsfs.h nblockedsfsloop.h nblockedsfspair.h nblockedsfstriple.h nl31pillow.h nlayeredchain.h nlayeredchainpair.h nlayeredlensspace.h nlayeredloop.h nlayeredsolidtorus.h nlayeredsurfacebundle.h nlayering.h npillowtwosphere.h npluggedtorusbundle.h nplugtrisolidtorus.h nsatannulus.h nsatblock.h nsatblockstarter.h nsatblocktypes.h nsatregion.h nsnappeacensustri.h nsnappedball.h nsnappedtwosphere.h nspiralsolidtorus.h nstandardtri.h ntrisolidtorus.h ntrivialtri.h ntxicore.h DESTINATION ${INCLUDEDIR}/subcomplex COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/subcomplex/cube.eps000644 000765 000024 00000007127 12234011536 017670 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: cube.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha7 %%CreationDate: Thu Feb 2 23:18:12 2006 %%BoundingBox: 0 0 189 155 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 155 moveto 0 0 lineto 189 0 lineto 189 155 lineto closepath clip newpath -339.4 270.0 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 5400 2475 m 5400 4275 l 7200 4275 l 7200 2475 l 5400 2475 l 6570 1845 l 8370 1845 l 7200 2475 l gs col0 s gr % Polyline n 8370 1845 m 8370 3645 l 7200 4275 l gs col0 s gr % Polyline [15 45] 45 sd n 6570 1845 m 6570 3645 l 8370 3645 l gs col0 s gr [] 0 sd % Polyline [15 45] 45 sd n 6570 3645 m 5400 4275 l gs col0 s gr [] 0 sd % Polyline [60] 0 sd n 5400 4275 m 8370 1845 l gs col0 s gr [] 0 sd % Polyline [60] 0 sd n 7200 4275 m 6570 3645 l gs col0 s gr [] 0 sd % Polyline n 7200 2475 m 6570 1845 l gs col0 s gr % Polyline n 5400 4275 m 7200 2475 l gs col0 s gr % Polyline n 7200 4275 m 8370 1845 l gs col0 s gr % Polyline [15 45] 45 sd n 5400 4275 m 6570 1845 l gs col0 s gr [] 0 sd % Polyline [15 45] 45 sd n 6570 3645 m 8370 1845 l gs col0 s gr [] 0 sd % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/cube.fig000644 000765 000024 00000001545 12234011536 017644 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 8 5400 2475 5400 4275 7200 4275 7200 2475 5400 2475 6570 1845 8370 1845 7200 2475 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 8370 1845 8370 3645 7200 4275 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 6570 1845 6570 3645 8370 3645 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 6570 3645 5400 4275 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 5400 4275 8370 1845 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7200 4275 6570 3645 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7200 2475 6570 1845 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 5400 4275 7200 2475 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7200 4275 8370 1845 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 5400 4275 6570 1845 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 6570 3645 8370 1845 regina-4.95/engine/subcomplex/cube.png000644 000765 000024 00000003664 12234011536 017667 0ustar00babstaff000000 000000 ‰PNG  IHDRƒllä^í pHYs±±Åa†tIMEÖ ë(*SIDAThÞÅš;«$EÇϽl`àõ1t"3ƒNT4ØÌ@hÁ/P“ˆ¡=‰™B7¸‰ â Š‰ T¡{ÁÄ`¡+P6¤*ØÄÈc0ý¨ª®Çéž+¸wÝ=¿ú×yÕãáoÖßrüõÕ÷o ¿z~·V‡Z¾ Aq+‚úùýn%CÓä9Ô7‚¨ÿç?òU M“ç ²Û@Ô²øîÓs.É­ç"b‹ « ¯l‚ Ä9 2ƒâ¥õþJˆ¶X•ˆUOfPe©.¯NˆíÕ=¯¹PˆQÐìá¨9[çV›ÐG( ËÔY E‡Š÷ÓëÕUÃq‘´=!"b$w4šÌ®P¢–Epî ½8³Ò:.¬Äë s”txGÄ”Mîh˜óôg ÄÐ!Õ£ˆˆB$\wôv¬Z厈ˆ}1ÇÃìŽÆ/n‡˜%-/)N †’«`‡ÖC(Î7Ãÿ£ ¦;Î÷ž6‡!)ï+UŒÁq·ýJ£C¼ï•ñ1„¦»Õ*%\IÇw}dX¸ãüM¿Á;ÌzÓ–&q(îèØd btÇ‹•W¦†“ ƒÏç^¨µq‘´Glû¥ 6CÄ}¦™€°Ü{åòù¼îh l0ix!I[Žˆ½1ˆFw'† 3Ä8ávh!’avC’ÀÒ¿OAø#œù¦H¢îhXdÊf·Cjif¦ (€æŽÔò ²ÝÑ@°â¼e+âŽË=KW??›³»g¯¯ûáϽ¥•fvAYš…¼aSÅnúUž°f›‹¸§M°ìN æCùÐù g ag_˜m˜w³ˆ°‚û ÷]Z;ÃEÓaéE¡Æ ÓY ._ÃpçÙ$„z¸.cqhc±©BYÐ1W6W ƒ”9aþ¶°…,ó׋§Æ"soêÒÝaà_^Ûåë×£40H…‹ ›2d¾ç=¬×a dr 2~ù¼‚ †ŒÅ£ÅA¦ —¿×xl+ÉpÖ‰ˆå"È÷»taK¾ha%Á)³{3¼ðL“’ù¢NEMG…§“JÒ«êÖÅ>a¥Âa¶[_$k¼á-Á YœÁAhŠ·âù·ØÂàõ”‚†|øj‘¦QšÄGÁl„úñg¶¢0ÔÑjÂF覲Nú2Àµœí”˩ТñÀìéž^Â99ËRAç—ÝÛJêÐSUh9A‡Ðl6ªƒÎÂY̶…lªÕ_4Á§±Ð" »p&µ#ƒ< Ö;upM7ÂÀBË,ýI–´ oB°´†Ö²¡Ì‰hðqáÕ•ˆ=äY°¢0U€œ,´Ëdè4¥|=žM—ÝEv@Š-û›,ˆðˆÍ5ò.}Ctb´)Nz£#"¶*ªH¨ ÚCˆaØ›s¼ì$¥ŠØX[`ËMœålŠ" Þšk4Ç ƒµt²K”!QÂ{z 7yD ÍØNSÁéAFrÝÑêx–¡»Ã4H«„kRS”}â±Þ)ŸÒ–sSµKÜ7ÏQÿR ÄÔj‹³ ¥ê#ë¥bµ ^ô9š#¸U‹ÈôšÞ6è Â*T"±›²|\ëuðdÙI…Ã.·Öa+¼–:,·­f[P¾4×ã², B'‡~KRš¢Dé@ŒZ¬yŒuc?.4›Z“­9k²VØåaœi2,thüZp!M­aÁyŒ©Ü @F“v6DÄBÃèܽHÐòi³Òõ‹Æo ¥Û»œÔ¿ðô.6{ÂÁõ”ìk€¤†´œþwÈæ‡_ÝâJ)¡ÝÔЀ–ÐimEµU¸1ëô§ånEÙ¬EΛ"¾DÄVÊ´5.-±@p6ÚU2Ó sS‘xFÆbh]„ÒZVUJ©T¡Ý»È±€ÀX ¶`çfá]y 3PÏÈ Æ‘U¨ÅJP‰É¤i1TQ¸:¼:¨B! ž>càä323Ã|:ᢂá§}ëÏCDál‰{tp²môûlB$eXžs˜&“W…BUŽ$¥Œ«ñ É0½*ö†Á”ëN‡ˆh%Û{Ï9¸caD›ŠI‹án8 â)@“á4#”´ÓHÄÉËÀîÑAJñ6aŠß‚v Ðb8M*L±‡l‹~\wô1ôÂt nÅR™e·{—8vtaÊ*±Dë‡è†‚x ÐbÑúÎS­¶Q%Ý1£§ÜdŠÊÚ¿àÄS€c»#õáÑqØ`°º±ýÁ;¯9 Ψµ½*—ë Á0…!CÅi(ƪ麳QØp£îè2¨^}ô˜^!Ä]TŒ·9Ý»ûø•w4ËẶƒ#§w²ÎJú@þöËgoÊM¦è@ìj²fåšûö›ó›ܤ÷/¯ëÏÝçß¿û%ܪuç¿_~‰¯Uôî½^ƒÛµgOž¬—ô?#à(ÄÿmU\IEND®B`‚regina-4.95/engine/subcomplex/diag92.eps000644 000765 000024 00000010103 12234011536 020015 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: diag92.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha5 %%CreationDate: Wed Sep 28 00:55:56 2005 %%For: bab@skaro (Ben Burton,,,) %%BoundingBox: 0 0 115 172 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 172 moveto 0 0 lineto 115 0 lineto 115 172 lineto closepath clip newpath -155.1 298.4 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 3825 3825 m 4275 4275 l 4275 3825 l 3825 3375 l 3825 3825 l 4275 3825 l gs col0 s gr /Times-Italic ff 190.50 scf sf 3960 3780 m gs 1 -1 sc (w0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4140 4005 m gs 1 -1 sc (w1) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline n 2925 2475 m 2475 2925 l 2475 2475 l 2925 2025 l 2925 2475 l 2475 2475 l gs col0 s gr % Polyline n 2925 2025 m 3375 2475 l 3375 2925 l 2925 3375 l gs col0 s gr % Polyline n 2925 2475 m 3825 3375 l 3375 3825 l 2475 2925 l gs col0 s gr % Polyline n 3825 3825 m 3375 4275 l 3825 4725 l 4275 4275 l gs col0 s gr % Polyline n 3375 3825 m 3375 4275 l gs col0 s gr /Times-Italic ff 190.50 scf sf 2610 2655 m gs 1 -1 sc (u0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 2790 2385 m gs 1 -1 sc (u1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 3825 4320 m gs 1 -1 sc (q1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 3375 3420 m gs 1 -1 sc (q3) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 2925 2970 m gs 1 -1 sc (q4) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 3600 3870 m gs 1 -1 sc (q2) dup sw pop 2 div neg 0 rm col0 sh gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/diag92.fig000644 000765 000024 00000002032 12234011536 017775 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 3825 3375 4275 4275 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 3825 3825 4275 4275 4275 3825 3825 3375 3825 3825 4275 3825 4 1 0 50 -1 3 12 0.0000 0 150 240 3960 3780 w0\001 4 1 0 50 -1 3 12 0.0000 0 135 240 4140 4005 w1\001 -6 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 2925 2475 2475 2925 2475 2475 2925 2025 2925 2475 2475 2475 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 2925 2025 3375 2475 3375 2925 2925 3375 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 2925 2475 3825 3375 3375 3825 2475 2925 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 3825 3825 3375 4275 3825 4725 4275 4275 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3375 3825 3375 4275 4 1 0 50 -1 3 12 0.0000 0 150 210 2610 2655 u0\001 4 1 0 50 -1 3 12 0.0000 0 135 210 2790 2385 u1\001 4 1 0 50 -1 3 12 0.0000 0 180 210 3825 4320 q1\001 4 1 0 50 -1 3 12 0.0000 0 195 210 3375 3420 q3\001 4 1 0 50 -1 3 12 0.0000 0 180 210 2925 2970 q4\001 4 1 0 50 -1 3 12 0.0000 0 180 210 3600 3870 q2\001 regina-4.95/engine/subcomplex/diag92.png000644 000765 000024 00000005026 12234011536 020022 0ustar00babstaff000000 000000 ‰PNG  IHDR—Þ£Í pHYs × ×B(›xtEXtSoftwareESP Ghostscript 815.00yxü: IDATxœíÜ?’¬ºð3¯îx™ë9â.W¯\åÀ S®²GLìˆY]o=p8pÖ½(¯–ФÎè%|º ¤# $ÙÁS43>~WüpÞ@ÿ—í‡ÿ5@Ó~s¹5[Wÿ— ŒM³tõ§ÿ|†…¬š«?]ÿLQaV®þtM¨Ž ³qõ§kBfáz±âÂÌ®™ft ¬˜0“KbE„\+V<ïÚ°¢ÁX—‚ ƹ”¬H0Æ¥aÅé]ZV˜ÖŰbÀt.–¦qXáaj׊õPL¦tI¬þãMÙuaa*—Ü[YZ¨g S¸ÖÛV›kæ Ûºž¬á2ý>ô:WPV­ËG %À5€s¨ÖÊRùçãmÝ_¯•˜Ñ­$bVcØSõ€k…{2©cú+XÉýµlò}N_EBD·$cÿ_zLr {b›Ý†¢%zÜxV0˜j% ¼KªÍ<‰n=Z•‚KdáŒÒ„Œ+lYòá4%âOÜb °*g—ñ ‚kþa“ë+ìå:Èò{º³¼Ã~ òÂò {ÌÓ]~ÿOÓ–Ng WûFDDÆÀÏsñq6Â<6»ñÕϬL®§>°ElV®Ï¬$Š S¸ÚïoéŸYI¶9÷5I7&wñìWO?yÇÓ¸SèþóãÜ;×,ÉZá÷goEí±ÅõºÓ¸PNDƒŠ6»Úï'¢ÛÏÔDÔχY‰6»¦;6'ӽƊ¶¬Ç6£¡-è‘]ÒLÊ® §; z\ -+Lì¯ÛP´T\§ô¬gE‚ ýõ‘ömBÕ×Ôp¬H°ù€¸¾ÓX§Û¦>À†9®®î4´½E£Çtçm–¦qXáaj—‘¦tY°BÃT.+V`˜ÂeÉ S\Ù²‚Â6.VHØÚåÄ [¹Yá`²Ë™ &¹v°BÁD—kPˆ¨¿U7ݳ"ï°,Xm›÷ï~8ß²T7oØ73ëÒ×Ô?†¡`­%ן|Âào¿2wWºùk°°`&~bG{ÜÀÀe9ÐРÛwë¥It]Ð.˜á–P‘辘=0Ö¥Jܳóð®mâ¾­Å}.ƒk¸w·wžÏä’÷¿]ç4ºÄD‹SuE‰úù³#ÌìZ-¢»;êÄãË5%Ú§|Œ]³q= W§Íkhe¬½œ¾¬\óŽm®hò8—iÃÆøuaÌá"ërúÞb,u«Æ_þå×…ñOŒë5ûœKì~g;këb×Á2 cXé?®–«ÒÚÅÁ¦!Øû¸ê·¯.íšÆr³w1°i¶¤ê̲Ð4–¿ƒ‹M4Ávi4å^éââC°]Úá鲂9¹ô‰Æ!Ø'ëé²¹¹v_P¼X/—EŒ£k'lbM.sŒ«klfÍ.cŒ³kla-.SŒ»Ë&°—!f‡Ë&²D³Çå“X’‹ºè®2$ÊMfÉ.ö”»CÅ'JmÅZ¹¸ge{Tl¢ØÖ¬µ‹9ìRq‰BÛ°6.mÌn—lËÚºt1û]F˜‚¥pib¸ 0KåRÇqñŠ*–Ò¥Œ9äb`jÖÆÕ¤T+c޹´0 kíš¾¡ÚÆti`:ÖÚ•6@^«bŽº”0- MÔw pFÈ+UÌa—¦g¡iPÞäùbMrVÅwm` MT%ÎYƒ{…¤î¤]óàZ%r,4 pÍïçªA9‚Tó8‹㣮¢ô_ÿq¿wýúþö¾zÁ/¡KEý%—¿¡Z¿ è¡¿Äÿꪷê3ÆR¸³l ¡çâ  ¨ÇRfzÌkN\¯Ä;ä‘§¦ºhÊçÔTJà ̓ë•(²æ‘Íûª¿0£f4c†ùraÌ;ùÎç5² ”šûæÍ…1¯Ö2²yÖÞwp°7õjïõ¼'Þ>î)½çÑ-wÏ¿÷5æ Woûã³ ¯EÎ#›Ro¡,ksi ðsüR´žh¸ òŒ.óÇÍŸT—™euƒ0ýõÙ¬¤E<Ë {¬|n÷ëï7#›Su6†]Dô•Ëß6|RýüA¿*…RcÁ\o·Û»’ÅÀ„RcÁ\À³(ЖÅÀ–é#Õi–Xì2õDôER©±8®K€Ú ÑÇðt ßCDqmX ìœ>è+h(I*5Ã¥`-°l('úªˆíRa/‚KÉšaÂÕ«Xj,¼KÚamNIÿ(V¥Æ‚»´¬,)rÊ’Š¨ý±mß– v’O>êV—Š«×Àç!SoM=–%Û¯ñº ,æÈÔedéa!],-, ËŠ¥ƒ…sY²4°`.k–ÊåÀR¹œX*X—#K ârfma!\;XX—KUZ†ùwYº‚ÔÌ»ËÜ[Ú‚Ô"Ì·ËfÛÒ~n-À<»XÖó³y® õóëbYÏÏæ¥»D=Ì«‹e]nõÄPz†y¼¾ÿw-?¿³9¤Öµú€ßñ¯:cÞå›G6uïOO­üÕ·‹…MïlŽ9ÿbÝ«šW{l6†mgªÒæ×ůJó;›Kñ8Ï.f|gS¨içÛÅÀÈôΦXj£×ëDﺈ±ñÉŠX5Ä»‹šTÌÄ¿k/L®±Àµ¶*ýµ¶®HÄåÛÊ ãr…më÷r¹Áe…B¹\`ªjGÁ\ö0e¦p.[˜º6T@—LS²*¤Ë¦«¤Ôe†i |…u™`úºc]<Œ)‡ÚÅÁ¸*mÁ]z[<.¼Kã Fp©a†z‰1\*˜©Œc×f¬.ǵ†™‹^FrÉ0‹Zœ±\"̦ül4׳ªŠÏ5ÁìŠõFt=a–5„cº¨¤÷̲´qT•D–—=¾¿êµEz/͹ýærkÿo¾Hm5é†IEND®B`‚regina-4.95/engine/subcomplex/diagbdry.eps000644 000765 000024 00000011176 12234011536 020536 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: diagbdry.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha5 %%CreationDate: Wed Sep 28 00:53:08 2005 %%For: bab@skaro (Ben Burton,,,) %%BoundingBox: 0 0 168 67 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 67 moveto 0 0 lineto 168 0 lineto 168 67 lineto closepath clip newpath -131.2 193.5 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw gs clippath 2973 2739 m 3091 2620 l 3049 2578 l 2930 2696 l 2930 2696 l 3037 2633 l 2973 2739 l cp eoclip n 2610 3060 m 3060 2610 l gs col0 s gr gr % arrowhead n 2973 2739 m 3037 2633 l 2930 2696 l col0 s /Symbol ff 190.50 scf sf 2925 2970 m gs 1 -1 sc (b) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline gs clippath 4460 2973 m 4579 3091 l 4621 3049 l 4503 2930 l 4503 2930 l 4567 3037 l 4460 2973 l cp eoclip n 4140 2610 m 4590 3060 l gs col0 s gr gr % arrowhead n 4460 2973 m 4567 3037 l 4503 2930 l col0 s /Symbol ff 190.50 scf sf 4275 2970 m gs 1 -1 sc (b) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline n 2925 2475 m 2475 2925 l 2475 2475 l 2925 2025 l 2925 2475 l 2475 2475 l gs col0 s gr % Polyline gs clippath 2325 2628 m 2325 2460 l 2265 2460 l 2265 2628 l 2265 2628 l 2295 2508 l 2325 2628 l cp eoclip n 2295 2925 m 2295 2475 l gs col0 s gr gr % arrowhead n 2325 2628 m 2295 2508 l 2265 2628 l col0 s % Polyline n 4275 2475 m 4725 2925 l 4725 2475 l 4275 2025 l 4275 2475 l 4725 2475 l gs col0 s gr % Polyline gs clippath 4125 2178 m 4125 2010 l 4065 2010 l 4065 2178 l 4065 2178 l 4095 2058 l 4125 2178 l cp eoclip n 4095 2475 m 4095 2025 l gs col0 s gr gr % arrowhead n 4125 2178 m 4095 2058 l 4065 2178 l col0 s /Times-Italic ff 190.50 scf sf 2610 2655 m gs 1 -1 sc (u0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 2790 2385 m gs 1 -1 sc (u1) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 2160 2745 m gs 1 -1 sc (a) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4410 2430 m gs 1 -1 sc (w0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 4590 2655 m gs 1 -1 sc (w1) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 3960 2295 m gs 1 -1 sc (a) dup sw pop 2 div neg 0 rm col0 sh gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/diagbdry.fig000644 000765 000024 00000002123 12234011536 020504 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 2610 2610 3060 3060 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2610 3060 3060 2610 4 1 0 50 -1 32 12 0.0000 4 195 105 2925 2970 b\001 -6 6 4140 2610 4590 3060 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 4140 2610 4590 3060 4 1 0 50 -1 32 12 0.0000 4 195 105 4275 2970 b\001 -6 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 2925 2475 2475 2925 2475 2475 2925 2025 2925 2475 2475 2475 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2295 2925 2295 2475 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 4275 2475 4725 2925 4725 2475 4275 2025 4275 2475 4725 2475 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 4095 2475 4095 2025 4 1 0 50 -1 3 12 0.0000 0 150 210 2610 2655 u0\001 4 1 0 50 -1 3 12 0.0000 0 135 210 2790 2385 u1\001 4 1 0 50 -1 32 12 0.0000 4 105 120 2160 2745 a\001 4 1 0 50 -1 3 12 0.0000 0 150 240 4410 2430 w0\001 4 1 0 50 -1 3 12 0.0000 0 135 240 4590 2655 w1\001 4 1 0 50 -1 32 12 0.0000 4 105 120 3960 2295 a\001 regina-4.95/engine/subcomplex/diagbdry.png000644 000765 000024 00000003272 12234011536 020531 0ustar00babstaff000000 000000 ‰PNG  IHDR×[ÅdOÊ pHYs × ×B(›xtEXtSoftwareESP Ghostscript 815.00yxü:AIDATxœíœÏªã6Æ¿ ]–‚î Lq謺®.}] Ý;ÐpÁaº.ÄtÑuü ô¢Gˆè¢ËÓ];Plhwݨ ß$Ö[’c;™a¾Å+K¿œ#éœcgf¥^Ýz#éׇ¥A¸ò!nâ%î;Ô\ùj€›xjå 6W.’ëoÒ*Q)—›Üìz®\l®¾G‡~úY¹${?°Ï®wd,ü®]“ý3<üãZ®±±ðo?°+¹FÇâVDmò»n}Žþæ ×Û|ÖØU\ãcA<> £Ñì?œ Õç_ýb¶º]ñ ®)°8ˆ¾ÀàÖß§À§5ܮ؛k,0sÁ Ö×§ÁÂ¥Öº]±§½FÅÚÍgÏËÌÑ­Óbý¸ÆÅZÐr“‰Ün¦†ºÀzùá¸N˜!%$^EÌÙ³ÃûØkäµ%@Æc®íëa/Ër¸ ¡Ê'hï°X¸½šXb1‹B9*wßV‹s)Ö¢‘·*AVñ<ÉwÖsKW X¨jk‹PHŠü!ÚÄOØ{u·»b WUœp á޴µ^ÀÁ·»,Ìk,>_»';ýûÁÅç³ÜêŠAözqBV.€]‚:ܾ¥øbÍYd±Xˆ½Îk‹S<*¯CfD-·”PnÛ<¸.[†`Èbìˆ3ÖAyr0,`þ\Ó]sT»[`­x, ˜`Þ\êq Nø糡fë¯uT!#ŠœEU¯q ÌwßPέc„#nôHæ­Q1 ^œFÝ<<í¥Ç0NTè%‚<…È€*?o]ŠÅü¸&ÊŽ=ň¨b ΫU´>·7Á¼¸î ‹Ä ”¤ÒìA ¶`>ëë¾°@7§š=j]Ö˜×a¤}yŸÁÜ\w‡Õ©ØÌ¹Y/Ü¡m^1%»*Å6Å“;1ª6 M•ÊUjé¡vgW—át`¥«K*¥”–ý°ú.ì ]¬' §èzáS°íóü¯¦År‚>³p‰..>Ÿ)ñåÔX`2’ë­íF-¾Ø–«âr==VX£ŒdrUäu;×)‘;õ½VØ%7¹8ýÒ,pµó]9·ÂRÁ.Y¦RF2¹l%¦S¥¦‘ÈávXM°F–©”‘,\Ô|BÈJ ìÔDî†X °F–©”‘ ®ŠÌX`/•šf"7!–¥ð{£Ee¦jÉàâÔöäóT©¹$rv,¯33Xbi¶ÀYf³ŒdpÑÀZo}©Ô\9+V1/Œ¶!”аs–©–‘Ú,õ ì@Òƒ,}¬\ÿ[ǘÏ×Ép.IEND®B`‚regina-4.95/engine/subcomplex/diagdistort.eps000644 000765 000024 00000010621 12234011536 021260 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: diagdistort.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha5 %%CreationDate: Wed Sep 28 01:04:26 2005 %%For: bab@skaro (Ben Burton,,,) %%BoundingBox: 0 0 243 87 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 87 moveto 0 0 lineto 243 0 lineto 243 87 lineto closepath clip newpath -84.3 284.2 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 2025 3375 m 1575 3825 l 2025 4275 l 2475 3825 l 2025 3375 l cp gs col0 s gr % Polyline n 1485 3735 m 1575 3825 l gs col0 s gr % Polyline n 1935 3285 m 2025 3375 l gs col0 s gr % Polyline n 2475 3825 m 2565 3915 l gs col0 s gr % Polyline n 2025 4275 m 2115 4365 l gs col0 s gr % Polyline [15 45] 45 sd n 2475 4275 m 2700 4500 l gs col0 s gr [] 0 sd % Polyline [15 45] 45 sd n 1350 3150 m 1575 3375 l gs col0 s gr [] 0 sd /Times-Italic ff 190.50 scf sf 2025 3870 m gs 1 -1 sc (qk) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline n 4725 3825 m 5175 4275 l 5175 3825 l 4725 3375 l 4725 3825 l 5175 3825 l gs col0 s gr /Times-Italic ff 190.50 scf sf 4860 3780 m gs 1 -1 sc (w0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 5040 4005 m gs 1 -1 sc (w1) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline n 4725 3375 m 4275 3825 l 4275 4275 l 4725 3825 l gs col0 s gr % Polyline n 4275 4275 m 4365 4365 l gs col0 s gr % Polyline n 4275 3825 m 4185 3735 l gs col0 s gr % Polyline n 4725 3375 m 4635 3285 l gs col0 s gr % Polyline [15 45] 45 sd n 4050 3150 m 4275 3375 l gs col0 s gr [] 0 sd % Polyline [15 45] 45 sd n 4725 4275 m 4950 4500 l gs col0 s gr [] 0 sd /Times-Italic ff 190.50 scf sf 4500 3870 m gs 1 -1 sc (qk) dup sw pop 2 div neg 0 rm col0 sh gr % Polyline 15.000 slw gs clippath 3430 3885 m 3750 3885 l 3750 3765 l 3430 3765 l 3430 3765 l 3670 3825 l 3430 3885 l cp eoclip n 3015 3825 m 3735 3825 l gs col0 s gr gr % arrowhead n 3430 3885 m 3670 3825 l 3430 3765 l 3430 3885 l cp gs 0.00 setgray ef gr col0 s % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/diagdistort.fig000644 000765 000024 00000002624 12234011536 021242 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 1350 3150 2700 4500 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 2025 3375 1575 3825 2025 4275 2475 3825 2025 3375 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 1485 3735 1575 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 1935 3285 2025 3375 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2475 3825 2565 3915 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2025 4275 2115 4365 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 2475 4275 2700 4500 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 1350 3150 1575 3375 4 1 0 50 -1 3 12 0.0000 0 195 195 2025 3870 qk\001 -6 6 4050 3150 5175 4500 6 4725 3375 5175 4275 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 4725 3825 5175 4275 5175 3825 4725 3375 4725 3825 5175 3825 4 1 0 50 -1 3 12 0.0000 0 150 240 4860 3780 w0\001 4 1 0 50 -1 3 12 0.0000 0 135 240 5040 4005 w1\001 -6 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 4725 3375 4275 3825 4275 4275 4725 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4275 4275 4365 4365 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4275 3825 4185 3735 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4725 3375 4635 3285 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 4050 3150 4275 3375 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 4725 4275 4950 4500 4 1 0 50 -1 3 12 0.0000 0 195 195 4500 3870 qk\001 -6 2 1 0 2 0 7 50 -1 -1 6.000 0 0 -1 1 0 2 1 1 2.00 120.00 240.00 3015 3825 3735 3825 regina-4.95/engine/subcomplex/diagdistort.png000644 000765 000024 00000003773 12234011536 021267 0ustar00babstaff000000 000000 ‰PNG  IHDR4s3À¡# pHYs × ×B(›xtEXtSoftwareESP Ghostscript 815.00yxü:‚IDATxœí=ÏãDÇ繃!!ùÄSÒ ù: K $Då‰É))QQ&µó(œ§XHôvAbÑAeK×€tè†"qâ—ÝÙÍËãuü¯ò\v7;¿óìËì®÷a’®ž³áSS3[¤k€Y°‚4€\´FÕÖ6ׄÙð¨µ+CôK·‚•S@Bê•¿ÿ÷µ6´êys*ÉL²™u`ÔÒæöÑ.³ž ^Q€°Š òÖn½M¡E­ÓFºÙñ3Õd¾§ßÔj—Ñi¤ŠÃãÇNź&Dˆ[?Âh'ˆ€X:®áÄ£Û{ž¨ÑýfI¿º4µÞþri îH­W­Ž ⶸ –ˆÄeØbz$ÒrÔÔc /‘wajÑ;OÍÁ¨õ+Õ1!BÌÄmˆˆ˜¹¶Ó×Lúã´=5%³r["b ¥/_–Zôlߘ˜‚‹ÃN•Ä&DˆX"–²ª¨ŒÎÜLÍl›àÖ/1ö0‹äd.J­†f .[’˜ ooÚsí¹Íg}ªè7×y «Ms'¨;d¡tö妩úûß~ÿ§õ÷“×o¾÷­ÏÙ€Wkýâå©:2Kª˜¹¼!øÂûñ»ïåÙ6ó@¤þb«*ß}õÙ/\jqçïÿÚ¿~xõǪr4Îs†¿ž˜ÉL˜ÿDîwrI›Ôytåùˆ[(q;Ø*žäÒÏ.ç¡M÷„7>ŽT?-.âôQbB«ìn#iȹÁÇ] Æ>†~¹“%+ýì‚íÚš0Ä4± a¬~ZNÅÈz?yîÂ#K,@Vvégˆ—£=; b šÐ„}¯¦Vª¡)sg…âÀìbÔ"8 b·gì™Pävg ME->`™]ŠZ|0Ä.´® §1–ÂǤ És€¯¬_ƒÙp¢’-h]šãR©Ý^Œ$4F«(V‹b6@hµÇò »ãb†ˆKDÜÁih†Ô:̆"´îüçhwDˆîÑAÄ¥W£ŒÜzÉLÿeÕl©3R€zqãÝ$@zœ;¨ÃÝÔFÀ¬a÷)$ UÔ_ +ÚÔFÁìd·ëòµïlœ£aÔb±&µ‘0;ÙúàäUPm†‘ ¨No0Ð>e<–¹Y#$¹­Y‘½g#7KCf&†¦Šfn&I’½gûI%5ßÜËKf98N/¾ÅÙjŤ62f »Y›úXÔFÇLn7oû(ƒÚ™IífnT&©’™Ìnî–x‚ÚH™Iìf¾PR-3±Ýüc> j#f&´[ã@™”Ú¨™‰ìÖ9º(¡6rf»µÉ ©žYßn½ãØjwÀ¬g·æÁÿµ»`Öµ[÷jw¬c·öËLZ¹ï†YÛný׿4rß³–Ý&¯l­sß³&5“Wrß³5£—ÎyÉ,¿?f'jf¯7ô’Yz̼dVñÅ4^òõ7–1ûóݳ™xIÀXÂS­ÕÚêž{¼¦ ÆRYInïÆY«Q2æôºjï÷Íõ~B_âÐ5vଛ/òYb—‡^¬ë:ã²-ÞÊq@à–rÏÀ•^ÙZ‹E-Ð)F5ú¿ù¼‹iîÔCÏ» ÏKèw¯J«Â”sÜâ ¤…Љ†¤M®>#u“{£*Û.| *<]¶e Ýï©w/ÓT©.¢½´AÍ 8rTì䞺áõ»šð$­ð ¡ml» ³Q›ÜÓ@ÓíØJ‰ç·…f]³– ©Mîi É= 4A3ÐÍ@ÿüY¨L˜¿‰SIEND®B`‚regina-4.95/engine/subcomplex/diaginit.eps000644 000765 000024 00000014520 12234011536 020535 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: diaginit.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha5 %%CreationDate: Wed Sep 28 01:00:30 2005 %%For: bab@skaro (Ben Burton,,,) %%BoundingBox: 0 0 158 186 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 186 moveto 0 0 lineto 158 0 lineto 158 186 lineto closepath clip newpath -155.1 312.6 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /reencdict 12 dict def /ReEncode { reencdict begin /newcodesandnames exch def /newfontname exch def /basefontname exch def /basefontdict basefontname findfont def /newfont basefontdict maxlength dict def basefontdict { exch dup /FID ne { dup /Encoding eq { exch dup length array copy newfont 3 1 roll put } { exch newfont 3 1 roll put } ifelse } { pop pop } ifelse } forall newfont /FontName newfontname put newcodesandnames aload pop 128 1 255 { newfont /Encoding get exch /.notdef put } for newcodesandnames length 2 idiv { newfont /Encoding get 3 1 roll put } repeat newfontname newfont definefont pop end } def /isovec [ 8#055 /minus 8#200 /grave 8#201 /acute 8#202 /circumflex 8#203 /tilde 8#204 /macron 8#205 /breve 8#206 /dotaccent 8#207 /dieresis 8#210 /ring 8#211 /cedilla 8#212 /hungarumlaut 8#213 /ogonek 8#214 /caron 8#220 /dotlessi 8#230 /oe 8#231 /OE 8#240 /space 8#241 /exclamdown 8#242 /cent 8#243 /sterling 8#244 /currency 8#245 /yen 8#246 /brokenbar 8#247 /section 8#250 /dieresis 8#251 /copyright 8#252 /ordfeminine 8#253 /guillemotleft 8#254 /logicalnot 8#255 /hyphen 8#256 /registered 8#257 /macron 8#260 /degree 8#261 /plusminus 8#262 /twosuperior 8#263 /threesuperior 8#264 /acute 8#265 /mu 8#266 /paragraph 8#267 /periodcentered 8#270 /cedilla 8#271 /onesuperior 8#272 /ordmasculine 8#273 /guillemotright 8#274 /onequarter 8#275 /onehalf 8#276 /threequarters 8#277 /questiondown 8#300 /Agrave 8#301 /Aacute 8#302 /Acircumflex 8#303 /Atilde 8#304 /Adieresis 8#305 /Aring 8#306 /AE 8#307 /Ccedilla 8#310 /Egrave 8#311 /Eacute 8#312 /Ecircumflex 8#313 /Edieresis 8#314 /Igrave 8#315 /Iacute 8#316 /Icircumflex 8#317 /Idieresis 8#320 /Eth 8#321 /Ntilde 8#322 /Ograve 8#323 /Oacute 8#324 /Ocircumflex 8#325 /Otilde 8#326 /Odieresis 8#327 /multiply 8#330 /Oslash 8#331 /Ugrave 8#332 /Uacute 8#333 /Ucircumflex 8#334 /Udieresis 8#335 /Yacute 8#336 /Thorn 8#337 /germandbls 8#340 /agrave 8#341 /aacute 8#342 /acircumflex 8#343 /atilde 8#344 /adieresis 8#345 /aring 8#346 /ae 8#347 /ccedilla 8#350 /egrave 8#351 /eacute 8#352 /ecircumflex 8#353 /edieresis 8#354 /igrave 8#355 /iacute 8#356 /icircumflex 8#357 /idieresis 8#360 /eth 8#361 /ntilde 8#362 /ograve 8#363 /oacute 8#364 /ocircumflex 8#365 /otilde 8#366 /odieresis 8#367 /divide 8#370 /oslash 8#371 /ugrave 8#372 /uacute 8#373 /ucircumflex 8#374 /udieresis 8#375 /yacute 8#376 /thorn 8#377 /ydieresis] def /Times-Italic /Times-Italic-iso isovec ReEncode /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 2925 2475 m 2475 2925 l 2475 2475 l 2925 2025 l 2925 2475 l 2475 2475 l gs col0 s gr % Polyline n 2925 2025 m 3375 2475 l 3375 2925 l 2925 3375 l gs col0 s gr % Polyline n 2925 2475 m 3825 3375 l 3375 3825 l 2475 2925 l gs col0 s gr % Polyline n 3375 3825 m 3465 3915 l gs col0 s gr % Polyline n 3825 3375 m 3915 3465 l gs col0 s gr % Polyline n 4050 4500 m 4500 4050 l 4950 4500 l 4500 4950 l 4050 4500 l cp gs col0 s gr % Polyline n 3960 4410 m 4050 4500 l gs col0 s gr % Polyline n 4410 3960 m 4500 4050 l gs col0 s gr % Polyline [15 45] 45 sd n 3825 3825 m 4050 4050 l gs col0 s gr [] 0 sd /Times-Italic-iso ff 190.50 scf sf 2610 2655 m gs 1 -1 sc (u0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic-iso ff 190.50 scf sf 2790 2385 m gs 1 -1 sc (u1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic-iso ff 190.50 scf sf 4500 4545 m gs 1 -1 sc (q1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic-iso ff 190.50 scf sf 2925 2970 m gs 1 -1 sc (q\(n-5\)) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic-iso ff 190.50 scf sf 3375 3420 m gs 1 -1 sc (q\(n-6\)) dup sw pop 2 div neg 0 rm col0 sh gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/diaginit.fig000644 000765 000024 00000002014 12234011536 020506 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 2925 2475 2475 2925 2475 2475 2925 2025 2925 2475 2475 2475 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 2925 2025 3375 2475 3375 2925 2925 3375 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 2925 2475 3825 3375 3375 3825 2475 2925 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3375 3825 3465 3915 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3825 3375 3915 3465 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 4050 4500 4500 4050 4950 4500 4500 4950 4050 4500 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3960 4410 4050 4500 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4410 3960 4500 4050 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 3825 3825 4050 4050 4 1 0 50 -1 3 12 0.0000 0 150 210 2610 2655 u0\001 4 1 0 50 -1 3 12 0.0000 0 135 210 2790 2385 u1\001 4 1 0 50 -1 3 12 0.0000 0 180 210 4500 4545 q1\001 4 1 0 50 -1 3 12 0.0000 0 180 495 2925 2970 q(n-5)\001 4 1 0 50 -1 3 12 0.0000 0 180 495 3375 3420 q(n-6)\001 regina-4.95/engine/subcomplex/diaginit.png000644 000765 000024 00000005120 12234011536 020526 0ustar00babstaff000000 000000 ‰PNG  IHDRÍð†3¿Æ pHYs × ×B(›xtEXtSoftwareESP Ghostscript 815.00yxü: ×IDATxœíÝ-¯óÈðsÛ¥ÞO°ÊâJU½*Y°Äûú0?´,QQAŸàÐe (ªTÉ!å6[êHË+›–Tÿ;ÎŒ=ž¿ÍIµ‡Ý\':¿;“\Ç3>çô¿ñÀªñ«†oØjš/›¦±R|ewXsü9H¶Ídøí_mŽjŽéÿNáÆ¹,«™ÖÓðwùå²u2‹Ãf¦5Ç4$ ò‰ûd³Ð´˜·à˜5OÌ;pŒšæ 8&ˆáÏ1hd {Ž^3Äpçh5c sŽN£Âðæh4j kδf Ù3©™Æ0æLit¾œ Ö£Ö 0M0:‚'GùýF”Ç1†(àø}G56òÈ„‡XùLŽ££›á{機Êqt0Œ:*TÙóçŠ*8ê0S<ê1Fš“S\CHCj ;Îp¦uÓ,ªC¢[B4=шN6׎ €ë UP¨©}D96ÜFGþL{}”}‰"ºoôoTši§Ù=¼=â;Qs3\ä5Ù„qê§ŠŠàT i`5ÙˆAÔÒÓFœ×ûFþ§y RÏ(ƒÏ{§×˜ÎšuÁ†óÔ,Áðátše6œV³Ã…óÑ&œÑYñår_þi:¦ù|]'§ùÑδÌtØ¥9¯NpX%£%a·&})³0=6ç²<¬4—2#zŽæRfDoÁQhîß~HgÅæ8cÍýx­Ï×Ï=æ 8cÍçk„÷þGßóÒ<Úéu¡ˆˆú±‘0ì9½æþí™èöÝc"*ŸÿZîœ^ó¼JsˆMwf„aÎyÍ´{H{LMDt9´§m oÎKÓ_¥¡æÒ^yVbXsı¹=â;Å—æ|H‰&1œ9ÂØå= Ó—¯)'Ò`s^W9ªUQXµ?j0Daj<£ö¯±‘¯Òh1lGgâ¬Ó€áÊQkŒ¦¥ÆÓ£ÒXaXrK GÎXcaÈi0ü8C†g qÄpãÈg 3ޤ™áÅ5³0¬8‚F)oÊ'ýøññQÒíy)„ç¥Q`åö§2RÜß.ņÓkTÓì˜*Ÿsy´£òpá<5LsoˆÎa@Íýüh΃›Ö.ß•DDMÙ?Â…'d‰´îž_‘Gõá £"ÉyY¾Ž#ˆrá±"ª5[ vŠvl#s¹ÅT6GDDè–5ƒ·OÞiazüÏfrë€OŸ$á•* JóÂC¨ºë…Q{H”KO+¾‘wx€Ÿ")0rªó@Mòxø¬"P‘œ}ò—È7§ÝgSHyP„*‰‘G²ˆó«ðÛ¢’ @I¯”dƒ—ñÝ®!)%U˜¡Š$ $Âv"$Á)­ ”&Z’ _ÆC<÷@ ò(‚x~ÎUÊgæÒGX’©^fïèwtÉyÄ1€“.³Z…ñÍyíOóh?¼ê\õŒ.Ä·Ò ã™#ì¶›ŸG"î¶óÉwBÎÍ#‘·zäH;ˆçå‘ ÷AúãÈû¡çä1Âxä vw»ç¡Àøã ï#pÍC‰ñÆÝá–ÇÆg|ÇŠK“Oœ±Æ! ÆG¡±ÎC‹ñÂQi,ó0`|p”«<ŒµÆ" Ìþœ 1+Ìîœ)!KÌÞœI6kÌΜi&̾f2'Ì®f"GÌž­F™‡3fGŽ^£Ècf?ŽA3Êcf7ŽI3ÈÃS‹WH÷á5R*Lq? O2×þ:é.³FÈC…©’ñcÒ¤}Òë·{p,4}Êi*sÌž‹$ÂbÉM—ÇSç5€SÔù©ªO©ð»*x^ôïVÞžc¥AÕCL»4 õÚ艈ZÇpmtaº†°Ó ˆ>ɘ,ÒÕNÑ Wñýs+¤` Ùœc©AñG)niÒÚèki”òçKËk£õ~¿)ÇV3ø³vK£˜X%YˆáÚhfÛŽŽµFætK£Sk£ñypÅ`m´³'›½FÊ£[Z­#:\ym´+ª°%ÇA3È£j¸¬ö"6ä¸hä<âpXÊ]lÇqÒˆytû:l×F¥Ú›qÜ4³ó")Š©—…£f&g¯ª*®šYœÝJÄ8kfpö«wã®qæìX¼g†Æ‘³g%¢9'ήe•fi8ûÖˆš§±æì\ðj¦Æ’³wõ®¹+Îî¥Èfk,8û×U›¯1r<‰[ 1p|T¼[¢Ñr¼”ï[¤ÑpüÔ"\¦™äx*¬¸P3ÁñU%r©FÉñVòr±FÁñW¿s¹fÄñXŒt̀㳲ê‰ãµLì*ã·æí:šžã¹€ïJšŽã»ñZQí³žETùÆàc½Þ«åûój/6/Vìïþë滋›a_³[©ÍÍÒÛV]q¦Qyö[ºkÝN²v·²o²^+Vî‹kŹ=Œ‡ÌŒugyžl«k¨üâ¯èõú«86©ZîICM>ª}i¶ ½¾mÎ \K—øÓØLµ“#‡ùLsü¼÷ÛUÞxVàX'ǯÆ|VàÆa>ÓÈm²ù›pïuM31Â?}oËñ®Q·tãòïŸlGÇ»Æ.=DØkœzˆp׸õa®qì!Â[ãÚC„µÆ¹‡g{Æš=Døjæôa«™ÕC„«f^¦š™=Dxjæöa©1õ™äpÔÌï!ÂP³ ‡?Í’"ì4‹zˆpÓ,ë!ÂL³°‡/ÍÒ"¬4‹{ˆpÒ,ï!ÂH³B>š5zˆ°Ñ¬ÒCÄŸ¦”ò°À¨þÁ 8ÇFÌÈ)ÊuÇßÖ8a§n¦.Š#ÆiTxü2+ît> ¦÷%Š7Ö/Oj~tyh1mÙT¤®Ç ¼ #include #include "algebra/nabeliangroup.h" #include "manifold/nsfs.h" #include "triangulation/ncomponent.h" #include "triangulation/nedge.h" #include "triangulation/ntetrahedron.h" #include "subcomplex/naugtrisolidtorus.h" #include "subcomplex/nlayeredchain.h" namespace regina { const int NAugTriSolidTorus::CHAIN_NONE = 0; const int NAugTriSolidTorus::CHAIN_MAJOR = 1; const int NAugTriSolidTorus::CHAIN_AXIS = 2; NAugTriSolidTorus::~NAugTriSolidTorus() { if (core) delete core; for (int i = 0; i < 3; i++) if (augTorus[i]) delete augTorus[i]; } NAugTriSolidTorus* NAugTriSolidTorus::clone() const { NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core->clone(); for (int i = 0; i < 3; i++) { if (augTorus[i]) ans->augTorus[i] = augTorus[i]->clone(); ans->edgeGroupRoles[i] = edgeGroupRoles[i]; } ans->chainIndex = chainIndex; ans->chainType = chainType; ans->torusAnnulus = torusAnnulus; return ans; } NManifold* NAugTriSolidTorus::getManifold() const { NSFSpace* ans = new NSFSpace(); if (chainType == CHAIN_MAJOR) { // Layered solid torus + layered chain. ans->insertFibre(2, 1); ans->insertFibre(chainIndex + 1, 1); long q, r; if (edgeGroupRoles[torusAnnulus][2] == 2) { if (augTorus[torusAnnulus]) { r = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][0]); q = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][1]); } else { r = 1; q = 1; } } else { if (augTorus[torusAnnulus]) { r = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][0]); q = -augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][1]); } else { r = (edgeGroupRoles[torusAnnulus][0] == 2 ? 2 : 1); q = -(edgeGroupRoles[torusAnnulus][1] == 2 ? 2 : 1); } } r = r - q; if (r < 0) { r = -r; q = -q; } if (r == 0) { delete ans; return 0; } else ans->insertFibre(r, q); } else if (chainType == CHAIN_AXIS) { // Layered solid torus + layered chain. ans->insertFibre(2, 1); ans->insertFibre(2, -1); long q, r; if (edgeGroupRoles[torusAnnulus][2] == 2) { if (augTorus[torusAnnulus]) { r = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][0]); q = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][1]); } else { r = 1; q = 1; } } else { if (augTorus[torusAnnulus]) { r = augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][0]); q = -augTorus[torusAnnulus]->getMeridinalCuts( edgeGroupRoles[torusAnnulus][1]); } else { r = (edgeGroupRoles[torusAnnulus][0] == 2 ? 2 : 1); q = -(edgeGroupRoles[torusAnnulus][1] == 2 ? 2 : 1); } } long alpha = q - chainIndex * r; long beta = -r; if (alpha < 0) { alpha = -alpha; beta = -beta; } if (alpha == 0) { delete ans; return 0; } else ans->insertFibre(alpha, beta); } else { // Three layered solid tori. ans->insertFibre(1, 1); long alpha, beta; for (int i = 0; i < 3; i++) { if (edgeGroupRoles[i][2] == 2) { if (augTorus[i]) { alpha = augTorus[i]->getMeridinalCuts(edgeGroupRoles[i][0]); beta = augTorus[i]->getMeridinalCuts(edgeGroupRoles[i][1]); } else { alpha = 1; beta = 1; } } else { if (augTorus[i]) { alpha = augTorus[i]->getMeridinalCuts(edgeGroupRoles[i][0]); beta = -augTorus[i]->getMeridinalCuts(edgeGroupRoles[i][1]); } else { alpha = (edgeGroupRoles[i][0] == 2 ? 2 : 1); beta = -(edgeGroupRoles[i][1] == 2 ? 2 : 1); } } if (alpha == 0) { delete ans; return 0; } else ans->insertFibre(alpha, beta); } } ans->reduce(); return ans; } NAugTriSolidTorus* NAugTriSolidTorus::isAugTriSolidTorus( const NComponent* comp) { // Basic property checks. if ((! comp->isClosed()) || (! comp->isOrientable())) return 0; if (comp->getNumberOfVertices() > 1) return 0; // We have a 1-vertex closed orientable triangulation. unsigned long nTet = comp->getNumberOfTetrahedra(); if (nTet < 3) return 0; // Handle the 3-tetrahedron case separately. if (nTet == 3) { // Note that there cannot be a layered chain. NTetrahedron* base = comp->getTetrahedron(0); NTriSolidTorus* core; NPerm4 annulusMap[3]; // Check every possible choice of vertex roles in tetrahedron 0. // Note that (a,b,c,d) gives an equivalent core to (d,c,b,a). int i, j; for (i = 0; i < 24; i++) { // Make sure we don't check each possible core twice. if (NPerm4::S4[i][0] > NPerm4::S4[i][3]) continue; core = NTriSolidTorus::formsTriSolidTorus(base, NPerm4::S4[i]); if (core) { // Check that the annuli are being glued to themselves. // Since the component is orientable, that's all we need // to know. for (j = 0; j < 3; j++) { if (! core->isAnnulusSelfIdentified(j, annulusMap + j)) { delete core; core = 0; break; } } if (core) { // We got one! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; // Work out how the mobius strip is glued onto each // annulus. for (j = 0; j < 3; j++) { switch (annulusMap[j][0]) { case 0: ans->edgeGroupRoles[j] = NPerm4(2, 0, 1, 3); break; case 2: ans->edgeGroupRoles[j] = NPerm4(1, 2, 0, 3); break; case 3: ans->edgeGroupRoles[j] = NPerm4(0, 1, 2, 3); break; } } ans->chainIndex = 0; ans->chainType = CHAIN_NONE; ans->torusAnnulus = -1; return ans; } } } // Didn't find anything. return 0; } // We have strictly more than three tetrahedra. // There must be bewteen 0 and 3 layered solid tori (note that there // will be no layered solid tori other than the (0-3) glued to the // boundary annuli on the core, since no other tetrahedron is glued // to itself. int nLayered = 0; NLayeredSolidTorus* layered[4]; unsigned long usedTets = 0; for (unsigned long t = 0; t < nTet; t++) { layered[nLayered] = NLayeredSolidTorus::formsLayeredSolidTorusBase( comp->getTetrahedron(t)); if (layered[nLayered]) { usedTets += layered[nLayered]->getNumberOfTetrahedra(); nLayered++; if (nLayered == 4) { // Too many layered solid tori. for (int i = 0; i < nLayered; i++) delete layered[i]; return 0; } } } if (nLayered == 0) { // Our only chance now is a layered chain plus a degenerate // layered solid torus. // Start with tetrahedron 0. Either it belongs to the chain or // it belongs to the core. NTetrahedron* tet = comp->getTetrahedron(0); // Run through all possible cores to which it might belong. int i; NPerm4 p, annulusPerm; NTriSolidTorus* core; int torusAnnulus; unsigned long chainLen; for (i = 0; i < 24; i++) { p = NPerm4::S4[i]; if (p[0] > p[3]) continue; core = NTriSolidTorus::formsTriSolidTorus(tet, p); if (! core) continue; // Let's try this core. // Look for an identified annulus. for (torusAnnulus = 0; torusAnnulus < 3; torusAnnulus++) if (core->isAnnulusSelfIdentified(torusAnnulus, &annulusPerm)) { // Look now for a layered chain. // If we don't find it, the entire core must be wrong. int chainType = CHAIN_NONE; if ((chainLen = core->areAnnuliLinkedMajor(torusAnnulus))) chainType = CHAIN_MAJOR; else if ((chainLen = core->areAnnuliLinkedAxis(torusAnnulus))) chainType = CHAIN_AXIS; if (chainType == CHAIN_NONE || chainLen + 3 != nTet) break; // We have the entire structure! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; switch (annulusPerm[0]) { case 0: ans->edgeGroupRoles[torusAnnulus] = NPerm4(2,0,1,3); break; case 2: ans->edgeGroupRoles[torusAnnulus] = NPerm4(1,2,0,3); break; case 3: ans->edgeGroupRoles[torusAnnulus] = NPerm4(0,1,2,3); break; } ans->chainIndex = chainLen; ans->chainType = chainType; ans->torusAnnulus = torusAnnulus; return ans; } // Didn't find anything. delete core; } // Wasn't the core. Must have been the chain. NTetrahedron* top; NTetrahedron* bottom; NPerm4 topRoles; NPerm4 bottomRoles; int j; int chainType; for (i = 0; i < 6; i++) { p = NPerm4::S3[i]; NLayeredChain chain(tet, p); chain.extendMaximal(); // Note that the chain will run into one of the core tetrahedra. if (chain.getIndex() + 2 == nTet) chainType = CHAIN_MAJOR; else if (chain.getIndex() + 3 == nTet) chainType = CHAIN_AXIS; else continue; // Look for the corresponding core. // The identified annulus on the core will have to be annulus 0. // Test the chain at both ends (bottom / top). for (j = 0; j < 2; j++) { if (chainType == CHAIN_MAJOR) { core = NTriSolidTorus::formsTriSolidTorus(chain.getBottom(), chain.getBottomVertexRoles() * NPerm4(2, 3, 0, 1)); if (core) { // Test that everything is put together properly. top = chain.getTop(); topRoles = chain.getTopVertexRoles(); if ((top->adjacentTetrahedron(topRoles[0]) == core->getTetrahedron(1)) && (top->adjacentTetrahedron(topRoles[3]) == core->getTetrahedron(2)) && (top->adjacentGluing(topRoles[0]) * topRoles * NPerm4(1, 0, 2, 3) == core->getVertexRoles(1)) && (top->adjacentGluing(topRoles[3]) * topRoles * NPerm4(0, 1, 3, 2) == core->getVertexRoles(2)) && core->isAnnulusSelfIdentified( 0, &annulusPerm)) { // We have the entire structure! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; switch (annulusPerm[0]) { case 0: ans->edgeGroupRoles[0] = NPerm4(2, 0, 1, 3); break; case 2: ans->edgeGroupRoles[0] = NPerm4(1, 2, 0, 3); break; case 3: ans->edgeGroupRoles[0] = NPerm4(0, 1, 2, 3); break; } ans->chainIndex = chain.getIndex() - 1; ans->chainType = chainType; ans->torusAnnulus = 0; return ans; } delete core; } } else if (chainType == CHAIN_AXIS) { bottom = chain.getBottom(); bottomRoles = chain.getBottomVertexRoles(); NTetrahedron* startCore = bottom->adjacentTetrahedron( bottomRoles[2]); if (startCore) core = NTriSolidTorus::formsTriSolidTorus(startCore, bottom->adjacentGluing( bottomRoles[2]) * bottomRoles * NPerm4(0, 3, 2, 1)); else core = 0; if (core) { // Test that everything is put together properly. top = chain.getTop(); topRoles = chain.getTopVertexRoles(); if ((bottom->adjacentTetrahedron(bottomRoles[1]) == core->getTetrahedron(1)) && (top->adjacentTetrahedron(topRoles[0]) == core->getTetrahedron(0)) && (top->adjacentTetrahedron(topRoles[3]) == core->getTetrahedron(2)) && (bottom->adjacentGluing( bottomRoles[1]) * bottomRoles * NPerm4(2, 1, 0, 3) == core->getVertexRoles(1)) && (top->adjacentGluing(topRoles[0]) * topRoles * NPerm4(3, 0, 1, 2) == core->getVertexRoles(0)) && (top->adjacentGluing(topRoles[3]) * topRoles * NPerm4(1, 2, 3, 0) == core->getVertexRoles(2)) && core->isAnnulusSelfIdentified( 0, &annulusPerm)) { // We have the entire structure! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; switch (annulusPerm[0]) { case 0: ans->edgeGroupRoles[0] = NPerm4(2, 0, 1, 3); break; case 2: ans->edgeGroupRoles[0] = NPerm4(1, 2, 0, 3); break; case 3: ans->edgeGroupRoles[0] = NPerm4(0, 1, 2, 3); break; } ans->chainIndex = chain.getIndex(); ans->chainType = chainType; ans->torusAnnulus = 0; return ans; } delete core; } } // If we just tested the bottom, prepare to test the top. if (j == 0) chain.reverse(); } } // Didn't find anything. return 0; } // We now know nLayered >= 1. int i, j; // Determine whether or not this augmented solid torus must contain a // layered chain. bool needChain = (usedTets + 3 != nTet); if (needChain && nLayered != 1) { for (j = 0; j < nLayered; j++) delete layered[j]; return 0; } // Examine each layered solid torus. NTetrahedron* top[3]; for (i = 0; i < nLayered; i++) { top[i] = layered[i]->getTopLevel(); if (top[i]->adjacentTetrahedron(layered[i]->getTopFace(0)) == top[i]->adjacentTetrahedron(layered[i]->getTopFace(1))) { // These two top triangles should be glued to different // tetrahedra. for (j = 0; j < nLayered; j++) delete layered[j]; return 0; } } // Run to the top of the first layered solid torus; this should give // us our core. int topFace = layered[0]->getTopFace(0); NTetrahedron* coreTet = top[0]->adjacentTetrahedron(topFace); // We will declare that this triangle hooks onto vertex roles 0, 1 and 3 // of the first core tetrahedron. Thus the vertex roles permutation // should map 0, 1 and 3 (in some order) to all vertices except for // topCoreFace. int topCoreFace = top[0]->adjacentFace(topFace); NPerm4 swap3Top(3, topCoreFace); NPerm4 swap23(2, 3); NTriSolidTorus* core; NTetrahedron* coreTets[3]; NPerm4 coreVertexRoles[3]; int whichLayered[3]; int usedLayered; NPerm4 edgeGroupRoles[3]; int torusAnnulus; NPerm4 q; for (int p = 0; p < 6; p++) { core = NTriSolidTorus::formsTriSolidTorus(coreTet, swap3Top * NPerm4::S3[p] * swap23); if (core) { // We have a potential core. // Now all that remains is to ensure that the layered solid // tori hang from it accordingly. for (j = 0; j < 3; j++) { coreTets[j] = core->getTetrahedron(j); coreVertexRoles[j] = core->getVertexRoles(j); } usedLayered = 0; torusAnnulus = -1; for (j = 0; j < 3; j++) { // Check annulus j. // Recall that the 3-manifold is orientable so we don't // have to check for wacky reversed gluings. if (core->isAnnulusSelfIdentified(j, &q)) { // We have a degenerate (2,1,1) glued in here. if (needChain) { // We already know there is a non-degenerate // layered solid torus floating about, and the // other two annuli are reserved for the layered // chain. delete core; core = 0; break; } whichLayered[j] = -1; switch (q[0]) { case 0: edgeGroupRoles[j] = NPerm4(2, 0, 1, 3); break; case 2: edgeGroupRoles[j] = NPerm4(1, 2, 0, 3); break; case 3: edgeGroupRoles[j] = NPerm4(0, 1, 2, 3); break; } } else { // There should be a layered solid torus glued in here. for (whichLayered[j] = 0; whichLayered[j] < nLayered; whichLayered[j]++) if (coreTets[(j+1)%3]->adjacentTetrahedron( coreVertexRoles[(j+1)%3][2]) == top[whichLayered[j]] && coreTets[(j+2)%3]->adjacentTetrahedron( coreVertexRoles[(j+2)%3][1]) == top[whichLayered[j]]) { // Annulus j is glued to torus whichLayered[j]. q = coreTets[(j+1)%3]-> adjacentGluing( coreVertexRoles[(j+1)%3][2]) * coreVertexRoles[(j+1)%3]; // q maps vertex roles in core tetrahedron j+1 to // vertices of the top tetrahedron in // layered[whichLayered[j]]. edgeGroupRoles[j] = NPerm4( layered[whichLayered[j]]->getTopEdgeGroup( NEdge::edgeNumber[q[0]][q[3]]), layered[whichLayered[j]]->getTopEdgeGroup( NEdge::edgeNumber[q[0]][q[1]]), layered[whichLayered[j]]->getTopEdgeGroup( NEdge::edgeNumber[q[1]][q[3]]), 3); usedLayered++; break; } if (whichLayered[j] >= nLayered) { // This annulus was glued neither to itself nor // to a layered solid torus. if (needChain) whichLayered[j] = -1; else { delete core; core = 0; break; } } else { // This annulus was glued to a layered solid torus. if (needChain) torusAnnulus = j; } } } if (! core) continue; if (usedLayered < nLayered) { // We didn't use all our layered solid tori. delete core; continue; } if (needChain) { // We found our one layered solid torus. The other two // boundary annuli *must* be linked via a layered chain. int chainType = CHAIN_NONE; unsigned long chainLen; if ((chainLen = core->areAnnuliLinkedMajor(torusAnnulus))) chainType = CHAIN_MAJOR; else if ((chainLen = core->areAnnuliLinkedAxis(torusAnnulus))) chainType = CHAIN_AXIS; if (chainType == CHAIN_NONE || usedTets + chainLen + 3 != nTet) { delete core; continue; } // We've got one! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; for (j = 0; j < 3; j++) { if (whichLayered[j] >= 0) { ans->augTorus[j] = layered[whichLayered[j]]; ans->edgeGroupRoles[j] = edgeGroupRoles[j]; } } ans->chainIndex = chainLen; ans->chainType = chainType; ans->torusAnnulus = torusAnnulus; return ans; } else { // We're not looking for a layered chain. // This means we have found the entire structure! NAugTriSolidTorus* ans = new NAugTriSolidTorus(); ans->core = core; for (j = 0; j < 3; j++) { ans->edgeGroupRoles[j] = edgeGroupRoles[j]; if (whichLayered[j] >= 0) ans->augTorus[j] = layered[whichLayered[j]]; } ans->chainIndex = 0; ans->chainType = CHAIN_NONE; ans->torusAnnulus = -1; return ans; } } } // Nothing was found. for (i = 0; i < nLayered; i++) delete layered[i]; return 0; } std::ostream& NAugTriSolidTorus::writeCommonName(std::ostream& out, bool tex) const { if (chainIndex) { // We have a layered solid torus and a layered chain. NPerm4 roles = edgeGroupRoles[torusAnnulus]; const NLayeredSolidTorus* torus = augTorus[torusAnnulus]; long params[3]; if (torus) { params[0] = torus->getMeridinalCuts(0); params[1] = torus->getMeridinalCuts(1); params[2] = - torus->getMeridinalCuts(2); } else { params[0] = 1; params[1] = 1; params[2] = -2; } if (params[roles[0]] < 0) { params[0] = - params[0]; params[1] = - params[1]; params[2] = - params[2]; } if (chainType == CHAIN_MAJOR) out << (tex ? "J_{" : "J("); else out << (tex ? "X_{" : "X("); return out << chainIndex << " | " << params[roles[0]] << ',' << params[roles[1]] << (tex ? '}' : ')'); } else { // We have three layered solid tori. std::pair allParams[3]; int nAllParams = 0; NPerm4 roles; const NLayeredSolidTorus* torus; long params[3]; std::pair lstParams; int i; for (i = 0; i < 3; i++) { roles = edgeGroupRoles[i]; torus = augTorus[i]; if (torus) { params[0] = torus->getMeridinalCuts(0); params[1] = torus->getMeridinalCuts(1); params[2] = - torus->getMeridinalCuts(2); } else { params[0] = 1; params[1] = 1; params[2] = -2; } lstParams = std::make_pair(params[roles[0]], params[roles[1]]); if (lstParams.first < 0) { lstParams.first = - lstParams.first; lstParams.second = - lstParams.second; } if (! (lstParams.first == 2 && lstParams.second == -1)) allParams[nAllParams++] = lstParams; } sort(allParams, allParams + nAllParams); out << (tex ? "A_{" : "A("); for (i = 0; i < nAllParams; i++) { if (i > 0) out << " | "; out << allParams[i].first << ',' << allParams[i].second; } return out << (tex ? '}' : ')'); } } std::ostream& NAugTriSolidTorus::writeName(std::ostream& out) const { return writeCommonName(out, false); } std::ostream& NAugTriSolidTorus::writeTeXName(std::ostream& out) const { return writeCommonName(out, true); } void NAugTriSolidTorus::writeTextLong(std::ostream& out) const { out << (chainIndex ? "Chained " : "Augmented ") << "triangular solid torus " << (torusAnnulus == -1 ? "(three tori): " : "(torus + chain): "); writeName(out); } } // namespace regina regina-4.95/engine/subcomplex/naugtrisolidtorus.h000644 000765 000024 00000031524 12234011536 022211 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/naugtrisolidtorus.h * \brief Deals with augmented triangular solid torus components of a * triangulation. */ #ifndef __NAUGTRISOLIDTORUS_H #ifndef __DOXYGEN #define __NAUGTRISOLIDTORUS_H #endif #include "regina-core.h" #include "subcomplex/ntrisolidtorus.h" #include "subcomplex/nlayeredsolidtorus.h" namespace regina { class NComponent; /** * \weakgroup subcomplex * @{ */ /** * Represents an augmented triangular solid torus component of a * triangulation. Such a component is obtained as follows. Begin with * a three-tetrahedron triangular solid torus (as described by * NTriSolidTorus). Observe that the three axis edges divide the * boundary into three annuli. Then take one of the following actions. * * - To each of these annuli, glue a layered solid torus. * Note that the degenerate (2,1,1) layered solid torus * (i.e., a one-triangle mobius strip) is allowed and corresponds to * simply gluing the two triangles of the annulus together. * * - To one of these annuli, glue a layered solid torus as described * above. Join the other two annuli with a layered chain * in either the manner described by * NTriSolidTorus::areAnnuliLinkedMajor() or the manner described by * NTriSolidTorus::areAnnuliLinkedAxis(). * * It will be assumed that all layered solid tori other than the * degenerate (2,1,1) will have (3,2,1) layered solid tori at their * bases. That is, layered solid tori that begin with the degenerate * (2,1,1) and layer over the boundary of the mobius strip are \b not * considered in this class. * * Note that (unless a (1,1,0) layered solid torus is used with the 0 * edge glued to an axis edge) the resulting space will be a Seifert * fibred space over the 2-sphere with at most three exceptional fibres. * * Of the optional NStandardTriangulation routines, getManifold() is * implemented for most augmented triangular solid tori and * getHomologyH1() is not implemented at all. * * \testpart */ class REGINA_API NAugTriSolidTorus : public NStandardTriangulation { public: static const int CHAIN_NONE; /**< Indicates that this augmented triangular solid torus contains no layered chain. */ static const int CHAIN_MAJOR; /**< Indicates that this augmented triangular solid torus contains a layered chain attached as described by NTriSolidTorus::areAnnuliLinkedMajor(). */ static const int CHAIN_AXIS; /**< Indicates that this augmented triangular solid torus contains a layered chain attached as described by NTriSolidTorus::areAnnuliLinkedAxis(). */ private: NTriSolidTorus* core; /**< The triangular solid torus at the core of this triangulation. */ NLayeredSolidTorus* augTorus[3]; /**< The layered solid tori attached to the boundary annuli. If one of the layered solid tori is a degenerate (2,1,1) triangle, the corresponding pointer will be 0. Note that augTorus[i] will be attached to annulus \c i of the triangular solid torus. */ NPerm4 edgeGroupRoles[3]; /**< Permutation edgeGroupRoles[i] describes the role played by each top level edge group of layered solid torus i. For permutation p, group p[0] is glued to an axis edge, group p[1] is glued to a major edge and group p[2] is glued to a minor edge. */ unsigned long chainIndex; /**< The number of tetrahedra in the layered chain if present, or 0 if there is no layered chain. */ int chainType; /**< The way in which the layered chain is attached, or \a CHAIN_NONE if there is no layered chain. */ int torusAnnulus; /**< The annulus to which the single layered solid torus is attached (if there is a layered chain), or -1 if there is no layered chain. */ public: /** * Destroys this augmented solid torus; note that the corresponding * triangular and layered solid tori will also be destroyed. */ virtual ~NAugTriSolidTorus(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NAugTriSolidTorus* clone() const; /** * Returns the triangular solid torus at the core of this * triangulation. * * @return the core triangular solid torus. */ const NTriSolidTorus& getCore() const; /** * Returns the layered solid torus attached to the requested * annulus on the boundary of the core triangular solid torus. * If the layered solid torus is a degenerate (2,1,1) mobius * band (i.e., the two triangles of the corresponding annulus have * simply been glued together), \c null will be returned. * * @param annulus specifies which annulus to examine; this must * be 0, 1 or 2. * @return the corresponding layered solid torus. */ const NLayeredSolidTorus* getAugTorus(int annulus) const; /** * Returns a permutation describing the role played by each top * level edge group of the layered solid torus glued to the * requested annulus of the core triangular solid torus. See * NLayeredSolidTorus::getTopEdge() for details regarding edge groups. * * If the permutation returned is p, edge group p[0] * will be glued to an axis edge, group p[1] will be * glued to a major edge and group p[2] will be glued * to a minor edge. p[3] will always be 3. * * Even if the corresponding layered solid torus is a degenerate * (2,1,1) mobius band (i.e., getAugTorus() returns \c null), * the concept of edge groups is still * meaningful and this routine will return correct results. * * @param annulus specifies which annulus to examine; this must * be 0, 1 or 2. It is the layered solid torus glued to this * annulus whose edge groups will be described. * @return a permutation describing the roles of the * corresponding top level edge groups. */ NPerm4 getEdgeGroupRoles(int annulus) const; /** * Returns the number of tetrahedra in the layered chain linking * two of the boundary annuli of the core triangular solid torus. * Note that this count does not include any of the tetrahedra * actually belonging to the triangular solid torus. * * @return the number of tetrahedra in the layered chain, or 0 * if there is no layered chain linking two boundary annuli. */ unsigned long getChainLength() const; /** * Returns the way in which a layered chain links * two of the boundary annuli of the core triangular solid torus. * This will be one of the chain type constants defined in this * class. * * @return the type of layered chain, or \a CHAIN_NONE * if there is no layered chain linking two boundary annuli. */ int getChainType() const; /** * Returns the single boundary annulus of the core triangular * solid torus to which a layered solid torus is attached. This * routine is only meaningful if the other two annuli are linked * by a layered chain. * * The integer returned will be 0, 1 or 2; see the * NTriSolidTorus class notes for how the boundary annuli are * numbered. * * @return the single annulus to which the layered solid torus * is attached, or -1 if there is no layered chain (and thus all * three annuli have layered solid tori attached). */ int getTorusAnnulus() const; /** * Determines whether the core triangular solid torus has two of * its boundary annuli linked by a layered chain as described in * the general class notes. * * @return \c true if and only if the layered chain described in * the class notes is present. */ bool hasLayeredChain() const; /** * Determines if the given triangulation component is an * augmented triangular solid torus. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * augmented triangular solid torus, or \c null if the given * component is not an augmented triangular solid torus. */ static NAugTriSolidTorus* isAugTriSolidTorus(const NComponent* comp); NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new structure with all subcomponent pointers * initialised to \c null. */ NAugTriSolidTorus(); /** * Contains code common to both writeName() and writeTeXName(). * * @param out the output stream to which to write. * @param tex \c true if this routine is called from * writeTeXName() or \c false if it is called from writeName(). * @return a reference to \a out. */ std::ostream& writeCommonName(std::ostream& out, bool tex) const; }; /*@}*/ // Inline functions for NAugTriSolidTorus inline NAugTriSolidTorus::NAugTriSolidTorus() : core(0), chainType(CHAIN_NONE) { augTorus[0] = augTorus[1] = augTorus[2] = 0; } inline const NTriSolidTorus& NAugTriSolidTorus::getCore() const { return *core; } inline const NLayeredSolidTorus* NAugTriSolidTorus::getAugTorus( int annulus) const { return augTorus[annulus]; } inline NPerm4 NAugTriSolidTorus::getEdgeGroupRoles(int annulus) const { return edgeGroupRoles[annulus]; } inline unsigned long NAugTriSolidTorus::getChainLength() const { return chainIndex; } inline int NAugTriSolidTorus::getChainType() const { return chainType; } inline int NAugTriSolidTorus::getTorusAnnulus() const { return torusAnnulus; } inline bool NAugTriSolidTorus::hasLayeredChain() const { return (chainIndex != 0); } } // namespace regina #endif regina-4.95/engine/subcomplex/nblockedsfs.cpp000644 000765 000024 00000044063 12234011536 021242 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/nsfs.h" #include "subcomplex/nblockedsfs.h" #include "subcomplex/nlayeredsolidtorus.h" #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatblocktypes.h" #include "subcomplex/nsatregion.h" #include // For labs(). #include namespace regina { namespace { /** * An anonymous inline boolean xor. Experiences with plain C have * spoiled me for life from using equality/xor operators with bools. */ inline bool regXor(bool a, bool b) { return ((a && ! b) || (b && !a)); } } /** * A subclass of NSatBlockStartSearcher that, upon finding a starter * saturated block, attempts to flesh this out to a saturated region * that fills the entire triangulation (including all internal triangles). */ struct NBlockedSFSSearcher : public NSatBlockStarterSearcher { NSatRegion* region; /**< The saturated region if one has been found, or 0 if we are still searching. */ /** * Creates a new searcher whose \a region pointer is null. */ NBlockedSFSSearcher() : region(0) { } protected: bool useStarterBlock(NSatBlock* starter); }; NBlockedSFS::~NBlockedSFS() { if (region_) delete region_; } bool NBlockedSFS::isPluggedIBundle(std::string& name) const { // The triangulation needs to be closed. if (region_->numberOfBoundaryAnnuli() > 0) return false; unsigned long n = region_->numberOfBlocks(); if (n < 3 || n > 4) return false; // Try one thing at a time. const NSatBlock* block; const NSatCube* cube; const NSatReflectorStrip* ref; const NSatTriPrism* tri; const NSatTriPrism* triAdj; unsigned adjAnn; unsigned long i, j; int delta, deltaAdj; bool consistent; for (i = 0; i < n; i++) { block = region_->block(i).block; cube = dynamic_cast(block); if (cube) { if (cube->adjacentBlock(0) == cube && cube->adjacentAnnulus(0) == 2) { if (cube->adjacentReflected(0) || cube->adjacentBackwards(0)) return false; return findPluggedTori(true, 3, name, cube->adjacentBlock(1), true, cube->adjacentBlock(3), false); } else if (cube->adjacentBlock(1) == cube && cube->adjacentAnnulus(1) == 3) { if (cube->adjacentReflected(1) || cube->adjacentBackwards(1)) return false; return findPluggedTori(true, 3, name, cube->adjacentBlock(0), true, cube->adjacentBlock(2), false); } else if (cube->adjacentBlock(0) == cube && cube->adjacentAnnulus(0) == 1) { if (cube->adjacentReflected(0) || cube->adjacentBackwards(0)) return false; return findPluggedTori(false, 1, name, cube->adjacentBlock(2), false, cube->adjacentBlock(3), true); } else if (cube->adjacentBlock(1) == cube && cube->adjacentAnnulus(1) == 2) { if (cube->adjacentReflected(1) || cube->adjacentBackwards(1)) return false; return findPluggedTori(false, 1, name, cube->adjacentBlock(3), false, cube->adjacentBlock(0), true); } else if (cube->adjacentBlock(2) == cube && cube->adjacentAnnulus(2) == 3) { if (cube->adjacentReflected(2) || cube->adjacentBackwards(2)) return false; return findPluggedTori(false, 1, name, cube->adjacentBlock(0), false, cube->adjacentBlock(1), true); } else if (cube->adjacentBlock(3) == cube && cube->adjacentAnnulus(3) == 0) { if (cube->adjacentReflected(3) || cube->adjacentBackwards(3)) return false; return findPluggedTori(false, 1, name, cube->adjacentBlock(1), false, cube->adjacentBlock(2), true); } } ref = dynamic_cast(block); if (ref) { if (ref->twistedBoundary()) return false; if (ref->nAnnuli() == 1) { tri = dynamic_cast(ref->adjacentBlock(0)); if (! tri) return false; adjAnn = ref->adjacentAnnulus(0); if (tri->isMajor()) return findPluggedTori(false, 4, name, tri->adjacentBlock((adjAnn + 2) % 3), true, tri->adjacentBlock((adjAnn + 1) % 3), false); else return findPluggedTori(false, 4, name, tri->adjacentBlock((adjAnn + 1) % 3), false, tri->adjacentBlock((adjAnn + 2) % 3), true); } else if (ref->nAnnuli() == 2) { return findPluggedTori(true, 4, name, ref->adjacentBlock(0), true, ref->adjacentBlock(1), true); } else return false; } tri = dynamic_cast(block); if (tri) { for (j = 0; j < 3; j++) { // Try the thick case... if (tri->adjacentBlock(j) == tri && tri->adjacentAnnulus(j) == ((j + 1) % 3)) { if (tri->adjacentReflected(j) || tri->adjacentBackwards(j)) return false; triAdj = dynamic_cast( tri->adjacentBlock((j + 2) % 3)); if (! triAdj) return false; // Do we have major to major and minor to minor? consistent = true; if (tri->isMajor()) consistent = ! consistent; if (triAdj->isMajor()) consistent = ! consistent; if (tri->adjacentReflected((j + 2) % 3)) consistent = ! consistent; if (tri->adjacentBackwards((j + 2) % 3)) consistent = ! consistent; adjAnn = tri->adjacentAnnulus((j + 2) % 3); if (consistent) { if (triAdj->isMajor()) return findPluggedTori(false, 2, name, triAdj->adjacentBlock((adjAnn + 1) % 3), false, triAdj->adjacentBlock((adjAnn + 2) % 3), true); else return findPluggedTori(false, 2, name, triAdj->adjacentBlock((adjAnn + 2) % 3), true, triAdj->adjacentBlock((adjAnn + 1) % 3), false); } else { if (triAdj->isMajor()) return findPluggedTori(false, 3, name, triAdj->adjacentBlock((adjAnn + 2) % 3), true, triAdj->adjacentBlock((adjAnn + 1) % 3), true); else return findPluggedTori(false, 3, name, triAdj->adjacentBlock((adjAnn + 1) % 3), false, triAdj->adjacentBlock((adjAnn + 2) % 3), false); } } // ... and try the thin case. if (! (triAdj = dynamic_cast( tri->adjacentBlock(j)))) continue; // Do we have major to major and minor to minor? consistent = true; if (tri->isMajor()) consistent = ! consistent; if (triAdj->isMajor()) consistent = ! consistent; if (tri->adjacentReflected(j)) consistent = ! consistent; if (tri->adjacentBackwards(j)) consistent = ! consistent; adjAnn = tri->adjacentAnnulus(j); for (delta = 1; delta <= 2; delta++) if (tri->adjacentBlock((j + delta) % 3) == triAdj) { if (regXor(tri->adjacentReflected(j), tri->adjacentReflected((j + delta) % 3))) return false; if (! regXor(tri->adjacentBackwards(j), tri->adjacentBackwards((j + delta) % 3))) return false; // We have our Mobius strip! // Make sure we come at it via the correct joining. deltaAdj = (tri->adjacentBackwards(j) ? 3 - delta : delta); if (tri->adjacentAnnulus((j + delta) % 3) != (adjAnn + deltaAdj) % 3) { // It's not the way we want to see it, but // we'll come at it from the correct joining later. continue; } // Our LSTs need to be measured against the // major edges in all cases here. return findPluggedTori(true, consistent ? 2 : 1, name, tri->adjacentBlock((j + 2 * delta) % 3), tri->isMajor(), triAdj->adjacentBlock((adjAnn + 2 * deltaAdj) % 3), triAdj->isMajor()); } } } } // Nothing. return false; } NManifold* NBlockedSFS::getManifold() const { NSFSpace* ans = region_->createSFS(false); if (! ans) return 0; ans->reduce(); // If we have SFS(RP2/n2) with one exceptional fibre, rewrite it as // SFS(S2) with three exceptional fibres. if (ans->baseClass() == NSFSpace::n2 && ans->baseGenus() == 1 && (! ans->baseOrientable()) && ans->punctures() == 0 && ans->reflectors() == 0 && ans->fibreCount() <= 1) { NSFSpace* altAns = new NSFSpace(/* S2 x S1 */); altAns->insertFibre(2, 1); altAns->insertFibre(2, -1); NSFSFibre rp2Fibre; if (ans->fibreCount() == 0) { rp2Fibre.alpha = 1; rp2Fibre.beta = ans->obstruction(); } else { rp2Fibre = ans->fibre(0); rp2Fibre.beta += rp2Fibre.alpha * ans->obstruction(); } // Make sure we're not going to try inserting (0,k). if (rp2Fibre.beta != 0) { altAns->insertFibre(rp2Fibre.beta, rp2Fibre.alpha); altAns->reduce(); delete ans; return altAns; } else delete altAns; } return ans; } std::ostream& NBlockedSFS::writeName(std::ostream& out) const { out << "Blocked SFS ["; region_->writeBlockAbbrs(out, false); return out << ']'; } std::ostream& NBlockedSFS::writeTeXName(std::ostream& out) const { out << "\\mathrm{BSFS}\\left["; region_->writeBlockAbbrs(out, true); return out << "\\right]"; } void NBlockedSFS::writeTextLong(std::ostream& out) const { region_->writeDetail(out, "Blocked SFS"); } NBlockedSFS* NBlockedSFS::isBlockedSFS(NTriangulation* tri) { // Basic property checks. if (tri->getNumberOfComponents() > 1) return 0; if (tri->isIdeal()) return 0; // Watch out for twisted block boundaries that are incompatible with // neighbouring blocks! These will result in edges joined to // themselves in reverse. if (! tri->isValid()) return 0; // Hunt for a starting block. NBlockedSFSSearcher searcher; searcher.findStarterBlocks(tri); // Any luck? if (searcher.region) { // The region expansion worked, and the triangulation is known // to be connected. // This means we've got one! return new NBlockedSFS(searcher.region); } // Nope. return 0; } bool NBlockedSFSSearcher::useStarterBlock(NSatBlock* starter) { // The region pointer should be null, but just in case... if (region) { delete starter; return false; } // See if we can flesh out an entire triangulation component from // the starter block. At this point the region will own the given // starter block. region = new NSatRegion(starter); if (! region->expand(usedTets, true)) { // Nup. Destroy the temporary structures and keep searching. delete region; region = 0; return true; } // Got one! Stop the search. return false; } bool NBlockedSFS::findPluggedTori(bool thin, int id, std::string& name, const NSatBlock* torus0, bool horiz0, const NSatBlock* torus1, bool horiz1) { long p0, q0; long p1, q1; if (torus0->adjacentReflected(0)) horiz0 = ! horiz0; if (torus0->adjacentBackwards(0)) horiz0 = ! horiz0; if (torus1->adjacentReflected(1)) horiz1 = ! horiz1; if (torus1->adjacentBackwards(1)) horiz1 = ! horiz1; const NSatLST* lst; const NSatMobius* mobius; NPerm4 roles; if ((mobius = dynamic_cast(torus0))) { if (mobius->position() == 2) { p0 = 2; q0 = -1; } else if (mobius->position() == 1) { p0 = 1; q0 = (horiz0 ? -2 : 1); } else { p0 = 1; q0 = (horiz0 ? 1 : -2); } } else if ((lst = dynamic_cast(torus0))) { roles = lst->roles(); p0 = lst->lst()->getMeridinalCuts(roles[0]); q0 = lst->lst()->getMeridinalCuts(roles[horiz0 ? 1 : 2]); if (! ((roles[2] == 2 && horiz0) || (roles[1] == 2 && ! horiz0))) q0 = -q0; } else return false; if ((mobius = dynamic_cast(torus1))) { if (mobius->position() == 2) { p1 = 2; q1 = -1; } else if (mobius->position() == 1) { p1 = 1; q1 = (horiz1 ? -2 : 1); } else { p1 = 1; q1 = (horiz1 ? 1 : -2); } } else if ((lst = dynamic_cast(torus1))) { roles = lst->roles(); p1 = lst->lst()->getMeridinalCuts(roles[0]); q1 = lst->lst()->getMeridinalCuts(roles[horiz1 ? 1 : 2]); if (! ((roles[2] == 2 && horiz1) || (roles[1] == 2 && ! horiz1))) q1 = -q1; } else return false; // Do a little normalisation. if ((thin && (id == 3 || id == 4)) || ((! thin) && id == 1)) { // Complementing does nothing. if (p0 > 0 && p1 > 0 && q0 < 0 && q1 < 0 && q0 > -p0 && q1 > -p1 && 2 * q0 <= -p0 && 2 * q1 <= -p1) { q0 = -p0 - q0; q1 = -p1 - q1; } } if (labs(p1) > labs(p0) || (labs(p1) == labs(p0) && labs(q1) < labs(q0))) { long tmp; if (thin || ((! thin) && (id == 1 || id ==3))) { // Swapping does nothing. tmp = p0; p0 = p1; p1 = tmp; tmp = q0; q0 = q1; q1 = tmp; } else if (id == 2 || id == 4) { // If we swap then we also complement. tmp = p0; p0 = p1; p1 = tmp; tmp = q0; q0 = q1; q1 = tmp; q0 = -p0 - q0; q1 = -p1 - q1; } } // All good. Build the full name and quit. std::ostringstream ans; ans << (thin ? 'H' : 'K') << "(T~" << (thin ? 6 : 5) << '^' << id; if (p0 != 2 || q0 != -1 || p1 != 2 || q1 != -1) ans << " | " << p0 << ',' << q0; if (p1 != 2 || q1 != -1) ans << " | " << p1 << ',' << q1; ans << ')'; name = ans.str(); return true; } } // namespace regina regina-4.95/engine/subcomplex/nblockedsfs.h000644 000765 000024 00000022732 12234011536 020706 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nblockedsfs.h * \brief Supports Seifert fibred spaces that are triangulated using * saturated blocks. */ #ifndef __NBLOCKEDSFS_H #ifndef __DOXYGEN #define __NBLOCKEDSFS_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" namespace regina { class NSatBlock; class NSatRegion; /** * \weakgroup subcomplex * @{ */ /** * Represents a blocked Seifert fibred space (possibly with boundary). * This is a particular type of triangulation of a Seifert fibred space, * where this triangulation is formed from a single saturated region. * A saturated region is in turn formed from saturated blocks by identifying * their boundary annuli in pairs; see the NSatRegion class for details. * * Note that the routines writeName() and writeTeXName() do \e not * offer enough information to uniquely identify the triangulation, * since this essentially requires a 2-dimensional assembling of * saturated blocks. For full detail, writeTextLong() may be used instead. * * The optional NStandardTriangulation routine getManifold() is * implemented for this class, but getHomologyH1() is not. * * \testpart */ class REGINA_API NBlockedSFS : public NStandardTriangulation { private: NSatRegion* region_; /**< The single saturated region that describes this entire triangulation. */ public: /** * Destroys this structure and its constituent components. */ ~NBlockedSFS(); /** * Returns details of the single saturated region that * fills this triangulation. * * @return the single saturated region. */ const NSatRegion& region() const; /** * Determines whether this triangulation is a plugged thin * I-bundle or a plugged thick I-bundle. These structures are * described in "Structures of small closed non-orientable * 3-manifold triangulations", Benjamin A. Burton, * J. Knot Theory Ramifications 16 (2007), 545--574. * * \ifacespython The argument \a name is not present. Instead, * this routine returns a tuple of size two: the boolean usually * returned from this function, and the string usually returned in * the argument \a name. * * @param name used to return the name of the plugged thin/thick * I-bundle, if the triangulation is of this form. If the * triangulation is not of this form, this string is not touched. * @return \c true if this triangulation is indeed a plugged thin * I-bundle or a plugged thick I-bundle. */ bool isPluggedIBundle(std::string& name) const; NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Determines if the given triangulation is a blocked Seifert * fibred space. * * @param tri the triangulation to examine. * @return a newly created structure containing details of the * blocked Seifert fibred space, or \c null if the given * triangulation is not a blocked Seifert fibred space. */ static NBlockedSFS* isBlockedSFS(NTriangulation* tri); private: /** * Constructs a new blocked Seifert fibred space, as described by * the given saturated region. The new object will take ownership * of the given region. * * Note that the new object must describe an existing triangulation. * * @param region the region describing this entire triangulation. */ NBlockedSFS(NSatRegion* region); /** * Attempts to identify the solid torus plugs in a plugged thin * I-bundle or a plugged thick I-bundle. This routine is * internal to isPluggedIBundle(). * * It is assumed that the plugged thin/thick I-bundle has been * completely identified, with the exception of the two solid * torus plugs. Corresponding parameters describing the * core I-bundle must be passed, along with two blocks that * should correspond to the two plugs. * * If the two blocks are indeed solid torus plugs (either layered * solid tori or Mobius bands), the full name of the plugged * thin/thick I-bundle will be filled in and \c true will be * returned. Note that this name may be normalised or otherwise * modified to return a simpler set of parameters for the same * triangulation. If either block is not a solid torus plug then * \c false will be returned. * * @param thin \c true if the overall structure being identified * is a plugged thin I-bundle, or \c false if it is a plugged * thick I-bundle. * @param id identifies the particular thin/thick twisted * I-bundle into which the solid tori are plugged. This must be * 1, 2, 3 or 4, to distinguish between the four thin twisted * I-bundles or the four thick twisted I-bundles described in * the paper "Structures of small closed non-orientable * 3-manifold triangulations" (see isPluggedIBundle for details). * @param name used to return the full parameterised name of this * triangulation. If the two given blocks are not solid torus * plugs, this string is not touched. * @param torus0 the block that should provide the solid torus plug * corresponding to the first pair of integers in the plugged * thin/thick I-bundle parameters. * @param horiz0 \c true if the first pair of integers in the * plugged thin/thick I-bundle parameters should measure the * number of times the meridinal curve cuts the vertical and * horizontal edges of the adjacent block (not the block * \a torus0, but its neighbour), or \c false if the vertical * and diagonal edges should be used instead. * @param torus1 the block that should provide the solid torus plug * corresponding to the second pair of integers in the plugged * thin/thick I-bundle parameters. * @param horiz1 \c true if the second pair of integers in the * plugged thin/thick I-bundle parameters should measure the * number of times the meridinal curve cuts the vertical and * horizontal edges of the adjacent block (not the block * \a torus1, but its neighbour), or \c false if the vertical * and diagonal edges should be used instead. * @return \c true if the two given blocks are both solid torus * plugs (either layered solid tori or Mobius bands), or \c false * otherwise. */ static bool findPluggedTori(bool thin, int id, std::string& name, const NSatBlock* torus0, bool horiz0, const NSatBlock* torus1, bool horiz1); }; /*@}*/ // Inline functions for NBlockedSFS inline NBlockedSFS::NBlockedSFS(NSatRegion* region) : region_(region) { } inline const NSatRegion& NBlockedSFS::region() const { return *region_; } } // namespace regina #endif regina-4.95/engine/subcomplex/nblockedsfsloop.cpp000644 000765 000024 00000022020 12234011536 022121 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/ngraphloop.h" #include "manifold/nsfs.h" #include "subcomplex/nblockedsfsloop.h" #include "subcomplex/nlayering.h" #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatregion.h" namespace regina { /** * A subclass of NSatBlockStarterSearcher that, upon finding a starter * block, attempts to flesh this out to an entire saturated region with * two identified torus boundaries, as described by the NBlockedSFSLoop * class. */ struct NBlockedSFSLoopSearcher : public NSatBlockStarterSearcher { NSatRegion* region; /**< The bounded saturated region, if the entire NBlockedSFSLoop structure has been successfully found; otherwise, 0 if we are still searching. */ NMatrix2 matchingReln; /**< The matrix describing how the two boundary annuli of the saturated region are joined together. This matrix expresses the fibre/base curves on one boundary annulus in terms of the fibre/base curves on the other, as described by NGraphLoop::matchingReln(). */ /** * Creates a new searcher whose \a region pointer is null. */ NBlockedSFSLoopSearcher() : region(0) { } protected: bool useStarterBlock(NSatBlock* starter); }; NBlockedSFSLoop::~NBlockedSFSLoop() { if (region_) delete region_; } NManifold* NBlockedSFSLoop::getManifold() const { NSFSpace* sfs = region_->createSFS(false); if (! sfs) return 0; if (sfs->punctures() == 1) { // The region has one larger boundary, but we pinch it to create // two smaller boundaries. sfs->addPuncture(); } sfs->reduce(false); return new NGraphLoop(sfs, matchingReln_); } std::ostream& NBlockedSFSLoop::writeName(std::ostream& out) const { out << "Blocked SFS Loop ["; region_->writeBlockAbbrs(out, false); return out << ']'; } std::ostream& NBlockedSFSLoop::writeTeXName(std::ostream& out) const { out << "\\mathrm{BSFS\\_Loop}\\left["; region_->writeBlockAbbrs(out, true); return out << "\\right]"; } void NBlockedSFSLoop::writeTextLong(std::ostream& out) const { out << "Blocked SFS Loop, matching relation " << matchingReln_ << '\n'; region_->writeDetail(out, "Internal region"); } NBlockedSFSLoop* NBlockedSFSLoop::isBlockedSFSLoop(NTriangulation* tri) { // Basic property checks. if (! tri->isClosed()) return 0; if (tri->getNumberOfComponents() > 1) return 0; // Watch out for twisted block boundaries that are incompatible with // neighbouring blocks! Also watch for saturated tori being joined // to saturated Klein bottles. Any of these issues will result in // edges joined to themselves in reverse. if (! tri->isValid()) return 0; // Hunt for a starting block. NBlockedSFSLoopSearcher searcher; searcher.findStarterBlocks(tri); // Any luck? if (searcher.region) { // The expansion and self-adjacency worked, and the triangulation // is known to be closed and connected. // This means we've got one! return new NBlockedSFSLoop(searcher.region, searcher.matchingReln); } // Nope. return 0; } bool NBlockedSFSLoopSearcher::useStarterBlock(NSatBlock* starter) { // The region pointer should be null, but just in case... if (region) { delete starter; return false; } // Flesh out the triangulation as far as we can. We're aiming for // precisely two boundary annuli remaining. // Note that the starter block will now be owned by region. region = new NSatRegion(starter); region->expand(usedTets); if (region->numberOfBoundaryAnnuli() != 2) { delete region; region = 0; return true; } NSatBlock* bdryBlock[2]; unsigned bdryAnnulus[2]; bool bdryRefVert[2], bdryRefHoriz[2]; region->boundaryAnnulus(0, bdryBlock[0], bdryAnnulus[0], bdryRefVert[0], bdryRefHoriz[0]); region->boundaryAnnulus(1, bdryBlock[1], bdryAnnulus[1], bdryRefVert[1], bdryRefHoriz[1]); // We either want two disjoint one-annulus torus boundaries, or else a // single two-annulus boundary that is pinched to turn each annulus into // a two-sided torus. The following test will handle all cases. We // don't worry about the degenerate case of fibres mapping to fibres // through the layering in the pinched case, since this will fail // our test anyway (either boundaries do not form tori, or they are // not two-sided). NSatAnnulus bdry0 = bdryBlock[0]->annulus(bdryAnnulus[0]); NSatAnnulus bdry1 = bdryBlock[1]->annulus(bdryAnnulus[1]); if (! (bdry0.isTwoSidedTorus() && bdry1.isTwoSidedTorus())) { delete region; region = 0; return true; } // Look for a layering on the first boundary annulus. // Extend the layering one tetrahedron at a time, to make sure we // don't loop back onto ourselves. NLayering layering(bdry0.tet[0], bdry0.roles[0], bdry0.tet[1], bdry0.roles[1]); NSatAnnulus layerTop; NMatrix2 layerToBdry1; while (true) { layerTop.tet[0] = layering.getNewBoundaryTet(0); layerTop.tet[1] = layering.getNewBoundaryTet(1); layerTop.roles[0] = layering.getNewBoundaryRoles(0); layerTop.roles[1] = layering.getNewBoundaryRoles(1); // Have we reached the second boundary? if (bdry1.isJoined(layerTop, layerToBdry1)) break; // We haven't joined up yet. Either extend or die. if (! layering.extendOne()) { // The layering dried up and we didn't make it. delete region; region = 0; return true; } if (usedTets.find(layering.getNewBoundaryTet(0)) != usedTets.end() || usedTets.find(layering.getNewBoundaryTet(1)) != usedTets.end()) { // Gone too far -- we've looped back upon ourselves. delete region; region = 0; return true; } usedTets.insert(layering.getNewBoundaryTet(0)); usedTets.insert(layering.getNewBoundaryTet(1)); } // This is it! Build the matching matrix and stop searching. // First find mappings from the fibre/base curves (fi, oi) to // annulus #i edges (first triangle: 01, first triangle: 02). // Note that each of these matrices is self-inverse. NMatrix2 curves0ToAnnulus0(bdryRefVert[0] ? 1 : -1, 0, 0, bdryRefHoriz[0] ? -1 : 1); NMatrix2 curves1ToAnnulus1(bdryRefVert[1] ? 1 : -1, 0, 0, bdryRefHoriz[1] ? -1 : 1); // Put it all together. // Remember that curves1ToAnnulus1 is self-inverse. matchingReln = curves1ToAnnulus1 * layerToBdry1 * layering.boundaryReln() * curves0ToAnnulus0; return false; } } // namespace regina regina-4.95/engine/subcomplex/nblockedsfsloop.h000644 000765 000024 00000020211 12234011536 021566 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nblockedsfsloop.h * \brief Supports self-identified Seifert fibred spaces that are * triangulated using saturated blocks. */ #ifndef __NBLOCKEDSFSLOOP_H #ifndef __DOXYGEN #define __NBLOCKEDSFSLOOP_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "subcomplex/nstandardtri.h" namespace regina { class NSatRegion; /** * \weakgroup subcomplex * @{ */ /** * Represents a blocked Seifert fibred space with two boundary tori that * are joined together. This is a particular type of triangulation of a * graph manifold, formed from a single saturated region whose two torus * boundaries are identified. An optional layering may be placed * between the two torus boundaries to allow for a more interesting * relationship between the two sets of boundary curves. For more * detail on saturated regions and their constituent saturated blocks, * see the NSatRegion class; for more detail on layerings, see the * NLayering class. * * The saturated region may have two boundary components formed from one * saturated annulus each. Alternatively, it may have one boundary * formed from two saturated annuli, where this boundary is pinched * together so that each annulus becomes a two-sided torus (both of which * are later joined together). None of the boundary components (or the * two-sided tori discussed above) may be twisted (i.e., they must be * tori, not Klein bottles). * * The way in which the two torus boundaries are identified is specified * by a 2-by-2 matrix, which expresses curves representing the fibres * and base orbifold on the second boundary in terms of such curves on * the first boundary (see the page on \ref sfsnotation for terminology). * * More specifically, suppose that \a f0 and \a o0 are directed curves * on the first boundary torus and \a f1 and \a o1 are directed curves * on the second boundary torus, where \a f0 and \a f1 represent the * fibres of the region and \a o0 and \a o1 represent the base orbifold. * Then the boundaries are joined according to the following relation: * *
 *     [f1]       [f0]
 *     [  ] = M * [  ]
 *     [o1]       [o0]
 * 
* * If a layering is present between the two torus boundaries, then the * corresponding boundary curves are not identified directly. In this * case, the matrix \a M shows how the layering relates the curves on * each boundary. * * Note that the routines writeName() and writeTeXName() do \e not * offer enough information to uniquely identify the triangulation, * since this essentially requires 2-dimensional assemblings of * saturated blocks. For full details, writeTextLong() may be used instead. * * The optional NStandardTriangulation routine getManifold() is * implemented for this class, but getHomologyH1() is not. */ class REGINA_API NBlockedSFSLoop : public NStandardTriangulation { private: NSatRegion* region_; /**< The saturated region whose two torus boundaries are joined. */ NMatrix2 matchingReln_; /**< Specifies how the two boundary tori are joined, as described in the class notes above. */ public: /** * Destroys this structure and its constituent components. */ ~NBlockedSFSLoop(); /** * Returns details of the saturated region from which this * triangulation is formed. See the class notes above for * further information. * * @return details of the saturated region. */ const NSatRegion& region() const; /** * Returns the matrix describing how the two torus boundaries of * the saturated region are joined. Note that if a layering is * placed between the two boundary tori, then any changes to the * boundary relationships caused by the layering are included in * this matrix. * * See the class notes above for precise information on how this * matrix is presented. * * @return the matrix describing how the boundaries of the * region are joined. */ const NMatrix2& matchingReln() const; NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Determines if the given triangulation is a blocked Seifert * fibred space with identified boundaries, as described by this * class. * * @param tri the triangulation to examine. * @return a newly created structure containing details of the * blocked self-identified Seifert fibred space, or \c null if * the given triangulation is not of this form. */ static NBlockedSFSLoop* isBlockedSFSLoop(NTriangulation* tri); private: /** * Constructs a new blocked self-identified Seifert fibred * space, as described by the given saturated region and * matching relation. The new object will take ownership of the * given region. * * Note that the new object must describe an existing triangulation. * * @param region the saturated region from which the structure * is formed. * @param matchingReln describes how the two boundaries of the * region are joined, as described in the class notes above. */ NBlockedSFSLoop(NSatRegion* region, const NMatrix2& matchingReln); }; /*@}*/ // Inline functions for NBlockedSFSLoop inline NBlockedSFSLoop::NBlockedSFSLoop(NSatRegion* region, const NMatrix2& matchingReln) : region_(region), matchingReln_(matchingReln) { } inline const NSatRegion& NBlockedSFSLoop::region() const { return *region_; } inline const NMatrix2& NBlockedSFSLoop::matchingReln() const { return matchingReln_; } } // namespace regina #endif regina-4.95/engine/subcomplex/nblockedsfspair.cpp000644 000765 000024 00000025215 12234011536 022114 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/ngraphpair.h" #include "manifold/nsfs.h" #include "subcomplex/nblockedsfspair.h" #include "subcomplex/nlayering.h" #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatregion.h" namespace regina { /** * A subclass of NSatBlockStarterSearcher that, upon finding a starter * block, attempts to flesh this out to a pair of saturated regions * joined along their single torus boundaries, as desribed by the * NBlockedSFSPair class. */ struct NBlockedSFSPairSearcher : public NSatBlockStarterSearcher { NSatRegion* region[2]; /**< The two bounded saturated regions that are joined together, if the entire NBlockedSFSPair structure has been successfully found; otherwise, two null pointers if we are still searching. */ NMatrix2 matchingReln; /**< The matrix describing how the region boundaries are joined together. This matrix expresses the fibre/base curves on the second region boundary in terms of the fibre/base curves on the first, as described by NGraphPair::matchingReln(). */ /** * Creates a new searcher whose \a region pointers are both null. */ NBlockedSFSPairSearcher() { region[0] = region[1] = 0; } protected: bool useStarterBlock(NSatBlock* starter); }; NBlockedSFSPair::~NBlockedSFSPair() { if (region_[0]) delete region_[0]; if (region_[1]) delete region_[1]; } NManifold* NBlockedSFSPair::getManifold() const { NSFSpace* sfs0 = region_[0]->createSFS(false); if (! sfs0) return 0; NSFSpace* sfs1 = region_[1]->createSFS(false); if (! sfs1) { delete sfs0; return 0; } // Reduce the Seifert fibred space representations and finish up. sfs0->reduce(false); sfs1->reduce(false); if (*sfs1 < *sfs0) return new NGraphPair(sfs1, sfs0, matchingReln_.inverse()); else return new NGraphPair(sfs0, sfs1, matchingReln_); } std::ostream& NBlockedSFSPair::writeName(std::ostream& out) const { out << "Blocked SFS Pair ["; region_[0]->writeBlockAbbrs(out, false); out << " | "; region_[1]->writeBlockAbbrs(out, false); return out << ']'; } std::ostream& NBlockedSFSPair::writeTeXName(std::ostream& out) const { out << "\\mathrm{BSFS\\_Pair}\\left["; region_[0]->writeBlockAbbrs(out, true); out << "\\,|\\,"; region_[1]->writeBlockAbbrs(out, true); return out << "\\right]"; } void NBlockedSFSPair::writeTextLong(std::ostream& out) const { out << "Blocked SFS pair, matching relation " << matchingReln_ << "\n"; region_[0]->writeDetail(out, "First region"); region_[1]->writeDetail(out, "Second region"); } NBlockedSFSPair* NBlockedSFSPair::isBlockedSFSPair(NTriangulation* tri) { // Basic property checks. if (! tri->isClosed()) return 0; if (tri->getNumberOfComponents() > 1) return 0; // Watch out for twisted block boundaries that are incompatible with // neighbouring blocks! Also watch for the boundary between blocks // being an annulus on one side and a Klein bottle on the other (or // two incompatible Klein bottles for that matter). // // These will result in edges joined to themselves in reverse. if (! tri->isValid()) return 0; // Hunt for a starting block. NBlockedSFSPairSearcher searcher; searcher.findStarterBlocks(tri); // Any luck? if (searcher.region[0]) { // The full expansion worked, and the triangulation is known // to be closed and connected. // This means we've got one! return new NBlockedSFSPair(searcher.region[0], searcher.region[1], searcher.matchingReln); } // Nope. return 0; } bool NBlockedSFSPairSearcher::useStarterBlock(NSatBlock* starter) { // The region pointers should be null, but just in case... if (region[0] || region[1]) { delete starter; return false; } // Flesh out the triangulation as far as we can. We're aiming for // just one boundary annulus remaining. // Note that the starter block will now be owned by region[0]. region[0] = new NSatRegion(starter); region[0]->expand(usedTets); if (region[0]->numberOfBoundaryAnnuli() != 1) { delete region[0]; region[0] = 0; return true; } // Insist on this boundary being untwisted. NSatBlock* bdryBlock; unsigned bdryAnnulus; bool bdryVert, bdryHoriz; region[0]->boundaryAnnulus(0, bdryBlock, bdryAnnulus, bdryVert, bdryHoriz); bool firstRegionReflected = ((bdryVert && ! bdryHoriz) || (bdryHoriz && ! bdryVert)); NSatBlock* tmpBlock; unsigned tmpAnnulus; bool tmpVert, tmpHoriz; bdryBlock->nextBoundaryAnnulus(bdryAnnulus, tmpBlock, tmpAnnulus, tmpVert, tmpHoriz, false); if (tmpVert) { delete region[0]; region[0] = 0; return true; } NSatAnnulus bdry = bdryBlock->annulus(bdryAnnulus); // We have a boundary annulus for the first region. // Hunt for a layering. NLayering layering(bdry.tet[0], bdry.roles[0], bdry.tet[1], bdry.roles[1]); layering.extend(); // Relation from fibre/orbifold to layering first triangle markings 01/02: NMatrix2 curves0ToLayering = layering.boundaryReln() * NMatrix2(-1, 0, 0, firstRegionReflected ? -1 : 1); // We make the shell of an other-side boundary annulus; we will fill // in the precise vertex role permutations later on. NSatAnnulus otherSide(layering.getNewBoundaryTet(0), NPerm4(), layering.getNewBoundaryTet(1), NPerm4()); if (otherSide.meetsBoundary()) { delete region[0]; region[0] = 0; return true; } // Mapping from (layering first triangle markings 01/02) to // (other side annulus first triangle markings 01/02). Like the other // side vertex roles, this mapping will be filled in later. NMatrix2 layeringToAnnulus1; // Try the three possible orientations for fibres on the other side. NSatBlock* otherStarter; for (int plugPos = 0; plugPos < 3; plugPos++) { // Construct the boundary annulus for the second region. // Refresh the tetrahedra as well as the vertex roles, since // it may have switched sides since our last run through the loop. otherSide.tet[0] = layering.getNewBoundaryTet(0); otherSide.tet[1] = layering.getNewBoundaryTet(1); if (plugPos == 0) { otherSide.roles[0] = layering.getNewBoundaryRoles(0); otherSide.roles[1] = layering.getNewBoundaryRoles(1); layeringToAnnulus1 = NMatrix2(1, 0, 0, 1); } else if (plugPos == 1) { otherSide.roles[0] = layering.getNewBoundaryRoles(0) * NPerm4(1, 2, 0, 3); otherSide.roles[1] = layering.getNewBoundaryRoles(1) * NPerm4(1, 2, 0, 3); layeringToAnnulus1 = NMatrix2(-1, 1, -1, 0); } else { otherSide.roles[0] = layering.getNewBoundaryRoles(0) * NPerm4(2, 0, 1, 3); otherSide.roles[1] = layering.getNewBoundaryRoles(1) * NPerm4(2, 0, 1, 3); layeringToAnnulus1 = NMatrix2(0, -1, 1, -1); } // Clear out the used tetrahedron list. Everything before the new // layering boundary is self-contained, so we won't run into it // again on the other side. We'll just re-insert the layering // boundary tetrahedra. usedTets.clear(); usedTets.insert(layering.getNewBoundaryTet(0)); usedTets.insert(layering.getNewBoundaryTet(1)); // See if we can flesh the other side out to an entire region. otherSide.switchSides(); if ((otherStarter = NSatBlock::isBlock(otherSide, usedTets))) { region[1] = new NSatRegion(otherStarter); region[1]->expand(usedTets); if (region[1]->numberOfBoundaryAnnuli() == 1) { // This is it! Stop searching. // Do a final conversion from annulus first triangle markings // 01/02 and exit. matchingReln = NMatrix2(-1, 0, 0, 1) * layeringToAnnulus1 * curves0ToLayering; return false; } // Nup, this one didn't work. delete region[1]; region[1] = 0; } } // Sigh, nothing works. delete region[0]; region[0] = 0; return true; } } // namespace regina regina-4.95/engine/subcomplex/nblockedsfspair.h000644 000765 000024 00000020124 12234011536 021553 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nblockedsfspair.h * \brief Supports joined pairs of Seifert fibred spaces that are * triangulated using saturated blocks. */ #ifndef __NBLOCKEDSFSPAIR_H #ifndef __DOXYGEN #define __NBLOCKEDSFSPAIR_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "subcomplex/nstandardtri.h" namespace regina { class NSatRegion; /** * \weakgroup subcomplex * @{ */ /** * Represents a blocked pair of Seifert fibred spaces joined along a single * connecting torus. This is a particular type of triangulation of a graph * manifold, formed from two saturated regions whose torus boundaries are * identified. An optional layering may be placed between the two torus * boundaries to allow for a more interesting relationship between the boundary * curves of each region. For more detail on saturated regions and their * constituent saturated blocks, see the NSatRegion class; for more detail * on layerings, see the NLayering class. * * Each of the two saturated regions must have precisely one boundary * component formed from just one saturated annulus, and this boundary may * not be twisted (i.e., it must be a torus, not a Klein bottle). The * way in which the boundaries from each region are identified is * specified by a 2-by-2 matrix \a M, which expresses curves * representing the fibres and base orbifold of the second region in * terms of the first (see the page on \ref sfsnotation for terminology). * * More specifically, suppose that \a f0 and \a o0 are directed curves on * the first region boundary and \a f1 and \a o1 are directed curves on the * second region boundary, where \a f0 and \a f1 represent the fibres of * each region and \a o0 and \a o1 represent the base orbifolds. Then * the boundaries are joined according to the following relation: * *
 *     [f1]       [f0]
 *     [  ] = M * [  ]
 *     [o1]       [o0]
 * 
* * If a layering is present between the two boundaries, then the * boundary curves are not identified directly. In this case, the matrix * \a M shows how the layering relates the curves on each region boundary. * * Note that the routines writeName() and writeTeXName() do \e not * offer enough information to uniquely identify the triangulation, * since this essentially requires 2-dimensional assemblings of * saturated blocks. For full details, writeTextLong() may be used instead. * * The optional NStandardTriangulation routine getManifold() is * implemented for this class, but getHomologyH1() is not. * * \testpart */ class REGINA_API NBlockedSFSPair : public NStandardTriangulation { private: NSatRegion* region_[2]; /**< The two saturated regions whose boundaries are joined. */ NMatrix2 matchingReln_; /**< Specifies how the two region boundaries are joined, as described in the class notes above. */ public: /** * Destroys this structure and its constituent components. */ ~NBlockedSFSPair(); /** * Returns details of one of the two bounded saturated regions that * form this triangulation. See the class notes above for further * information. * * @param which 0 if the first region should be returned, or * 1 if the second region should be returned. * @return details of the requested saturated region. */ const NSatRegion& region(int which) const; /** * Returns the matrix describing how the two saturated region * boundaries are joined. Note that if a layering is placed * between the two region boundaries, then any changes to the * boundary relationships caused by the layering are included * in this matrix. * * See the class notes above for precise information on how this * matrix is presented. * * @return the matrix describing how the region boundaries are * joined. */ const NMatrix2& matchingReln() const; NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Determines if the given triangulation is a blocked pair of * Seifert fibred spaces, as described by this class. * * @param tri the triangulation to examine. * @return a newly created structure containing details of the * blocked pair, or \c null if the given triangulation is not of * this form. */ static NBlockedSFSPair* isBlockedSFSPair(NTriangulation* tri); private: /** * Constructs a new blocked pair of Seifert fibred spaces, as * described by the given saturated regions and matching * relation. The new object will take ownership of each of the * regions passed. * * Note that the new object must describe an existing triangulation. * * @param region0 the first saturated region. * @param region1 the second saturated region. * @param matchingReln describes how the first and second region * boundaries are joined, as detailed in the class notes above. */ NBlockedSFSPair(NSatRegion* region0, NSatRegion* region1, const NMatrix2& matchingReln); }; /*@}*/ // Inline functions for NBlockedSFSPair inline NBlockedSFSPair::NBlockedSFSPair(NSatRegion* region0, NSatRegion* region1, const NMatrix2& matchingReln) : matchingReln_(matchingReln) { region_[0] = region0; region_[1] = region1; } inline const NSatRegion& NBlockedSFSPair::region(int which) const { return *region_[which]; } inline const NMatrix2& NBlockedSFSPair::matchingReln() const { return matchingReln_; } } // namespace regina #endif regina-4.95/engine/subcomplex/nblockedsfstriple.cpp000644 000765 000024 00000033437 12234011536 022465 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/ngraphtriple.h" #include "manifold/nsfs.h" #include "subcomplex/nblockedsfstriple.h" #include "subcomplex/nlayering.h" #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatregion.h" #include namespace regina { /** * A subclass of NSatBlockStarterSearcher that, upon finding a starter * block, attempts to flesh this out to a group of three saturated regions * joined along their torus boundaries, as desribed by the * NBlockedSFSTriple class. * * The starter block will be assumed to belong to the central region (not * one of the end regions). */ struct NBlockedSFSTripleSearcher : public NSatBlockStarterSearcher { NSatRegion* end[2]; /**< The two end regions of the NBlockedSFSTriple structure, if such a structure has been successfully found; otherwise, two null pointers if we are still searching. */ NSatRegion* centre; /**< The central region of the NBlockedSFSTriple structure, if such a structure has been successfully found; otherwise, a null pointer if we are still searching. */ NMatrix2 matchingReln[2]; /**< The matrices describing how the various region boundaries are joined together. Here matrix \a matchingReln[i] expresses the fibre/base curves on region \a end[i] in terms of the fibre/base curves on the corresponding central region boundary. See NBlockedSFSTriple::matchingReln() for further details. */ /** * Creates a new searcher whose \a end and \a centre region pointers * are all null. */ NBlockedSFSTripleSearcher() { end[0] = end[1] = centre = 0; } protected: bool useStarterBlock(NSatBlock* starter); }; NBlockedSFSTriple::~NBlockedSFSTriple() { if (end_[0]) delete end_[0]; if (end_[1]) delete end_[1]; if (centre_) delete centre_; } NManifold* NBlockedSFSTriple::getManifold() const { // Go ahead and create the Seifert fibred spaces. NSFSpace* end0 = end_[0]->createSFS(false); if (! end0) return 0; NSFSpace* end1 = end_[1]->createSFS(false); if (! end1) { delete end0; return 0; } NSFSpace* hub = centre_->createSFS(false); if (! hub) { delete end0; delete end1; return 0; } if (hub->punctures() == 1) { // The region has one larger boundary, but we pinch it to create // two smaller boundaries. hub->addPuncture(); } // Reduce the Seifert fibred space representations and finish up. end0->reduce(false); end1->reduce(false); hub->reduce(false); return new NGraphTriple(end0, hub, end1, matchingReln_[0], matchingReln_[1]); } std::ostream& NBlockedSFSTriple::writeName(std::ostream& out) const { out << "Blocked SFS Triple ["; end_[0]->writeBlockAbbrs(out, false); out << " | "; centre_->writeBlockAbbrs(out, false); out << " | "; end_[1]->writeBlockAbbrs(out, false); return out << ']'; } std::ostream& NBlockedSFSTriple::writeTeXName(std::ostream& out) const { out << "\\mathrm{BSFS\\_Triple}\\left["; end_[0]->writeBlockAbbrs(out, true); out << "\\,|\\,"; centre_->writeBlockAbbrs(out, true); out << "\\,|\\,"; end_[1]->writeBlockAbbrs(out, true); return out << "\\right]"; } void NBlockedSFSTriple::writeTextLong(std::ostream& out) const { out << "Blocked SFS triple\n"; out << "Matching relation (centre -> end #1): " << matchingReln_[0] << '\n'; out << "Matching relation (centre -> end #2): " << matchingReln_[1] << '\n'; centre_->writeDetail(out, "Central region"); end_[0]->writeDetail(out, "First end region"); end_[1]->writeDetail(out, "Second end region"); } NBlockedSFSTriple* NBlockedSFSTriple::isBlockedSFSTriple( NTriangulation* tri) { // Basic property checks. if (! tri->isClosed()) return 0; if (tri->getNumberOfComponents() > 1) return 0; // Watch out for twisted block boundaries that are incompatible with // neighbouring blocks! Also watch for the boundary between blocks // being an annulus on one side and a Klein bottle on the other (or // two incompatible Klein bottles for that matter). // // These will result in edges joined to themselves in reverse. if (! tri->isValid()) return 0; // Hunt for a starting block. NBlockedSFSTripleSearcher searcher; searcher.findStarterBlocks(tri); // Any luck? if (searcher.centre) { // The full expansion worked, and the triangulation is known // to be closed and connected. // This means we've got one! return new NBlockedSFSTriple(searcher.end[0], searcher.centre, searcher.end[1], searcher.matchingReln[0], searcher.matchingReln[1]); } // Nope. return 0; } bool NBlockedSFSTripleSearcher::useStarterBlock(NSatBlock* starter) { // The region pointers should be null, but just in case... if (end[0] || end[1] || centre) { delete starter; return false; } // Flesh out the triangulation as far as we can. We're aiming for // precisely two disjoint boundary annuli remaining. // Note that the starter block will now be owned by centre. centre = new NSatRegion(starter); centre->expand(usedTets); if (centre->numberOfBoundaryAnnuli() != 2) { delete centre; centre = 0; return true; } // Insist on the boundary annuli being disjoint and untwisted. NSatBlock* bdryBlock[2]; unsigned bdryAnnulus[2]; bool bdryVert[2], bdryHoriz[2], bdryRef[2]; centre->boundaryAnnulus(0, bdryBlock[0], bdryAnnulus[0], bdryVert[0], bdryHoriz[0]); centre->boundaryAnnulus(1, bdryBlock[1], bdryAnnulus[1], bdryVert[1], bdryHoriz[1]); bdryRef[0] = ((bdryVert[0] && ! bdryHoriz[0]) || (bdryHoriz[0] && ! bdryVert[0])); bdryRef[1] = ((bdryVert[1] && ! bdryHoriz[1]) || (bdryHoriz[1] && ! bdryVert[1])); // We either want two disjoint one-annulus boundaries, or else a // single two-annulus boundary that is pinched to turn each annulus // into a two-sided torus. The following test handles all cases. NSatAnnulus bdry[2]; bdry[0] = bdryBlock[0]->annulus(bdryAnnulus[0]); bdry[1] = bdryBlock[1]->annulus(bdryAnnulus[1]); if (! (bdry[0].isTwoSidedTorus() && bdry[1].isTwoSidedTorus())) { delete centre; centre = 0; return true; } // Hunt for layerings, but gently gently -- we don't want to loop // from one boundary back onto the other. std::auto_ptr layering[2]; int e; for (e = 0; e < 2; e++) { layering[e].reset(new NLayering(bdry[e].tet[0], bdry[e].roles[0], bdry[e].tet[1], bdry[e].roles[1])); while (layering[e]->extendOne()) { if (usedTets.find(layering[e]->getNewBoundaryTet(0)) != usedTets.end() || usedTets.find(layering[e]->getNewBoundaryTet(1)) != usedTets.end()) { // Oops, we've run back into something we've already seen. delete centre; centre = 0; return true; } usedTets.insert(layering[e]->getNewBoundaryTet(0)); usedTets.insert(layering[e]->getNewBoundaryTet(1)); } } // Start looking for the end regions. int plugPos; NSatBlock* otherStarter; NMatrix2 curvesCentreToLayering, layeringToEndAnnulus; for (e = 0; e < 2; e++) { // Relation from centre fibre/orbifold to layering first triangle // markings 01/02: curvesCentreToLayering = layering[e]->boundaryReln() * NMatrix2(-1, 0, 0, bdryRef[e] ? -1 : 1); // We make the shell of an other-side boundary annulus; we will fill // in the precise vertex role permutations later on. NSatAnnulus otherSide(layering[e]->getNewBoundaryTet(0), NPerm4(), layering[e]->getNewBoundaryTet(1), NPerm4()); if (otherSide.meetsBoundary()) { delete centre; centre = 0; if (e == 1) { delete end[0]; end[0] = 0; } return true; } // Try the three possible orientations for fibres on the other side. for (plugPos = 0; plugPos < 3; plugPos++) { // Construct the boundary annulus for the end region. // Refresh the tetrahedra as well as the vertex roles, since // it may have switched sides since our last run through the loop. otherSide.tet[0] = layering[e]->getNewBoundaryTet(0); otherSide.tet[1] = layering[e]->getNewBoundaryTet(1); // In each case, also fill in the mapping from (layering first // triangle markings 01/02) to (other side annulus first triangle // markings 01/02). This is stored in layeringToEndAnnulus. if (plugPos == 0) { otherSide.roles[0] = layering[e]->getNewBoundaryRoles(0); otherSide.roles[1] = layering[e]->getNewBoundaryRoles(1); layeringToEndAnnulus = NMatrix2(1, 0, 0, 1); } else if (plugPos == 1) { otherSide.roles[0] = layering[e]->getNewBoundaryRoles(0) * NPerm4(1, 2, 0, 3); otherSide.roles[1] = layering[e]->getNewBoundaryRoles(1) * NPerm4(1, 2, 0, 3); layeringToEndAnnulus = NMatrix2(-1, 1, -1, 0); } else { otherSide.roles[0] = layering[e]->getNewBoundaryRoles(0) * NPerm4(2, 0, 1, 3); otherSide.roles[1] = layering[e]->getNewBoundaryRoles(1) * NPerm4(2, 0, 1, 3); layeringToEndAnnulus = NMatrix2(0, -1, 1, -1); } // Clear out the used tetrahedron list. Everything between the // two layering boundaries is self-contained, so we won't run // into any of it again on the other side. We'll just re-insert // the layering boundary tetrahedra. usedTets.clear(); usedTets.insert(layering[0]->getNewBoundaryTet(0)); usedTets.insert(layering[0]->getNewBoundaryTet(1)); usedTets.insert(layering[1]->getNewBoundaryTet(0)); usedTets.insert(layering[1]->getNewBoundaryTet(1)); // See if we can flesh the other side out to an entire region. otherSide.switchSides(); if ((otherStarter = NSatBlock::isBlock(otherSide, usedTets))) { end[e] = new NSatRegion(otherStarter); end[e]->expand(usedTets); if (end[e]->numberOfBoundaryAnnuli() == 1) { // Got it! // Do a final conversion from annulus first triangle // markings 01/02 and move onto the next end space. matchingReln[e] = NMatrix2(-1, 0, 0, 1) * layeringToEndAnnulus * curvesCentreToLayering; break; } // Nup, this one didn't work. delete end[e]; end[e] = 0; } } // Did we manage to fill in this end space? if (! end[e]) { // Nope. Keep searching. delete centre; centre = 0; if (e == 1) { delete end[0]; end[0] = 0; } return true; } } // w00t! It all worked out. // Stop searching, we're done. return false; } } // namespace regina regina-4.95/engine/subcomplex/nblockedsfstriple.h000644 000765 000024 00000027252 12234011536 022130 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nblockedsfstriple.h * \brief Supports joined sequences of three Seifert fibred spaces that * are triangulated using saturated blocks. */ #ifndef __NBLOCKEDSFSTRIPLE_H #ifndef __DOXYGEN #define __NBLOCKEDSFSTRIPLE_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "subcomplex/nstandardtri.h" namespace regina { class NSatRegion; /** * \weakgroup subcomplex * @{ */ /** * Represents a blocked sequence of three Seifert fibred spaces joined * along connecting tori. This is a particular type of triangulation * of a graph manifold, formed from three saturated regions whose * various torus boundaries are identified as described below. * Optional layerings may be placed between torus boundaries to * allow for more interesting relationships between the * respective boundary curves of each region. For more detail on * saturated regions and their constituent saturated blocks, see the * NSatRegion class; for more detail on layerings, see the NLayering class. * * The three saturated regions must be joined together as illustrated * below. Each large box represents a saturated region, and the small * tunnels show where the region boundaries are joined (possibly via * layerings). * *
 *     /----------------\   /------------------\   /----------------\
 *     |                |   |                  |   |                |
 *     |  End region 0   ---   Central region   ---   End region 1  |
 *     |                 ---                    ---                 |
 *     |                |   |                  |   |                |
 *     \----------------/   \------------------/   \----------------/
 * 
* * Each of the end regions must have precisely one boundary component * formed from just one saturated annulus. The central region may have * two boundary components formed from one saturated annulus each. * Alternatively, it may have one boundary formed from two saturated * annuli, where this boundary is pinched together so that each annulus * becomes a two-sided torus joined to one of the end regions. None of * the boundary components (or the two-sided tori discussed above) may * be twisted (i.e., they must be tori, not Klein bottles). * * The ways in which the various region boundaries are identified are * specified by 2-by-2 matrices, which express curves representing the * fibres and base orbifold of each end region in terms of the central * region (see the page on \ref sfsnotation for terminology). * * Specifically, consider the matrix \a M that describes the joining of * the central region and the first end region (marked in the diagram * above as end region 0). Suppose that \a f and \a o are directed * curves on the central region boundary and \a f0 and \a o0 are directed * curves on the first end region boundary, where \a f and \a f0 represent * the fibres of each region and \a o and \a o0 represent the base orbifolds. * Then the boundaries are joined according to the following relation: * *
 *     [f0]       [f ]
 *     [  ] = M * [  ]
 *     [o0]       [o ]
 * 
* * Likewise, let \a M' be the matrix describing how the central region * and the second end region (marked in the diagram as end region 1) are * joined. Let \a f' and \a o' be directed curves on the other central * region boundary and \a f1 and \a o1 be directed curves on the second * end region boundary, where \a f' and \a f1 represent fibres and * \a o and \a o1 represent the base orbifolds. Then the boundaries are * joined according to the relation: * *
 *     [f1]        [f']
 *     [  ] = M' * [  ]
 *     [o1]        [o']
 * 
* * If a layering is present between two regions, then the corresponding * boundary curves are not identified directly. In this case, the relevant * matrix \a M or \a M' shows how the layering relates the curves on each * region boundary. * * Note that the routines writeName() and writeTeXName() do \e not * offer enough information to uniquely identify the triangulation, * since this essentially requires 2-dimensional assemblings of * saturated blocks. For full details, writeTextLong() may be used instead. * * The optional NStandardTriangulation routine getManifold() is * implemented for this class, but getHomologyH1() is not. * * \testpart */ class REGINA_API NBlockedSFSTriple : public NStandardTriangulation { private: NSatRegion* end_[2]; /**< The two end regions, i.e., the saturated regions with just one boundary annulus. */ NSatRegion* centre_; /**< The central region, i.e., the saturated region with two boundary annuli that meets both end regions. */ NMatrix2 matchingReln_[2]; /**< Specifies how the various region boundaries are joined (possibly via layerings), as described in the class notes above. In particular, \a matchingReln_[i] describes how end region \a i is joined to the central region. See the class notes for further details. */ public: /** * Destroys this structure and its constituent components. */ ~NBlockedSFSTriple(); /** * Returns details of the requested end region, as described in * the class notes above. The end regions are the two saturated * regions with one boundary annulus each, which are both joined * to the central region. * * @param which 0 if the first end region should be returned * (marked as end region 0 in the class notes), or 1 if the * second end region should be returned (marked as end region 1 * in the class notes). * @return details of the requested end region. */ const NSatRegion& end(int which) const; /** * Returns details of the central saturated region, as described * in the class notes above. This is the saturated region with * two boundary annuli, each of which is joined to one of the * end regions. * * @return details of the central region. */ const NSatRegion& centre() const; /** * Returns the matrix describing how the given end region is * joined to the central region. Note that if a layering is * placed between the two respective region boundaries, then * any changes to the boundary relationships caused by the * layering are included in this matrix. * * See the class notes above for precise information on how each * matrix is presented. * * @param which 0 if the matrix returned should describe how the * central region is joined to the first end region (marked end * region 0 in the class notes), or 1 if the matrix returned * should describe how the central region is joined to the * second end region (marked end region 1 in the class notes). * @return the matrix describing how the requested region * boundaries are joined. */ const NMatrix2& matchingReln(int which) const; NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Determines if the given triangulation is a blocked sequence of * three Seifert fibred spaces, as described in the class notes * above. * * @param tri the triangulation to examine. * @return a newly created structure containing details of the * blocked triple, or \c null if the given triangulation is not of * this form. */ static NBlockedSFSTriple* isBlockedSFSTriple(NTriangulation* tri); private: /** * Constructs a new blocked sequence of three Seifert fibred spaces, * as described by the given saturated regions and matching relations. * The new object will take ownership of each of the regions passed. * * See the class notes above for details of terminology used here. * * Note that the new object must describe an existing triangulation. * * @param end0 the first end region. * @param centre the central region. * @param end1 the second end region. * @param matchingReln0 describes how the first end region is * joined to the central region. * @param matchingReln1 describes how the second end region is * joined to the central region. */ NBlockedSFSTriple(NSatRegion* end0, NSatRegion* centre, NSatRegion* end1, const NMatrix2& matchingReln0, const NMatrix2& matchingReln1); }; /*@}*/ // Inline functions for NBlockedSFSTriple inline NBlockedSFSTriple::NBlockedSFSTriple(NSatRegion* end0, NSatRegion* centre, NSatRegion* end1, const NMatrix2& matchingReln0, const NMatrix2& matchingReln1) { end_[0] = end0; centre_ = centre; end_[1] = end1; matchingReln_[0] = matchingReln0; matchingReln_[1] = matchingReln1; } inline const NSatRegion& NBlockedSFSTriple::end(int which) const { return *end_[which]; } inline const NSatRegion& NBlockedSFSTriple::centre() const { return *centre_; } inline const NMatrix2& NBlockedSFSTriple::matchingReln(int which) const { return matchingReln_[which]; } } // namespace regina #endif regina-4.95/engine/subcomplex/nl31pillow.cpp000644 000765 000024 00000010736 12234011536 020751 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nlensspace.h" #include "triangulation/ncomponent.h" #include "triangulation/ntetrahedron.h" #include "triangulation/nvertex.h" #include "subcomplex/nl31pillow.h" namespace regina { NL31Pillow* NL31Pillow::clone() const { NL31Pillow* ans = new NL31Pillow(); ans->tet[0] = tet[0]; ans->tet[1] = tet[1]; ans->interior[0] = interior[0]; ans->interior[1] = interior[1]; return ans; } NL31Pillow* NL31Pillow::isL31Pillow(const NComponent* comp) { // Basic property check. if (comp->getNumberOfTetrahedra() != 2 || comp->getNumberOfVertices() != 2 || comp->getNumberOfEdges() != 4 || (! comp->isClosed()) || (! comp->isOrientable())) return 0; // Verify that the vertices have degrees 2 and 6. int internalVertex; unsigned long deg0 = comp->getVertex(0)->getNumberOfEmbeddings(); if (deg0 == 2) internalVertex = 0; else if (deg0 == 6) internalVertex = 1; else return 0; // Verify that all four faces of one tetrahedron join to the other. NTetrahedron* tet[2]; tet[0] = comp->getTetrahedron(0); tet[1] = comp->getTetrahedron(1); if (tet[0]->adjacentTetrahedron(0) != tet[1] || tet[0]->adjacentTetrahedron(1) != tet[1] || tet[0]->adjacentTetrahedron(2) != tet[1] || tet[0]->adjacentTetrahedron(3) != tet[1]) return 0; // At this point we can prove through enumeration of all // 2-tetrahedron triangulations that we have our triangular pillow // L(3,1). NL31Pillow* ans = new NL31Pillow(); ans->tet[0] = tet[0]; ans->tet[1] = tet[1]; for (int i = 0; i < 2; i++) { const NVertexEmbedding& emb = comp->getVertex(internalVertex)-> getEmbedding(i); if (emb.getTetrahedron() == tet[0]) ans->interior[0] = emb.getVertex(); else ans->interior[1] = emb.getVertex(); } return ans; } NManifold* NL31Pillow::getManifold() const { return new NLensSpace(3, 1); } NAbelianGroup* NL31Pillow::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); ans->addTorsionElement(3); return ans; } } // namespace regina regina-4.95/engine/subcomplex/nl31pillow.h000644 000765 000024 00000014353 12234011536 020415 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nl31pillow.h * \brief Deals with triangular pillow L(3,1) components of a triangulation. */ #ifndef __NL31PILLOW_H #ifndef __DOXYGEN #define __NL31PILLOW_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" namespace regina { class NTetrahedron; /** * \weakgroup subcomplex * @{ */ /** * Represents a triangular pillow L(3,1) component of a triangulation. * * A triangular pillow L(3,1) is a two-tetrahedron two-vertex * triangulation of the lens space L(3,1) formed as follows. * * A triangular pillow is formed from two tetrahedra with a single * degree three vertex in the interior of the pillow. The two boundary * triangles of this pillow are then identified with a one-third twist. * * All optional NStandardTriangulation routines are implemented for this * class. * * \testpart */ class REGINA_API NL31Pillow : public NStandardTriangulation { private: NTetrahedron* tet[2]; /**< The two tetrahedra in the triangular pillow. */ unsigned interior[2]; /**< The vertex of each tetrahedron that corresponds to the interior vertex of the triangular pillow. */ public: /** * Destroys this structure. */ virtual ~NL31Pillow(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NL31Pillow* clone() const; /** * Returns one of the two tetrahedra involved in this structure. * * @param whichTet specifies which tetrahedron to return; this * must be either 0 or 1. * @return the requested tetrahedron. */ NTetrahedron* getTetrahedron(int whichTet) const; /** * Returns the vertex number of the given tetrahedron * corresponding to the degree three vertex in the interior of * the triangular pillow. See the general class notes for * further details. * * The specific tetrahedron to examine is determined by the * argument \a whichTet; this will be the tetrahedron * getTetrahedron(whichTet). * * @param whichTet specifies which tetrahedron to examine; * this must be either 0 or 1. * @return the vertex of tetrahedron \a whichTet corresponding * to the vertex in the interior of the triangular pillow; this * will be between 0 and 3 inclusive. */ unsigned getInteriorVertex(int whichTet) const; /** * Determines if the given triangulation component is a * triangular pillow L(3,1). * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * triangular pillow L(3,1), or \c null if the given component is * not a triangular pillow L(3,1). */ static NL31Pillow* isL31Pillow(const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NL31Pillow(); }; /*@}*/ // Inline functions for NL31Pillow inline NL31Pillow::NL31Pillow() { } inline NL31Pillow::~NL31Pillow() { } inline NTetrahedron* NL31Pillow::getTetrahedron(int whichTet) const { return tet[whichTet]; } inline unsigned NL31Pillow::getInteriorVertex(int whichTet) const { return interior[whichTet]; } inline std::ostream& NL31Pillow::writeName(std::ostream& out) const { return out << "L'(3,1)"; } inline std::ostream& NL31Pillow::writeTeXName(std::ostream& out) const { return out << "L'_{3,1}"; } inline void NL31Pillow::writeTextLong(std::ostream& out) const { out << "Triangular pillow lens space L(3,1)"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredchain.cpp000644 000765 000024 00000011112 12234011536 021540 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "triangulation/nedge.h" #include "triangulation/ntetrahedron.h" #include "subcomplex/nlayeredchain.h" namespace regina { bool NLayeredChain::extendAbove() { NTetrahedron* adj = top->adjacentTetrahedron(topVertexRoles[0]); if (adj == bottom || adj == top || adj == 0) return false; if (adj != top->adjacentTetrahedron(topVertexRoles[3])) return false; // Check the gluings. NPerm4 adjRoles = top->adjacentGluing(topVertexRoles[0]) * topVertexRoles * NPerm4(0, 1); if (adjRoles != top->adjacentGluing(topVertexRoles[3]) * topVertexRoles * NPerm4(2, 3)) return false; // We can extend the layered chain. top = adj; topVertexRoles = adjRoles; index++; return true; } bool NLayeredChain::extendBelow() { NTetrahedron* adj = bottom->adjacentTetrahedron(bottomVertexRoles[1]); if (adj == bottom || adj == top || adj == 0) return false; if (adj != bottom->adjacentTetrahedron(bottomVertexRoles[2])) return false; // Check the gluings. NPerm4 adjRoles = bottom->adjacentGluing(bottomVertexRoles[1]) * bottomVertexRoles * NPerm4(0, 1); if (adjRoles != bottom->adjacentGluing(bottomVertexRoles[2]) * bottomVertexRoles * NPerm4(2, 3)) return false; // We can extend the layered chain. bottom = adj; bottomVertexRoles = adjRoles; index++; return true; } bool NLayeredChain::extendMaximal() { bool changed = false; while (extendAbove()) changed = true; while (extendBelow()) changed = true; return changed; } void NLayeredChain::reverse() { NTetrahedron* tmp = top; top = bottom; bottom = tmp; NPerm4 pTmp = topVertexRoles * NPerm4(1, 0, 3, 2); topVertexRoles = bottomVertexRoles * NPerm4(1, 0, 3, 2); bottomVertexRoles = pTmp; } void NLayeredChain::invert() { topVertexRoles = topVertexRoles * NPerm4(3, 2, 1, 0); bottomVertexRoles = bottomVertexRoles * NPerm4(3, 2, 1, 0); } NManifold* NLayeredChain::getManifold() const { return new NHandlebody(index <= 1 ? 0 : 1, true); } NAbelianGroup* NLayeredChain::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (index > 1) ans->addRank(); return ans; } } // namespace regina regina-4.95/engine/subcomplex/nlayeredchain.h000644 000765 000024 00000030211 12234011536 021206 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredchain.h * \brief Deals with layered chains in a triangulation. */ #ifndef __NLAYEREDCHAIN_H #ifndef __DOXYGEN #define __NLAYEREDCHAIN_H #endif #include "regina-core.h" #include "maths/nperm4.h" #include "subcomplex/nstandardtri.h" namespace regina { class NTetrahedron; /** * \weakgroup subcomplex * @{ */ /** * Represents a layered chain in a triangulation. * * A layered chain is a set of \a n tetrahedra glued to each other by * layerings. For each tetrahedron, select two top faces, two bottom * faces and two hinge edges, so that the top faces are adjacent, the * bottom faces are adjacent, the hinge edges are opposite and each * hinge meets both a top and a bottom face. The tetrahedron can thus * be thought of as a fattened square with the top and bottom faces * above and below the square respectively, and the hinges as the top * and bottom edges of the square. The left and right edges of the * square are identified to form an annulus. * * For each \a i, the top faces of tetrahedron \a i are glued to the * bottom faces of tetrahedron i+1. This is done by layering the * upper tetrahedron upon the annulus formed by the top faces of the * lower tetrahedron. The layering should be done over the left or * right edge of the lower square (note that these two edges are * actually identified). The top hinges of each tetrahedron should be * identified, as should the bottom hinges. * * The bottom faces of the first tetrahedron and the top faces of the * last tetrahedron form the boundary of the layered chain. If there is * more than one tetrahedron, the layered chain forms a solid torus with * two vertices whose axis is parallel to each hinge edge. * * The \a index of the layered chain is the number of tetrahedra it * contains. A layered chain must contain at least one tetrahedron. * * Note that for the purposes of getManifold() and getHomologyH1(), a * layered chain containing only one tetrahedron will be considered as a * standalone tetrahedron that forms a 3-ball (and not a solid torus). * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NLayeredChain : public NStandardTriangulation { private: NTetrahedron* bottom; /**< The bottom tetrahedron of this layered chain. */ NTetrahedron* top; /**< The top tetrahedron of this layered chain. */ unsigned long index; /**< The number of tetrahedra in this layered chain. */ NPerm4 bottomVertexRoles; /**< The permutation described by getBottomVertexRoles(). */ NPerm4 topVertexRoles; /**< The permutation described by getTopVertexRoles(). */ public: /** * Creates a new layered chain containing only the given * tetrahedron. This new layered chain will have index 1, but * may be extended using extendAbove(), extendBelow() or * extendMaximal(). * * @param tet the tetrahedron that will make up this layered * chain. * @param vertexRoles a permutation describing the role each * tetrahedron vertex must play in the layered chain; this must be * in the same format as the permutation returned by * getBottomVertexRoles() and getTopVertexRoles(). */ NLayeredChain(NTetrahedron* tet, NPerm4 vertexRoles); /** * Creates a new layered chain that is a clone of the given * structure. * * @param cloneMe the layered chain to clone. */ NLayeredChain(const NLayeredChain& cloneMe); /** * Destroys this layered chain. */ virtual ~NLayeredChain(); /** * Returns the bottom tetrahedron of this layered chain. * * @return the bottom tetrahedron. */ NTetrahedron* getBottom() const; /** * Returns the top tetrahedron of this layered chain. * * @return the top tetrahedron. */ NTetrahedron* getTop() const; /** * Returns the number of tetrahedra in this layered chain. * * @return the number of tetrahedra. */ unsigned long getIndex() const; /** * Returns a permutation represeting the role that each vertex * of the bottom tetrahedron plays in the layered chain. * The permutation returned (call this p) maps 0, 1, 2 and * 3 to the four vertices of the bottom tetrahedron so that the * edge from p[0] to p[1] is the top hinge, * the edge from p[2] to p[3] is the bottom * hinge, faces p[1] and p[2] are the (boundary) * bottom faces and faces p[0] and p[3] are the top * faces. * * See the general class notes for further details. * * @return a permutation representing the roles of the vertices * of the bottom tetrahedron. */ NPerm4 getBottomVertexRoles() const; /** * Returns a permutation represeting the role that each vertex * of the top tetrahedron plays in the layered chain. * The permutation returned (call this p) maps 0, 1, 2 and * 3 to the four vertices of the top tetrahedron so that the * edge from p[0] to p[1] is the top hinge, * the edge from p[2] to p[3] is the bottom * hinge, faces p[1] and p[2] are the bottom * faces and faces p[0] and p[3] are the * (boundary) top faces. * * See the general class notes for further details. * * @return a permutation representing the roles of the vertices * of the top tetrahedron. */ NPerm4 getTopVertexRoles() const; /** * Checks to see whether this layered chain can be extended to * include the tetrahedron above the top tetrahedron (and still * remain a layered chain). If so, this layered chain will be * modified accordingly (note that its index will be increased * by one and its top tetrahedron will change). * * @return \c true if and only if this layered chain was * extended. */ bool extendAbove(); /** * Checks to see whether this layered chain can be extended to * include the tetrahedron below the bottom tetrahedron (and still * remain a layered chain). If so, this layered chain will be * modified accordingly (note that its index will be increased * by one and its bottom tetrahedron will change). * * @return \c true if and only if this layered chain was * extended. */ bool extendBelow(); /** * Extends this layered chain to a maximal length layered chain. * Both extendAbove() and extendBelow() will be used until this * layered chain can be extended no further. * * @return \c true if and only if this layered chain was * extended. */ bool extendMaximal(); /** * Reverses this layered chain so the top tetrahedron becomes * the bottom and vice versa. The upper and lower hinges will * remain the upper and lower hinges respectively. * * Note that this operation will cause the hinge edges to point * in the opposite direction around the solid torus formed by * this layered chain. * * Note that only the representation of the chain is altered; * the underlying triangulation is not changed. */ void reverse(); /** * Inverts this layered chain so the upper hinge becomes the * lower and vice versa. The top and bottom tetrahedra will * remain the top and bottom tetrahedra respectively. * * Note that this operation will cause the hinge edges to point * in the opposite direction around the solid torus formed by * this layered chain. * * Note that only the representation of the chain is altered; * the underlying triangulation is not changed. */ void invert(); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; }; /*@}*/ // Inline functions for NLayeredChain inline NLayeredChain::NLayeredChain(NTetrahedron* tet, NPerm4 vertexRoles) : bottom(tet), top(tet), index(1), bottomVertexRoles(vertexRoles), topVertexRoles(vertexRoles) { } inline NLayeredChain::NLayeredChain(const NLayeredChain& cloneMe) : NStandardTriangulation(), bottom(cloneMe.bottom), top(cloneMe.top), index(cloneMe.index), bottomVertexRoles(cloneMe.bottomVertexRoles), topVertexRoles(cloneMe.topVertexRoles) { } inline NLayeredChain::~NLayeredChain() { } inline NTetrahedron* NLayeredChain::getBottom() const { return bottom; } inline NTetrahedron* NLayeredChain::getTop() const { return top; } inline unsigned long NLayeredChain::getIndex() const { return index; } inline NPerm4 NLayeredChain::getBottomVertexRoles() const { return bottomVertexRoles; } inline NPerm4 NLayeredChain::getTopVertexRoles() const { return topVertexRoles; } inline std::ostream& NLayeredChain::writeName(std::ostream& out) const { return out << "Chain(" << index << ')'; } inline std::ostream& NLayeredChain::writeTeXName(std::ostream& out) const { return out << "\\mathit{Chain}(" << index << ')'; } inline void NLayeredChain::writeTextLong(std::ostream& out) const { out << "Layered chain of index " << index; } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredchainpair.cpp000644 000765 000024 00000021423 12234011536 022422 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nsfs.h" #include "maths/nmatrixint.h" #include "triangulation/ncomponent.h" #include "triangulation/ntetrahedron.h" #include "subcomplex/nlayeredchainpair.h" namespace regina { NLayeredChainPair* NLayeredChainPair::clone() const { NLayeredChainPair* ans = new NLayeredChainPair(); if (chain[0]) ans->chain[0] = new NLayeredChain(*chain[0]); if (chain[1]) ans->chain[1] = new NLayeredChain(*chain[1]); return ans; } NLayeredChainPair* NLayeredChainPair::isLayeredChainPair( const NComponent* comp) { // Basic property check. if ((! comp->isClosed()) || (! comp->isOrientable())) return 0; unsigned long nTet = comp->getNumberOfTetrahedra(); if (nTet < 2) return 0; if (comp->getNumberOfVertices() != 1) return 0; // We have at least two tetrahedra and precisely 1 vertex. // The component is closed and orientable (and connected, since it's // a component). // Start with tetrahedron 0. This must belong to *some* chain. NTetrahedron* base = comp->getTetrahedron(0); NLayeredChain* first; NLayeredChain* second; // Note that we only need check permutations in S3 since we can // arbitrarily assign the role of one vertex in the tetrahedron. NTetrahedron* firstBottom; NTetrahedron* firstTop; NTetrahedron* secondBottom; NTetrahedron* secondTop; NPerm4 firstBottomRoles, firstTopRoles, secondBottomRoles, secondTopRoles; for (int p = 0; p < 6; p++) { first = new NLayeredChain(base, NPerm4::S3[p]); first->extendMaximal(); firstTop = first->getTop(); firstBottom = first->getBottom(); firstTopRoles = first->getTopVertexRoles(); firstBottomRoles = first->getBottomVertexRoles(); // Check to see if the first chain fills the entire component. if (first->getIndex() == nTet) { // The only success here will be if we have a chain pair of // indices (n-1) and 1, which is in fact a layered loop. NLayeredChain* longChain = new NLayeredChain( firstBottom, firstBottomRoles); if (longChain->extendBelow()) if (longChain->getBottom() == firstTop && longChain->getBottomVertexRoles() == firstTopRoles * NPerm4(3, 2, 1, 0)) { // We've got a layered loop! NLayeredChainPair* ans = new NLayeredChainPair(); if (nTet == 2) { // The new chain is already too long. delete longChain; longChain = new NLayeredChain( firstBottom, firstBottomRoles); } // Extend longChain to (n-1) tetrahedra. while (longChain->getIndex() + 1 < nTet) longChain->extendBelow(); ans->chain[1] = longChain; ans->chain[0] = new NLayeredChain( firstBottom->adjacentTetrahedron( firstBottomRoles[0]), firstBottom->adjacentGluing( firstBottomRoles[0]) * firstBottomRoles * NPerm4(0, 2, 1, 3)); delete first; return ans; } delete longChain; delete first; continue; } // At this point we must have run into the second chain. secondBottom = firstTop->adjacentTetrahedron(firstTopRoles[3]); if (secondBottom == firstTop || secondBottom == firstBottom || secondBottom == 0) { delete first; continue; } second = new NLayeredChain(secondBottom, firstTop->adjacentGluing(firstTopRoles[3]) * firstTopRoles * NPerm4(1, 3, 0, 2)); while (second->extendAbove()) ; if (second->getIndex() + first->getIndex() != nTet) { delete first; delete second; continue; } secondTop = second->getTop(); secondTopRoles = second->getTopVertexRoles(); secondBottomRoles = second->getBottomVertexRoles(); // At this point we have two chains that together have the // correct number of tetrahedra. All we need do is check the // remaining three between-chain gluings. if (secondTop == firstTop->adjacentTetrahedron(firstTopRoles[0]) && secondBottom == firstBottom->adjacentTetrahedron( firstBottomRoles[2]) && secondTop == firstBottom->adjacentTetrahedron( firstBottomRoles[1]) && secondTopRoles == firstTop->adjacentGluing( firstTopRoles[0]) * firstTopRoles * NPerm4(0, 2, 1, 3) && secondBottomRoles == firstBottom->adjacentGluing( firstBottomRoles[2]) * firstBottomRoles * NPerm4(3, 1, 2, 0) && secondTopRoles == firstBottom->adjacentGluing( firstBottomRoles[1]) * firstBottomRoles * NPerm4(2, 0, 3, 1)) { // We found one! NLayeredChainPair* ans = new NLayeredChainPair(); if (first->getIndex() > second->getIndex()) { ans->chain[0] = second; ans->chain[1] = first; } else { ans->chain[0] = first; ans->chain[1] = second; } return ans; } else { delete first; delete second; } } // Nothing was found. Sigh. return 0; } NManifold* NLayeredChainPair::getManifold() const { NSFSpace* ans = new NSFSpace(); ans->insertFibre(2, -1); ans->insertFibre(chain[0]->getIndex() + 1, 1); ans->insertFibre(chain[1]->getIndex() + 1, 1); ans->reduce(); return ans; } NAbelianGroup* NLayeredChainPair::getHomologyH1() const { // The first homology group can be obtained from the matrix: // // [ 1 -1 1 ] // [ n_1 1 1 ] // [ 1 n_2 -1 ] // // This is established simply by examining the edges on the boundary // of each layered chain. NAbelianGroup* ans = new NAbelianGroup(); NMatrixInt mat(3, 3); mat.initialise(1); mat.entry(0, 1) = mat.entry(2, 2) = -1; mat.entry(1, 0) = chain[0]->getIndex(); mat.entry(2, 1) = chain[1]->getIndex(); ans->addGroup(mat); return ans; } } // namespace regina regina-4.95/engine/subcomplex/nlayeredchainpair.h000644 000765 000024 00000014726 12234011536 022077 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredchainpair.h * \brief Deals with layered chain pair components of a triangulation. */ #ifndef __NLAYEREDCHAINPAIR_H #ifndef __DOXYGEN #define __NLAYEREDCHAINPAIR_H #endif #include "regina-core.h" #include "subcomplex/nlayeredchain.h" namespace regina { class NComponent; /** * \weakgroup subcomplex * @{ */ /** * Represents a layered chain pair component of a triangulation. * * A layered chain pair consists of two layered chains (as described by * class NLayeredChain) glued together in a particular way. * * Orient the hinge edges and diagonals of each chain so they all point * in the same direction around the solid tori formed by each layered * chain (a \e diagonal is an edge between the two top triangular faces or an * edge between the two bottom triangular faces of a layered chain). * * The two top faces of the first chain are glued to a top and bottom * face of the second chain, and the two bottom faces of the first chain * are glued to a top and bottom face of the second chain. * * The four oriented diagonals are all identified as a single edge. Of the * remaining unglued edges (two hinge edges and two non-hinge edges per chain), * each hinge edge of one chain must be identified to a non-hinge edge of * the other chain and vice versa. From here the face identifications are * uniquely determined. * * Note that a layered chain pair in which one of the chains contains * only one tetrahedron is in fact a layered loop with a twist * (see class NLayeredLoop). * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NLayeredChainPair : public NStandardTriangulation { private: NLayeredChain* chain[2]; /**< The two layered chains that make up this pair. */ public: /** * Destroys this layered chain pair. */ virtual ~NLayeredChainPair(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NLayeredChainPair* clone() const; /** * Returns the requested layered chain used to form this structure. * If the two chains have different lengths, the shorter chain * will be chain 0 and the longer chain will be chain 1. * * @param which specifies which chain to return; this must be 0 * or 1. * @return the requested layered chain. */ const NLayeredChain* getChain(int which) const; /** * Determines if the given triangulation component is a layered * chain pair. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * layered chain pair, or \c null if the given component is * not a layered chain pair. */ static NLayeredChainPair* isLayeredChainPair(const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NLayeredChainPair(); }; /*@}*/ // Inline functions for NLayeredChainPair inline NLayeredChainPair::NLayeredChainPair() { chain[0] = chain[1] = 0; } inline NLayeredChainPair::~NLayeredChainPair() { if (chain[0]) delete chain[0]; if (chain[1]) delete chain[1]; } inline const NLayeredChain* NLayeredChainPair::getChain(int which) const { return chain[which]; } inline std::ostream& NLayeredChainPair::writeName(std::ostream& out) const { return out << "C(" << chain[0]->getIndex() << ',' << chain[1]->getIndex() << ')'; } inline std::ostream& NLayeredChainPair::writeTeXName(std::ostream& out) const { return out << "C_{" << chain[0]->getIndex() << ',' << chain[1]->getIndex() << '}'; } inline void NLayeredChainPair::writeTextLong(std::ostream& out) const { out << "Layered chain pair (chain lengths " << chain[0]->getIndex() << ", " << chain[1]->getIndex() << ')'; } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredlensspace.cpp000644 000765 000024 00000016065 12234011536 022447 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nlensspace.h" #include "maths/numbertheory.h" #include "subcomplex/nlayeredlensspace.h" #include "triangulation/nedge.h" #include "triangulation/ncomponent.h" #include "triangulation/ntetrahedron.h" namespace regina { NLayeredLensSpace* NLayeredLensSpace::clone() const { NLayeredLensSpace* ans = new NLayeredLensSpace(); ans->torus = torus->clone(); ans->mobiusBoundaryGroup = mobiusBoundaryGroup; ans->p = p; ans->q = q; return ans; } NLayeredLensSpace* NLayeredLensSpace::isLayeredLensSpace( const NComponent* comp) { // Basic property check. if ((! comp->isClosed()) || (! comp->isOrientable())) return 0; if (comp->getNumberOfVertices() > 1) return 0; unsigned long nTet = comp->getNumberOfTetrahedra(); NLayeredSolidTorus* torus; for (unsigned long i = 0; i < nTet; i++) { torus = NLayeredSolidTorus::formsLayeredSolidTorusBase( comp->getTetrahedron(i)); if (torus) { // We have found a layered solid torus; either this makes the // layered lens space or nothing makes the layered lens space. NTetrahedron* tet = torus->getTopLevel(); int tf0 = torus->getTopFace(0); int tf1 = torus->getTopFace(1); if (tet->adjacentTetrahedron(tf0) != tet) { delete torus; return 0; } /* We already know the component is orientable; no need to check orientation! if (perm.sign() == 1) { delete torus; return 0; }*/ // This is the real thing! NLayeredLensSpace* ans = new NLayeredLensSpace(); ans->torus = torus; NPerm4 perm = tet->adjacentGluing(tf0); if (perm[tf1] == tf0) { // Snapped shut. ans->mobiusBoundaryGroup = torus->getTopEdgeGroup( 5 - NEdge::edgeNumber[tf0][tf1]); } else { // Twisted shut. ans->mobiusBoundaryGroup = torus->getTopEdgeGroup( NEdge::edgeNumber[perm[tf1]][tf0]); } // Work out p and q. switch (ans->mobiusBoundaryGroup) { // For layered solid torus (x < y < z): case 0: // L( x + 2y, y ) ans->p = torus->getMeridinalCuts(1) + torus->getMeridinalCuts(2); ans->q = torus->getMeridinalCuts(1); break; case 1: // L( 2x + y, x ) ans->p = torus->getMeridinalCuts(0) + torus->getMeridinalCuts(2); ans->q = torus->getMeridinalCuts(0); break; case 2: // L( y - x, x ) ans->p = torus->getMeridinalCuts(1) - torus->getMeridinalCuts(0); if (ans->p == 0) ans->q = 1; else ans->q = torus->getMeridinalCuts(0) % ans->p; break; } // Find the nicest possible value for q. // Choices are +/- q, +/- 1/q. if (ans->p > 0) { if (2 * ans->q > ans->p) ans->q = ans->p - ans->q; if (ans->q > 0) { unsigned long qAlt = modularInverse(ans->p, ans->q); if (2 * qAlt > ans->p) qAlt = ans->p - qAlt; if (qAlt < ans->q) ans->q = qAlt; } } return ans; } } return 0; } NManifold* NLayeredLensSpace::getManifold() const { return new NLensSpace(p, q); } NAbelianGroup* NLayeredLensSpace::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (p == 0) ans->addRank(); else if (p > 1) ans->addTorsionElement(p); return ans; } std::ostream& NLayeredLensSpace::writeName(std::ostream& out) const { if (p == 3 && q == 1) { out << "L(3,1)"; if (torus->getNumberOfTetrahedra() != 2) return out; else if (isSnapped()) return out << " (1)"; else return out << " (2)"; } else return out << "L(" << p << ',' << q << ')'; } std::ostream& NLayeredLensSpace::writeTeXName(std::ostream& out) const { if (p == 3 && q == 1) { out << "L_{3,1}"; if (torus->getNumberOfTetrahedra() != 2) return out; else if (isSnapped()) return out << "^{(1)}"; else return out << "^{(2)}"; } else return out << "L_{" << p << ',' << q << '}'; } } // namespace regina regina-4.95/engine/subcomplex/nlayeredlensspace.h000644 000765 000024 00000017024 12234011536 022110 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredlensspace.h * \brief Deals with layered lens space components of a triangulation. */ #ifndef __NLAYEREDLENSSPACE_H #ifndef __DOXYGEN #define __NLAYEREDLENSSPACE_H #endif #include "regina-core.h" #include "subcomplex/nlayeredsolidtorus.h" #include "subcomplex/nstandardtri.h" namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Represents a layered lens space component of a triangulation. * A layered lens space is considered to be any layered solid torus glued * to a degenerate (2,1,1) layered solid torus (i.e., a one-triangle mobius * strip). Note that the three possible gluing options represent the * three possible ways of closing the initial torus - either twisting it * shut (in one of two possible ways) or snapping it shut without any twist. * * A layered lens space must contain at least one tetrahedron. * * All optional NStandardTriangulation routines are implemented for this * class. * * \testpart */ class REGINA_API NLayeredLensSpace : public NStandardTriangulation { private: NLayeredSolidTorus* torus; /**< The layered solid torus that forms the basis of this layered lens space. */ int mobiusBoundaryGroup; /**< The edge group of the top level tetrahedron in the layered solid torus to which the boundary of the mobius strip is glued. */ unsigned long p,q; /**< The lens space parameters for L(p,q). */ public: /** * Destroys this lens space; note that the corresponding layered * solid torus will also be destroyed. */ virtual ~NLayeredLensSpace(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NLayeredLensSpace* clone() const; /** * Returns the first parameter \a p of this lens space L(p,q). * * @return the first parameter \a p. */ unsigned long getP() const; /** * Returns the second parameter \a q of this lens space L(p,q). * * @return the second parameter \a q. */ unsigned long getQ() const; /** * Returns the layered solid torus to which the mobius strip is * glued. * * @return the layered solid torus. */ const NLayeredSolidTorus& getTorus() const; /** * Determines which edge of the layered solid torus is glued to * the boundary of the mobius strip (i.e., the weight 2 edge * of the degenerate (2,1,1) layered solid torus). The return * value will be one of the three top level tetrahedron edge * groups in the layered solid torus; see * NLayeredSolidTorus::getTopEdge() for further details about * edge groups. * * @return the top level edge group of the layered solid torus to * which the mobius strip boundary is glued. */ int getMobiusBoundaryGroup() const; /** * Determines if the layered solid torus that forms the basis for * this lens space is snapped shut (folded closed without a twist). * * @return \c true if and only if the torus is snapped shut. */ bool isSnapped() const; /** * Determines if the layered solid torus that forms the basis for * this lens space is twisted shut (folded closed with a twist). * * @return \c true if and only if the torus is twisted shut. */ bool isTwisted() const; /** * Determines if the given triangulation component is a layered * lens space. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * layered lens space, or \c null if the given component is * not a layered lens space. */ static NLayeredLensSpace* isLayeredLensSpace(const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NLayeredLensSpace(); }; /*@}*/ // Inline functions for NLayeredLensSpace inline NLayeredLensSpace::NLayeredLensSpace() { } inline NLayeredLensSpace::~NLayeredLensSpace() { delete torus; } inline unsigned long NLayeredLensSpace::getP() const { return p; } inline unsigned long NLayeredLensSpace::getQ() const { return q; } inline const NLayeredSolidTorus& NLayeredLensSpace::getTorus() const { return *torus; } inline int NLayeredLensSpace::getMobiusBoundaryGroup() const { return mobiusBoundaryGroup; } inline bool NLayeredLensSpace::isSnapped() const { return (torus->getTopEdge(mobiusBoundaryGroup, 1) == -1); } inline bool NLayeredLensSpace::isTwisted() const { return (torus->getTopEdge(mobiusBoundaryGroup, 1) != -1); } inline void NLayeredLensSpace::writeTextLong(std::ostream& out) const { out << "Layered lens space "; writeName(out); } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredloop.cpp000644 000765 000024 00000020563 12234011536 021441 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nlensspace.h" #include "manifold/nsfs.h" #include "triangulation/nedge.h" #include "triangulation/ncomponent.h" #include "triangulation/ntetrahedron.h" #include "subcomplex/nlayeredloop.h" namespace regina { NLayeredLoop* NLayeredLoop::clone() const { NLayeredLoop* ans = new NLayeredLoop(); ans->length = length; ans->hinge[0] = hinge[0]; ans->hinge[1] = hinge[1]; return ans; } NManifold* NLayeredLoop::getManifold() const { if (hinge[1]) { // Not twisted. return new NLensSpace(length, 1); } else { // Twisted. NSFSpace* ans = new NSFSpace(); ans->insertFibre(2, -1); ans->insertFibre(2, 1); ans->insertFibre(length, 1); ans->reduce(); return ans; } } NLayeredLoop* NLayeredLoop::isLayeredLoop(const NComponent* comp) { // Basic property check. if ((! comp->isClosed()) || (! comp->isOrientable())) return 0; unsigned long nTet = comp->getNumberOfTetrahedra(); if (nTet == 0) return 0; unsigned long nVertices = comp->getNumberOfVertices(); if (nVertices > 2) return 0; bool twisted = (nVertices == 1); // We have at least 1 tetrahedron and precisely 1 or 2 vertices. // The component is closed and orientable (and connected, since it's // a component). // Pick our base tetrahedron. NTetrahedron* base = comp->getTetrahedron(0); NTetrahedron* tet = base; NTetrahedron* next; int baseTop0, baseTop1, baseBottom0, baseBottom1; int top0, top1, bottom0, bottom1; int adjTop0 = 0, adjTop1 = 0, adjBottom0 = 0, adjBottom1 = 0; int hinge0, hinge1; NPerm4 p; bool ok; // Declare 0 to be a top face; find its partner. baseTop0 = 0; next = base->adjacentTetrahedron(0); for (baseTop1 = 1; baseTop1 < 4; baseTop1++) { if (base->adjacentTetrahedron(baseTop1) != next) continue; // Find the vertex joined to baseTop0 by a hinge. for (baseBottom0 = 1; baseBottom0 < 4; baseBottom0++) { if (baseBottom0 == baseTop1) continue; baseBottom1 = 6 - baseBottom0 - baseTop0 - baseTop1; // Some basic property checks. if (base->adjacentTetrahedron(baseBottom0) != base->adjacentTetrahedron(baseBottom1)) continue; hinge0 = NEdge::edgeNumber[baseTop0][baseBottom0]; hinge1 = NEdge::edgeNumber[baseTop1][baseBottom1]; if (twisted) { if (base->getEdge(hinge0) != base->getEdge(hinge1)) continue; if (base->getEdge(hinge0)->getNumberOfEmbeddings() != 2 * nTet) continue; } else { if (base->getEdge(hinge0)->getNumberOfEmbeddings() != nTet) continue; if (base->getEdge(hinge1)->getNumberOfEmbeddings() != nTet) continue; } top0 = baseTop0; top1 = baseTop1; bottom0 = baseBottom0; bottom1 = baseBottom1; // Follow the gluings up. ok = true; while (true) { // Already set: tet, next, topi, bottomi. // Check that both steps up lead to the same tetrahedron. // Note that this check has already been done for the first // iteration of this loop; never mind, no big loss. if (tet->adjacentTetrahedron(top0) != tet->adjacentTetrahedron(top1)) { ok = false; break; } // Check that the corresponding gluings are correct. p = tet->adjacentGluing(top0); adjTop0 = p[bottom0]; adjTop1 = p[top1]; adjBottom0 = p[top0]; adjBottom1 = p[bottom1]; p = tet->adjacentGluing(top1); // Note that only three of the four comparisons are needed. if (adjTop0 != p[top0] || adjTop1 != p[bottom1] || adjBottom0 != p[bottom0]) { ok = false; break; } // If we've finished the loop, exit at this point so we // can check that it all glued up correctly. if (next == base) break; // We haven't finished the loop, so the next // tetrahedron should be different from this one. if (next == tet) { ok = false; break; } // Move to the next tetrahedron. top0 = adjTop0; top1 = adjTop1; bottom0 = adjBottom0; bottom1 = adjBottom1; tet = next; next = tet->adjacentTetrahedron(top0); } if (ok) { // Make sure the final gluing wraps everything up // correctly. if (twisted) { if (adjTop0 != baseTop1 || adjTop1 != baseTop0 || adjBottom0 != baseBottom1) continue; } else { if (adjTop0 != baseTop0 || adjTop1 != baseTop1 || adjBottom0 != baseBottom0) continue; } // We have a solution! NLayeredLoop* ans = new NLayeredLoop(); ans->length = nTet; ans->hinge[0] = base->getEdge(hinge0); ans->hinge[1] = (twisted ? 0 : base->getEdge(hinge1)); return ans; } } } // Nothing found. return 0; } NAbelianGroup* NLayeredLoop::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (hinge[1]) { // Untwisted. if (length > 1) ans->addTorsionElement(length); } else { // Twisted. if (length % 2 == 0) ans->addTorsionElement(2, 2); else ans->addTorsionElement(4); } return ans; } } // namespace regina regina-4.95/engine/subcomplex/nlayeredloop.h000644 000765 000024 00000016567 12234011536 021117 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredloop.h * \brief Deals with layered loop components of a triangulation. */ #ifndef __NLAYEREDLOOP_H #ifndef __DOXYGEN #define __NLAYEREDLOOP_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" namespace regina { class NEdge; /** * \weakgroup subcomplex * @{ */ /** * Represents a layered loop component of a triangulation. * * A layered loop is a layered chain of \a n tetrahedra whose bottom * tetrahedron is layered onto its top tetrahedron to form a complete * loop. See the NLayeredChain class notes for a description of a * layered chain. * * To make a layered chain into a layered * loop, the bottom faces of the first tetrahedron will be * layered upon the top faces of the last tetrahedron, completing the loop. * At this stage there is a choice. The layering can be done in the usual * fashion, or there may be a \a twist in which the upper square (the * bottom faces of the first tetrahedron) is rotated 180 degrees before * being layered on the lower annulus (the top faces of the last * tetrahedron). * * If there is a twist, the two hinge edges become identified and the * entire component has a single vertex. If there is no twist, the * two hinge edges remain distinct (and disjoint) and the entire * component has two vertices. * * The \a length of the layered loop is the number of tetrahedra it * contains. A layered loop must contain at least one tetrahedron. * * All optional NStandardTriangulation routines are implemented for this * class. * * \testpart */ class REGINA_API NLayeredLoop : public NStandardTriangulation { private: unsigned long length; /**< The length of this layered loop. */ NEdge* hinge[2]; /**< The hinge edge(s) of this layered loop. If the loop is twisted, the second element in this array will be \c null. */ public: /** * Destroys this layered loop. */ virtual ~NLayeredLoop(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NLayeredLoop* clone() const; /** * Returns the length of this layered loop. * See the general class notes for further details. * * @return the length of this layered loop. */ unsigned long getLength() const; /** * Returns the length of this layered loop. * See the general class notes for further details. * * \deprecated The preferred way of accessing the length of a * layered loop is through getLength(). * * @return the length of this layered loop. */ unsigned long getIndex() const; /** * Determines if this layered loop contains a twist. * See the general class notes for further details. * * @return \c true if and only if this layered loop contains a * twist. */ bool isTwisted() const; /** * Returns the requested hinge edge of this layered loop. * See the general class notes for further details. * If there is only one hinge but parameter \a which is 1, * \c null will be returned. * * @param which specifies which hinge to return; this must be 0 * or 1. * @return the requested hinge edge. */ NEdge* getHinge(int which) const; /** * Determines if the given triangulation component is a layered * loop. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * layered loop, or \c null if the given component is * not a layered loop. */ static NLayeredLoop* isLayeredLoop(const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NLayeredLoop(); }; /*@}*/ // Inline functions for NLayeredLoop inline NLayeredLoop::NLayeredLoop() { } inline NLayeredLoop::~NLayeredLoop() { } inline unsigned long NLayeredLoop::getLength() const { return length; } inline unsigned long NLayeredLoop::getIndex() const { return length; } inline bool NLayeredLoop::isTwisted() const { return (hinge[1] == 0); } inline NEdge* NLayeredLoop::getHinge(int which) const { return hinge[which]; } inline std::ostream& NLayeredLoop::writeName(std::ostream& out) const { return out << (hinge[1] ? "C(" : "C~(") << length << ')'; } inline std::ostream& NLayeredLoop::writeTeXName(std::ostream& out) const { return out << (hinge[1] ? "C_{" : "\\tilde{C}_{") << length << '}'; } inline void NLayeredLoop::writeTextLong(std::ostream& out) const { out << "Layered loop (" << (hinge[1] ? "not twisted" : "twisted") << ") of length " << length; } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredsolidtorus.cpp000644 000765 000024 00000072127 12234011536 022702 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "triangulation/nedge.h" #include "triangulation/nfacepair.h" #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangulation.h" #include "subcomplex/nlayeredsolidtorus.h" namespace regina { NLayeredSolidTorus* NLayeredSolidTorus::clone() const { NLayeredSolidTorus* ans = new NLayeredSolidTorus(); int i,j; ans->nTetrahedra = nTetrahedra; ans->base = base; ans->topLevel = topLevel; for (i = 0; i < 6; i++) { ans->baseEdge[i] = baseEdge[i]; ans->baseEdgeGroup[i] = baseEdgeGroup[i]; ans->topEdgeGroup[i] = topEdgeGroup[i]; } for (i = 0; i < 2; i++) { ans->baseFace[i] = baseFace[i]; ans->topFace[i] = topFace[i]; } for (i = 0; i < 3; i++) { for (j = 0; j < 2; j++) ans->topEdge[i][j] = topEdge[i][j]; ans->meridinalCuts[i] = meridinalCuts[i]; } return ans; } void NLayeredSolidTorus::transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) { unsigned i, j; unsigned long baseTetID = originalTri->tetrahedronIndex(base); unsigned long topTetID = originalTri->tetrahedronIndex(topLevel); // Data members nTetrahedra and meridinalCuts remain unchanged. // Transform edge numbers (note that -1s can also be present for topEdge[]): for (i = 0; i < 6; i++) baseEdge[i] = NEdge::edgeNumber [iso->facePerm(baseTetID)[NEdge::edgeVertex[baseEdge[i]][0]]] [iso->facePerm(baseTetID)[NEdge::edgeVertex[baseEdge[i]][1]]]; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) { if (topEdge[i][j] < 0) continue; topEdge[i][j] = NEdge::edgeNumber [iso->facePerm(topTetID)[NEdge::edgeVertex[topEdge[i][j]][0]]] [iso->facePerm(topTetID)[NEdge::edgeVertex[topEdge[i][j]][1]]]; } // Inverse arrays for edge numbers: for (i = 0; i < 6; i++) baseEdgeGroup[baseEdge[i]] = (i == 0 ? 1 : i < 3 ? 2 : 3); unsigned missingEdge = 0 + 1 + 2 + 3 + 4 + 5; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) if (topEdge[i][j] != -1) { topEdgeGroup[topEdge[i][j]] = i; missingEdge -= topEdge[i][j]; } topEdgeGroup[missingEdge] = -1; // Transform face numbers: for (i = 0; i < 2; i++) { baseFace[i] = iso->facePerm(baseTetID)[baseFace[i]]; topFace[i] = iso->facePerm(topTetID)[topFace[i]]; } // Transform tetrahedra: base = newTri->getTetrahedron(iso->tetImage(baseTetID)); topLevel = newTri->getTetrahedron(iso->tetImage(topTetID)); } NLayeredSolidTorus* NLayeredSolidTorus::formsLayeredSolidTorusBase( NTetrahedron* tet) { int baseFace1; int baseFace2 = -1; NPerm4 basePerm; bool okay; int i, j; for (baseFace1 = 0; baseFace1 < 3; baseFace1++) if (tet->adjacentTetrahedron(baseFace1) == tet) { // This tetrahedron is glued to itself. baseFace2 = tet->adjacentFace(baseFace1); basePerm = tet->adjacentGluing(baseFace1); okay = true; for (i = 0; i < 4; i++) { if (i == baseFace1 || i == baseFace2) continue; // No vertex should be glued to itself! if (basePerm[i] == i) { okay = false; break; } } if (okay && basePerm[baseFace2] != baseFace1) break; else baseFace2 = -1; } if (baseFace2 == -1) return 0; // We have a layered solid torus!! // Fill in the details for the bottom layer. NLayeredSolidTorus* ans = new NLayeredSolidTorus(); ans->nTetrahedra = 1; ans->base = tet; ans->baseFace[0] = baseFace1; ans->baseFace[1] = baseFace2; ans->topFace[0] = basePerm[baseFace2]; ans->topFace[1] = basePerm[ans->topFace[0]]; ans->baseEdge[0] = NEdge::edgeNumber[baseFace1][baseFace2]; ans->baseEdge[1] = NEdge::edgeNumber[ans->topFace[1]][baseFace2]; ans->baseEdge[2] = NEdge::edgeNumber[ans->topFace[0]][baseFace1]; ans->baseEdge[3] = NEdge::edgeNumber[ans->topFace[0]][baseFace2]; ans->baseEdge[4] = NEdge::edgeNumber[ans->topFace[0]][ans->topFace[1]]; ans->baseEdge[5] = NEdge::edgeNumber[ans->topFace[1]][baseFace1]; for (i = 0; i < 6; i++) ans->baseEdgeGroup[ans->baseEdge[i]] = (i == 0 ? 1 : i < 3 ? 2 : 3); ans->topLevel = tet; ans->meridinalCuts[0] = 1; ans->meridinalCuts[1] = 2; ans->meridinalCuts[2] = 3; ans->topEdge[0][0] = ans->baseEdge[5]; ans->topEdge[0][1] = ans->baseEdge[3]; ans->topEdge[1][0] = ans->baseEdge[1]; ans->topEdge[1][1] = ans->baseEdge[2]; ans->topEdge[2][0] = ans->baseEdge[0]; ans->topEdge[2][1] = -1; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) if (ans->topEdge[i][j] != -1) ans->topEdgeGroup[ans->topEdge[i][j]] = i; ans->topEdgeGroup[ans->baseEdge[4]] = -1; // Now run through and look for layers to add to the torus. int adjFace[2]; // Faces of adjacent tetrahedron glued to the torus. int adjEdge; // Layering edge of the adjacent tetrahedron. int layerOnEdge[2]; // The two edges of the current top tetrahedron // corresponding to adjEdge. int newTopEdge; // New boundary edge of degree 1 in the torus. NPerm4 adjPerm[2]; int layerOnGroup; while (true) { // Is there a new layer? tet = ans->topLevel->adjacentTetrahedron(ans->topFace[0]); if (tet == 0 || tet == ans->topLevel || tet != ans->topLevel->adjacentTetrahedron(ans->topFace[1])) break; // There is a new tetrahedron glued to both torus boundary triangles. adjPerm[0] = ans->topLevel->adjacentGluing( ans->topFace[0]); adjPerm[1] = ans->topLevel->adjacentGluing( ans->topFace[1]); if (adjPerm[0].sign() != adjPerm[1].sign()) break; // See what the new boundary edge would be. adjFace[0] = ans->topLevel->adjacentFace(ans->topFace[0]); adjFace[1] = ans->topLevel->adjacentFace(ans->topFace[1]); newTopEdge = NEdge::edgeNumber[adjFace[0]][adjFace[1]]; adjEdge = 5 - newTopEdge; // See which edges of the current top tetrahedron are being // layered upon. layerOnEdge[0] = NEdge::edgeNumber [adjPerm[0].preImageOf(NEdge::edgeVertex[adjEdge][0])] [adjPerm[0].preImageOf(NEdge::edgeVertex[adjEdge][1])]; layerOnEdge[1] = NEdge::edgeNumber [adjPerm[1].preImageOf(NEdge::edgeVertex[adjEdge][0])] [adjPerm[1].preImageOf(NEdge::edgeVertex[adjEdge][1])]; if (layerOnEdge[0] != layerOnEdge[1] && layerOnEdge[0] + layerOnEdge[1] != 5) break; // We have a new layer! // Before changing anything else, rearrange the topEdge and // meridinalCuts arrays. layerOnGroup = ans->topEdgeGroup[layerOnEdge[0]]; switch(layerOnGroup) { case 0: // p q r -> q r q+r ans->meridinalCuts[0] = ans->meridinalCuts[1]; ans->meridinalCuts[1] = ans->meridinalCuts[2]; ans->meridinalCuts[2] = ans->meridinalCuts[0] + ans->meridinalCuts[1]; ans->followEdge(0, 1); ans->followEdge(1, 2); ans->topEdge[2][0] = newTopEdge; ans->topEdge[2][1] = -1; break; case 1: // p q r -> p r p+r ans->meridinalCuts[1] = ans->meridinalCuts[2]; ans->meridinalCuts[2] = ans->meridinalCuts[0] + ans->meridinalCuts[1]; ans->followEdge(0, 0); ans->followEdge(1, 2); ans->topEdge[2][0] = newTopEdge; ans->topEdge[2][1] = -1; break; case 2: if (ans->meridinalCuts[1] - ans->meridinalCuts[0] < ans->meridinalCuts[0]) { // p q r -> q-p p q ans->meridinalCuts[2] = ans->meridinalCuts[1]; ans->meridinalCuts[1] = ans->meridinalCuts[0]; ans->meridinalCuts[0] = ans->meridinalCuts[2] - ans->meridinalCuts[1]; ans->followEdge(2, 1); ans->followEdge(1, 0); ans->topEdge[0][0] = newTopEdge; ans->topEdge[0][1] = -1; } else { // p q r -> p q-p q ans->meridinalCuts[2] = ans->meridinalCuts[1]; ans->meridinalCuts[1] = ans->meridinalCuts[2] - ans->meridinalCuts[0]; ans->followEdge(2, 1); ans->followEdge(0, 0); ans->topEdge[1][0] = newTopEdge; ans->topEdge[1][1] = -1; } break; } ans->topFace[0] = NEdge::edgeVertex[adjEdge][0]; ans->topFace[1] = NEdge::edgeVertex[adjEdge][1]; // Massage the indices in topEdge to match topFace. for (i = 0; i < 3; i++) { // Make sure ans->topEdge[i][0] is in face ans->topFace[0]. if (ans->topFace[0] == NEdge::edgeVertex[ans->topEdge[i][0]][0] || ans->topFace[0] == NEdge::edgeVertex[ans->topEdge[i][0]][1]) { j = ans->topEdge[i][0]; ans->topEdge[i][0] = ans->topEdge[i][1]; ans->topEdge[i][1] = j; } } ans->topLevel = tet; for (i = 0; i < 3; i++) for (j = 0; j < 2; j++) if (ans->topEdge[i][j] != -1) ans->topEdgeGroup[ans->topEdge[i][j]] = i; ans->topEdgeGroup[adjEdge] = -1; ans->nTetrahedra++; } return ans; } NLayeredSolidTorus* NLayeredSolidTorus::formsLayeredSolidTorusTop( NTetrahedron* tet, unsigned topFace1, unsigned topFace2) { NTetrahedron* top = tet; NTetrahedron* next; NPerm4 cross1, cross2; NPerm4 canon1, canon2; NFacePair pair = NFacePair(topFace1, topFace2).complement(); NPerm4 vRoles(pair.upper(), topFace1, topFace2, pair.lower()); NPerm4 topRoles(vRoles); NPerm4 nextRoles; long w = 1, x = 0, y = 0, z = 1; long w_, x_, y_, z_; int rotation; unsigned long nTets = 1; NPerm4 rot180(3, 2, 1, 0); while (true) { // INVARIANT: // // We are currently looking at tetrahedron tet. // The faces of tet closest to the top of the layered solid // torus are vRoles[1] and vRoles[2]. The faces we have not yet // looked at are vRoles[0] and vRoles[3]. // // Denote directed edges a = vRoles[01], b = vRoles[02], and // similarly let p = topRoles[01], q = topRoles[02] (where topRoles // was the original permutation vRoles for the original top-level // tetrahedron top). Then these edges are related as follows: // // p == w.a + x.b // q == y.a + z.b // // The count nTets reflects the number of tetrahedra seen so // far, including this one. // Verify that both new faces go to the same tetrahedron (which // exists). next = tet->adjacentTetrahedron(vRoles[0]); if (! next) return 0; if (next != tet->adjacentTetrahedron(vRoles[3])) return 0; // Are we folding over? cross1 = tet->adjacentGluing(vRoles[0]); cross2 = tet->adjacentGluing(vRoles[3]); if (next == tet && cross1[vRoles[0]] == vRoles[3]) { // Could be. Certainly faces vRoles[0,3] are joined to // each other. This is our final iteration -- either it // works or it doesn't. // Find the permutation that maps canonical vertices 123 to 012. canon1 = vRoles.inverse() * cross1 * vRoles; // Run through the three orientation-preserving permutations. // Note that canon1[0] == 3. if (canon1 == NPerm4(3, 1, 2, 0)) { // Tetrahedron is folded shut over edge vRoles[12]. // This does not give an LST(3,2,1) base, so we are not // interested. return 0; } else if (canon1 == NPerm4(3, 0, 1, 2)) { rotation = 1; // a, b have weights 1, 2. } else if (canon1 == NPerm4(3, 2, 0, 1)) { rotation = 2; // a, b have weights 2, 1. } else { // We have an orientation-reversing permutation. return 0; } // We got one! NLayeredSolidTorus* ans = new NLayeredSolidTorus(); ans->nTetrahedra = nTets; ans->base = tet; ans->baseFace[0] = vRoles[3]; // Face vRoles[012] ans->baseFace[1] = vRoles[0]; // Face vRoles[123] ans->baseEdge[0] = NEdge::edgeNumber[vRoles[0]][vRoles[3]]; if (rotation == 1) { ans->baseEdge[1] = NEdge::edgeNumber[vRoles[0]][vRoles[2]]; ans->baseEdge[2] = NEdge::edgeNumber[vRoles[1]][vRoles[3]]; ans->baseEdge[3] = NEdge::edgeNumber[vRoles[0]][vRoles[1]]; ans->baseEdge[4] = NEdge::edgeNumber[vRoles[1]][vRoles[2]]; ans->baseEdge[5] = NEdge::edgeNumber[vRoles[2]][vRoles[3]]; } else { ans->baseEdge[1] = NEdge::edgeNumber[vRoles[0]][vRoles[1]]; ans->baseEdge[2] = NEdge::edgeNumber[vRoles[2]][vRoles[3]]; ans->baseEdge[3] = NEdge::edgeNumber[vRoles[0]][vRoles[2]]; ans->baseEdge[4] = NEdge::edgeNumber[vRoles[2]][vRoles[1]]; ans->baseEdge[5] = NEdge::edgeNumber[vRoles[1]][vRoles[3]]; } ans->baseEdgeGroup[ans->baseEdge[0]] = 1; ans->baseEdgeGroup[ans->baseEdge[1]] = 2; ans->baseEdgeGroup[ans->baseEdge[2]] = 2; ans->baseEdgeGroup[ans->baseEdge[3]] = 3; ans->baseEdgeGroup[ans->baseEdge[4]] = 3; ans->baseEdgeGroup[ans->baseEdge[5]] = 3; long cuts01, cuts13, cuts30; if (rotation == 1) { // (a,b) == (1,2). cuts01 = w + 2 * x; // w.a + x.b cuts13 = y + 2 * z; // y.a + z.b } else { // (a,b) == (2,1). cuts01 = 2 * w + x; // w.a + x.b cuts13 = 2 * y + z; // y.a + z.b } cuts30 = - cuts01 - cuts13; if (cuts01 < 0) cuts01 = -cuts01; if (cuts13 < 0) cuts13 = -cuts13; if (cuts30 < 0) cuts30 = -cuts30; ans->topLevel = top; ans->topFace[0] = topRoles[2]; // Face topRoles[013] ans->topFace[1] = topRoles[1]; // Face topRoles[023] // Run through all six possible orderings. int group01, group13, group30; if (cuts01 <= cuts13) { // 01 13 if (cuts13 <= cuts30) { // 01 13 30 group01 = 0; group13 = 1; group30 = 2; } else if (cuts30 <= cuts01) { // 30 01 13 group30 = 0; group01 = 1; group13 = 2; } else { // 01 30 13 group01 = 0; group30 = 1; group13 = 2; } } else { // 13 01 if (cuts30 <= cuts13) { // 30 13 01 group30 = 0; group13 = 1; group01 = 2; } else if (cuts01 <= cuts30) { // 13 01 30 group13 = 0; group01 = 1; group30 = 2; } else { // 13 30 01 group13 = 0; group30 = 1; group01 = 2; } } ans->meridinalCuts[group01] = cuts01; ans->meridinalCuts[group13] = cuts13; ans->meridinalCuts[group30] = cuts30; ans->topEdge[group01][0] = NEdge::edgeNumber[topRoles[0]][topRoles[1]]; ans->topEdge[group01][1] = NEdge::edgeNumber[topRoles[2]][topRoles[3]]; ans->topEdge[group13][0] = NEdge::edgeNumber[topRoles[1]][topRoles[3]]; ans->topEdge[group13][1] = NEdge::edgeNumber[topRoles[0]][topRoles[2]]; ans->topEdge[group30][0] = NEdge::edgeNumber[topRoles[3]][topRoles[0]]; ans->topEdge[group30][1] = -1; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[0]][topRoles[1]]] = group01; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[2]][topRoles[3]]] = group01; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[1]][topRoles[3]]] = group13; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[0]][topRoles[2]]] = group13; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[3]][topRoles[0]]] = group30; ans->topEdgeGroup[NEdge::edgeNumber[topRoles[1]][topRoles[2]]] = -1; // All done! return ans; } // We're looking for an entirely new tetrahedron. // Make sure we're not looping back in a cycle or anything kinky. if (next == tet || next == top) return 0; // Set up nextRoles so that faces tet/vRoles[0,3] are joined to // faces next/nextRoles[1,2] respectively. pair = NFacePair(cross1[vRoles[0]], cross2[vRoles[3]]).complement(); nextRoles = NPerm4(pair.upper(), cross1[vRoles[0]], cross2[vRoles[3]], pair.lower()); // Find the mapping between the canonical 0123 as described by // vRoles and the canonical 0123 as described by nextRoles. // There are two such mappings, for the gluings on faces // vRoles[0,3] respectively. canon1 = nextRoles.inverse() * cross1 * vRoles; canon2 = nextRoles.inverse() * cross2 * vRoles; // Make sure it's actually a layering, i.e., canon1 and canon2 are // compatible. if (rot180 * canon1 * rot180 != canon2) return 0; // Update the matrix [ a,b | c,d ]. // It seems sanest to take cases based on the six possible // permutations. Use canon2, which starts at face 3 (012). // Note that canon2[3] == 2. // Old a, b : 01, 02. // New a, b : 01, 13. if (canon2 == NPerm4(0, 1, 3, 2)) { // 012 -> 013. // old a = a // old b = a+b // p = w.(old_a) + x.(old_b) = (w+x).a + x.b // q = y.(old_a) + z.(old_b) = (y+z).a + z.b w_ = w + x; x_ = x; y_ = y + z; z_ = z; } else if (canon2 == NPerm4(0, 3, 1, 2)) { // 012 -> 031. // old a = a+b // old b = a // p = w.(old_a) + x.(old_b) = (w+x).a + w.b // q = y.(old_a) + z.(old_b) = (y+z).a + y.b w_ = w + x; x_ = w; y_ = y + z; z_ = y; } else if (canon2 == NPerm4(1, 0, 3, 2)) { // 012 -> 103. // old a = -a // old b = b // p = w.(old_a) + x.(old_b) = -w.a + x.b // q = y.(old_a) + z.(old_b) = -y.a + z.b w_ = -w; x_ = x; y_ = -y; z_ = z; } else if (canon2 == NPerm4(1, 3, 0, 2)) { // 012 -> 130. // old a = b // old b = -a // p = w.(old_a) + x.(old_b) = -x.a + w.b // q = y.(old_a) + z.(old_b) = -z.a + y.b w_ = -x; x_ = w; y_ = -z; z_ = y; } else if (canon2 == NPerm4(3, 0, 1, 2)) { // 012 -> 301. // old a = -(a+b) // old b = -b // p = w.(old_a) + x.(old_b) = -w.a - (w+x).b // q = y.(old_a) + z.(old_b) = -y.a - (y+z).b w_ = -w; x_ = -w - x; y_ = -y; z_ = -y - z; } else if (canon2 == NPerm4(3, 1, 0, 2)) { // 012 -> 310. // old a = -b // old b = -(a+b) // p = w.(old_a) + x.(old_b) = -x.a - (w+x).b // q = y.(old_a) + z.(old_b) = -z.a - (y+z).b w_ = -x; x_ = -w - x; y_ = -z; z_ = -y - z; } else { // Impossible. We should never get to this point. std::cerr << "ERROR: Bad permutation canon2." << std::endl; return 0; } w = w_; x = x_; y = y_; z = z_; // Adjust the other variables in preparation for the next loop // iteration. tet = next; vRoles = nextRoles; nTets++; } // The loop has no break so we should never get here, but what the hell. return 0; } NLayeredSolidTorus* NLayeredSolidTorus::isLayeredSolidTorus(NComponent* comp) { // Start with some basic property checks. if (! comp->isOrientable()) return 0; if (comp->getNumberOfBoundaryComponents() != 1) return 0; if (comp->getBoundaryComponent(0)->getNumberOfTriangles() != 2) return 0; NTriangleEmbedding f0 = comp->getBoundaryComponent(0)->getTriangle(0)-> getEmbedding(0); NTriangleEmbedding f1 = comp->getBoundaryComponent(0)->getTriangle(1)-> getEmbedding(0); NTetrahedron* top = f0.getTetrahedron(); if (f1.getTetrahedron() != top) return 0; // We have precisely one boundary component, which consists of two // triangular faces belonging to the same tetrahedron. // Follow the adjacent tetrahedra down to what should be the base // tetrahedron. Don't worry about gluing permutations for now. // Run a full search. // We use formsLayeredSolidTorusBase(), which works out the full structure // for us. It would be faster to just follow straight down from // the top level tetrahedron (which we already know), but this would // also require us to code up the entire structure again. NFacePair underFaces = NFacePair(f0.getTriangle(), f1.getTriangle()).complement(); NTetrahedron* currTet = top; NTetrahedron* nextTet; while (1) { // INV: Thus far we have seen a chain of tetrahedra, with each // tetrahedron glued to the next along two faces. // See where the next two faces lead. // Note that they cannot lead back to a previous tetrahedron, // since each previous tetrahedron already has all four faces // accounted for (thus showing this loop will terminate). // They also cannot be boundary faces, since there are only two // boundary faces and these have already been seen. nextTet = currTet->adjacentTetrahedron(underFaces.lower()); if (nextTet != currTet->adjacentTetrahedron(underFaces.upper())) return 0; // They both lead to the same adjacent tetrahedron. // Have we reached the end? if (nextTet == currTet) break; // No; we have simply moved on to the next tetrahedron. underFaces = NFacePair(currTet->adjacentFace(underFaces.lower()), currTet->adjacentFace(underFaces.upper())).complement(); currTet = nextTet; } // Here we are at the bottom. Now check the individual permutations // and fill in the structural details. return formsLayeredSolidTorusBase(currTet); } void NLayeredSolidTorus::followEdge(int destGroup, int sourceGroup) { int pos; NPerm4 adjPerm; for (int i = 1; i >= 0; i--) { pos = (topEdge[sourceGroup][i] == -1 ? 0 : i); adjPerm = topLevel->adjacentGluing(topFace[i]); topEdge[destGroup][i] = NEdge::edgeNumber [adjPerm[NEdge::edgeVertex[topEdge[sourceGroup][pos]][0]]] [adjPerm[NEdge::edgeVertex[topEdge[sourceGroup][pos]][1]]]; } } NManifold* NLayeredSolidTorus::getManifold() const { return new NHandlebody(1, true); } NAbelianGroup* NLayeredSolidTorus::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(); return ans; } NTriangulation* NLayeredSolidTorus::flatten(const NTriangulation* original, int mobiusBandBdry) const { // Create a new triangulation and identify the top-level and // base tetrahedra. NTriangulation* ans = new NTriangulation(*original); NTetrahedron* newTop = ans->getTetrahedron( original->tetrahedronIndex(topLevel)); NTetrahedron* newBase = ans->getTetrahedron( original->tetrahedronIndex(base)); NPacket::ChangeEventSpan span(ans); // Reglue the top faces before deleting the layered solid torus. NTetrahedron* adj0 = newTop->adjacentTetrahedron(topFace[0]); NTetrahedron* adj1 = newTop->adjacentTetrahedron(topFace[1]); if (adj0 && adj1 && (adj0 != newTop)) { // A permutation for each adjacent tetrahedron. // These permutations map: // 1,2 -> vertices corresponding to top edge group 0 // 0,2 -> vertices corresponding to top edge group 1 // 0,1 -> vertices corresponding to top edge group 2 // Start by representing vertices of this tetrahedron instead. NPerm4 groups0 = NPerm4( 6 - NEdge::edgeVertex[topEdge[0][0]][0] - NEdge::edgeVertex[topEdge[0][0]][1] - topFace[0], 6 - NEdge::edgeVertex[topEdge[1][0]][0] - NEdge::edgeVertex[topEdge[1][0]][1] - topFace[0], 6 - NEdge::edgeVertex[topEdge[2][0]][0] - NEdge::edgeVertex[topEdge[2][0]][1] - topFace[0], topFace[0]); NFacePair underFaces = NFacePair(topFace[0], topFace[1]).complement(); NPerm4 groups1 = NPerm4(topFace[0], topFace[1]) * NPerm4(underFaces.lower(), underFaces.upper()) * groups0; // Move these to vertices of the adjacent tetrahedra. groups0 = newTop->adjacentGluing(topFace[0]) * groups0; groups1 = newTop->adjacentGluing(topFace[1]) * groups1; // And do the regluing. adj0->unjoin(groups0[3]); adj1->unjoin(groups1[3]); adj0->joinTo(groups0[3], adj1, groups1 * NPerm4((mobiusBandBdry + 1) % 3, (mobiusBandBdry + 2) % 3) * groups0.inverse()); } // Delete the layered solid torus tetrahedra. NTetrahedron* curr; NTetrahedron* next; NFacePair currBdryFaces; NPerm4 adjPerm; curr = newBase; currBdryFaces = NFacePair(baseFace[0], baseFace[1]).complement(); while (curr) { next = curr->adjacentTetrahedron(currBdryFaces.lower()); currBdryFaces = NFacePair(curr->adjacentFace(currBdryFaces.lower()), curr->adjacentFace(currBdryFaces.upper())).complement(); ans->removeTetrahedron(curr); curr = next; } // And we're done. return ans; } } // namespace regina regina-4.95/engine/subcomplex/nlayeredsolidtorus.h000644 000765 000024 00000050565 12234011536 022351 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredsolidtorus.h * \brief Deals with layered solid tori in a triangulation. */ #ifndef __NLAYEREDSOLIDTORUS_H #ifndef __DOXYGEN #define __NLAYEREDSOLIDTORUS_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" namespace regina { class NIsomorphism; class NTetrahedron; class NTriangulation; /** * \weakgroup subcomplex * @{ */ /** * Represents a layered solid torus in a triangulation. * A layered solid torus must contain at least one tetrahedron. * * Note that this class \b only represents layered solid tori with a * (3,2,1) at their base. Thus triangulations that begin with a * degenerate (2,1,1) mobius strip and layer over the mobius strip * boundary (including the minimal (1,1,0) triangulation) are not * described by this class. * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NLayeredSolidTorus : public NStandardTriangulation { private: unsigned long nTetrahedra; /**< The number of tetrahedra in this torus. */ NTetrahedron* base; /**< The tetrahedron that is glued to itself at the base of this torus. */ int baseEdge[6]; /**< The edges of the base tetrahedron that are identified as a group of 1, 2 or 3 according to whether the index is 0, 1-2 or 3-5 respectively. See getBaseEdge() for further details. */ int baseEdgeGroup[6]; /**< Classifies the edges of the base tetrahedron according to whether they are identified in a group of 1, 2 or 3. */ int baseFace[2]; /**< The two faces of the base tetrahedron that are glued to each other. */ NTetrahedron* topLevel; /**< The tetrahedron on the boundary of this torus. */ int topEdge[3][2]; /**< Returns the edges of the top tetrahedron that the meridinal disc cuts fewest, middle or most times according to whether the first index is 0, 1 or 2 respectively. See getTopEdge() for further details. */ unsigned long meridinalCuts[3]; /**< Returns the number of times the meridinal disc cuts each boundary edge; this array is in non-decreasing order. */ int topEdgeGroup[6]; /**< Classifies the edges of the boundary tetrahedron according to whether the meridinal disc cuts them fewest, middle or most times. */ int topFace[2]; /**< The two faces of the boundary tetrahedron that form the torus boundary. */ public: /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NLayeredSolidTorus* clone() const; /** * Returns the number of tetrahedra in this layered solid torus. * * @return the number of tetrahedra. */ unsigned long getNumberOfTetrahedra() const; /** * Returns the tetrahedron that is glued to itself at the base of * this layered solid torus. * * @return the base tetrahedron. */ NTetrahedron* getBase() const; /** * Returns the requested edge of the base tetrahedron belonging * to the given group. The layering identifies the six edges * of the base tetrahedron into a group of three, a group of two * and a single unidentified edge; these are referred to as * groups 3, 2 and 1 respectively. * * Note that getBaseEdgeGroup(getBaseEdge(group, index)) == * group for all values of \c group and \c index. * * Edges getBaseEdge(2,0) and getBaseEdge(3,0) * will both belong to face getBaseFace(0). * Edges getBaseEdge(2,1) and getBaseEdge(3,2) * will both belong to face getBaseFace(1). * * @param group the group that the requested edge should belong * to; this must be 1, 2 or 3. * @param index the index within the given group of the requested * edge; this must be between 0 and group-1 inclusive. * Note that in group 3 the edge at index 1 is adjacent to both the * edges at indexes 0 and 2. * @return the edge number in the base tetrahedron of the * requested edge; this will be between 0 and 5 inclusive. */ int getBaseEdge(int group, int index) const; /** * Returns the group that the given edge of the base tetrahedron * belongs to. See getBaseEdge() for further details about * groups. * * Note that getBaseEdgeGroup(getBaseEdge(group, index)) == * group for all values of \c group and \c index. * * @param edge the edge number in the base tetrahedron of the * given edge; this must be between 0 and 5 inclusive. * @return the group to which the given edge belongs; this will * be 1, 2 or 3. */ int getBaseEdgeGroup(int edge) const; /** * Returns one of the two faces of the base tetrahedron that are * glued to each other. * * @param index specifies which of the two faces to return; this * must be 0 or 1. * @return the requested face number in the base tetrahedron; * this will be between 0 and 3 inclusive. */ int getBaseFace(int index) const; /** * Returns the top level tetrahedron in this layered solid torus. * This is the tetrahedron that would be on the boundary of the * torus if the torus were the entire manifold. * * @return the top level tetrahedron. */ NTetrahedron* getTopLevel() const; /** * Returns the number of times the meridinal disc of the torus * cuts the top level tetrahedron edges in the given group. * See getTopEdge() for further details about groups. * * @param group the given edge group; this must be 0, 1 or 2. * @return the number of times the meridinal disc cuts the edges * in the given group. */ unsigned long getMeridinalCuts(int group) const; /** * Returns the requested edge of the top level tetrahedron belonging * to the given group. The layering reduces five of the top * level tetrahedron edges to three boundary edges of the solid * torus; this divides the five initial edges into groups of size * two, two and one. * * Group 0 represents the boundary edge that the meridinal disc * cuts fewest times. Group 2 represents the boundary edge that * the meridinal disc cuts most times. Group 1 is in the middle. * * Note that getTopEdgeGroup(getTopEdge(group, index)) == * group for all values of \c group and \c index that * actually correspond to an edge. * * Edges getTopEdge(group, 0) will all belong to face * getTopFace(0). Edges getTopEdge(group, 1) * (if they exist) will all belong to face getTopFace(1). * * @param group the group that the requested edge should belong * to; this must be 0, 1 or 2. * @param index the index within the given group of the requested * edge; this must be 0 or 1. Note that one of the groups only * contains one tetrahedron edge, in which case this edge will be * stored at index 0. * @return the edge number in the top level tetrahedron of the * requested edge (between 0 and 5 inclusive), or -1 if there is * no such edge (only possible if the given group was the group * of size one and the given index was 1). */ int getTopEdge(int group, int index) const; /** * Returns the group that the given edge of the top level * tetrahedron belongs to. See getTopEdge() for further details * about groups. * * Note that getTopEdgeGroup(getTopEdge(group, index)) == * group for all values of \c group and \c index that * actually correspond to an edge. * * @param edge the edge number in the top level tetrahedron of * the given edge; this must be between 0 and 5 inclusive. * @return the group to which the given edge belongs (0, 1 or 2), * or -1 if this edge does not belong to any group (only possible * if this is the unique edge in the top tetrahedron not on the * torus boundary). */ int getTopEdgeGroup(int edge) const; /** * Returns one of the two faces of the top level tetrahedron that * form the boundary of this layered solid torus. * * @param index specifies which of the two faces to return; this * must be 0 or 1. * @return the requested face number in the top level tetrahedron; * this will be between 0 and 3 inclusive. */ int getTopFace(int index) const; /** * Flattens this layered solid torus to a Mobius band. * A newly created modified triangulation is returned; the * original triangulation is unchanged. * * Note that there are three different ways in which this layered * solid torus can be flattened, corresponding to the three * different edges of the boundary torus that could become the * boundary edge of the new Mobius band. * * @param original the triangulation containing this layered * solid torus; this triangulation will not be changed. * @param mobiusBandBdry the edge group on the boundary of this * layered solid torus that will become the boundary of the new * Mobius band (the remaining edge groups will become internal * edges of the new Mobius band). This must be 0, 1 or 2. * See getTopEdge() for further details about edge groups. * @return a newly created triangulation in which this layered * solid torus has been flattened to a Mobius band. */ NTriangulation* flatten(const NTriangulation* original, int mobiusBandBdry) const; /** * Adjusts the details of this layered solid torus according to * the given isomorphism between triangulations. * * The given isomorphism must describe a mapping from \a originalTri * to \a newTri, and this layered solid torus must currently * refer to tetrahedra in \a originalTri. After this routine is * called this structure will instead refer to the corresponding * tetrahedra in \a newTri (with changes in vertex/face * numbering also accounted for). * * \pre This layered solid torus currently refers to tetrahedra * in \a originalTri, and \a iso describes a mapping from * \a originalTri to \a newTri. * * @param originalTri the triangulation currently referenced by this * layered solid torus. * @param iso the mapping from \a originalTri to \a newTri. * @param newTri the triangulation to be referenced by the updated * layered solid torus. */ void transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri); /** * Determines if the given tetrahedron forms the base of a * layered solid torus within a triangulation. The torus need * not be the entire triangulation; the top level tetrahedron of * the torus may be glued to something else (or to itself). * * Note that the base tetrahedron of a layered solid torus is the * tetrahedron furthest from the boundary of the torus, i.e. the * tetrahedron glued to itself with a twist. * * @param tet the tetrahedron to examine as a potential base. * @return a newly created structure containing details of the * layered solid torus, or \c null if the given tetrahedron is * not the base of a layered solid torus. */ static NLayeredSolidTorus* formsLayeredSolidTorusBase( NTetrahedron* tet); /** * Determines if the given tetrahedron forms the top level * tetrahedron of a layered solid torus, with the two given * faces of this tetrahedron representing the boundary of the * layered solid torus. * * Note that the two given faces need not be boundary triangles in the * overall triangulation. That is, the layered solid torus may be * a subcomplex of some larger triangulation. For example, the * two given faces may be joined to some other tetrahedra outside * the layered solid torus or they may be joined to each other. * In fact, they may even extend this smaller layered solid torus * to a larger layered solid torus. * * @param tet the tetrahedron to examine as a potential top * level of a layered solid torus. * @param topFace1 the face number of the given tetrahedron that * should represent the first boundary triangle of the layered solid * torus. This should be between 0 and 3 inclusive. * @param topFace2 the face number of the given tetrahedron that * should represent the second boundary triangle of the layered solid * torus. This should be between 0 and 3 inclusive, and should * not be equal to \a topFace1. * @return a newly created structure containing details of the * layered solid torus, or \c null if the given tetrahedron with * its two faces do not form the top level of a layered solid torus. */ static NLayeredSolidTorus* formsLayeredSolidTorusTop( NTetrahedron* tet, unsigned topFace1, unsigned topFace2); /** * Determines if the given triangulation component forms a * layered solid torus in its entirity. * * Note that, unlike formsLayeredSolidTorusBase(), this routine * tests for a component that is a layered solid torus with no * additional tetrahedra or gluings. That is, the two boundary * triangles of the layered solid torus must in fact be boundary * triangles of the component. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * layered solid torus, or \c null if the given component is not * a layered solid torus. */ static NLayeredSolidTorus* isLayeredSolidTorus(NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Create a new uninitialised structure. */ NLayeredSolidTorus(); /** * Fills topEdge[destGroup] with the edges produced by * following the edges in group \c sourceGroup from the current * top level tetrahedron up to the next layered tetrahedron. * * Note that which edge is placed in topEdge[][0] and * which edge is placed in topEdge[][1] will be an * arbitrary decision; these may need to be switched later on. * * \pre There is a next layered tetrahedron. * \pre Member variables \a topLevel and \a topFace have not yet been * changed to reflect the next layered tetrahedron. * \pre The edges in group \a destGroup in the next layered * tetrahedron are actually layered onto the edges in group \a * sourceGroup in the current top level tetrahedron. * * @param sourceGroup the group in the current top level * tetrahedron to which the edges belong. * @param destGroup the group in the next layered tetrahedron to * which the same edges belong. */ void followEdge(int destGroup, int sourceGroup); }; /*@}*/ // Inline functions for NLayeredSolidTorus inline NLayeredSolidTorus::NLayeredSolidTorus() { } inline unsigned long NLayeredSolidTorus::getNumberOfTetrahedra() const { return nTetrahedra; } inline NTetrahedron* NLayeredSolidTorus::getBase() const { return base; } inline int NLayeredSolidTorus::getBaseEdge(int group, int index) const { return group == 1 ? baseEdge[index] : group == 2 ? baseEdge[1 + index] : baseEdge[3 + index]; } inline int NLayeredSolidTorus::getBaseEdgeGroup(int edge) const { return baseEdgeGroup[edge]; } inline int NLayeredSolidTorus::getBaseFace(int index) const { return baseFace[index]; } inline NTetrahedron* NLayeredSolidTorus::getTopLevel() const { return topLevel; } inline unsigned long NLayeredSolidTorus::getMeridinalCuts(int group) const { return meridinalCuts[group]; } inline int NLayeredSolidTorus::getTopEdge(int group, int index) const { return topEdge[group][index]; } inline int NLayeredSolidTorus::getTopEdgeGroup(int edge) const { return topEdgeGroup[edge]; } inline int NLayeredSolidTorus::getTopFace(int index) const { return topFace[index]; } inline std::ostream& NLayeredSolidTorus::writeName(std::ostream& out) const { return out << "LST(" << meridinalCuts[0] << ',' << meridinalCuts[1] << ',' << meridinalCuts[2] << ')'; } inline std::ostream& NLayeredSolidTorus::writeTeXName(std::ostream& out) const { return out << "\\mathop{\\rm LST}(" << meridinalCuts[0] << ',' << meridinalCuts[1] << ',' << meridinalCuts[2] << ')'; } inline void NLayeredSolidTorus::writeTextLong(std::ostream& out) const { out << "( " << meridinalCuts[0] << ", " << meridinalCuts[1] << ", " << meridinalCuts[2] << " ) layered solid torus"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayeredsurfacebundle.cpp000644 000765 000024 00000017220 12234011536 023306 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/ntorusbundle.h" #include "subcomplex/nlayeredsurfacebundle.h" #include "subcomplex/nlayering.h" #include "subcomplex/ntxicore.h" #include "triangulation/nisomorphism.h" #include "triangulation/ntriangulation.h" namespace regina { namespace { const NTxIDiagonalCore core_T_6_1(6, 1); const NTxIDiagonalCore core_T_7_1(7, 1); const NTxIDiagonalCore core_T_8_1(8, 1); const NTxIDiagonalCore core_T_8_2(8, 2); const NTxIDiagonalCore core_T_9_1(9, 1); const NTxIDiagonalCore core_T_9_2(9, 2); const NTxIDiagonalCore core_T_10_1(10, 1); const NTxIDiagonalCore core_T_10_2(10, 2); const NTxIDiagonalCore core_T_10_3(10, 3); const NTxIDiagonalCore core_T_11_1(11, 1); const NTxIDiagonalCore core_T_11_2(11, 2); const NTxIDiagonalCore core_T_11_3(11, 3); const NTxIDiagonalCore core_T_12_1(12, 1); const NTxIDiagonalCore core_T_12_2(12, 2); const NTxIDiagonalCore core_T_12_3(12, 3); const NTxIDiagonalCore core_T_12_4(12, 4); const NTxIParallelCore core_T_p; } NLayeredTorusBundle::~NLayeredTorusBundle() { delete coreIso_; } NLayeredTorusBundle* NLayeredTorusBundle::isLayeredTorusBundle( NTriangulation* tri) { // Basic property checks. if (! tri->isClosed()) return 0; if (tri->getNumberOfVertices() > 1) return 0; if (tri->getNumberOfComponents() > 1) return 0; if (tri->getNumberOfTetrahedra() < 6) return 0; // We have a 1-vertex 1-component closed triangulation with at least // six tetrahedra. // Hunt for the core thin torus bundle. NLayeredTorusBundle* ans; if ((ans = hunt(tri, core_T_6_1))) return ans; if ((ans = hunt(tri, core_T_7_1))) return ans; if ((ans = hunt(tri, core_T_8_1))) return ans; if ((ans = hunt(tri, core_T_8_2))) return ans; if ((ans = hunt(tri, core_T_9_1))) return ans; if ((ans = hunt(tri, core_T_9_2))) return ans; if ((ans = hunt(tri, core_T_10_1))) return ans; if ((ans = hunt(tri, core_T_10_2))) return ans; if ((ans = hunt(tri, core_T_10_3))) return ans; if ((ans = hunt(tri, core_T_11_1))) return ans; if ((ans = hunt(tri, core_T_11_2))) return ans; if ((ans = hunt(tri, core_T_11_3))) return ans; if ((ans = hunt(tri, core_T_12_1))) return ans; if ((ans = hunt(tri, core_T_12_2))) return ans; if ((ans = hunt(tri, core_T_12_3))) return ans; if ((ans = hunt(tri, core_T_12_4))) return ans; if ((ans = hunt(tri, core_T_p))) return ans; return 0; } NLayeredTorusBundle* NLayeredTorusBundle::hunt(NTriangulation* tri, const NTxICore& core) { std::list isos; if (! core.core().findAllSubcomplexesIn(*tri, isos)) return 0; // Run through each isomorphism and look for the corresponding layering. NMatrix2 matchReln; for (std::list::const_iterator it = isos.begin(); it != isos.end(); it++) { // Apply the layering to the lower boundary and see if it // matches nicely with the upper. NLayering layering( tri->getTetrahedron((*it)->tetImage(core.bdryTet(1,0))), (*it)->facePerm(core.bdryTet(1,0)) * core.bdryRoles(1,0), tri->getTetrahedron((*it)->tetImage(core.bdryTet(1,1))), (*it)->facePerm(core.bdryTet(1,1)) * core.bdryRoles(1,1)); layering.extend(); if (layering.matchesTop( tri->getTetrahedron((*it)->tetImage(core.bdryTet(0,0))), (*it)->facePerm(core.bdryTet(0,0)) * core.bdryRoles(0,0), tri->getTetrahedron((*it)->tetImage(core.bdryTet(0,1))), (*it)->facePerm(core.bdryTet(0,1)) * core.bdryRoles(0,1), matchReln)) { // It's a match! NLayeredTorusBundle* ans = new NLayeredTorusBundle(core); ans->coreIso_ = *it; ans->reln_ = core.bdryReln(0) * matchReln * core.bdryReln(1).inverse(); // Delete the remaining isomorphisms that we never even // looked at. for (it++; it != isos.end(); it++) delete *it; return ans; } // No match. Delete this isomorphism; we won't need it any more. delete *it; continue; } // Nothing found. return 0; } NManifold* NLayeredTorusBundle::getManifold() const { // Note that this one-liner appears again in getHomologyH1(), where // we use the underlying NTorusBundle for homology calculations. return new NTorusBundle(core_.parallelReln() * reln_); } NAbelianGroup* NLayeredTorusBundle::getHomologyH1() const { // It's implemented in NTorusBundle, so ride on that for now. // We'll implement it directly here in good time. return NTorusBundle(core_.parallelReln() * reln_).getHomologyH1(); } std::ostream& NLayeredTorusBundle::writeCommonName(std::ostream& out, bool tex) const { if (tex) { out << "B_{"; core_.writeTeXName(out); } else { out << "B("; core_.writeName(out); } out << " | " << reln_[0][0] << ',' << reln_[0][1]; out << " | " << reln_[1][0] << ',' << reln_[1][1]; return out << (tex ? "}" : ")"); } void NLayeredTorusBundle::writeTextLong(std::ostream& out) const { out << "Layered torus bundle: "; writeName(out); } } // namespace regina regina-4.95/engine/subcomplex/nlayeredsurfacebundle.h000644 000765 000024 00000030654 12234011536 022761 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayeredsurfacebundle.h * \brief Deals with layered surface bundle triangulations. */ #ifndef __NLAYEREDSURFACEBUNDLE_H #ifndef __DOXYGEN #define __NLAYEREDSURFACEBUNDLE_H #endif #include #include "regina-core.h" #include "maths/nmatrix2.h" #include "subcomplex/nstandardtri.h" #include "triangulation/ntriangulation.h" namespace regina { class NTxICore; /** * \weakgroup subcomplex * @{ */ /** * Describes a layered torus bundle. This is a triangulation of a * torus bundle over the circle formed as follows. * * We begin with a thin I-bundle over the torus, i.e,. a triangulation * of the product T x I that is only one tetrahedron thick. * This is referred to as the \a core, and is described by an object * of type NTxICore. * * We then identify the upper and lower torus boundaries of this core * according to some homeomorphism of the torus. This may be impossible * due to incompatible boundary edges, and so we allow a layering of * tetrahedra over one of the boundari tori in order to adjust the * boundary edges accordingly. Layerings are described in more detail * in the NLayering class. * * Given the parameters of the core T x I and the specific * layering, the monodromy for this torus bundle over the circle can be * calculated. The getManifold() routine returns details of the * corresponding 3-manifold. * * All optional NStandardTriangulation routines are implemented for this * class. * * \testpart */ class REGINA_API NLayeredTorusBundle : public NStandardTriangulation { private: const NTxICore& core_; /**< The core T x I triangulation whose boundaries are joined (possibly via a layering of tetrahedra). */ NIsomorphism* coreIso_; /**< Describes how the tetrahedra and vertices of the core T x I triangulation returned by NTxICore::core() map to the tetrahedra and vertices of the larger layered torus bundle under consideration. */ NMatrix2 reln_; /**< Describes how the layering of tetrahedra maps the lower boundary curves to the upper boundary curves. See layeringReln() for details. */ public: /** * Destroys this layered torus bundle and all of its internal * components. */ virtual ~NLayeredTorusBundle(); /** * Returns the T x I triangulation at the core of this * layered surface bundle. This is the product T x I * whose boundaries are joined (possibly via some layering of * tetrahedra). * * Note that the triangulation returned by NTxICore::core() * (that is, NLayeredSurfaceBundle::core().core()) may * well use different tetrahedron and vertex numbers. That is, * an isomorphic copy of it appears within this layered surface * bundle but the individual tetrahedra and vertices may have * been permuted. For a precise mapping from the NTxICore::core() * triangulation to this triangulation, see the routine coreIso(). * * @return the core T x I triangulation. */ const NTxICore& core() const; /** * Returns the isomorphism describing how the core T x I * appears as a subcomplex of this layered surface bundle. * * As described in the core() notes, the core T x I * triangulation returned by NTxICore::core() appears within this * layered surface bundle, but not necessarily with the same * tetrahedron or vertex numbers. * * This routine returns an isomorphism that maps the tetrahedra * and vertices of the core T x I triangulation (as * returned by NLayeredSurfaceBundle::core().core()) to the * tetrahedra and vertices of this overall layered surface bundle. * * The isomorphism that is returned belongs to this object, and * should not be modified or destroyed. * * @return the isomorphism from the core T x I to this * layered surface bundle. */ const NIsomorphism* coreIso() const; /** * Returns a 2-by-2 matrix describing how the layering of * tetrahedra relates curves on the two torus boundaries of the * core T x I. * * The NTxICore class documentation describes generating \a alpha * and \a beta curves on the two torus boundaries of the core * T x I (which are referred to as the \e upper and * \e lower boundaries). The two boundary tori are parallel in * two directions: through the core, and through the layering. * It is desirable to know the parallel relationship between * the two sets of boundary curves in each direction. * * The relationship through the core is already described by * NTxICore::parallelReln(). This routine describes the * relationship through the layering. * * Let \a a_u and \a b_u be the \a alpha and \a beta curves on * the upper boundary torus, and let \a a_l and \a b_l be the * \a alpha and \a beta curves on the lower boundary torus. * Suppose that the upper \a alpha is parallel to * \a w.\a a_l + \a x.\a b_l, and that the upper \a beta is * parallel to \a y.\a a_l + \a z.\a b_l. Then the matrix * returned will be * *
         *     [ w  x ]
         *     [      ] .
         *     [ y  z ]
         * 
* * In other words, * *
         *     [ a_u ]                      [ a_l ]
         *     [     ]  =  layeringReln() * [     ] .
         *     [ b_u ]                      [ b_l ]
         * 
* * It can be observed that this matrix expresses the upper * boundary curves in terms of the lower, whereas * NTxICore::parallelReln() expresses the lower boundary curves * in terms of the upper. This means that the monodromy * describing the overall torus bundle over the circle can be * calculated as *
         *     M  =  layeringReln() * core().parallelReln()
         * 
* or alternatively using the similar matrix *
         *     M'  =  core().parallelReln() * layeringReln() .
         * 
* * Note that in the degenerate case where there is no layering at * all, this matrix is still perfectly well defined; in this * case it describes a direct identification between the upper * and lower boundary tori. * * @return the relationship through the layering between the * upper and lower boundary curves of the core T x I. */ const NMatrix2& layeringReln() const; /** * Determines if the given triangulation is a layered surface bundle. * * @param tri the triangulation to examine. * @return a newly created structure containing details of the * layered surface bundle, or \c null if the given triangulation * is not a layered surface bundle. */ static NLayeredTorusBundle* isLayeredTorusBundle(NTriangulation* tri); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new structure based upon the given core T x I * triangulation. Aside from this core, the structure will * remain uninitialised. * * Note that only a reference to the core T x I is stored. * This class does not manage the life span of the core; it is * assumed that the core will remain in existence for at least * as long as this object does. (Usually the core is a static or * global variable that is not destroyed until the program exits.) * * @param whichCore a reference to the core T x I * triangulation upon which this layered surface bundle is based. */ NLayeredTorusBundle(const NTxICore& whichCore); /** * Contains code common to both writeName() and writeTeXName(). * * @param out the output stream to which to write. * @param tex \c true if this routine is called from * writeTeXName() or \c false if it is called from writeName(). * @return a reference to \a out. */ std::ostream& writeCommonName(std::ostream& out, bool tex) const; /** * Internal to isLayeredTorusBundle(). Determines if the given * triangulation is a layered surface bundle with the given core * T x I triangulation (up to isomorphism). * * @param tri the triangulation to examine. * @param core the core T x I to search for. * @return a newly created structure containing details of the * layered surface bundle, or \c null if the given triangulation is * not a layered surface bundle with the given T x I core. */ static NLayeredTorusBundle* hunt(NTriangulation* tri, const NTxICore& core); }; /*@}*/ // Inline functions for NLayeredTorusBundle inline NLayeredTorusBundle::NLayeredTorusBundle(const NTxICore& whichCore) : core_(whichCore), coreIso_(0) { } inline const NTxICore& NLayeredTorusBundle::core() const { return core_; } inline const NIsomorphism* NLayeredTorusBundle::coreIso() const { return coreIso_; } inline const NMatrix2& NLayeredTorusBundle::layeringReln() const { return reln_; } inline std::ostream& NLayeredTorusBundle::writeName(std::ostream& out) const { return writeCommonName(out, false); } inline std::ostream& NLayeredTorusBundle::writeTeXName(std::ostream& out) const { return writeCommonName(out, true); } } // namespace regina #endif regina-4.95/engine/subcomplex/nlayering.cpp000644 000765 000024 00000020112 12234011536 020722 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/ntetrahedron.h" #include "subcomplex/nlayering.h" namespace regina { NLayering::NLayering(NTetrahedron* bdry0, NPerm4 roles0, NTetrahedron* bdry1, NPerm4 roles1) : size(0), reln(1, 0, 0, 1) { oldBdryTet[0] = newBdryTet[0] = bdry0; oldBdryTet[1] = newBdryTet[1] = bdry1; oldBdryRoles[0] = newBdryRoles[0] = roles0; oldBdryRoles[1] = newBdryRoles[1] = roles1; } bool NLayering::extendOne() { // See if we move to a common new tetrahedron. // Also make sure this really is a new tetrahedron, so we don't get // stuck in a loop. NTetrahedron* next = newBdryTet[0]->adjacentTetrahedron( newBdryRoles[0][3]); if (next == 0 || next == newBdryTet[0] || next == newBdryTet[1] || next == oldBdryTet[0] || next == oldBdryTet[1]) return false; if (next != newBdryTet[1]->adjacentTetrahedron(newBdryRoles[1][3])) return false; // Get the mappings from the boundary vertex roles to the new tetrahedron // vertices. NPerm4 cross0 = newBdryTet[0]->adjacentGluing( newBdryRoles[0][3]) * newBdryRoles[0]; NPerm4 cross1 = newBdryTet[1]->adjacentGluing( newBdryRoles[1][3]) * newBdryRoles[1]; // Is it actually a layering? if (cross1 == cross0 * NPerm4(3, 2, 1, 0)) { // We're layering over the edge joining vertex roles 1 and 2. size++; newBdryRoles[0] = cross0 * NPerm4(0, 1, 3, 2); newBdryRoles[1] = cross0 * NPerm4(3, 2, 0, 1); newBdryTet[0] = newBdryTet[1] = next; // new a = old a = reln00 p + reln01 q // new b = old a + old b = (reln00 + reln10) p + (reln01 + reln11) q reln[1][0] += reln[0][0]; reln[1][1] += reln[0][1]; return true; } else if (cross1 == cross0 * NPerm4(2, 3, 0, 1)) { // We're layering over the edge joining vertex roles 0 and 2. size++; newBdryRoles[0] = cross0 * NPerm4(0, 1, 3, 2); newBdryRoles[1] = cross0 * NPerm4(2, 3, 1, 0); newBdryTet[0] = newBdryTet[1] = next; // new a = old a = reln00 p + reln01 q // new b = old b - old a = (reln10 - reln00) p + (reln11 - reln01) q reln[1][0] -= reln[0][0]; reln[1][1] -= reln[0][1]; return true; } else if (cross1 == cross0 * NPerm4(1, 0, 3, 2)) { // We're layering over the edge joining vertex roles 0 and 1. size++; newBdryRoles[0] = cross0 * NPerm4(0, 3, 2, 1); newBdryRoles[1] = cross0 * NPerm4(1, 2, 3, 0); newBdryTet[0] = newBdryTet[1] = next; // new a = old a - old b = (reln00 - reln10) p + (reln01 - reln11) q // new b = old b = reln10 p + reln11 q reln[0][0] -= reln[1][0]; reln[0][1] -= reln[1][1]; return true; } // It's not a layering at all. return false; } unsigned long NLayering::extend() { unsigned long added = 0; while (extendOne()) added++; return added; } bool NLayering::matchesTop(NTetrahedron* upperBdry0, NPerm4 upperRoles0, NTetrahedron* upperBdry1, NPerm4 upperRoles1, NMatrix2& upperReln) const { // We can cut half our cases by assuming that upperBdry0 meets with // newBdryTet[0] and that upperBdry1 meets with newBdryTet[1]. bool rot180; if (upperBdry0->adjacentTetrahedron(upperRoles0[3]) == newBdryTet[1] && upperBdry0->adjacentFace(upperRoles0[3]) == newBdryRoles[1][3]) { // If it does match, it's the opposite matching (upperBdry0 with // newBdryTet[1] and vice versa). Switch them and remember what // we did. NTetrahedron* tmpTet = upperBdry0; upperBdry0 = upperBdry1; upperBdry1 = tmpTet; NPerm4 tmpPerm = upperRoles0; upperRoles0 = upperRoles1; upperRoles1 = tmpPerm; rot180 = true; } else { // If it does match, it's what we'd like. rot180 = false; } // Do we meet the right tetrahedra and faces? if (upperBdry0->adjacentTetrahedron(upperRoles0[3]) != newBdryTet[0]) return false; if (upperBdry0->adjacentFace(upperRoles0[3]) != newBdryRoles[0][3]) return false; if (upperBdry1->adjacentTetrahedron(upperRoles1[3]) != newBdryTet[1]) return false; if (upperBdry1->adjacentFace(upperRoles1[3]) != newBdryRoles[1][3]) return false; // Find the mapping from the upper vertex roles to the boundary // vertex roles. Verify that this mapping is consistent for both faces. NPerm4 cross = newBdryRoles[0].inverse() * upperBdry0-> adjacentGluing(upperRoles0[3]) * upperRoles0; if (cross != newBdryRoles[1].inverse() * upperBdry1-> adjacentGluing(upperRoles1[3]) * upperRoles1) return false; // It's a match! Run through the six possible mappings to get the // relationship matrix correct. if (cross == NPerm4(0, 1, 2, 3)) { // It's the identity. upperReln = reln; } else if (cross == NPerm4(0, 2, 1, 3)) { // new a = + old b // new b = + old a upperReln = NMatrix2(0, 1, 1, 0) * reln; } else if (cross == NPerm4(1, 0, 2, 3)) { // new a = - old a // new b = - old a + old b upperReln = NMatrix2(-1, 0, -1, 1) * reln; } else if (cross == NPerm4(1, 2, 0, 3)) { // new a = - old a + old b // new b = - old a upperReln = NMatrix2(-1, 1, -1, 0) * reln; } else if (cross == NPerm4(2, 0, 1, 3)) { // new a = - old b // new b = + old a - old b upperReln = NMatrix2(0, -1, 1, -1) * reln; } else if (cross == NPerm4(2, 1, 0, 3)) { // new a = + old a - old b // new b = - old b upperReln = NMatrix2(1, -1, 0, -1) * reln; } // Don't forget to account for the 180 degree rotation if it // happened. if (rot180) upperReln.negate(); return true; } } // namespace regina regina-4.95/engine/subcomplex/nlayering.h000644 000765 000024 00000046526 12234011536 020410 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nlayering.h * \brief Assists with the analysis of layerings upon a torus boundary. */ #ifndef __NLAYERING_H #ifndef __DOXYGEN #define __NLAYERING_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "maths/nperm4.h" #include "utilities/boostutils.h" namespace regina { class NTetrahedron; /** * \weakgroup subcomplex * @{ */ /** * Represents a layering of zero or more tetrahedra upon a torus * boundary. * * A \e layering involves laying a new tetrahedron flat upon two * adjacent boundary triangles in order to change the boundary curves. Many * tetrahedra may be layered upon a boundary in succession in order to * change the boundary curves more dramatically. * * A torus boundary is specified by two tetrahedra (which may be the same) * and two permutations. Each permutation maps (0,1,2) in the diagram * below to the corresponding vertex numbers in each tetrahedron (and * therefore maps 3 to the corresponding face number). * *
 *     *--->>--*
 *     |0  2 / |
 *     |    / 1|
 *     v   /   v
 *     |1 /    |
 *     | / 2  0|
 *     *--->>--*
 * 
* * In particular, if the two tetrahedra are \a t0 and \a t1 and the two * corresponding permutations are \a p0 and \a p1, then: * - the torus boundary is formed from faces \a p0[3] and \a p1[3] of * tetrahedra \a t0 and \a t1 respectively; * - edges \a p0[0]-\a p0[1] and \a p1[1]-\a p1[0] of tetrahedra * \a t0 and \a t1 respectively are identified; * - edges \a p0[1]-\a p0[2] and \a p1[2]-\a p1[1] of tetrahedra * \a t0 and \a t1 respectively are identified; * - edges \a p0[2]-\a p0[0] and \a p1[0]-\a p1[2] of tetrahedra * \a t0 and \a t1 respectively are identified. * * Note that we do not actually require these triangular faces to form a torus, * and this is never verifed by any of the routines in this class. What * these routines do is use the diagram above to define the rules of * what forms a valid layering (and in fact the layering itself will * often be the cause of these edge identifications). This allows the * NLayering class a little more versatility in degenerate and boundary cases. * * This class keeps track of an \e old boundary, which is the original * pair of triangles upon which the first tetrahedron is layered, and a * \e new boundary, which is formed by the last layered tetrahedron and * contains the modified boundary curves. If no tetrahedra are layered * at all then the old and new boundaries will be identical. * * This class is used to search for layerings as follows. The * constructor is called with a particular pair of triangles that will form * the old boundary (note that these are generally \e not boundary triangles * in the triangulation, since we are searching for layerings that have * been placed upon them). This forms a trivial (zero-tetrahedron) * layering. The routines extend() or extendOne() are then called to see * how many additional tetrahedra have been layered upon this pair of triangles * according to the rules above. */ class REGINA_API NLayering : public boost::noncopyable { private: unsigned long size; /**< The number of tetrahedra that have been layered. */ NTetrahedron* oldBdryTet[2]; /**< The two tetrahedra of the old boundary (these may be the same). See the class notes for details. */ NPerm4 oldBdryRoles[2]; /**< The corresponding two permutations of the old boundary. See the class notes for details. */ NTetrahedron* newBdryTet[2]; /**< The two tetrahedra of the new boundary (these may be the same). See the class notes for details. */ NPerm4 newBdryRoles[2]; /**< The corresponding two permutations of the new boundary. See the class notes for details. */ NMatrix2 reln; /**< A matrix that expresses the new boundary curves in terms of the old, assuming that the old boundary is in fact a torus as described in the class notes. The first row of \a reln expresses the new \a roles[0-1] curve in terms of the old \a roles[0-1] and \a roles[0-2] curves, and the second row expresses the new \a roles[0-2] curve in a similar fashion (here we always talk in terms of the first tetrahedron for each boundary). It is guaranteed that the determinant of this matrix is 1. */ public: /** * Creates a new trivial (zero-tetrahedron) layering upon the * given boundary. * * The boundary is described by two tetrahedra and two * permutations as explained in the class notes. Note that the * given tetrahedra need not be boundary triangles in the triangulation * (and if search routines such as extend() are called then they * almost certainly should not be). * * @param bdry0 the tetrahedron providing the first triangle of the * boundary. * @param roles0 the permutation describing how this first triangle is * formed from three vertices of tetrahedron \a bdry0, as * described in the class notes. * @param bdry1 the tetrahedron providing the second triangle of the * boundary. * @param roles1 the permutation describing how this second triangle is * formed from three vertices of tetrahedron \a bdry1. */ NLayering(NTetrahedron* bdry0, NPerm4 roles0, NTetrahedron* bdry1, NPerm4 roles1); /** * Returns the number of individual tetrahedra that have been * layered onto the original boundary, according to the data * stored in this structure. * * This begins at zero when the class constructor is called, and * it increases if the routines extend() or extendOne() find that * additional layerings have taken place. * * @return the number of layered tetrahedra. */ unsigned long getSize() const; /** * Returns the tetrahedra that provide the old boundary triangles. * These belong to the original boundary before any layerings * take place. * * See the NLayering class notes for details on how a torus * boundary is formed from two tetrahedra and two permutations. * * @param which specifies which tetrahedron to return; this must * be either 0 or 1. * @return the requested tetrahedron of the old boundary. */ NTetrahedron* getOldBoundaryTet(unsigned which) const; /** * Returns the permutations that describe the old boundary triangles. * These refer to the original boundary before any layerings * take place. * * See the NLayering class notes for details on how a torus * boundary is formed from two tetrahedra and two permutations. * * @param which specifies which permutation to return; this must * be either 0 or 1. * @return the requested permutation describing the old boundary. */ NPerm4 getOldBoundaryRoles(unsigned which) const; /** * Returns the tetrahedra that provide the new boundary triangles. * These belong to the final boundary after layerings have been * performed. * * See the NLayering class notes for details on how a torus * boundary is formed from two tetrahedra and two permutations. * * @param which specifies which tetrahedron to return; this must * be either 0 or 1. * @return the requested tetrahedron of the new boundary. */ NTetrahedron* getNewBoundaryTet(unsigned which) const; /** * Returns the permutations that describe the new boundary triangles. * These refer to the final boundary after layerings have been * performed. * * See the NLayering class notes for details on how a torus * boundary is formed from two tetrahedra and two permutations. * * @param which specifies which permutation to return; this must * be either 0 or 1. * @return the requested permutation describing the new boundary. */ NPerm4 getNewBoundaryRoles(unsigned which) const; /** * Returns a 2-by-2 matrix describing the relationship between * curves on the old and new boundary tori. Note that this * relationship will often be non-trivial, since one of the * key reasons for layering is to modify boundary curves. * * Let \a t and \a p be the first tetrahedron and * permutation of the old boundary (as returned by * getOldBoundaryTet(0) and getOldBoundaryRoles(0)), and let * \a old_x and \a old_y be the directed edges \a p[0]-\a p[1] * and \a p[0]-\a p[2] respectively of tetrahedron \a t (these * are the leftmost and uppermost edges of the diagram below). * Likewise, let \a s and \a q be the first tetrahedron and * permutation of the new boundary (as returned by * getNewBoundaryTet(0) and getNewBoundaryRoles(0)), and let * \a new_x and \a new_y be the directed edges \a q[0]-\a q[1] * and \a q[0]-\a q[2] respectively of tetrahedron \a s. * *
         *     *--->>--*
         *     |0  2 / |
         *     |    / 1|
         *     v   /   v
         *     |1 /    |
         *     | / 2  0|
         *     *--->>--*
         * 
* * Assuming both boundaries are tori, edges \a old_x and \a old_y are * generators of the old boundary torus and edges \a new_x and * \a new_y are generators of the new boundary torus. Suppose * that this routine returns the matrix \a M. This signifies * that, using additive notation: * *
         *     [new_x]         [old_x]
         *     [     ]  =  M * [     ] .
         *     [new_y]         [old_y]
         * 
* * In other words, the matrix that is returned expresses the * generator curves of the new boundary in terms of the * generator curves of the old boundary. * * Note that the determinant of this matrix will always be 1. * * @return the matrix relating the old and new boundary curves. */ const NMatrix2& boundaryReln() const; /** * Examines whether a single additional tetrahedron has been * layered upon the current new boundary. * * The new boundary triangles are assumed to form a torus as * described in the class notes (this is not verified, and there * are degenerate cases where this will likely be false). This * defines three possible ways in which an additional tetrahedron * may be layered (over the three boundary edges respectively). * * If it is found that an additional tetrahedron does exist and * has been joined to the new boundary in one of these three * possible ways, this structure is extended to incorporate the * additional tetrahedron. The size will grow by one, and the * new boundary will become the remaining two faces of this * additional tetrahedron. * * @return \c true if a tetrahedron was found as described above * and this structure was extended accordingly, or \c false otherwise. */ bool extendOne(); /** * Examines whether one or more additional tetrahedra have been * layered upon the current new boundary. * * Specifically, this routine calls extendOne() as many times as * possible. If \a k additional layerings are discovered as a * result, the size of this structure will have grown by \a k * and the new boundary will be changed to describe the * remaining two faces of the \a kth layered tetrahedron. * * It is guaranteed that, once this routine is finished, the new * boundary will not have any additional tetrahedron layered * upon it. That is, if extendOne() were called again then it * would return \c false. * * @return the number of additional layered tetrahedra that were * discovered. */ unsigned long extend(); /** * Determines whether the new torus boundary of this structure * is identified with the given torus boundary. In other words, * this routine determines whether the new torus boundary of * this structure and the given torus boundary represent * opposite sides of the same two triangles. * * The two boundaries must be identified according to some * homeomorphism of the torus. Note that there are 12 different * ways in which this can be done (two choices for which * tetrahedron face joins with which, and then six possible * rotations and reflections). * * As with the other routines in this class, this routine does * not verify that either boundary in fact forms a torus. * Instead, it uses this assumption to define the rules of what * identifications are allowable. * * If there is a match, the given matrix \a upperReln will be * modified to describe how the edges of the given boundary * relate to the edges of the old boundary torus. Note that * this relationship depends on how the intermediate tetrahedra * are layered (and in fact the purpose of a layering is often to * produce such a non-trivial relationship). * * Specifically, let \a t0 and \a p0 be the first tetrahedron and * permutation of the old boundary (as returned by * getOldBoundaryTet(0) and getOldBoundaryRoles(0)), and let * \a x and \a y be the directed edges \a p0[0]-\a p0[1] and * \a p0[0]-\a p0[2] of tetrahedron \a t0 respectively (these * are the leftmost and uppermost edges of the diagram below). * Likewise, let \a u and \a q be the first tetrahedron and * permutation of the given boundary (as passed by parameters * \a upperBdry0 and \a upperRoles0), and let * \a a and \a b be the directed edges \a q[0]-\a q[1] and * \a q[0]-\a q[2] of tetrahedron \a u respectively. * *
         *     *--->>--*
         *     |0  2 / |
         *     |    / 1|
         *     v   /   v
         *     |1 /    |
         *     | / 2  0|
         *     *--->>--*
         * 
* * Assuming both boundaries are tori, edges \a x and \a y are * generators of the original torus boundary and edges \a a and * \a b are generators of the given torus boundary. Using * additive notation, the matrix \a upperReln is modified so * that * *
         *     [a]                 [x]
         *     [ ]  =  upperReln * [ ] .
         *     [b]                 [y]
         * 
* * In other words, the modified \a upperReln matrix expresses * the generator curves of the given boundary in terms of the * generator curves of the old boundary. * * If no match is found, the matrix \a upperReln is not touched. * * @param upperBdry0 the tetrahedron providing the first triangle of * the given boundary. * @param upperRoles0 the permutation describing how this * first triangle is formed from three vertices of tetrahedron * upperBdry0, as described in the class notes. * @param upperBdry1 the tetrahedron providing the second triangle of * the given boundary. * @param upperRoles1 the permutation describing how this second * triangle is formed from three vertices of tetrahedron upperBdry1. * @param upperReln the matrix that is changed to reflect the * relationship between the old boundary of this structure and * the given boundary. * @return \c true if the given boundary is found to matche the * new boundary of this structure, or \c false otherwise. */ bool matchesTop(NTetrahedron* upperBdry0, NPerm4 upperRoles0, NTetrahedron* upperBdry1, NPerm4 upperRoles1, NMatrix2& upperReln) const; }; /*@}*/ // Inline functions for NLayering inline unsigned long NLayering::getSize() const { return size; } inline NTetrahedron* NLayering::getOldBoundaryTet(unsigned which) const { return oldBdryTet[which]; } inline NPerm4 NLayering::getOldBoundaryRoles(unsigned which) const { return oldBdryRoles[which]; } inline NTetrahedron* NLayering::getNewBoundaryTet(unsigned which) const { return newBdryTet[which]; } inline NPerm4 NLayering::getNewBoundaryRoles(unsigned which) const { return newBdryRoles[which]; } inline const NMatrix2& NLayering::boundaryReln() const { return reln; } } // namespace regina #endif regina-4.95/engine/subcomplex/npillowtwosphere.cpp000644 000765 000024 00000007611 12234011536 022370 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "subcomplex/npillowtwosphere.h" #include "triangulation/ntriangle.h" namespace regina { NPillowTwoSphere* NPillowTwoSphere::clone() const { NPillowTwoSphere* ans = new NPillowTwoSphere(); ans->triangle[0] = triangle[0]; ans->triangle[1] = triangle[1]; ans->triMapping = triMapping; return ans; } NPillowTwoSphere* NPillowTwoSphere::formsPillowTwoSphere( NTriangle* tri1, NTriangle* tri2) { if (tri1 == tri2 || tri1->isBoundary() || tri2->isBoundary()) return 0; NEdge* edge[2][3]; int i; for (i = 0; i < 3; i++) { edge[0][i] = tri1->getEdge(i); edge[1][i] = tri2->getEdge(i); } if (edge[0][0] == edge[0][1] || edge[0][0] == edge[0][2] || edge[0][1] == edge[0][2]) return 0; // The first triangle has three distinct edges. See if it matches to the // second triangle. int joinTo0 = -1; for (i = 0; i < 3; i++) if (edge[0][0] == edge[1][i]) { joinTo0 = i; break; } if (joinTo0 == -1) return 0; // Now make sure the edges all match up and with the correct // permutations. NPerm4 perm = tri2->getEdgeMapping(joinTo0) * tri1->getEdgeMapping(0).inverse(); for (i = 1; i < 3; i++) { if (edge[0][i] != edge[1][perm[i]]) return 0; if (! (tri2->getEdgeMapping(perm[i]) == perm * tri1->getEdgeMapping(i))) return 0; } // We have an answer. NPillowTwoSphere* ans = new NPillowTwoSphere(); ans->triangle[0] = tri1; ans->triangle[1] = tri2; ans->triMapping = perm; return ans; } } // namespace regina regina-4.95/engine/subcomplex/npillowtwosphere.h000644 000765 000024 00000016405 12234011536 022036 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/npillowtwosphere.h * \brief Deals with 2-spheres made from two triangles glued along their * three edges. */ #ifndef __NPILLOWTWOSPHERE_H #ifndef __DOXYGEN #define __NPILLOWTWOSPHERE_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" namespace regina { class NTriangle; class NTriangulation; /** * \weakgroup subcomplex * @{ */ /** * Represents a 2-sphere made from two triangles glued together along their * three edges. The two triangles must be distinct and the three edges of * each triangle must also be distinct. Neither of the triangles may be * boundary triangles. * These two triangless together form an embedded 2-sphere in the triangulation * (with the exception that two or three points of the sphere corresponding * to the triangles vertices may be identified). * * This 2-sphere can be cut along and the two resulting 2-sphere * boundaries filled in with 3-balls, and the resulting triangulation has * the same number of tetrahedra as the original. If the original * 2-sphere was separating, the resulting triangulation will contain the * two terms of the corresponding connected sum. */ class REGINA_API NPillowTwoSphere : public ShareableObject { private: NTriangle* triangle[2]; /**< The two triangles whose edges are joined. */ NPerm4 triMapping; /**< A mapping from vertices (0,1,2) of the first triangle to vertices (0,1,2) of the second triangle describing how the triangle boundaries are joined. */ public: /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NPillowTwoSphere* clone() const; /** * Returns one of the two triangles whose boundaries are joined. * * @param index specifies which of the two triangles to return; * this must be either 0 or 1. * @return the corresponding triangle. */ NTriangle* getTriangle(int index) const; /** * A deprecated alias for getTriangle(). * * This routine returns one of the two triangles whose boundaries * are joined. See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @param index specifies which of the two triangles to return; * this must be either 0 or 1. * @return the corresponding triangle. */ NTriangle* getFace(int index) const; /** * Returns a permutation describing how the boundaries of the two * triangles are joined. * * The permutation will map vertices (0,1,2) of * getTriangle(0) to vertices (0,1,2) of * getTriangle(1). The map will represent how the vertices * of the triangles are identified by the three edge gluings. * * @return a permutation describing how the triangle boundaries are * joined. */ NPerm4 getTriangleMapping() const; /** * A deprecated alias for getTriangleMapping(). * * This routine returns a permutation describing how the boundaries * of the two triangles are joined. See getTriangleMapping() * for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangleMapping() instead. * * @return a permutation describing how the triangle boundaries are * joined. */ NPerm4 getFaceMapping() const; /** * Determines if the two given triangles together form a pillow * 2-sphere. * * \pre The two given triangles are distinct. * * @param tri1 the first triangle to examine. * @param tri2 the second triangle to examine. * @return a newly created structure containing details of the * pillow 2-sphere, or \c null if the given triangles do not * form a pillow 2-sphere. */ static NPillowTwoSphere* formsPillowTwoSphere(NTriangle* tri1, NTriangle* tri2); void writeTextShort(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NPillowTwoSphere(); }; /*@}*/ // Inline functions for NPillowTwoSphere inline NPillowTwoSphere::NPillowTwoSphere() { } inline NTriangle* NPillowTwoSphere::getTriangle(int index) const { return triangle[index]; } inline NTriangle* NPillowTwoSphere::getFace(int index) const { return triangle[index]; } inline NPerm4 NPillowTwoSphere::getTriangleMapping() const { return triMapping; } inline NPerm4 NPillowTwoSphere::getFaceMapping() const { return triMapping; } inline void NPillowTwoSphere::writeTextShort(std::ostream& out) const { out << "Pillow 2-sphere"; } } // namespace regina #endif regina-4.95/engine/subcomplex/npluggedtorusbundle.cpp000644 000765 000024 00000032365 12234011536 023043 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/ngraphloop.h" #include "manifold/nsfs.h" #include "subcomplex/nlayering.h" #include "subcomplex/npluggedtorusbundle.h" #include "subcomplex/nsatregion.h" #include "subcomplex/ntxicore.h" #include "triangulation/nisomorphism.h" #include "triangulation/ntriangulation.h" namespace regina { namespace { const NTxIDiagonalCore core_T_6_1(6, 1); const NTxIDiagonalCore core_T_7_1(7, 1); const NTxIDiagonalCore core_T_8_1(8, 1); const NTxIDiagonalCore core_T_8_2(8, 2); const NTxIDiagonalCore core_T_9_1(9, 1); const NTxIDiagonalCore core_T_9_2(9, 2); const NTxIDiagonalCore core_T_10_1(10, 1); const NTxIDiagonalCore core_T_10_2(10, 2); const NTxIDiagonalCore core_T_10_3(10, 3); const NTxIParallelCore core_T_p; } NPluggedTorusBundle::~NPluggedTorusBundle() { delete bundleIso_; delete region_; } NManifold* NPluggedTorusBundle::getManifold() const { NSFSpace* sfs = region_->createSFS(false); if (! sfs) return 0; if (sfs->punctures() == 1) { // The region has one larger boundary, but we pinch it to create // two smaller boundaries. sfs->addPuncture(); } sfs->reduce(false); return new NGraphLoop(sfs, matchingReln_); } std::ostream& NPluggedTorusBundle::writeName(std::ostream& out) const { out << "Plugged Torus Bundle ["; bundle_.writeName(out); out << " | "; region_->writeBlockAbbrs(out, false); return out << ']'; } std::ostream& NPluggedTorusBundle::writeTeXName(std::ostream& out) const { out << "\\mathrm{PTB}\\left["; bundle_.writeTeXName(out); out << "\\,|\\n"; region_->writeBlockAbbrs(out, true); return out << "\\right]"; } void NPluggedTorusBundle::writeTextLong(std::ostream& out) const { out << "Plugged torus bundle, fibre/orbifold relation " << matchingReln_ << '\n'; out << "Thin I-bundle: "; bundle_.writeName(out); out << '\n'; region_->writeDetail(out, "Saturated region"); } NPluggedTorusBundle* NPluggedTorusBundle::isPluggedTorusBundle( NTriangulation* tri) { // Basic property checks. if (! tri->isClosed()) return 0; if (tri->getNumberOfComponents() > 1) return 0; // The smallest non-trivial examples of these have nine tetrahedra // (six for the TxI core and another three for a non-trivial region). if (tri->getNumberOfTetrahedra() < 9) return 0; // We have a closed and connected triangulation with at least // nine tetrahedra. // Hunt for the thin torus bundle. NPluggedTorusBundle* ans; if ((ans = hunt(tri, core_T_6_1))) return ans; if ((ans = hunt(tri, core_T_7_1))) return ans; if ((ans = hunt(tri, core_T_8_1))) return ans; if ((ans = hunt(tri, core_T_8_2))) return ans; if ((ans = hunt(tri, core_T_9_1))) return ans; if ((ans = hunt(tri, core_T_9_2))) return ans; if ((ans = hunt(tri, core_T_10_1))) return ans; if ((ans = hunt(tri, core_T_10_2))) return ans; if ((ans = hunt(tri, core_T_10_3))) return ans; if ((ans = hunt(tri, core_T_p))) return ans; return 0; } NPluggedTorusBundle* NPluggedTorusBundle::hunt(NTriangulation* triang, const NTxICore& bundle) { std::list isos; if (! bundle.core().findAllSubcomplexesIn(*triang, isos)) return 0; int regionPos; NPerm4 annulusToUpperLayer; NSatAnnulus upperAnnulus, lowerAnnulus, bdryAnnulus; NSatBlock::TetList avoidTets; NSatBlock* starter; NSatRegion* region; bool bdryRefVert, bdryRefHoriz; // Run through each isomorphism and look for the corresponding layering. for (std::list::const_iterator it = isos.begin(); it != isos.end(); it++) { // Apply layerings to the upper and lower boundaries. NLayering layerUpper( triang->getTetrahedron((*it)->tetImage(bundle.bdryTet(0,0))), (*it)->facePerm(bundle.bdryTet(0,0)) * bundle.bdryRoles(0,0), triang->getTetrahedron((*it)->tetImage(bundle.bdryTet(0,1))), (*it)->facePerm(bundle.bdryTet(0,1)) * bundle.bdryRoles(0,1)); layerUpper.extend(); NLayering layerLower( triang->getTetrahedron((*it)->tetImage(bundle.bdryTet(1,0))), (*it)->facePerm(bundle.bdryTet(1,0)) * bundle.bdryRoles(1,0), triang->getTetrahedron((*it)->tetImage(bundle.bdryTet(1,1))), (*it)->facePerm(bundle.bdryTet(1,1)) * bundle.bdryRoles(1,1)); layerLower.extend(); // Count tetrahedra to ensure that the layerings haven't crossed. // In fact, we should have at least three spare tetrahedra for // housing a non-trivial saturated region. if (layerLower.getSize() + layerUpper.getSize() + bundle.core().getNumberOfTetrahedra() + 3 > triang->getNumberOfTetrahedra()) { // No good. Move on. delete *it; continue; } lowerAnnulus.tet[0] = layerLower.getNewBoundaryTet(0); lowerAnnulus.tet[1] = layerLower.getNewBoundaryTet(1); lowerAnnulus.roles[0] = layerLower.getNewBoundaryRoles(0); lowerAnnulus.roles[1] = layerLower.getNewBoundaryRoles(1); // Look for the saturated region. for (regionPos = 0; regionPos < 3; regionPos++) { // Construct the permutation from 0/1/2 markings on the // first saturated annulus boundary to 0/1/2 markings on the // first boundary triangle above the layering. annulusToUpperLayer = NPerm4(regionPos, (regionPos + 1) % 3, (regionPos + 2) % 3, 3); upperAnnulus.tet[0] = layerUpper.getNewBoundaryTet(0); upperAnnulus.tet[1] = layerUpper.getNewBoundaryTet(1); upperAnnulus.roles[0] = layerUpper.getNewBoundaryRoles(0) * annulusToUpperLayer; upperAnnulus.roles[1] = layerUpper.getNewBoundaryRoles(1) * annulusToUpperLayer; // Recall that we already know the triangulation to be closed. upperAnnulus.switchSides(); // Construct the list of tetrahedra to avoid when searching for the // saturated region. Don't worry about all the internal tetrahedra // within the layerings or the thin I-bundle; as long as we've got // the boundary tetrahedra we'll be fine. avoidTets.clear(); avoidTets.insert(layerUpper.getNewBoundaryTet(0)); avoidTets.insert(layerUpper.getNewBoundaryTet(1)); avoidTets.insert(layerLower.getNewBoundaryTet(0)); avoidTets.insert(layerLower.getNewBoundaryTet(1)); starter = NSatBlock::isBlock(upperAnnulus, avoidTets); if (! starter) continue; // We have a starter block. Make a region out of it, and // ensure that region has precisely two boundary annuli. region = new NSatRegion(starter); region->expand(avoidTets, false); if (region->numberOfBoundaryAnnuli() != 2) { delete region; continue; } // From the NSatRegion specifications we know that the first // boundary annulus will be upperAnnulus. Find the second. bdryAnnulus = region->boundaryAnnulus(1, bdryRefVert, bdryRefHoriz); // Hope like hell that this meets up with the lower layering // boundary. Note that this will force it to be a torus also. NMatrix2 upperRolesToLower; if (! lowerAnnulus.isJoined(bdryAnnulus, upperRolesToLower)) { delete region; continue; } // All good! // Better work out what we've got here. // Mapping from fibre/base curves (f0, o0) to upperAnnulus // edges (first triangle: 01, first triangle: 02). NMatrix2 curvesToUpperAnnulus(-1, 0, 0, 1); // Mapping from upperAnnulus edges (first: 01, first: 02) to // upper layering boundary roles (first: 01, first: 02). NMatrix2 upperAnnulusToUpperLayer; if (regionPos == 0) upperAnnulusToUpperLayer = NMatrix2(1, 0, 0, 1); else if (regionPos == 1) upperAnnulusToUpperLayer = NMatrix2(0, -1, 1, -1); else upperAnnulusToUpperLayer = NMatrix2(-1, 1, -1, 0); // Mapping from upper layering boundary roles // (first: 01, first: 02) to the bundle boundary 0 roles // (first: 01, first: 02) is layerUpper.boundaryReln().inverse(). // Mapping from bundle boundary 0 roles (first: 01, first: 02) to // bundle boundary 0 (alpha, beta) is bundle.bdryReln(0). // Mapping from bundle boundary 0 (alpha, beta) to bundle boundary 1 // (alpha, beta) is bundle.parallelReln(). // Mapping from bundle boundary 1 (alpha, beta) to bundle boundary 1 // roles (first: 01, first: 02) is bundle.bdryReln(1).inverse(). // Mapping from bundle boundary 1 roles (first: 01, first: 02) to // lower layering boundary roles (first: 01, first: 02) is // layerLower.boundaryReln(). // Mapping from lower layering boundary roles (first: 01, first: 02) // to lower annulus boundary roles (first: 01, first: 02) is the // identity. // SO: Here comes the mapping from fibre/base curves (f0, o0) // to lower annulus boundary roles (first: 01, first: 02): NMatrix2 curvesToLowerAnnulus = layerLower.boundaryReln() * bundle.bdryReln(1).inverse() * bundle.parallelReln() * bundle.bdryReln(0) * layerUpper.boundaryReln().inverse() * upperAnnulusToUpperLayer * curvesToUpperAnnulus; // Now let's work out the mapping from fibre/base curves (f1, o1) // to bdryAnnulus roles (first: 01, first: 02). This is // rather simpler. NMatrix2 curvesToBdryAnnulus(bdryRefVert ? 1 : -1, 0, 0, bdryRefHoriz ? -1 : 1); // Finally, we already know how the two annuli are joined // together -- we worked this out earlier as upperRolesToLower. // Note that curvesToBdryAnnulus is self-inverse, so we won't // bother inverting it even though we should. NPluggedTorusBundle* ans = new NPluggedTorusBundle(bundle, *it, region, curvesToBdryAnnulus * upperRolesToLower.inverse() * curvesToLowerAnnulus); // Before we head home, delete the remaining isomorphisms // that we never looked at. for (it++; it != isos.end(); it++) delete *it; return ans; } // No match. Delete this isomorphism; we won't need it any more. delete *it; continue; } // Nothing found. return 0; } } // namespace regina regina-4.95/engine/subcomplex/npluggedtorusbundle.h000644 000765 000024 00000032136 12234011536 022504 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/npluggedtorusbundle.h * \brief Supports self-identified Seifert fibred spaces that are * triangulated using a combination of thin I-bundles and saturated blocks. */ #ifndef __NPLUGGEDTORUSBUNDLE_H #ifndef __DOXYGEN #define __NPLUGGEDTORUSBUNDLE_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "subcomplex/nstandardtri.h" namespace regina { class NIsomorphism; class NSatRegion; class NTxICore; /** * \weakgroup subcomplex * @{ */ /** * Describes a triangulation of a graph manifold formed by joining a * bounded saturated region with a thin I-bundle over the torus, * possibly with layerings in between. * * The thin I-bundle must be untwisted, so that it forms the product * T x I with two boundary tori. Moreover, it must be isomorphic * to some existing instance of the class NTxICore. * * The saturated region is described by an object of the class NSatRegion. * This region must have precisely two boundary annuli. These may be * two separate torus boundaries (each formed from its own saturated annulus). * Alternatively, the saturated region may have a single boundary formed * from both saturated annuli, where this boundary is pinched together * so that each annulus becomes its own two-sided torus. * * Either way, the saturated region effectively has two torus boundaries, * each formed from two triangles of the triangulation. These boundaries * are then joined to the two torus boundaries of the thin I-bundle, * possibly with layerings in between (for more detail on layerings, see * the NLayering class). This is illustrated in the following diagram, * where the small tunnels show where the torus boundaries are joined * (possibly via layerings). * *
 *    /--------------------\     /-----------------\
 *    |                     -----                  |
 *    |                     -----                  |
 *    |  Saturated region  |     |  Thin I-bundle  |
 *    |                     -----                  |
 *    |                     -----                  |
 *    \--------------------/     \-----------------/
 * 
* * The effect of the thin I-bundle and the two layerings is essentially * to join the two boundaries of the saturated region according to some * non-trivial homeomorphism of the torus. This homeomorphism is * specified by a 2-by-2 matrix \a M as follows. * * Suppose that \a f0 and \a o0 are directed curves on the first * boundary torus and \a f1 and \a o1 are directed curves on the second * boundary torus, where \a f0 and \a f1 represent the fibres of the * saturated region and \a o0 and \a o1 represent the base orbifold (see * the page on \ref sfsnotation for terminology). * Then the torus boundaries of the saturated region are identified by * the thin I-bundle and layerings according to the following relation: * *
 *     [f1]       [f0]
 *     [  ] = M * [  ]
 *     [o1]       [o0]
 * 
* * Note that the routines writeName() and writeTeXName() do \e not offer * enough information to uniquely identify the triangulation, since this * essentially requires 2-dimensional assemblings of saturated blocks. * For more detail, writeTextLong() may be used instead. * * The optional NStandardTriangulation routine getManifold() is * implemented for this class, but getHomologyH1() is not. */ class REGINA_API NPluggedTorusBundle : public NStandardTriangulation { private: const NTxICore& bundle_; /**< The thin I-bundle that appears within this triangulation. This thin I-bundle is referenced from elsewhere (i.e., it is not owned by this object), and its tetrahedra do not belong to this triangulation (instead see the data member \a bundleIso_). */ NIsomorphism* bundleIso_; /**< A mapping from the thin I-bundle \a bundle_ to this triangulation. This is required since the thin I-bundle \a bundle_ is external, and does not refer directly to this triangulation. */ NSatRegion* region_; /**< The saturated region that appears within this triangulation. This region is owned by this object, and refers to tetrahedra within this triangulation. */ NMatrix2 matchingReln_; /**< Describes how the two torus boundaries of the saturated region are joined, as discussed in the class notes above. */ public: /** * Destroys this structure and its constituent components. * * As an exception, the thin I-bundle is not destroyed, since * it is assumed that this is referenced from elsewhere. */ ~NPluggedTorusBundle(); /** * Returns an isomorphic copy of the thin I-bundle that forms part * of this triangulation. Like all objects of class NTxICore, the * thin I-bundle that is returned is an external object with its own * separate triangulation of the product T x I. For * information on how the thin I-bundle is embedded within this * triangulation, see the routine bundleIso(). * * @return the an isomorphic copy of the thin I-bundle within * this triangulation. */ const NTxICore& bundle() const; /** * Returns an isomorphism describing how the thin I-bundle forms * a subcomplex of this triangulation. * * The thin I-bundle returned by bundle() does not directly * refer to tetrahedra within this triangulation. Instead it * contains its own isomorphic copy of the thin I-bundle * triangulation (as is usual for objects of class NTxICore). * * The isomorphism returned by this routine is a mapping from * the triangulation bundle().core() to this triangulation, * showing how the thin I-bundle appears as a subcomplex of this * structure. * * @return an isomorphism from the thin I-bundle described * by bundle() to the tetrahedra of this triangulation. */ const NIsomorphism& bundleIso() const; /** * Returns the saturated region that forms part of this triangulation. * The region refers directly to tetrahedra within this triangulation * (as opposed to the thin I-bundle, which refers to a separate * external triangulation). * * @return the saturated region. */ const NSatRegion& region() const; /** * Returns the matrix describing how the two torus boundaries of * the saturated region are joined by the thin I-bundle and * layerings. See the class notes above for details. * * @return the matching relation between the two region boundaries. */ const NMatrix2& matchingReln() const; NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; /** * Determines if the given triangulation is a saturated region * joined to a thin I-bundle via optional layerings, as described * in the class notes above. * * @param tri the triangulation to examine. * @return a newly created object containing details of the * structure that was found, or \c null if the given * triangulation is not of the form described by this class. */ static NPluggedTorusBundle* isPluggedTorusBundle (NTriangulation* tri); private: /** * Creates a new structure of the form described in the class notes * above, based on the given constituent components. The new object * will take ownership of the given saturated region and isomorphism. * It will not take ownership of the given thin I-bundle. * * Note that the new object must refer to an existing triangulation. * * \warning The thin I-bundle \a bundle must have a lifetime at * least as long as the new object being created, since it will * be referenced directly by this new object. * * @param bundle the thin I-bundle whose isomorphic copy is used * within the triangulation described by the new object. * @param bundleIso the corresponding isomorphism from the given * thin I-bundle to the triangulation described by the new object. * @param region the saturated region used within the new object. * @param matchingReln the matching relation describing how the * two saturated region boundaries are joined by the thin * I-bundle and layerings, as described in the class notes above. */ NPluggedTorusBundle(const NTxICore& bundle, NIsomorphism* bundleIso, NSatRegion* region, const NMatrix2& matchingReln); /** * Determines whether the given triangulation is of the form * described by this class, with the constraint that the * thin I-bundle used within the triangulation must be isomorphic * to the given thin I-bundle. * * This routine is internal to isPluggedTorusBundle(). * * \pre The given triangulation is closed and connected. * * \warning If this routine is successful and a new object is * returned, this new object must not outlive the given thin * I-bundle (since the new object will in fact contain a direct * reference to this thin I-bundle). * * @param tri the triangulation to examine. * @param bundle the thin I-bundle whose isomorphic copy must be * used in the given triangulation. * @return a newly created object containing details of the * structure that was found, or \c null if the given triangulation * is not of the form described by this class using an isomorphic * copy of the given thin I-bundle. */ static NPluggedTorusBundle* hunt(NTriangulation* tri, const NTxICore& bundle); }; /*@}*/ // Inline functions for NPluggedTorusBundle inline NPluggedTorusBundle::NPluggedTorusBundle(const NTxICore& bundle, NIsomorphism* bundleIso, NSatRegion* region, const NMatrix2& matchingReln) : bundle_(bundle), bundleIso_(bundleIso), region_(region), matchingReln_(matchingReln) { } inline const NTxICore& NPluggedTorusBundle::bundle() const { return bundle_; } inline const NIsomorphism& NPluggedTorusBundle::bundleIso() const { return *bundleIso_; } inline const NSatRegion& NPluggedTorusBundle::region() const { return *region_; } inline const NMatrix2& NPluggedTorusBundle::matchingReln() const { return matchingReln_; } } // namespace regina #endif regina-4.95/engine/subcomplex/nplugtrisolidtorus.cpp000644 000765 000024 00000040037 12234011536 022736 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "manifold/nsfs.h" #include "triangulation/ntriangulation.h" #include "subcomplex/nplugtrisolidtorus.h" namespace regina { const int NPlugTriSolidTorus::CHAIN_NONE = 0; const int NPlugTriSolidTorus::CHAIN_MAJOR = 1; const int NPlugTriSolidTorus::CHAIN_MINOR = 3; const int NPlugTriSolidTorus::EQUATOR_MAJOR = 1; const int NPlugTriSolidTorus::EQUATOR_MINOR = 3; NPlugTriSolidTorus::~NPlugTriSolidTorus() { if (core) delete core; for (int i = 0; i < 3; i++) if (chain[i]) delete chain[i]; } NPlugTriSolidTorus* NPlugTriSolidTorus::clone() const { NPlugTriSolidTorus* ans = new NPlugTriSolidTorus(); ans->core = core->clone(); for (int i = 0; i < 3; i++) { if (chain[i]) ans->chain[i] = new NLayeredChain(*chain[i]); ans->chainType[i] = chainType[i]; } ans->equatorType = equatorType; return ans; } std::ostream& NPlugTriSolidTorus::writeName(std::ostream& out) const { long params[3]; int nParams = 0; int i; for (i = 0; i < 3; i++) if (chainType[i] != CHAIN_NONE) { if (chainType[i] == CHAIN_MAJOR) params[nParams++] = chain[i]->getIndex(); else params[nParams++] = -chain[i]->getIndex(); } std::sort(params, params + nParams); out << (equatorType == EQUATOR_MAJOR ? "P(" : "P'("); if (nParams == 0) return out << "0)"; for (i = 0; i < nParams; i++) { if (i > 0) out << ','; out << params[i]; } return out << ')'; } std::ostream& NPlugTriSolidTorus::writeTeXName(std::ostream& out) const { long params[3]; int nParams = 0; int i; for (i = 0; i < 3; i++) if (chainType[i] != CHAIN_NONE) { if (chainType[i] == CHAIN_MAJOR) params[nParams++] = chain[i]->getIndex(); else params[nParams++] = -chain[i]->getIndex(); } std::sort(params, params + nParams); out << (equatorType == EQUATOR_MAJOR ? "P_{" : "P'_{"); if (nParams == 0) return out << "0}"; for (i = 0; i < nParams; i++) { if (i > 0) out << ','; out << params[i]; } return out << '}'; } void NPlugTriSolidTorus::writeTextLong(std::ostream& out) const { out << "Plugged triangular solid torus: "; writeName(out); } NManifold* NPlugTriSolidTorus::getManifold() const { NSFSpace* ans = new NSFSpace(); ans->insertFibre(2, -1); ans->insertFibre(3, 1); long rot = (equatorType == EQUATOR_MAJOR ? 5 : 4); for (int i = 0; i < 3; i++) if (chainType[i] != CHAIN_NONE) { if (chainType[i] == equatorType) rot += chain[i]->getIndex(); else rot -= chain[i]->getIndex(); } if (rot != 0) ans->insertFibre(rot, 1); else { delete ans; return 0; } ans->reduce(); return ans; } NPlugTriSolidTorus* NPlugTriSolidTorus::isPlugTriSolidTorus( NComponent* comp) { // TODO: Each triangular solid torus is tested three times since we // can't call getTetrahedronIndex() from within a component only. // Basic property checks. if ((! comp->isClosed()) || (! comp->isOrientable())) return 0; if (comp->getNumberOfVertices() > 1) return 0; unsigned long nTet = comp->getNumberOfTetrahedra(); if (nTet < 5) return 0; // We have a 1-vertex closed orientable component with at least // 5 tetrahedra. // Hunt for a core. Make sure we find each triangular solid torus // just once. unsigned long tetIndex; int coreIndex; NTriSolidTorus* core; NTetrahedron* coreTet[3]; NEdge* axis[3]; NPerm4 coreRoles[3]; NTetrahedron* base[2]; NPerm4 baseRoles[2]; int i, j; bool error; NTetrahedron* plugTet[3][2]; NPerm4 plugRoles[3][2]; NPerm4 realPlugRoles[2]; NLayeredChain* chain[3]; int chainType[3]; int equatorType = 0; chain[0] = chain[1] = chain[2] = 0; for (tetIndex = 0; tetIndex < nTet - 2; tetIndex++) for (coreIndex = 0; coreIndex < 24; coreIndex++) { coreRoles[0] = NPerm4::S4[coreIndex]; if (coreRoles[0][0] > coreRoles[0][3]) continue; core = NTriSolidTorus::formsTriSolidTorus( comp->getTetrahedron(tetIndex), coreRoles[0]); if (! core) continue; for (i = 0; i < 3; i++) { coreTet[i] = core->getTetrahedron(i); coreRoles[i] = core->getVertexRoles(i); axis[i] = coreTet[i]->getEdge( NEdge::edgeNumber[coreRoles[i][0]][coreRoles[i][3]]); } if (axis[0] == axis[1] || axis[1] == axis[2] || axis[2] == axis[0]) { delete core; continue; } // We have the triangular solid torus and we know the three // axis edges are distinct. // Hunt for chains. for (i = 0; i < 3; i++) { base[0] = coreTet[(i + 1) % 3]->adjacentTetrahedron( coreRoles[(i + 1) % 3][2]); base[1] = coreTet[(i + 2) % 3]->adjacentTetrahedron( coreRoles[(i + 2) % 3][1]); if (base[0] != base[1]) { // No chain. chainType[i] = CHAIN_NONE; continue; } // Have we layered over the major axis? baseRoles[0] = coreTet[(i + 1) % 3]-> adjacentGluing(coreRoles[(i + 1) % 3][2]) * coreRoles[(i + 1) % 3] * NPerm4(0, 3, 2, 1); baseRoles[1] = coreTet[(i + 2) % 3]-> adjacentGluing(coreRoles[(i + 2) % 3][1]) * coreRoles[(i + 2) % 3] * NPerm4(2, 1, 0, 3); if (baseRoles[0] == baseRoles[1]) { chainType[i] = CHAIN_MAJOR; chain[i] = new NLayeredChain(base[0], baseRoles[0]); while (chain[i]->extendAbove()) ; continue; } // Have we layered over the minor axis? baseRoles[0] = coreTet[(i + 1) % 3]-> adjacentGluing(coreRoles[(i + 1) % 3][2]) * coreRoles[(i + 1) % 3] * NPerm4(3, 0, 2, 1); baseRoles[1] = coreTet[(i + 2) % 3]-> adjacentGluing(coreRoles[(i + 2) % 3][1]) * coreRoles[(i + 2) % 3] * NPerm4(2, 1, 3, 0); if (baseRoles[0] == baseRoles[1]) { chainType[i] = CHAIN_MINOR; chain[i] = new NLayeredChain(base[0], baseRoles[0]); while (chain[i]->extendAbove()) ; continue; } // It's not a chain but it can't be a plug either. // We'll notice the error because i will be less than 3. break; } // Check whether we broke out of the previous loop with an error. // Check also whether one of the chains is another in // reverse, and that we've found the correct number of // tetrahedra in total. error = false; if (i < 3) error = true; else if (chain[0] && chain[1] && chain[0]->getBottom() == chain[1]->getTop()) error = true; else if (chain[1] && chain[2] && chain[1]->getBottom() == chain[2]->getTop()) error = true; else if (chain[2] && chain[0] && chain[2]->getBottom() == chain[0]->getTop()) error = true; else if ((chain[0] ? chain[0]->getIndex() : 0) + (chain[1] ? chain[1]->getIndex() : 0) + (chain[2] ? chain[2]->getIndex() : 0) + 5 != nTet) error = true; if (error) { for (j = 0; j < 3; j++) if (chain[j]) { delete chain[j]; chain[j] = 0; } delete core; continue; } // Still hanging in. // We know there's only 2 tetrahedra left. // Now we need to check the plug. error = false; for (i = 0; i < 3; i++) { if (chain[i]) { plugTet[i][0] = chain[i]->getTop()->adjacentTetrahedron( chain[i]->getTopVertexRoles()[3]); plugTet[i][1] = chain[i]->getTop()->adjacentTetrahedron( chain[i]->getTopVertexRoles()[0]); plugRoles[i][0] = chain[i]->getTop()-> adjacentGluing(chain[i]-> getTopVertexRoles()[3]) * chain[i]->getTopVertexRoles() * (chainType[i] == CHAIN_MAJOR ? NPerm4(0, 1, 2, 3) : NPerm4(1, 0, 2, 3)); plugRoles[i][1] = chain[i]->getTop()-> adjacentGluing(chain[i]-> getTopVertexRoles()[0]) * chain[i]->getTopVertexRoles() * (chainType[i] == CHAIN_MAJOR ? NPerm4(2, 3, 1, 0) : NPerm4(3, 2, 1, 0)); } else { plugTet[i][0] = coreTet[(i + 1) % 3]-> adjacentTetrahedron(coreRoles[(i + 1) % 3][2]); plugTet[i][1] = coreTet[(i + 2) % 3]-> adjacentTetrahedron(coreRoles[(i + 2) % 3][1]); plugRoles[i][0] = coreTet[(i + 1) % 3]-> adjacentGluing(coreRoles[(i + 1) % 3][2]) * coreRoles[(i + 1) % 3] * NPerm4(0, 3, 1, 2); plugRoles[i][1] = coreTet[(i + 2) % 3]-> adjacentGluing(coreRoles[(i + 2) % 3][1]) * coreRoles[(i + 2) % 3] * NPerm4(0, 3, 2, 1); } } // Make sure we meet precisely two tetrahedra, three times // each. Note that this implies that the plug tetrahedra are // in fact thus far unseen. for (i = 0; i < 2; i++) if (plugTet[0][i] != plugTet[1][i] || plugTet[1][i] != plugTet[2][i]) { error = true; break; } // Make sure also that the gluing permutations for the plug // are correct. if (! error) { if (plugRoles[0][0][0] == plugRoles[1][0][0] && plugRoles[1][0][0] == plugRoles[2][0][0]) { // Type EQUATOR_MINOR. realPlugRoles[0] = plugRoles[0][0] * NPerm4(3, 2, 1, 0); realPlugRoles[1] = plugRoles[0][1] * NPerm4(3, 0, 2, 1); if (realPlugRoles[0] != plugRoles[1][0] * NPerm4(1, 3, 2, 0)) error = true; else if (realPlugRoles[0] != plugRoles[2][0] * NPerm4(2, 1, 3, 0)) error = true; else if (realPlugRoles[1] != plugRoles[1][1] * NPerm4(2, 3, 0, 1)) error = true; else if (realPlugRoles[1] != plugRoles[2][1] * NPerm4(0, 2, 3, 1)) error = true; else equatorType = EQUATOR_MINOR; } else if (plugRoles[0][0][1] == plugRoles[1][0][1] && plugRoles[1][0][1] == plugRoles[2][0][1]) { // Type EQUATOR_MAJOR. realPlugRoles[0] = plugRoles[0][0] * NPerm4(3, 2, 0, 1); realPlugRoles[1] = plugRoles[0][1] * NPerm4(3, 1, 2, 0); if (realPlugRoles[0] != plugRoles[1][0] * NPerm4(0, 3, 2, 1)) error = true; else if (realPlugRoles[0] != plugRoles[2][0] * NPerm4(2, 0, 3, 1)) error = true; else if (realPlugRoles[1] != plugRoles[1][1] * NPerm4(2, 3, 1, 0)) error = true; else if (realPlugRoles[1] != plugRoles[2][1] * NPerm4(1, 2, 3, 0)) error = true; else equatorType = EQUATOR_MAJOR; } else error = true; } // Finally check the internal triangle of the plug. if (! error) { if (plugTet[0][0]->adjacentTetrahedron(realPlugRoles[0][3]) != plugTet[0][1]) error = true; else if (plugTet[0][0]->adjacentGluing( realPlugRoles[0][3]) * realPlugRoles[0] != realPlugRoles[1]) error = true; } if (error) { for (j = 0; j < 3; j++) if (chain[j]) { delete chain[j]; chain[j] = 0; } delete core; continue; } // Success! NPlugTriSolidTorus* plug = new NPlugTriSolidTorus(); plug->core = core; for (i = 0; i < 3; i++) { plug->chain[i] = chain[i]; plug->chainType[i] = chainType[i]; } plug->equatorType = equatorType; return plug; } // Nothing was found. return 0; } } // namespace regina regina-4.95/engine/subcomplex/nplugtrisolidtorus.h000644 000765 000024 00000025071 12234011536 022404 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nplugtrisolidtorus.h * \brief Deals with plugged triangular solid torus components of a * triangulation. */ #ifndef __NPLUGTRISOLIDTORUS_H #ifndef __DOXYGEN #define __NPLUGTRISOLIDTORUS_H #endif #include "regina-core.h" #include "subcomplex/ntrisolidtorus.h" #include "subcomplex/nlayeredchain.h" namespace regina { class NComponent; /** * \weakgroup subcomplex * @{ */ /** * Represents a plugged triangular solid torus component of a * triangulation. Such a component is obtained as follows. * * Begin with a three-tetrahedron triangular solid torus (as described by * class NTriSolidTorus). Observe that the three axis edges divide the * boundary into three annuli. * * To each of these annuli a layered chain may be optionally attached. * If present, the chain should be attached so its hinge edges are * identified with the axis edges of the corresonding annulus and its bottom * tetrahedron is layered over either the major edge or minor edge of the * corresponding annulus. The top two triangular faces of the layered chain * should remain free. * * Thus we now have three annuli on the boundary, each represented as a * square two of whose (opposite) edges are axis edges of the original * triangular solid torus (and possibly also hinge edges of a layered * chain). * * Create a \e plug by gluing two tetrahedra together along a single * triangle. The six edges that do not run along this common triangle split the * plug boundary into three squares. These three squares must be glued * to the three boundary annuli previously described. Each axis edge meets * two of the annuli; the two corresponding edges of the plug must be * non-adjacent (have no common vertex) on the plug. * In this way each of the six edges of the plug not running along its * interior triangle corresponds to precisely one of the two instances of * precisely one of the three axis edges. * * If the axis edges are directed so that they all point the * same way around the triangular solid torus, these axis edges when * drawn on the plug must all point from one common tip of the plug to * the other (where the \e tips of the plug are the vertices not meeting the * interior triangle). The gluings must also be made so that the resulting * triangulation component is orientable. * * Of the optional NStandardTriangulation routines, getManifold() is * implemented for most plugged triangular solid tori and * getHomologyH1() is not implemented at all. * * \testpart */ class REGINA_API NPlugTriSolidTorus : public NStandardTriangulation { public: static const int CHAIN_NONE; /**< Indicates an annulus on the triangular solid torus boundary with no attached layered chain. */ static const int CHAIN_MAJOR; /**< Indicates an annulus on the triangular solid torus boundary with an attached layered chain layered over the major edge of the annulus. */ static const int CHAIN_MINOR; /**< Indicates an annulus on the triangular solid torus boundary with an attached layered chain layered over the minor edge of the annulus. */ static const int EQUATOR_MAJOR; /**< Indicates that, if no layered chains were present, the equator of the plug would consist of major edges of the core triangular solid torus. */ static const int EQUATOR_MINOR; /**< Indicates that, if no layered chains were present, the equator of the plug would consist of minor edges of the core triangular solid torus. */ private: NTriSolidTorus* core; /**< The triangular solid torus at the core of this triangulation. */ NLayeredChain* chain[3]; /**< The layered chains attached to the annuli on the triangular solid torus, or 0 for those annuli without attached layered chains. */ int chainType[3]; /**< The way in which the layered chain is attached to each annulus on the triangular solid torus, or \a CHAIN_NONE for those annuli without attached layered chains. */ int equatorType; /**< Indicates which types of edges form the equator of the plug. */ public: /** * Destroys this plugged solid torus; note that the corresponding * triangular solid torus and layered chains will also be destroyed. */ virtual ~NPlugTriSolidTorus(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NPlugTriSolidTorus* clone() const; /** * Returns the triangular solid torus at the core of this * triangulation. * * @return the core triangular solid torus. */ const NTriSolidTorus& getCore() const; /** * Returns the layered chain attached to the requested * annulus on the boundary of the core triangular solid torus. * If there is no attached layered chain, \c null will be returned. * * Note that the core triangular solid torus will be attached to * the bottom (as opposed to the top) of the layered chain. * * @param annulus specifies which annulus to examine; this must * be 0, 1 or 2. * @return the corresponding layered chain. */ const NLayeredChain* getChain(int annulus) const; /** * Returns the way in which a layered chain is attached to the * requested annulus on the boundary of the core triangular solid * torus. This will be one of the chain type constants defined * in this class. * * @param annulus specifies which annulus to examine; this must * be 0, 1 or 2. * @return the type of layered chain, or \a CHAIN_NONE * if there is no layered chain attached to the requested annulus. */ int getChainType(int annulus) const; /** * Returns which types of edges form the equator of the plug. * In the absence of layered chains these will either all be major * edges or all be minor edges. * * Layered chains complicate matters, but the roles that the major * and minor edges play on the boundary annuli of the triangular * solid torus can be carried up to the annuli at the top of each * layered chain; the edges filling the corresponding major or * minor roles will then form the equator of the plug. * * @return the types of edges that form the equator of the plug; * this will be one of the equator type constants defined in this * class. */ int getEquatorType() const; /** * Determines if the given triangulation component is a * plugged triangular solid torus. * * @param comp the triangulation component to examine. * @return a newly created structure containing details of the * plugged triangular solid torus, or \c null if the given * component is not a plugged triangular solid torus. */ static NPlugTriSolidTorus* isPlugTriSolidTorus(NComponent* comp); NManifold* getManifold() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new structure with all subcomponent pointers * initialised to \c null. */ NPlugTriSolidTorus(); }; /*@}*/ // Inline functions for NPlugTriSolidTorus inline NPlugTriSolidTorus::NPlugTriSolidTorus() : core(0) { chain[0] = chain[1] = chain[2] = 0; chainType[0] = chainType[1] = chainType[2] = CHAIN_NONE; } inline const NTriSolidTorus& NPlugTriSolidTorus::getCore() const { return *core; } inline const NLayeredChain* NPlugTriSolidTorus::getChain(int annulus) const { return chain[annulus]; } inline int NPlugTriSolidTorus::getChainType(int annulus) const { return chainType[annulus]; } inline int NPlugTriSolidTorus::getEquatorType() const { return equatorType; } } // namespace regina #endif regina-4.95/engine/subcomplex/nsatannulus.cpp000644 000765 000024 00000027421 12234011536 021317 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nmatrix2.h" #include "subcomplex/nsatannulus.h" #include "triangulation/nedge.h" #include "triangulation/nisomorphism.h" #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangulation.h" namespace regina { unsigned NSatAnnulus::meetsBoundary() const { unsigned ans = 0; if (! tet[0]->adjacentTetrahedron(roles[0][3])) ans++; if (! tet[1]->adjacentTetrahedron(roles[1][3])) ans++; return ans; } void NSatAnnulus::switchSides() { unsigned which, face; for (which = 0; which < 2; which++) { face = roles[which][3]; roles[which] = tet[which]->adjacentGluing(face) * roles[which]; tet[which] = tet[which]->adjacentTetrahedron(face); } } bool NSatAnnulus::isAdjacent(const NSatAnnulus& other, bool* refVert, bool* refHoriz) const { if (other.meetsBoundary()) return false; // See what is actually attached to the given annulus. NSatAnnulus opposite(other); opposite.switchSides(); if (opposite.tet[0] == tet[0] && opposite.tet[1] == tet[1]) { // Could be a match without horizontal reflection. if (opposite.roles[0] == roles[0] && opposite.roles[1] == roles[1]) { // Perfect match. if (refVert) *refVert = false; if (refHoriz) *refHoriz = false; return true; } if (opposite.roles[0] == roles[0] * NPerm4(0, 1) && opposite.roles[1] == roles[1] * NPerm4(0, 1)) { // Match with vertical reflection. if (refVert) *refVert = true; if (refHoriz) *refHoriz = false; return true; } } if (opposite.tet[0] == tet[1] && opposite.tet[1] == tet[0]) { // Could be a match with horizontal reflection. if (opposite.roles[0] == roles[1] * NPerm4(0, 1) && opposite.roles[1] == roles[0] * NPerm4(0, 1)) { // Match with horizontal reflection. if (refVert) *refVert = false; if (refHoriz) *refHoriz = true; return true; } if (opposite.roles[0] == roles[1] && opposite.roles[1] == roles[0]) { // Match with both reflections. if (refVert) *refVert = true; if (refHoriz) *refHoriz = true; return true; } } // No match. return false; } bool NSatAnnulus::isJoined(const NSatAnnulus& other, NMatrix2& matching) const { if (other.meetsBoundary()) return false; // See what is actually attached to the given annulus. NSatAnnulus opposite(other); opposite.switchSides(); bool swapTriangles; NPerm4 roleMap; // Maps this 0/1/2 roles -> opposite 0/1/2 roles. if (opposite.tet[0] == tet[0] && opposite.tet[1] == tet[1] && opposite.roles[0][3] == roles[0][3] && opposite.roles[1][3] == roles[1][3]) { swapTriangles = false; roleMap = opposite.roles[0].inverse() * roles[0]; if (roleMap != opposite.roles[1].inverse() * roles[1]) return false; } else if (opposite.tet[0] == tet[1] && opposite.tet[1] == tet[0] && opposite.roles[0][3] == roles[1][3] && opposite.roles[1][3] == roles[0][3]) { swapTriangles = true; roleMap = opposite.roles[1].inverse() * roles[0]; if (roleMap != opposite.roles[0].inverse() * roles[1]) return false; } else return false; // It's a match. We just need to work out the matching matrix. if (roleMap == NPerm4(0, 1, 2, 3)) { matching = NMatrix2(1, 0, 0, 1); } else if (roleMap == NPerm4(1, 2, 0, 3)) { matching = NMatrix2(-1, 1, -1, 0); } else if (roleMap == NPerm4(2, 0, 1, 3)) { matching = NMatrix2(0, -1, 1, -1); } else if (roleMap == NPerm4(0, 2, 1, 3)) { matching = NMatrix2(0, 1, 1, 0); } else if (roleMap == NPerm4(2, 1, 0, 3)) { matching = NMatrix2(1, -1, 0, -1); } else if (roleMap == NPerm4(1, 0, 2, 3)) { matching = NMatrix2(-1, 0, -1, 1); } if (swapTriangles) matching.negate(); return true; } bool NSatAnnulus::isTwoSidedTorus() const { // Check that the edges are identified in opposite pairs and that we // have no duplicates. NEdge* e01 = tet[0]->getEdge(NEdge::edgeNumber[roles[0][0]][roles[0][1]]); NEdge* e02 = tet[0]->getEdge(NEdge::edgeNumber[roles[0][0]][roles[0][2]]); NEdge* e12 = tet[0]->getEdge(NEdge::edgeNumber[roles[0][1]][roles[0][2]]); if (e01 != tet[1]->getEdge(NEdge::edgeNumber[roles[1][0]][roles[1][1]])) return false; if (e02 != tet[1]->getEdge(NEdge::edgeNumber[roles[1][0]][roles[1][2]])) return false; if (e12 != tet[1]->getEdge(NEdge::edgeNumber[roles[1][1]][roles[1][2]])) return false; if (e01 == e02 || e02 == e12 || e12 == e01) return false; // Verify that edges are consistently oriented, and that the // orientations of the edge links indicate a two-sided torus. NPerm4 map0, map1; int a, b, x, y; for (int i = 0; i < 3; i++) { // Examine edges corresponding to annulus markings a & b. // We also set x & y as the complement of {a,b} in {0,1,2,3}. switch (i) { case 0: a = 0; b = 1; x = 2; y = 3; break; case 1: a = 0; b = 2; x = 1; y = 3; break; case 2: a = 1; b = 2; x = 0; y = 3; break; } // Get mappings from tetrahedron edge roles to annulus vertex roles. map0 = roles[0].inverse() * tet[0]->getEdgeMapping( NEdge::edgeNumber[roles[0][a]][roles[0][b]]); map1 = roles[1].inverse() * tet[1]->getEdgeMapping( NEdge::edgeNumber[roles[1][a]][roles[1][b]]); // We should have {a,b} -> {a,b} and {x,y} -> {x,y} for each map. // Make sure that the two annulus edges are oriented in the same way // (i.e., (a,b) <-> (b,a)), and that the edge link runs in opposite // directions through the annulus on each side (i.e., (x,y) <-> (y,x)). if (map0 != NPerm4(a, b) * NPerm4(x, y) * map1) return false; } // No unpleasantries. return true; } void NSatAnnulus::transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) { unsigned which; unsigned long tetID; for (which = 0; which < 2; which++) { tetID = originalTri->tetrahedronIndex(tet[which]); tet[which] = newTri->getTetrahedron(iso->tetImage(tetID)); roles[which] = iso->facePerm(tetID) * roles[which]; } } void NSatAnnulus::attachLST(NTriangulation* tri, long alpha, long beta) const { // Save ourselves headaches later. Though this should never happen; // see the preconditions. if (alpha == 0) return; // Normalise to alpha positive. if (alpha < 0) { alpha = -alpha; beta = -beta; } // Pull out the degenerate case. if (alpha == 2 && beta == 1) { tet[0]->joinTo(roles[0][3], tet[1], roles[1] * NPerm4(0, 1) * roles[0].inverse()); return; } // Insert a real layered solid torus. How we do this depends on // relative signs and orderings. long diag = alpha - beta; // Our six possibilities are: // // 0 <= -diag < alpha <= beta: // 0 < alpha <= -diag < beta: // 0 < diag <= beta < alpha: // 0 <= beta < diag <= alpha: // 0 < -beta <= alpha < diag // 0 < alpha < -beta < diag // We can give the vertices of the tetrahedra "cut labels" as // follows (where the LST has parameters 0 <= cuts0 <= cuts1 <= cuts2): // // cuts0 // *-------* // |2 1 / | // | / 0| // cuts1 | / | cuts1 // |0 / | // | / 1 2| // *-------* // cuts0 long cuts0, cuts1; NPerm4 cutsToRoles; // Maps cut labels to annulus vertex roles. if (alpha <= beta) { if (-diag < alpha) { // 0 <= -diag < alpha <= beta: cuts0 = -diag; cuts1 = alpha; cutsToRoles = NPerm4(0, 2, 1, 3); } else { // 0 < alpha <= -diag < beta: cuts0 = alpha; cuts1 = -diag; cutsToRoles = NPerm4(2, 0, 1, 3); } } else if (0 <= beta) { if (diag <= beta) { // 0 < diag <= beta < alpha: cuts0 = diag; cuts1 = beta; cutsToRoles = NPerm4(0, 1, 2, 3); } else { // 0 <= beta < diag <= alpha: cuts0 = beta; cuts1 = diag; cutsToRoles = NPerm4(1, 0, 2, 3); } } else { if (-beta <= alpha) { // 0 < -beta <= alpha < diag cuts0 = -beta; cuts1 = alpha; cutsToRoles = NPerm4(1, 2, 0, 3); } else { // 0 < alpha < -beta < diag cuts0 = alpha; cuts1 = -beta; cutsToRoles = NPerm4(2, 1, 0, 3); } } NTetrahedron* lst = tri->insertLayeredSolidTorus(cuts0, cuts1); // The boundary of the new LST sits differently for the special // cases (0,1,1) and (1,1,2); see the insertLayeredSolidTorus() // documentation for details. if (cuts1 == 1) { lst->joinTo(3, tet[0], roles[0] * cutsToRoles * NPerm4(1, 2, 0, 3)); lst->joinTo(2, tet[1], roles[1] * cutsToRoles * NPerm4(2, 1, 3, 0)); } else { lst->joinTo(3, tet[0], roles[0] * cutsToRoles * NPerm4(0, 1, 2, 3)); lst->joinTo(2, tet[1], roles[1] * cutsToRoles * NPerm4(1, 0, 3, 2)); } } } // namespace regina regina-4.95/engine/subcomplex/nsatannulus.h000644 000765 000024 00000053571 12234011536 020771 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsatannulus.h * \brief Deals with saturated two-triangle annuli within a * Seifert fibred space. */ #ifndef __NSATANNULUS_H #ifndef __DOXYGEN #define __NSATANNULUS_H #endif #include "regina-core.h" #include "maths/nperm4.h" namespace regina { class NIsomorphism; class NMatrix2; class NTetrahedron; class NTriangulation; /** * \weakgroup subcomplex * @{ */ /** * Represents an annulus formed from a pair of triangles in a Seifert fibred * space. This annulus is saturated, i.e., a union of fibres. More than * that, the fibres run parallel to the two boundary edges of the annulus. * * The annulus is described from one side only. The description * includes an array of indices \a tet[] describing which two tetrahedra * provide the triangles of the annulus, as well as an array of permutations * \a roles[] detailing how the annulus matches up with the individual * tetrahedron vertices. * * The annulus can be drawn as follows, with the upper edge identified * with the lower: * *
 *            *--->---*
 *            |0  2 / |
 *    First   |    / 1|  Second
 *   triangle |   /   | triangle
 *            |1 /    |
 *            | / 2  0|
 *            *--->---*
 * 
* * Suppose that \a tet[0] and \a tet[1] are the tetrahedra providing the * first and second triangles respectively. Then the markings 0..2 on the * first triangle above correspond to vertices \a roles[0][0..2] of tetrahedron * \a tet[0], and likewise the markings 0..2 on the second triangle above * correspond to vertices \a roles[1][0..2] of tetrahedron \a tet[1]. * * Note that the diagram above can also be drawn as follows. * *
 *            *--->---*
 *            | \ 2  1|
 *    First   |0 \    |  Second
 *   triangle |   \   | triangle
 *            |    \ 0|
 *            |1  2 \ |
 *            *--->---*
 * 
* * Note also that the labelling of the tetrahedra and their vertices * establishes an orientation on the vertical fibres, as well as a * left-to-right direction across the annulus. * * For convenience we refer to edges \a roles[][0-1] as \e vertical, * edges \a roles[][0-2] as \e horizontal, and edge \a roles[][1-2] as * \e diagonal. This is illustrated in the following diagrams. * *
 *         V  Horizontal       V   Diagonal
 *         e  *--->---*        e  *--->---*
 *         r  |   g / |        r  |H\ 2  1|
 *         t  |  a / 1|        t  | o\    |
 *         i  | i /   |        i  |  r\   |
 *         c  |D /    |        c  |   i\ 0|
 *         a  | / 2  0|        a  |    z\ |
 *         l  *--->---*        l  *--->---*
 * 
* * \ifacespython The member arrays \a tet and \a roles are accessed for * reading through functions \a tet() and \a roles() respectively. For * instance, the first triangle tetrahedron for the saturated annulus \a a can * be accessed as a.tet(0). These same member arrays are * accessed for writing through functions \a setTet() and \a setRoles(), * so for instance the second triangle vertex roles for the saturated annulus * \a a can be modified by calling a.setRoles(1, newRoles). */ struct REGINA_API NSatAnnulus { NTetrahedron* tet[2]; /**< Describes which tetrahedra provide the first and second triangles. See the class notes for details. */ NPerm4 roles[2]; /**< Describes how the first and second triangles match up with individual tetrahedron vertices. See the class notes for details. */ /** * Creates a new uninitialised structure. Both tetrahedra will be * set to null pointers. */ NSatAnnulus(); /** * Creates a clone of the given structure. * * @param cloneMe the structure to clone. */ NSatAnnulus(const NSatAnnulus& cloneMe); /** * Creates a new structure initialised to the given values. See the * class notes for what the various tetrahedra and permutations mean. * * @param t0 the tetrahedron to assign to \a tet[0]. * @param r0 the permutation to assign to \a roles[0]. * @param t1 the tetrahedron to assign to \a tet[1]. * @param r1 the permutation to assign to \a roles[1]. */ NSatAnnulus(NTetrahedron* t0, NPerm4 r0, NTetrahedron* t1, NPerm4 r1); /** * Makes this equal to a clone of the given structure. * * @param cloneMe the structure to clone. * @return a reference to this structure. */ NSatAnnulus& operator = (const NSatAnnulus& cloneMe); /** * Determines whether or not this and the given structure describe * the same annulus with the same representation. This requires * both structures to have identical \a tet[] and \a roles[] arrays. * * @param other the structure to compare with this. * @return \c true if the structures describe the same annulus with * the same representation, or \c false if they do not. */ bool operator == (const NSatAnnulus& other) const; /** * Determines whether or not this and the given structure describe * the same annulus with the same representation. This requires * both structures to have identical \a tet[] and \a roles[] arrays. * * @param other the structure to compare with this. * @return \c true if the structures do not describe the same annulus * with the same representation, or \c false if they do. */ bool operator != (const NSatAnnulus& other) const; /** * Determines how many triangles of this annulus lie on the boundary * of the triangulation. * * Note that this routine can also be used as a boolean function * to determine whether any triangles of the annulus lie on the * triangulation boundary. * * @return the number of triangles of this annulus that lie on the boundary * of the triangulation; this will be 0, 1 or 2. */ unsigned meetsBoundary() const; /** * Converts this into a representation of the same annulus from the * other side. The first and second triangles and their 0..2 markings * (as described in the class notes) remain unchanged. However, the * two tetrahedra that are used to describe the annulus will be * replaced by their counterparts on the other side of the annulus * (i.e., the two new tetrahedra that meet the two original tetrahedra * along the annulus itself). * * \pre Neither triangle of this annulus is a boundary triangle of the * triangulation. */ void switchSides(); /** * Returns a representation of the same annulus from the other side. * This structure will not be changed. See switchSides() for further * details. * * \pre Neither triangle of this annulus is a boundary triangle of the * triangulation. * * @return a new representation of this annulus from the other side. */ NSatAnnulus otherSide() const; /** * Reverses the direction of the vertical fibres in this annulus * representation. The first and second triangles (as described in the * class notes) will remain unchanged, but the markings 0 and 1 on * each triangle will be switched. */ void reflectVertical(); /** * Returns a representation of this annulus in which the vertical * direction of the fibres has been reversed. This structure will * not be changed. See reflectVertical() for further details. * * @return a new representation of this annulus in which fibres have * been reversed. */ NSatAnnulus verticalReflection() const; /** * Performs a left-to-right reflection of this annulus * representation. The vertical direction of the fibres will remain * unchanged, but the first and second triangles will be switched (and * the 0..2 markings changed to compensate). */ void reflectHorizontal(); /** * Returns a left-to-right reflected representation of this annulus. * This structure will not be changed. See reflectHorizontal() for * further details. * * @return a new left-to-right reflection of this annulus. */ NSatAnnulus horizontalReflection() const; /** * Rotates the representation of this annulus by 180 degrees. * This has the effect of switching the first and second triangles and * also reversing the direction of the vertical fibres. * * Calling this routine is equivalent to calling reflectVertical() and * then reflectHorizontal(). */ void rotateHalfTurn(); /** * Returns a 180 degree rotated representation of this annulus. * This structure will not be changed. See rotateHalfTurn() for * further details. * * @return a new 180 degree rotation of this annulus. */ NSatAnnulus halfTurnRotation() const; /** * Determines whether this and the given annulus are adjacent, * possibly modulo vertical or horizontal reflections. That is, * this routine determines whether this and the given structure * represent opposite sides of the same saturated annulus, where the * fibres for both structures are consistent (though possibly reversed). * See switchSides() for details on what "opposite sides" means in * this context, and see reflectVertical() and reflectHorizontal() * for descriptions of the various types of reflection. * * Information regarding reflections is returned via the two boolean * pointers \a refVert and \a refHoriz. If the two annuli are * identically opposite each other as described by switchSides(), * both booleans will be set to \c false. If the two annuli are * identically opposite after one undergoes a vertical and/or * horizontal reflection, then the booleans \a refVert and/or * \a refHoriz will be set to \c true accordingly. * * The critical difference between this routine and isJoined() is * that this routine insists that the fibres on each annulus be * consistent. This routine is thus suitable for examining joins * between different sections of the same Seifert fibred space, * for example. * * \ifacespython This routine only takes a single argument (the * annulus \a other). The return value is a tuple of three * booleans: the usual return value, the value returned in \a refVert, * and the value returned in \a refHoriz. * * @param other the annulus to compare with this. * @param refVert returns information on whether the annuli are * adjacent modulo a vertical reflection. This is set to \c true * if a vertical reflection is required and \c false if it is not. * If no adjacency was found at all, this boolean is not touched. * A null pointer may be passed, in which case this information will * not be returned at all. * @param refHoriz returns information on whether the annuli are * adjacent modulo a horizontal reflection. This is set to \c true * if a horizontal reflection is required and \c false if it is not. * If no adjacency was found at all, this boolean is not touched. * A null pointer may be passed, in which case this information will * not be returned at all. * @return \c true if some adjacency was found (either with or * without reflections), or \c false if no adjacency was found at all. */ bool isAdjacent(const NSatAnnulus& other, bool* refVert, bool* refHoriz) const; /** * Determines whether this and the given annulus are joined in some * form, even if the fibres on each annulus are not consistent. * * This routine treats each annulus as though its boundaries are * identified to form a torus (though it does not actually test * whether this is true). It then examines whether this and the * given annulus represent opposite sides of the same torus. * More specifically, it tests whether both annuli are formed from * the same pair of triangles, and whether the mapping of 0/1/2 markings * from one annulus to the other is the same for each triangle. Note that * the triangles are allowed to be switched (i.e., the first triangle of one * annulus may be the second triangle of the other). * * The critical difference between this routine and isAdjacent() is * that this routine allows the fibres on each annulus to be * inconsistent. This routine is thus suitable for examining joins * between different Seifert fibred blocks in a graph manifold, for * example. * * If the two annuli are joined, the precise relationship between * the curves on each annulus will be returned in the matrix * \a matching. Specifically, let \a x and \a y be the oriented * curves running from markings 0-1 and 0-2 respectively on the * first triangle of this annulus. Likewise, let \a x' and \a y' run * from markings 0-1 and 0-2 respectively on the first triangle of the * annulus \a other. Then the joining between the two annuli can * be expressed as follows: * *
     *     [x ]                [x']
     *     [  ]  =  matching * [  ].
     *     [y ]                [y']
     * 
* * @param other the annulus to compare with this. * @param matching returns details on how the curves on each annulus * are related. If the this and the given annulus are not joined, * then this matrix is not touched. * @return \c true if this and the given annulus are found to be * joined, or \c false if they are not. */ bool isJoined(const NSatAnnulus& other, NMatrix2& matching) const; /** * Determines whether this annulus has its boundaries identified to * form an embedded two-sided torus within the surrounding triangulation. * * It will be verified that: * - the two triangles of this annulus are joined along all three pairs * of edges to form a torus; * - the three edges of this torus remain distinct (i.e., different edges * of the torus do not become identified within the larger triangulation); * - this torus is two-sided within the surrounding triangulation. * * @return \c true if this annulus forms an embedded two-sided torus as * described above, or \c false if it does not. */ bool isTwoSidedTorus() const; /** * Adjusts this annulus representation according to the given * isomorphism between triangulations. * * The given isomorphism must describe a mapping from \a originalTri * to \a newTri, and this annulus must refer to tetrahedra in * \a originalTri. This routine will adjust this annulus according * to the given isomorphism, so that it refers to the corresponding * tetrahedra in \a newTri (with the \a roles permutations also * updated accordingly). * * \pre This annulus refers to tetrahedra in \a originalTri, and * \a iso describes a mapping from \a originalTri to \a newTri. * * @param originalTri the triangulation currently used by this * annulus representation. * @param iso the mapping from \a originalTri to \a newTri. * @param newTri the triangulation to be used by the updated annulus * representation. */ void transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri); /** * Returns the image of this annulus representation under the given * isomorphism between triangulations. This annulus representation * will not be changed. See transform() for further details. * * @param originalTri the triangulation currently used by this * annulus representation. * @param iso the mapping from \a originalTri to \a newTri. * @param newTri the triangulation to be used by the new annulus * representation. */ NSatAnnulus image(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) const; /** * Attaches a layered solid torus to the this saturated annulus. * * The layered solid torus will be attached so that the * given values \a alpha and \a beta describe how the * meridinal disc cuts the vertical and horizontal edges of the * annulus respectively. * * The result will effectively insert an (\a alpha, \a beta) * exceptional fibre into the Seifert fibred space space, where * the vertical edges run parallel to the fibres and the horizontal * edges represent the base orbifold. The sign of the fibre is * consistent with the fibre inserted by NSatLST::adjustSFS() * (in particular, negating \a beta will negate the fibre). * * In the case of a (2,1) fibre, the layered solid torus will be * degenerate (i.e., the two triangles of the annulus will simply be * joined together). * * \pre The given value \a alpha is not zero. * \pre The given values \a alpha and \a beta are coprime. * * @param tri the triangulation into which the new tetrahedra should * be inserted. * @param alpha describes how the meridinal disc of the torus should * cut the vertical edges. This may be positive or negative. * @param beta describes how the meridinal disc of the torus should * cut the horizontal edges. Again this may be positive or negative. */ void attachLST(NTriangulation* tri, long alpha, long beta) const; }; /*@}*/ // Inline functions for NSatAnnulus inline NSatAnnulus::NSatAnnulus() { tet[0] = tet[1] = 0; } inline NSatAnnulus::NSatAnnulus(const NSatAnnulus& cloneMe) { tet[0] = cloneMe.tet[0]; tet[1] = cloneMe.tet[1]; roles[0] = cloneMe.roles[0]; roles[1] = cloneMe.roles[1]; } inline NSatAnnulus::NSatAnnulus(NTetrahedron* t0, NPerm4 r0, NTetrahedron* t1, NPerm4 r1) { tet[0] = t0; tet[1] = t1; roles[0] = r0; roles[1] = r1; } inline NSatAnnulus& NSatAnnulus::operator = (const NSatAnnulus& cloneMe) { tet[0] = cloneMe.tet[0]; tet[1] = cloneMe.tet[1]; roles[0] = cloneMe.roles[0]; roles[1] = cloneMe.roles[1]; return *this; } inline bool NSatAnnulus::operator == (const NSatAnnulus& other) const { return (tet[0] == other.tet[0] && tet[1] == other.tet[1] && roles[0] == other.roles[0] && roles[1] == other.roles[1]); } inline bool NSatAnnulus::operator != (const NSatAnnulus& other) const { return (tet[0] != other.tet[0] || tet[1] != other.tet[1] || roles[0] != other.roles[0] || roles[1] != other.roles[1]); } inline NSatAnnulus NSatAnnulus::otherSide() const { NSatAnnulus a(*this); a.switchSides(); return a; } inline void NSatAnnulus::reflectVertical() { roles[0] = roles[0] * NPerm4(0, 1); roles[1] = roles[1] * NPerm4(0, 1); } inline NSatAnnulus NSatAnnulus::verticalReflection() const { return NSatAnnulus(tet[0], roles[0] * NPerm4(0, 1), tet[1], roles[1] * NPerm4(0, 1)); } inline void NSatAnnulus::reflectHorizontal() { NTetrahedron* t = tet[0]; tet[0] = tet[1]; tet[1] = t; NPerm4 r = roles[0]; roles[0] = roles[1] * NPerm4(0, 1); roles[1] = r * NPerm4(0, 1); } inline NSatAnnulus NSatAnnulus::horizontalReflection() const { return NSatAnnulus(tet[1], roles[1] * NPerm4(0, 1), tet[0], roles[0] * NPerm4(0, 1)); } inline void NSatAnnulus::rotateHalfTurn() { NTetrahedron* t = tet[0]; tet[0] = tet[1]; tet[1] = t; NPerm4 r = roles[0]; roles[0] = roles[1]; roles[1] = r; } inline NSatAnnulus NSatAnnulus::halfTurnRotation() const { return NSatAnnulus(tet[1], roles[1], tet[0], roles[0]); } inline NSatAnnulus NSatAnnulus::image(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) const { NSatAnnulus a(*this); a.transform(originalTri, iso, newTri); return a; } } // namespace regina #endif regina-4.95/engine/subcomplex/nsatblock.cpp000644 000765 000024 00000011451 12234011536 020720 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "subcomplex/nsatblock.h" #include namespace regina { NSatBlock::NSatBlock(const NSatBlock& cloneMe) : ShareableObject(), nAnnuli_(cloneMe.nAnnuli_), annulus_(new NSatAnnulus[cloneMe.nAnnuli_]), twistedBoundary_(cloneMe.twistedBoundary_), adjBlock_(new NSatBlock*[cloneMe.nAnnuli_]), adjAnnulus_(new unsigned[cloneMe.nAnnuli_]), adjReflected_(new bool[cloneMe.nAnnuli_]), adjBackwards_(new bool[cloneMe.nAnnuli_]) { for (unsigned i = 0; i < nAnnuli_; i++) { annulus_[i] = cloneMe.annulus_[i]; adjBlock_[i] = cloneMe.adjBlock_[i]; adjAnnulus_[i] = cloneMe.adjAnnulus_[i]; adjReflected_[i] = cloneMe.adjReflected_[i]; adjBackwards_[i] = cloneMe.adjBackwards_[i]; } } void NSatBlock::transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) { for (unsigned i = 0; i < nAnnuli_; i++) annulus_[i].transform(originalTri, iso, newTri); } void NSatBlock::nextBoundaryAnnulus(unsigned thisAnnulus, NSatBlock*& nextBlock, unsigned& nextAnnulus, bool& refVert, bool& refHoriz, bool followPrev) { // Don't worry about testing the precondition (this annulus has no // adjacency) -- things won't break even if it's false. nextBlock = this; if (followPrev) nextAnnulus = (thisAnnulus == 0 ? nAnnuli_ - 1 : thisAnnulus - 1); else nextAnnulus = (thisAnnulus + 1 == nAnnuli_ ? 0 : thisAnnulus + 1); refVert = refHoriz = false; unsigned tmp; while (nextBlock->hasAdjacentBlock(nextAnnulus)) { // Push through to the next block... if (nextBlock->adjReflected_[nextAnnulus]) refVert = ! refVert; if (! nextBlock->adjBackwards_[nextAnnulus]) refHoriz = ! refHoriz; tmp = nextBlock->adjAnnulus_[nextAnnulus]; nextBlock = nextBlock->adjBlock_[nextAnnulus]; nextAnnulus = tmp; // ... and step to the previous/next annulus around. if (refHoriz == followPrev) { nextAnnulus = (nextAnnulus + 1 == nextBlock->nAnnuli_ ? 0 : nextAnnulus + 1); } else { nextAnnulus = (nextAnnulus == 0 ? nextBlock->nAnnuli_ - 1 : nextAnnulus - 1); } } } std::string NSatBlock::getAbbr(bool tex) const { std::ostringstream s; writeAbbr(s, tex); return s.str(); } bool NSatBlock::isBad(NTetrahedron* t, const TetList& list) { if (list.find(t) != list.end()) return true; return false; } } // namespace regina regina-4.95/engine/subcomplex/nsatblock.h000644 000765 000024 00000107557 12234011536 020402 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsatblock.h * \brief Deals with saturated blocks in triangulations of Seifert fibred * spaces. */ #ifndef __NSATBLOCK_H #ifndef __DOXYGEN #define __NSATBLOCK_H #endif #include "regina-core.h" #include "shareableobject.h" #include "subcomplex/nsatannulus.h" #include namespace regina { class NIsomorphism; struct NSatAnnulus; class NSFSpace; class NTetrahedron; class NTriangulation; /** * \weakgroup subcomplex * @{ */ /** * Represents a saturated block in a Seifert fibred space. A saturated * block is a connected set of tetrahedra built from a subset of fibres * (no fibres may enter or exit the boundary of the block). In addition, * the boundary of this block must be a ring of saturated annuli, as * described by the NSatAnnulus class. Aside from this ring of saturated * annuli, there may be no other boundary triangles within the block. * * The boundary annuli are numbered consecutively as illustrated below, * where the markings 0 and 1 within the triangles represent the first * and second triangle of each annulus (see the NSatAnnulus class notes for * details). Note that the following diagram is viewed from \e inside * the block. * *
 *               -+---+---+---+---+---+---+-
 *                |0 /|0 /|0 /|0 /|0 /|0 /|
 *            ... | / | / | / | / | / | / | ...
 *                |/ 1|/ 1|/ 1|/ 1|/ 1|/ 1|
 *               -+---+---+---+---+---+---+-
 * Annulus #  ...  n-2 n-1  0   1   2   3   ...
 * 
* * The ring of boundary annuli may optionally be twisted, so that together * the annuli form a long Mobius band. In this case, for the purposes of * labelling and marking annuli, the twist occurs between annuli n-1 and 0. * Be careful when dealing with blocks with twisted boundaries, since * with twists it is possible to identify an edge with itself in reverse * (thus producing something that is not a 3-manifold triangulation). * * Each saturated block corresponds to a piece of the base orbifold of the * larger Seifert fibred space. For the purpose of connecting the base * orbifold together, we assume that the boundary of this particular * piece runs horizontally in the diagram above (specifically following * the horizontal edges of the boundary annuli, as described in the * NSatAnnulus class notes). Insisting on such a boundary may lead to * (1,\a k) twists within the block; these are accounted for by the * virtual adjustSFS() routine. * * Saturated blocks are generally joined to one another (or themselves) * along their boundary annuli. For this purpose, each saturated block * contains a list of which annulus of this block is adjacent to which * annulus of which other block. Adjacencies may be \e reflected, meaning * that the adjacent annulus has its fibres reversed (i.e., the adjacent * annulus has undergone an up-to-down reflection); they may also be * \e backwards, meaning that the first triangle of one annulus is joined to * the second triangle of the other (and vice versa). * * \warning In addition to mandatory overrides such as clone() and * adjustSFS(), some subclasses will need to override the virtual * routine transform() in order to correctly adjust additional * triangulation-specific information stored in the subclass. See the * transform() documentation for further details. */ class REGINA_API NSatBlock : public ShareableObject { public: typedef std::set TetList; /**< The data structure used to store a list of tetrahedra that should not be examined by isBlock(). */ protected: unsigned nAnnuli_; /**< The number of boundary annuli. */ NSatAnnulus* annulus_; /**< Details of each boundary annulus, as seen from the inside of this saturated block. */ bool twistedBoundary_; /**< Is the ring of boundary annuli twisted to form a Mobius band? */ NSatBlock** adjBlock_; /**< The saturated block joined to each boundary annulus; this may be null if there is no adjacency or if this information is not known. */ unsigned* adjAnnulus_; /**< Describes which specific annulus of the adjacent saturated block is joined to each boundary annulus of this block. Values may be undefined if the corresponding entries in the \a adjBlock array is null. */ bool* adjReflected_; /**< Describes whether the adjacency for each boundary annulus is reflected (see the class notes above). Values may be undefined if the corresponding entries in the \a adjBlock array is null. */ bool* adjBackwards_; /**< Describes whether the adjacency for each boundary annulus is backwards (see the class notes above). Values may be undefined if the corresponding entries in the \a adjBlock array is null. */ public: /** * Creates a new clone of the given block. * * Note that the new \a adjBlock_ array will contain pointers to * the same adjacent blocks as the original. That is, adjacent * blocks will not be cloned also; instead pointers to adjacent * blocks will simply be copied across. * * @param cloneMe the saturated block to clone. */ NSatBlock(const NSatBlock& cloneMe); /** * Destroys all internal arrays. Note that any adjacent blocks * that are referenced by the \a adjBlock array will \e not be * destroyed. */ ~NSatBlock(); /** * Returns a newly created clone of this saturated block structure. * A clone of the correct subclass of NSatBlock will be returned. * For this reason, each subclass of NSatBlock must implement this * routine. * * @return a new clone of this block. */ virtual NSatBlock* clone() const = 0; /** * Returns the number of annuli on the boundary of this * saturated block. * * @return the number of boundary annuli. */ unsigned nAnnuli() const; /** * Returns details of the requested annulus on the boundary of * this saturated block. Annuli are numbered from 0 to * nAnnuli()-1 as described in the class notes. * * @param which indicates which boundary annulus is requested; * this must be between 0 and nAnnuli()-1 inclusive. * @return a reference to the requested boundary annulus. */ const NSatAnnulus& annulus(unsigned which) const; /** * Is the ring of boundary annuli twisted to form a long Mobius * strip? * * Recall from the class notes that the twist occurs between * boundary annuli nAnnuli()-1 and 0. * * @return \c true if the ring of boundary annuli is twisted, or * \c false if not. */ bool twistedBoundary() const; /** * Returns whether there is another saturated block listed as * being adjacent to the given boundary annulus of this block. * * @param whichAnnulus indicates which boundary annulus of this block * should be examined; this must be between 0 and nAnnuli()-1 * inclusive. * @return \c true if the given boundary annulus has an adjacent * block listed, or \c false otherwise. */ bool hasAdjacentBlock(unsigned whichAnnulus) const; /** * Returns the saturated block listed as being adjacent to the * given boundary annulus of this block. * * @param whichAnnulus indicates which boundary annulus of this block * should be examined; this must be between 0 and nAnnuli()-1 * inclusive. * @return the other block adjacent along this annulus, or 0 * if there is no adjacent block listed. */ NSatBlock* adjacentBlock(unsigned whichAnnulus) const; /** * Returns which specific annulus of the adjacent block is * listed as being adjacent to the given boundary annulus of * this block. * * \pre The given annulus of this block actually has an adjacent * block listed. * * @param whichAnnulus indicates which boundary annulus of this block * should be examined; this must be between 0 and nAnnuli()-1 * inclusive. * @return the corresponding annulus number on the other block * that is adjacent along this annulus. */ unsigned adjacentAnnulus(unsigned whichAnnulus) const; /** * Returns whether the adjacency along the given boundary annulus * of this block is reflected. See the class notes for a * discussion of reflected adjacencies. * * \pre The given annulus of this block actually has an adjacent * block listed. * * @param whichAnnulus indicates which boundary annulus of this block * should be examined; this must be between 0 and nAnnuli()-1 * inclusive. * @return \c true if the corresponding adjacency is reflected, * or \c false if it is not. */ bool adjacentReflected(unsigned whichAnnulus) const; /** * Returns whether the adjacency along the given boundary annulus * of this block is backwards. See the class notes for a * discussion of backwards adjacencies. * * \pre The given annulus of this block actually has an adjacent * block listed. * * @param whichAnnulus indicates which boundary annulus of this block * should be examined; this must be between 0 and nAnnuli()-1 * inclusive. * @return \c true if the corresponding adjacency is backwards, * or \c false if it is not. */ bool adjacentBackwards(unsigned whichAnnulus) const; /** * Lists the given saturated block as being adjacent to the * given boundary annulus of this block. Both block structures * (this and the given block) will be updated. * * @param whichAnnulus indicates which boundary annulus of this block * has the new adjacency; this must be between 0 and nAnnuli()-1 * inclusive. * @param adjBlock the other saturated block that is adjacent to * this. * @param adjAnnulus indicates which boundary annulus of the * adjacent block meets the given boundary annulus of this block; * this must be between 0 and adjBlock->nAnnuli()-1 inclusive. * @param adjReflected indicates whether the new adjacency is * reflected (see the class notes for details). * @param adjBackwards indicates whether the new adjacency is * backwards (see the class notes for details). */ void setAdjacent(unsigned whichAnnulus, NSatBlock* adjBlock, unsigned adjAnnulus, bool adjReflected, bool adjBackwards); /** * Adjusts the given Seifert fibred space to insert the contents * of this saturated block. In particular, the space should be * adjusted as though an ordinary solid torus (base orbifold a * disc, no twists or exceptional fibres) had been replaced by * this block. This description does not make sense for blocks * with twisted boundary; the twisted case is discussed below. * * If the argument \a reflect is \c true, it should be assumed * that this saturated block is being reflected before being * inserted into the larger Seifert fibred space. That is, any * twists or exceptional fibres should be negated before being * added. * * Regarding the signs of exceptional fibres: Consider a * saturated block containing a solid torus whose meridinal curve * runs \a p times horizontally around the boundary in order through * annuli 0,1,... and follows the fibres \a q times from bottom * to top (as depicted in the diagram in the NSatBlock class * notes). Then this saturated block adds a positive (\a p, \a q) * fibre to the underlying Seifert fibred space. * * If the ring of saturated annuli bounding this block is twisted * then the situation becomes more complex. It can be proven * that such a block must contain a twisted reflector boundary * in the base orbifold (use Z_2 homology with fibre-reversing * paths to show that the base orbifold must contain another * twisted boundary component, and then recall that real boundaries * are not allowed inside blocks). * * In this twisted boundary case, it should be assumed that the * twisted reflector boundary is already stored in the given Seifert * fibred space. This routine should make any further changes * that are required (there may well be none). That is, the * space should be adjusted as though a trivial Seifert fibred * space over the annulus with one twisted reflector boundary (and * one twisted puncture corresponding to the block boundary) had * been replaced by this block. In particular, this routine should * \e not add the reflector boundary itself. * * @param sfs the Seifert fibred space to adjust. * @param reflect \c true if this block is to be reflected, or * \c false if it should be inserted directly. */ virtual void adjustSFS(NSFSpace& sfs, bool reflect) const = 0; /** * Adjusts the structure of this block according to the given * isomorphism between triangulations. Any triangulation-specific * information will be transformed accordingly (for instance, the * routine NSatAnnulus::transform() will be called for each * boundary annulus). * * Information regarding adjacent blocks will \e not be changed. * Only structural information for this particular block will be * updated. * * The given isomorphism must describe a mapping from \a originalTri * to \a newTri, and this block must currently refer to tetrahedra in * \a originalTri. After this routine is called the block will * instead refer to the corresponding tetrahedra in \a newTri (with * changes in vertex/face numbering also accounted for). * * \pre This block currently refers to tetrahedra in \a originalTri, * and \a iso describes a mapping from \a originalTri to \a newTri. * * \warning Any subclasses of NSatBlock that store additional * triangulation-specific information will need to override this * routine. When doing so, be sure to call NSatBlock::transform() * so that the generic changes defined here will still take place. * * @param originalTri the triangulation currently used by this * saturated block. * @param iso the mapping from \a originalTri to \a newTri. * @param newTri the triangulation to be used by the updated * block structure. */ virtual void transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri); /** * Finds the next (or previous) boundary annulus around from this, * treating all adjacent blocks as part of a single large saturated * region. * * Suppose that all saturated blocks are merged together according * to adjacent boundary annuli, forming larger saturated structures. * The remaining annuli that do not have adjacent blocks will * group together to form several large boundary rings. Note that * each boundary ring might involve annuli from several * different blocks, and might or might not have a twist (thus * forming a large Klein bottle instead of a large torus). * * This routine is used to trace around such a boundary ring. It is * assumed that annulus \a thisAnnulus of this block forms part * of a boundary ring (i.e., it has no adjacent block). This * routine will then return the next/previous annulus around from * this in the large boundary ring. Here "next" means in the direction * following from the second triangle of this annulus, and * "previous" means in the direction following from the first triangle; * the boolean argument \a followPrev controls which we will be used. * This next/previous annulus might belong to another block, or it * might even be this original annulus again. * * The next/previous annulus itself is not returned, but rather a * reference as to how it appears within its enclosing saturated block. * Specifically, a block and corresponding annulus number will be * returned in the arguments \a nextBlock and \a nextAnnulus * respectively. * * It is possible that the next/previous annulus as it appears within * the returned block is oriented differently from how it appears * within this large boundary ring. For this reason, two * booleans are returned also. The argument \a refVert will * describe whether the annulus is reflected vertically as it * appears within the large boundary ring (i.e., the first and * second triangles remain the same but the fibre direction is * reversed). Similarly, the argument \a refHoriz will describe * whether the annulus is reflected horizontally as it appears * within the large boundary ring (i.e., first and second triangles * are switched but the fibre direction is unchanged). * * It is possible that both a horizontal and vertical reflection * take place. Note that any kind of reflection will also * affect the locations of the 0/1/2 markings as described in * the NSatAnnulus class notes. * * Finally, note that if the large boundary ring is twisted * (i.e., it forms a Klein bottle), then following the entire * boundary ring around using this routine will bring you back to * the starting annulus but with the \a refVert flag set. * * \pre Annulus \a thisAnnulus of this block has no block * adjacent to it. * * \warning If you wish to trace around an entire boundary * ring, you will need to adjust the argument \a followPrev * according to whether or not the current annulus * is reflected horizontally (since, under a horizontal * reflection, "next" becomes "previous" and vice versa). * * \ifacespython This routine only takes two arguments (\a thisAnnulus * and \a followPrev). The return value is a tuple of four * values: the block returned in \a nextBlock, the integer * returned in \a nextAnnulus, the boolean returned in \a refVert, * and the boolean returned in \a refHoriz. * * @param thisAnnulus describes which original boundary annulus of * this block to examine; this must be between 0 and nAnnuli()-1 * inclusive. * @param nextBlock a reference used to return the block * containing the next boundary annulus around from \a thisAnnulus. * @param nextAnnulus a reference used to return the specific * annulus number within \a nextBlock of the next annulus * around; this will be between 0 and \a nextBlock->nAnnuli()-1 * inclusive, and the corresponding annulus will have no block * adjacent to it. * @param refVert a reference used to return \c true if the next * annulus around is vertically reflected, or \c false if not; * see above for details. * @param refHoriz a reference used to return \c true if the next * annulus around is horizontally reflected, or \c false if not; * see above for details. * @param followPrev \c true if we should find the previous boundary * annulus, or \c false if we should find the next boundary annulus. */ void nextBoundaryAnnulus(unsigned thisAnnulus, NSatBlock*& nextBlock, unsigned& nextAnnulus, bool& refVert, bool& refHoriz, bool followPrev); /** * Returns an abbreviated name or symbol for this block. * This name will reflect the particular block type, but may not * provide thorough details. * * The name will be no more than a handful of characters long, and * will not include a newline (or surrounding dollar signs in TeX * mode). * * @param tex \c true if the name should be formatted for TeX, * or \c false if it should be in plain text format. * @return an abbreviated name for this block. */ std::string getAbbr(bool tex = false) const; /** * Writes an abbreviated name or symbol for this block to the * given output stream. This name should reflect the particular * block type, but need not provide thorough details. * * The output should be no more than a handful of characters long, * and no newline should be written. In TeX mode, no leading or * trailing dollar signs should be written. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @param tex \c true if the output should be formatted for TeX, * or \c false if it should be in plain text format. */ virtual void writeAbbr(std::ostream& out, bool tex = false) const = 0; /** * Implements a consistent ordering of saturated blocks. * This ordering is purely aesthetic on the part of the author, * and is subject to change in future versions of Regina. * * @param compare the saturated block with which this will be * compared. * @return \c true if this block comes before the given block * according to the ordering of saturated blocks, or \c false * if either the blocks are identical or this block comes after * the given block. */ bool operator < (const NSatBlock& compare) const; /** * Determines whether the given annulus is in fact a boundary * annulus for a recognised type of saturated block. The * annulus should be represented from the inside of the proposed * saturated block. * * Only certain types of saturated block are recognised by this * routine. More exotic saturated blocks will not be identified, * and this routine will return \c null in such cases. * * The given list of tetrahedra will not be examined by this * routine. That is, only saturated blocks that do not contain * any of these tetrahedra will be considered. As a consequence, * if the given annulus uses any of these tetrahedra then \c null * will be returned. * * If a block is found on the other hand, all of the tetrahedra * within this block will be added to the given list. * * In the event that a block is found, it is guaranteed that the * given annulus will be listed as annulus number 0 in the block * structure, without any horizontal or vertical reflection. * * \ifacespython The second argument \a avoidTets is not * present. An empty list will be passed instead. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatBlock* isBlock(const NSatAnnulus& annulus, TetList& avoidTets); protected: /** * Constructor for a block with the given number of annuli on * the boundary. * * All arrays will be constructed but their contents will remain * uninitialised, with the exception that the \a adjBlock array * will be filled with null pointers. * * @param nAnnuli the number of annuli on the boundary of this * block; this must be strictly positive. * @param twistedBoundary \c true if the ring of boundary annuli * is twisted to form a long Mobius band, or \c false (the default) * if it is not. */ NSatBlock(unsigned nAnnuli, bool twistedBoundary = false); /** * Determines whether the given tetrahedron is contained within the * given list. * * This is intended as a helper routine for isBlock() and * related routines. * * @param t the tetrahedron to search for. * @param list the list in which to search. * @return \c true if and only if the given tetrahedron was found. */ static bool isBad(NTetrahedron* t, const TetList& list); /** * Determines whether the given tetrahedron is contained within * the given list. * * This is intended as a helper routine for isBlock() and * related routines. It is a generic routine for working with * arbitrary list types. * * \pre Forward iterators of type List::const_iterator * that span the given list can be obtained by calling * list.begin() and list.end(). * * @param t the tetrahedron to search for. * @param list the list in which to search. * @return \c true if and only if the given tetrahedron was found. */ template static bool isBad(NTetrahedron* t, const List& list) { for (typename List::const_iterator it = list.begin(); it != list.end(); ++it) if (*it == t) return true; return false; } /** * Determines whether the given tetrahedron pointer is null. * * This is intended as a helper routine for isBlock() and * related routines. Despite its trivial implementation, it is * provided to make long blocks of code easier to read and * distinguish by functionality. * * The name notUnique() may seem strang for what is essentially * a nullity test; in fact this routine is offered as a * degenerate case of other variants of notUnique() that take * more tetrahedra as arguments. * * @param test the tetrahedron pointer to test. * @return \c true if \a test is null, or \c false otherwise. */ static bool notUnique(NTetrahedron* test); /** * Determines whether the given tetrahedron pointer is null * or equal to another from the given list. * * This is intended as a helper routine for isBlock() and * related routines. Despite its trivial implementation, it is * provided to make long blocks of code easier to read and * distinguish by functionality. * * @param test the tetrahedron pointer to test. * @param other1 another tetrahedron that will be compared with \a test. * @return \c true if \a test is null or equal to \a other1, * or \c false otherwise. */ static bool notUnique(NTetrahedron* test, NTetrahedron* other1); /** * Determines whether the given tetrahedron pointer is null * or equal to another from the given list. * * This is intended as a helper routine for isBlock() and * related routines. Despite its trivial implementation, it is * provided to make long blocks of code easier to read and * distinguish by functionality. * * @param test the tetrahedron pointer to test. * @param other1 another tetrahedron that will be compared with \a test. * @param other2 another tetrahedron that will be compared with \a test. * @return \c true if \a test is null or equal to \a other1 or * \a other2, or \c false otherwise. */ static bool notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2); /** * Determines whether the given tetrahedron pointer is null * or equal to another from the given list. * * This is intended as a helper routine for isBlock() and * related routines. Despite its trivial implementation, it is * provided to make long blocks of code easier to read and * distinguish by functionality. * * @param test the tetrahedron pointer to test. * @param other1 another tetrahedron that will be compared with \a test. * @param other2 another tetrahedron that will be compared with \a test. * @param other3 another tetrahedron that will be compared with \a test. * @return \c true if \a test is null or equal to \a other1, * \a other2 or \a other3, or \c false otherwise. */ static bool notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2, NTetrahedron* other3); /** * Determines whether the given tetrahedron pointer is null * or equal to another from the given list. * * This is intended as a helper routine for isBlock() and * related routines. Despite its trivial implementation, it is * provided to make long blocks of code easier to read and * distinguish by functionality. * * @param test the tetrahedron pointer to test. * @param other1 another tetrahedron that will be compared with \a test. * @param other2 another tetrahedron that will be compared with \a test. * @param other3 another tetrahedron that will be compared with \a test. * @param other4 another tetrahedron that will be compared with \a test. * @return \c true if \a test is null or equal to \a other1, * \a other2, \a other3 or \a other4, or \c false otherwise. */ static bool notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2, NTetrahedron* other3, NTetrahedron* other4); }; /*@}*/ // Inline functions for NSatBlock inline NSatBlock::NSatBlock(unsigned nAnnuli, bool twistedBoundary) : nAnnuli_(nAnnuli), annulus_(new NSatAnnulus[nAnnuli]), twistedBoundary_(twistedBoundary), adjBlock_(new NSatBlock*[nAnnuli]), adjAnnulus_(new unsigned[nAnnuli]), adjReflected_(new bool[nAnnuli]), adjBackwards_(new bool[nAnnuli]) { for (unsigned i = 0; i < nAnnuli; i++) adjBlock_[i] = 0; } inline NSatBlock::~NSatBlock() { delete[] annulus_; delete[] adjBlock_; delete[] adjAnnulus_; delete[] adjReflected_; delete[] adjBackwards_; } inline unsigned NSatBlock::nAnnuli() const { return nAnnuli_; } inline const NSatAnnulus& NSatBlock::annulus(unsigned which) const { return annulus_[which]; } inline bool NSatBlock::twistedBoundary() const { return twistedBoundary_; } inline bool NSatBlock::hasAdjacentBlock(unsigned whichAnnulus) const { return (adjBlock_[whichAnnulus] != 0); } inline NSatBlock* NSatBlock::adjacentBlock(unsigned whichAnnulus) const { return adjBlock_[whichAnnulus]; } inline unsigned NSatBlock::adjacentAnnulus(unsigned whichAnnulus) const { return adjAnnulus_[whichAnnulus]; } inline bool NSatBlock::adjacentReflected(unsigned whichAnnulus) const { return adjReflected_[whichAnnulus]; } inline bool NSatBlock::adjacentBackwards(unsigned whichAnnulus) const { return adjBackwards_[whichAnnulus]; } inline void NSatBlock::setAdjacent(unsigned whichAnnulus, NSatBlock* adjBlock, unsigned adjAnnulus, bool adjReflected, bool adjBackwards) { adjBlock_[whichAnnulus] = adjBlock; adjAnnulus_[whichAnnulus] = adjAnnulus; adjReflected_[whichAnnulus] = adjReflected; adjBackwards_[whichAnnulus] = adjBackwards; adjBlock->adjBlock_[adjAnnulus] = this; adjBlock->adjAnnulus_[adjAnnulus] = whichAnnulus; adjBlock->adjReflected_[adjAnnulus] = adjReflected; adjBlock->adjBackwards_[adjAnnulus] = adjBackwards; } inline bool NSatBlock::notUnique(NTetrahedron* test) { return (test == 0); } inline bool NSatBlock::notUnique(NTetrahedron* test, NTetrahedron* other1) { return (test == 0 || test == other1); } inline bool NSatBlock::notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2) { return (test == 0 || test == other1 || test == other2); } inline bool NSatBlock::notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2, NTetrahedron* other3) { return (test == 0 || test == other1 || test == other2 || test == other3); } inline bool NSatBlock::notUnique(NTetrahedron* test, NTetrahedron* other1, NTetrahedron* other2, NTetrahedron* other3, NTetrahedron* other4) { return (test == 0 || test == other1 || test == other2 || test == other3 || test == other4); } } // namespace regina #endif regina-4.95/engine/subcomplex/nsatblockstarter.cpp000644 000765 000024 00000014732 12234011536 022332 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatblocktypes.h" namespace regina { const NSatBlockStarterSet NSatBlockStarterSet::blocks; void NSatBlockStarterSet::initialise() { NSatBlockStarter* starter; starter = new NSatBlockStarter; starter->block_ = NSatTriPrism::insertBlock(starter->triangulation_, true); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatCube::insertBlock(starter->triangulation_); insert(starter); // Try various reflector strips of small length. starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 1, false); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 1, true); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 2, false); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 2, true); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 3, false); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 3, true); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 4, false); insert(starter); starter = new NSatBlockStarter; starter->block_ = NSatReflectorStrip::insertBlock(starter->triangulation_, 4, true); insert(starter); } void NSatBlockStarterSearcher::findStarterBlocks(NTriangulation* tri) { // Clean up usedTets if required. if (! usedTets.empty()) usedTets.clear(); // Hunt for a starting block. unsigned long i; NSatBlockStarterSet::iterator it; std::list isos; std::list::iterator isoIt; NSatBlock* starter; for (it = NSatBlockStarterSet::begin(); it != NSatBlockStarterSet::end(); it++) { // Look for this particular starting block. // Get trivialities out of the way first. if (tri->isOrientable() && ! (*it)->triangulation().isOrientable()) continue; if (tri->getNumberOfTetrahedra() < (*it)->triangulation().getNumberOfTetrahedra()) continue; // Find all isomorphisms of the starter block within the given // triangulation. if (! (*it)->triangulation().findAllSubcomplexesIn(*tri, isos)) continue; // Run through each isomorphism in the list and see if it leads // somewhere useful. // // All of the isomorphisms in this list _must_ be destroyed at // some point before we loop back to the next starter block. for (isoIt = isos.begin(); isoIt != isos.end(); isoIt++) { starter = (*it)->block()->clone(); starter->transform(&(*it)->triangulation(), *isoIt, tri); // Create an initial blacklist of tetrahedra consisting of // those in the isomorphic image of the initial starting block. for (i = 0; i < (*it)->triangulation().getNumberOfTetrahedra(); i++) usedTets.insert(tri->getTetrahedron((*isoIt)->tetImage(i))); // And process! // Note that useStarterBlock() passes ownership of the starter // block elsewhere. if (! useStarterBlock(starter)) { // The search ends now. // Don't forget to destroy all remaining isomorphisms. usedTets.clear(); while (isoIt != isos.end()) delete *isoIt++; return; } // Keep on searching. // Destroy this isomorphism and make things ready for the next one. usedTets.clear(); delete *isoIt; } // Make sure the list is empty again for the next time around. isos.clear(); } // Search over. Nothing here to see. } } // namespace regina regina-4.95/engine/subcomplex/nsatblockstarter.h000644 000765 000024 00000033361 12234011536 021776 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsatblockstarter.h * \brief Provides a hard-coded list of saturated blocks to use as starting * points for recognising larger Seifert fibred spaces. */ #ifndef __NSATBLOCKSTARTER_H #ifndef __DOXYGEN #define __NSATBLOCKSTARTER_H #endif #include "regina-core.h" #include "subcomplex/nsatblock.h" #include "triangulation/ntriangulation.h" #include "utilities/nlistoncall.h" namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Contains a triangulation of a saturated block along with the * accompanying saturated block description. Different objects of this * class will correspond to different types of saturated block. * * This is a support class for NSatBlockStarterSet, and as such it is a * read-only class to the rest of the world. * * This class is well-suited for subcomplex testing: if the * triangulation here is found to be a subcomplex of some larger * triangulation (see NTriangulation::isContainedIn()), then the * corresponding isomorphism can be used to copy this block structure * and transform it to describe the corresponding block in the larger * triangulation. * * As such, one of the core uses of this class is as a starting point * for identifying regions within triangulations that are formed by * joining saturated blocks together along their boundary annuli. See * the routines NSatBlockStarterSearcher::findStarterBlocks() and * NSatRegion::expand() for implementations of this. * * \ifacespython Not present. */ class REGINA_API NSatBlockStarter : regina::boost::noncopyable { private: NTriangulation triangulation_; /**< The triangulation of the saturated block. */ NSatBlock* block_; /**< Structural details of the saturated block. */ public: /** * Destroys both the internal triangulation and block structure. */ ~NSatBlockStarter(); /** * Returns a reference to the triangulation of the saturated * block. * * @return the block triangulation. */ const NTriangulation& triangulation() const; /** * Returns details that describe the structure of the saturated * block. * * @return the block structure. */ const NSatBlock* block() const; private: /** * Creates a new starter block. The triangulation will be empty * and the block pointer set to null. * * The triangulation must be fleshed out and the block structure * created before this object can be used. */ NSatBlockStarter(); friend class NSatBlockStarterSet; }; /** * Represents a set of starter blocks that can be used for identifying * triangulations of Seifert fibred spaces. * * This class provides a list of saturated blocks that can be used as * starting points for recognising triangulations; see the * NSatBlockStarter class notes for details. * * More importantly, this list is global and hard-coded. The only * access to the list is through the static routines begin() and end(). * * Creating the list of starter blocks is expensive, and so this is not * done until the first time that begin() is called. This way, if the list * is never used then the work is never done. As a consequence however, * you must be sure to call begin() before calling end() (which is the * usual way in which iterator loops are structured in code). * * Be aware that this list makes no claims to be exhaustive; it is * expected to grow as future versions of Regina are released. * * \ifacespython Not present. */ class REGINA_API NSatBlockStarterSet : private NListOnCall { public: /** * An iterator over the starter blocks in this list. This operates * as a forward iterator in a manner consistent with the standard C++ * library. */ typedef NListOnCall::iterator iterator; private: static const NSatBlockStarterSet blocks; /**< The hard-coded list of starter blocks. */ public: /** * Returns an iterator pointing to the first block in the * hard-coded list. * * The very first time this routine is called, the list will be * filled with items (and as such the call will be expensive). * Every subsequent call will be very cheap. * * @return an iterator pointing to the first starter block. */ static iterator begin(); /** * Returns an iterator pointing past the end of the hard-coded list * (i.e., just after the last item). * * \pre The begin() routine has been called at least once. * * @return a past-the-end iterator. */ static iterator end(); protected: void initialise(); private: /** * Creates a new list of starter blocks. This routine is * private since the only list that should exist is the global * hard-coded list. */ NSatBlockStarterSet(); }; /** * A helper class for locating and using starter blocks within a * triangulation. * * This class provides a means for searching for each starter * block in the global hard-coded NSatBlockStarterSet within a * given triangulation. More specifically, given some triangulation \a t, * this class can locate every isomorphic embedding of every starter * block in the global NSatBlockStarterSet as a subcomplex of \a t (see * NTriangulation::isContainedIn() for what is meant by "isomorphic * embedding"). * * The routine findStarterBlocks() runs the search. Each time an * isomorphic embedding of a starter block is discovered within the * given triangulation, the pure virtual routine useStarterBlock() will * be called. The block that is passed to useStarterBlock() will be a * new block that refers to the particular embedding of the starter block * within the given triangulation (as opposed to the original block * structure referring to the prebuilt triangulation in NSatBlockStarter). * * For each situation that requires searching for starter blocks, a * subclass of NSatBlockStarterSearcher will be required. This subclass * should override useStarterBlock() to perform whatever action is * necessary. * * Instead of locating all isomorphic embeddings of all starter blocks * in the global set, the search can be made to finish early once * certain conditions are met. This is done by implementing * useStarterBlock() to return \c false when the search should quit. * * \ifacespython Not present. */ class REGINA_API NSatBlockStarterSearcher { protected: NSatBlock::TetList usedTets; /**< Keeps track of which tetrahedra have used by the current embedding of the current starter block. See useStarterBlock() for further details. */ public: /** * Destroys this object and its internal structures. */ virtual ~NSatBlockStarterSearcher(); /** * Runs a search for every isomorphic embedding of every * starter block from the global NSatBlockStarterSet within the * given triangulation. Each time an embedding is discovered, * the pure virtual routine useStarterBlock() will be called. * * See the NSatBlockStarterSearcher class notes for greater * detail on what this search does and how it runs. * * For subclasses that make use of the \a usedTets data member, * it is worth noting that this routine empties the \a usedTets * list on both entry and exit, as well as every time that * useStarterBlock() returns after each new embedding is found. * * @param tri the triangulation in which to search for starter * blocks. */ void findStarterBlocks(NTriangulation* tri); protected: /** * Used by subclasses to process each starter block embedding that * is found. * * Suppose that the main search routine findStarterBlocks() has * been called with some triangulation \a t. Each time it * locates an isomorphic embedding of a starter block within \a t, * it will call useStarterBlock(). Subclasses of * NSatBlockStarterSearcher should therefore override * useStarterBlock() to process each embedding in whatever way * is appropriate for the problem at hand. * * The block passed in the argument \a starter is a newly * created structure describing the starter block as it appears * within the triangulation \a t. Thus different embeddings of * the same starter block within \a t will pass different * \a starter arguments to this routine. * It is the responsibility of useStarterBlock() to either * destroy the new block \a starter or pass ownership of it * elsewhere. * * When this routine is called, the data member \a usedTets * will contain a list of all tetrahedra from the triangulation * \a t that appear within the relevant starter block embedding. * The reimplementation of useStarterBlock() may modify this list * as it pleases, since the main search routine will empty the * list anyway when useStarterBlock() returns. One possible use * for the \a usedTets data member is for passing to * NSatBlock::isBlock() or NSatRegion::expand() as the list of * tetrahedra to avoid in further searches. * * This routine must return a boolean; this allows subclasses to * immediately terminate the main search once they have found * whatever it is they were looking for. A return value of * \c true signifies that the search should continue as normal, * whereas a return value of \c false signifies that the search * should end immediately (specifically, that findStarterBlocks() * should clean up and return before all remaining embeddings of all * starter blocks have been found). * * \warning Subclasses must remember to either destroy or claim * ownership of the newly created block \a starter. * * @param starter a newly created structure describing the * starter block as it appears within the larger triangulation * currently under examination. * @return \c true if the search for embeddings of starter blocks * should continue, or \c false if the search should stop immediately. */ virtual bool useStarterBlock(NSatBlock* starter) = 0; }; /*@}*/ // Inline functions for NSatBlockStarter inline NSatBlockStarter::NSatBlockStarter() : block_(0) { } inline NSatBlockStarter::~NSatBlockStarter() { if (block_) delete block_; } inline const NTriangulation& NSatBlockStarter::triangulation() const { return triangulation_; } inline const NSatBlock* NSatBlockStarter::block() const { return block_; } // Inline functions for NSatBlockStarterSet inline NSatBlockStarterSet::NSatBlockStarterSet() { } inline NSatBlockStarterSet::iterator NSatBlockStarterSet::begin() { return blocks.NListOnCall::begin(); } inline NSatBlockStarterSet::iterator NSatBlockStarterSet::end() { return blocks.NListOnCall::end(); } // Inline functions for NSatBlockStarterSearcher inline NSatBlockStarterSearcher::~NSatBlockStarterSearcher() { } } // namespace regina #endif regina-4.95/engine/subcomplex/nsatblocktypes.cpp000644 000765 000024 00000077443 12234011536 022022 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/nsfs.h" #include "subcomplex/nsatblocktypes.h" #include "subcomplex/nlayeredsolidtorus.h" #include "triangulation/nedge.h" #include "triangulation/nfacepair.h" #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangulation.h" #include #include // For exit(). #include #include namespace regina { bool NSatBlock::operator < (const NSatBlock& compare) const { const NSatTriPrism* tri1 = dynamic_cast(this); const NSatTriPrism* tri2 = dynamic_cast(&compare); if (tri1 && ! tri2) return true; if (tri2 && ! tri1) return false; if (tri1 && tri2) { // Major first, then minor. return (tri1->isMajor() && ! tri2->isMajor()); } const NSatCube* cube1 = dynamic_cast(this); const NSatCube* cube2 = dynamic_cast(&compare); if (cube1 && ! cube2) return true; if (cube2 && ! cube1) return false; if (cube1 && cube2) { // All cubes are considered equal. return false; } const NSatReflectorStrip* ref1 = dynamic_cast(this); const NSatReflectorStrip* ref2 = dynamic_cast(&compare); if (ref1 && ! ref2) return true; if (ref2 && ! ref1) return false; if (ref1 && ref2) { // Always put untwisted before twisted. if (ref1->twistedBoundary() && ! ref2->twistedBoundary()) return false; if (ref2->twistedBoundary() && ! ref1->twistedBoundary()) return true; return (ref1->nAnnuli() < ref2->nAnnuli()); } const NSatLST* lst1 = dynamic_cast(this); const NSatLST* lst2 = dynamic_cast(&compare); if (lst1 && ! lst2) return true; if (lst2 && ! lst1) return false; if (lst1 && lst2) { // Order first by LST parameters, then by roles. if (lst1->lst()->getMeridinalCuts(2) < lst2->lst()->getMeridinalCuts(2)) return true; if (lst1->lst()->getMeridinalCuts(2) > lst2->lst()->getMeridinalCuts(2)) return false; if (lst1->lst()->getMeridinalCuts(1) < lst2->lst()->getMeridinalCuts(1)) return true; if (lst1->lst()->getMeridinalCuts(1) > lst2->lst()->getMeridinalCuts(1)) return false; if (lst1->lst()->getMeridinalCuts(0) < lst2->lst()->getMeridinalCuts(0)) return true; if (lst1->lst()->getMeridinalCuts(0) > lst2->lst()->getMeridinalCuts(0)) return false; // Sorts by which edge group is joined to the vertical annulus // edges, then horizontal, then diagonal (though we won't bother // testing diagonal, since by that stage we will know the roles // permutations to be equal). if (lst1->roles()[0] < lst2->roles()[0]) return true; if (lst1->roles()[0] > lst2->roles()[0]) return false; if (lst1->roles()[1] < lst2->roles()[1]) return true; if (lst1->roles()[1] > lst2->roles()[1]) return false; // All equal. return false; } const NSatMobius* mob1 = dynamic_cast(this); const NSatMobius* mob2 = dynamic_cast(&compare); if (mob1 && ! mob2) return true; if (mob2 && ! mob1) return false; if (mob1 && mob2) { // Order by position in descending order (vertical first, then // horizontal, then finally diagonal). return (mob1->position() > mob2->position()); } const NSatLayering* layer1 = dynamic_cast(this); const NSatLayering* layer2 = dynamic_cast(&compare); if (layer1 && ! layer2) return true; if (layer2 && ! layer1) return false; if (layer1 && layer2) { // Horizontal, then diagonal. return (layer1->overHorizontal() && ! layer2->overHorizontal()); } return false; } NSatBlock* NSatBlock::isBlock(const NSatAnnulus& annulus, TetList& avoidTets) { NSatBlock* ans; // Run through the types of blocks that we know about. if ((ans = NSatMobius::isBlockMobius(annulus, avoidTets))) return ans; if ((ans = NSatLST::isBlockLST(annulus, avoidTets))) return ans; if ((ans = NSatTriPrism::isBlockTriPrism(annulus, avoidTets))) return ans; if ((ans = NSatCube::isBlockCube(annulus, avoidTets))) return ans; if ((ans = NSatReflectorStrip::isBlockReflectorStrip(annulus, avoidTets))) return ans; // As a last attempt, try a single layering. We don't have to worry // about the degeneracy, since we'll never get a loop of these // things (since that would form a disconnected component, and we // never use one as a starting block). if ((ans = NSatLayering::isBlockLayering(annulus, avoidTets))) return ans; // Nothing was found. return 0; } void NSatMobius::adjustSFS(NSFSpace& sfs, bool reflect) const { if (position_ == 0) { // Diagonal: sfs.insertFibre(1, reflect ? 1 : -1); } else if (position_ == 1) { // Horizontal: sfs.insertFibre(1, reflect ? -2 : 2); } else { // Vertical: sfs.insertFibre(2, reflect ? -1 : 1); } } void NSatMobius::writeTextShort(std::ostream& out) const { out << "Saturated Mobius band, boundary on "; if (position_ == 0) out << "diagonal"; else if (position_ == 1) out << "horizontal"; else if (position_ == 2) out << "vertical"; else out << "invalid"; out << " edge"; } void NSatMobius::writeAbbr(std::ostream& out, bool tex) const { out << (tex ? "M_" : "Mob("); if (position_ == 0) out << 'd'; else if (position_ == 1) out << 'h'; else if (position_ == 2) out << 'v'; if (! tex) out << ')'; } NSatMobius* NSatMobius::isBlockMobius(const NSatAnnulus& annulus, TetList&) { // The two tetrahedra must be joined together along the annulus triangles. if (annulus.tet[0]->adjacentTetrahedron(annulus.roles[0][3]) != annulus.tet[1]) return 0; NPerm4 annulusGluing = annulus.roles[1].inverse() * annulus.tet[0]->adjacentGluing(annulus.roles[0][3]) * annulus.roles[0]; if (annulusGluing[3] != 3) return 0; // The triangles are glued together. Is it one of the allowable // (orientable) permutations? int position = -1; if (annulusGluing == NPerm4(0, 1)) position = 2; // Vertical else if (annulusGluing == NPerm4(0, 2)) position = 1; // Horizontal else if (annulusGluing == NPerm4(1, 2)) position = 0; // Diagonal if (position < 0) { // Nope. It must be a non-orientable permutation. return 0; } // Got it! NSatMobius* ans = new NSatMobius(position); ans->annulus_[0] = annulus; return ans; } NSatLST::NSatLST(const NSatLST& cloneMe) : NSatBlock(cloneMe), lst_(cloneMe.lst_->clone()), roles_(cloneMe.roles_) { } NSatLST::~NSatLST() { delete lst_; } void NSatLST::adjustSFS(NSFSpace& sfs, bool reflect) const { long cutsVert = lst_->getMeridinalCuts(roles_[0]); long cutsHoriz = lst_->getMeridinalCuts(roles_[1]); if (roles_[2] == 2) { // Most cuts are on the diagonal, which means the meridinal // curve is negative. cutsHoriz = -cutsHoriz; } sfs.insertFibre(cutsVert, reflect ? -cutsHoriz : cutsHoriz); } void NSatLST::writeTextShort(std::ostream& out) const { out << "Saturated (" << lst_->getMeridinalCuts(0) << ", " << lst_->getMeridinalCuts(1) << ", " << lst_->getMeridinalCuts(2) << ") layered solid torus"; } void NSatLST::writeAbbr(std::ostream& out, bool tex) const { out << (tex ? "\\mathrm{LST}_{" : "LST(") << lst_->getMeridinalCuts(0) << ", " << lst_->getMeridinalCuts(1) << ", " << lst_->getMeridinalCuts(2) << (tex ? '}' : ')'); } void NSatLST::transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri) { // Start with the parent implementation. NSatBlock::transform(originalTri, iso, newTri); // Transform the layered solid torus also. lst_->transform(originalTri, iso, newTri); } NSatLST* NSatLST::isBlockLST(const NSatAnnulus& annulus, TetList& avoidTets) { // Do we move to a common usable tetrahedron? if (annulus.tet[0] != annulus.tet[1]) return 0; if (isBad(annulus.tet[0], avoidTets)) return 0; // Is it a layering? // Here we find the endpoints of the edge from which the two layered // triangles fold out. NFacePair centralEdge = NFacePair(annulus.roles[0][3], annulus.roles[1][3]).complement(); if (annulus.roles[1] != NPerm4(annulus.roles[0][3], annulus.roles[1][3]) * NPerm4(centralEdge.upper(), centralEdge.lower()) * annulus.roles[0]) return 0; // Find the layered solid torus. NLayeredSolidTorus* lst = NLayeredSolidTorus::formsLayeredSolidTorusTop( annulus.tet[0], annulus.roles[0][3], annulus.roles[1][3]); if (! lst) return 0; // Make sure we're not about to create a (0,k) curve. NPerm4 lstRoles( lst->getTopEdgeGroup( NEdge::edgeNumber[annulus.roles[0][0]][annulus.roles[0][1]]), lst->getTopEdgeGroup( NEdge::edgeNumber[annulus.roles[0][0]][annulus.roles[0][2]]), lst->getTopEdgeGroup( NEdge::edgeNumber[annulus.roles[0][1]][annulus.roles[0][2]]), 3); if (lst->getMeridinalCuts(lstRoles[0]) == 0) return 0; // Make two runs through the full set of tetrahedra. // The first run verifies that each tetrahedron is usable. // The second run inserts the tetrahedra into avoidTets. NTetrahedron* current = annulus.tet[0]; NFacePair currPair = centralEdge; NFacePair nextPair; while (current != lst->getBase()) { // INV: The current tetrahedron is usable. // INV: The next two faces to push through are in currPair. // Push through to the next tetrahedron. nextPair = NFacePair( current->adjacentFace(currPair.upper()), current->adjacentFace(currPair.lower()) ).complement(); current = current->adjacentTetrahedron(currPair.upper()); currPair = nextPair; // Make sure this next tetrahedron is usable. if (isBad(current, avoidTets)) return 0; } // All good! current = annulus.tet[0]; currPair = centralEdge; avoidTets.insert(current); while (current != lst->getBase()) { // INV: All tetrahedra up to and including current have been added. // INV: The next two faces to push through are in currPair. // Push through to the next tetrahedron. nextPair = NFacePair( current->adjacentFace(currPair.upper()), current->adjacentFace(currPair.lower()) ).complement(); current = current->adjacentTetrahedron(currPair.upper()); currPair = nextPair; // Add this next tetrahedron to the list. avoidTets.insert(current); } NSatLST* ans = new NSatLST(lst, lstRoles); ans->annulus_[0] = annulus; return ans; } void NSatTriPrism::adjustSFS(NSFSpace& sfs, bool reflect) const { if (major_) sfs.insertFibre(1, reflect ? -1 : 1); else sfs.insertFibre(1, reflect ? -2 : 2); } NSatTriPrism* NSatTriPrism::isBlockTriPrism(const NSatAnnulus& annulus, TetList& avoidTets) { NSatTriPrism* ans; // First try for one of major type. if ((ans = isBlockTriPrismMajor(annulus, avoidTets))) return ans; // Now try the reflected version. NSatAnnulus altAnnulus = annulus.verticalReflection(); if ((ans = isBlockTriPrismMajor(altAnnulus, avoidTets))) { // Reflect it back again but mark it as a minor variant. ans->major_ = false; ans->annulus_[0].reflectVertical(); ans->annulus_[1].reflectVertical(); ans->annulus_[2].reflectVertical(); return ans; } // Neither variant was found. return 0; } NSatTriPrism* NSatTriPrism::isBlockTriPrismMajor(const NSatAnnulus& annulus, TetList& avoidTets) { if (annulus.tet[0] == annulus.tet[1]) return 0; if (isBad(annulus.tet[0], avoidTets) || isBad(annulus.tet[1], avoidTets)) return 0; if (annulus.tet[0]->adjacentTetrahedron(annulus.roles[0][0]) != annulus.tet[1]) return 0; if (annulus.tet[0]->adjacentGluing(annulus.roles[0][0]) * annulus.roles[0] * NPerm4(1, 2) != annulus.roles[1]) return 0; // The two tetrahedra forming the annulus are joined together as // expected. Look for the third tetrahedron. NTetrahedron* adj = annulus.tet[0]->adjacentTetrahedron( annulus.roles[0][1]); if (adj == 0 || adj == annulus.tet[0] || adj == annulus.tet[1]) return 0; if (isBad(adj, avoidTets)) return 0; NPerm4 adjRoles = annulus.tet[0]->adjacentGluing(annulus.roles[0][1]) * annulus.roles[0] * NPerm4(0, 3); if (annulus.tet[1]->adjacentTetrahedron(annulus.roles[1][1]) != adj) return 0; if (annulus.tet[1]->adjacentGluing(annulus.roles[1][1]) * annulus.roles[1] * NPerm4(1, 3, 0, 2) != adjRoles) return 0; // All three tetrahedra are joined together as expected! NSatTriPrism* ans = new NSatTriPrism(true); const NPerm4 pairSwap(1, 0, 3, 2); ans->annulus_[0] = annulus; ans->annulus_[1].tet[0] = annulus.tet[1]; ans->annulus_[1].tet[1] = adj; ans->annulus_[1].roles[0] = annulus.roles[1] * pairSwap; ans->annulus_[1].roles[1] = adjRoles; ans->annulus_[2].tet[0] = adj; ans->annulus_[2].tet[1] = annulus.tet[0]; ans->annulus_[2].roles[0] = adjRoles * pairSwap; ans->annulus_[2].roles[1] = annulus.roles[0] * pairSwap; avoidTets.insert(annulus.tet[0]); avoidTets.insert(annulus.tet[1]); avoidTets.insert(adj); return ans; } NSatTriPrism* NSatTriPrism::insertBlock(NTriangulation& tri, bool major) { NTetrahedron* a = tri.newTetrahedron(); NTetrahedron* b = tri.newTetrahedron(); NTetrahedron* c = tri.newTetrahedron(); a->joinTo(1, c, NPerm4(2, 0, 3, 1)); b->joinTo(1, a, NPerm4(2, 0, 3, 1)); c->joinTo(1, b, NPerm4(2, 0, 3, 1)); NSatTriPrism* ans = new NSatTriPrism(major); const NPerm4 id; const NPerm4 pairSwap(1, 0, 3, 2); ans->annulus_[0].tet[0] = a; ans->annulus_[0].tet[1] = b; ans->annulus_[0].roles[0] = id; ans->annulus_[0].roles[1] = pairSwap; ans->annulus_[1].tet[0] = b; ans->annulus_[1].tet[1] = c; ans->annulus_[1].roles[0] = id; ans->annulus_[1].roles[1] = pairSwap; ans->annulus_[2].tet[0] = c; ans->annulus_[2].tet[1] = a; ans->annulus_[2].roles[0] = id; ans->annulus_[2].roles[1] = pairSwap; if (! major) { ans->annulus_[0].reflectVertical(); ans->annulus_[1].reflectVertical(); ans->annulus_[2].reflectVertical(); } return ans; } void NSatCube::adjustSFS(NSFSpace& sfs, bool reflect) const { sfs.insertFibre(1, reflect ? -2 : 2); } NSatCube* NSatCube::isBlockCube(const NSatAnnulus& annulus, TetList& avoidTets) { if (annulus.tet[0] == annulus.tet[1]) return 0; if (isBad(annulus.tet[0], avoidTets) || isBad(annulus.tet[1], avoidTets)) return 0; NTetrahedron* central0 = annulus.tet[0]->adjacentTetrahedron( annulus.roles[0][0]); NTetrahedron* central1 = annulus.tet[0]->adjacentTetrahedron( annulus.roles[0][1]); if (central0 == 0 || central0 == annulus.tet[0] || central0 == annulus.tet[1] || isBad(central0, avoidTets)) return 0; if (central1 == 0 || central1 == annulus.tet[0] || central1 == annulus.tet[1] || central1 == central0 || isBad(central0, avoidTets)) return 0; NPerm4 roles0 = annulus.tet[0]->adjacentGluing( annulus.roles[0][0]) * annulus.roles[0]; NPerm4 roles1 = annulus.tet[0]->adjacentGluing( annulus.roles[0][1]) * annulus.roles[0]; // We've got the two central tetrahedra. Now look for the remaining // three boundary tetrahedra. if (annulus.tet[1]->adjacentTetrahedron(annulus.roles[1][0]) != central0) return 0; if (annulus.tet[1]->adjacentTetrahedron(annulus.roles[1][1]) != central1) return 0; if (annulus.tet[1]->adjacentGluing(annulus.roles[1][0]) * annulus.roles[1] * NPerm4(3, 2, 1, 0) != roles0) return 0; if (annulus.tet[1]->adjacentGluing(annulus.roles[1][1]) * annulus.roles[1] * NPerm4(2, 3, 0, 1) != roles1) return 0; // We've got the two tetrahedra from the annulus boundary completely // sorted out. Just the two new boundary tetrahedra to go. NTetrahedron* bdry2 = central0->adjacentTetrahedron(roles0[1]); NPerm4 roles2 = central0->adjacentGluing(roles0[1]) * roles0; NTetrahedron* bdry3 = central0->adjacentTetrahedron(roles0[2]); NPerm4 roles3 = central0->adjacentGluing(roles0[2]) * roles0; if (bdry2 == 0 || bdry2 == annulus.tet[0] || bdry2 == annulus.tet[1] || bdry2 == central0 || bdry2 == central1 || isBad(bdry2, avoidTets)) return 0; if (bdry3 == 0 || bdry3 == annulus.tet[0] || bdry3 == annulus.tet[1] || bdry3 == central0 || bdry3 == central1 || bdry3 == bdry2 || isBad(bdry3, avoidTets)) return 0; if (central1->adjacentTetrahedron(roles1[0]) != bdry2) return 0; if (central1->adjacentTetrahedron(roles1[2]) != bdry3) return 0; if (central1->adjacentGluing(roles1[0]) * roles1 != roles2) return 0; if (central1->adjacentGluing(roles1[2]) * roles1 * NPerm4(1, 0, 3, 2) != roles3) return 0; // All looking good! NSatCube* ans = new NSatCube(); const NPerm4 id; ans->annulus_[0] = annulus; ans->annulus_[1].tet[0] = annulus.tet[1]; ans->annulus_[1].tet[1] = bdry2; ans->annulus_[1].roles[0] = annulus.roles[1] * NPerm4(1, 0, 3, 2); ans->annulus_[1].roles[1] = roles2; ans->annulus_[2].tet[0] = bdry2; ans->annulus_[2].tet[1] = bdry3; ans->annulus_[2].roles[0] = roles2 * NPerm4(1, 0, 3, 2); ans->annulus_[2].roles[1] = roles3 * NPerm4(2, 3, 0, 1); ans->annulus_[3].tet[0] = bdry3; ans->annulus_[3].tet[1] = annulus.tet[0]; ans->annulus_[3].roles[0] = roles3 * NPerm4(3, 2, 1, 0); ans->annulus_[3].roles[1] = annulus.roles[0] * NPerm4(1, 0, 3, 2); avoidTets.insert(annulus.tet[0]); avoidTets.insert(annulus.tet[1]); avoidTets.insert(bdry2); avoidTets.insert(bdry3); avoidTets.insert(central0); avoidTets.insert(central1); return ans; } NSatCube* NSatCube::insertBlock(NTriangulation& tri) { NTetrahedron* bdry0 = tri.newTetrahedron(); NTetrahedron* bdry1 = tri.newTetrahedron(); NTetrahedron* bdry2 = tri.newTetrahedron(); NTetrahedron* bdry3 = tri.newTetrahedron(); NTetrahedron* central0 = tri.newTetrahedron(); NTetrahedron* central1 = tri.newTetrahedron(); const NPerm4 id; bdry0->joinTo(1, central0, id); bdry0->joinTo(0, central1, NPerm4(0, 1)); bdry1->joinTo(2, central0, NPerm4(2, 1, 3, 0)); bdry1->joinTo(0, central1, NPerm4(0, 3)); bdry2->joinTo(0, central0, id); bdry2->joinTo(1, central1, NPerm4(0, 1)); bdry3->joinTo(3, central0, NPerm4(0, 3, 1, 2)); bdry3->joinTo(1, central1, NPerm4(1, 2)); NSatCube* ans = new NSatCube(); ans->annulus_[0].tet[0] = bdry0; ans->annulus_[0].tet[1] = bdry1; ans->annulus_[1].tet[0] = bdry1; ans->annulus_[1].tet[1] = bdry2; ans->annulus_[2].tet[0] = bdry2; ans->annulus_[2].tet[1] = bdry3; ans->annulus_[3].tet[0] = bdry3; ans->annulus_[3].tet[1] = bdry0; ans->annulus_[0].roles[0] = NPerm4(0, 1); ans->annulus_[0].roles[1] = NPerm4(2, 0, 3, 1); ans->annulus_[1].roles[0] = NPerm4(1, 2); ans->annulus_[1].roles[1] = NPerm4(0, 1); ans->annulus_[2].roles[0] = NPerm4(2, 3); ans->annulus_[2].roles[1] = NPerm4(0, 3); ans->annulus_[3].roles[0] = NPerm4(1, 3, 0, 2); ans->annulus_[3].roles[1] = NPerm4(2, 3); return ans; } void NSatReflectorStrip::adjustSFS(NSFSpace& sfs, bool) const { if (! twistedBoundary_) sfs.addReflector(false); } NSatReflectorStrip* NSatReflectorStrip::isBlockReflectorStrip( const NSatAnnulus& annulus, TetList& avoidTets) { // Hunt for the initial segment of the reflector strip that lies // behind the given annulus. if (annulus.tet[0] == annulus.tet[1]) return 0; if (isBad(annulus.tet[0], avoidTets) || isBad(annulus.tet[1], avoidTets)) return 0; NTetrahedron* middle = annulus.tet[0]->adjacentTetrahedron( annulus.roles[0][0]); NPerm4 middleRoles = annulus.tet[0]->adjacentGluing( annulus.roles[0][0]) * annulus.roles[0] * NPerm4(3, 1, 0, 2); if (notUnique(middle, annulus.tet[0], annulus.tet[1]) || isBad(middle, avoidTets)) if (middle != annulus.tet[0]->adjacentTetrahedron( annulus.roles[0][1])) return 0; if (middle != annulus.tet[1]->adjacentTetrahedron( annulus.roles[1][0])) return 0; if (middle != annulus.tet[1]->adjacentTetrahedron( annulus.roles[1][1])) return 0; if (middleRoles != annulus.tet[0]->adjacentGluing( annulus.roles[0][1]) * annulus.roles[0] * NPerm4(1, 3)) return 0; if (middleRoles != annulus.tet[1]->adjacentGluing( annulus.roles[1][0]) * annulus.roles[1] * NPerm4(0, 2, 3, 1)) return 0; if (middleRoles != annulus.tet[1]->adjacentGluing( annulus.roles[1][1]) * annulus.roles[1] * NPerm4(0, 2)) return 0; // We've found the initial segment. // Do we just have a segment of length one? if (annulus.tet[0]->adjacentTetrahedron(annulus.roles[0][2]) == annulus.tet[1]) { // It's either length one or nothing. if (annulus.roles[1] == annulus.tet[0]->adjacentGluing( annulus.roles[0][2]) * annulus.roles[0] * NPerm4(0, 1)) { // Got one that's untwisted. NSatReflectorStrip* ans = new NSatReflectorStrip(1, false); ans->annulus_[0] = annulus; avoidTets.insert(annulus.tet[0]); avoidTets.insert(middle); avoidTets.insert(annulus.tet[1]); return ans; } if (annulus.roles[1] == annulus.tet[0]->adjacentGluing( annulus.roles[0][2]) * annulus.roles[0]) { // Got one that's twisted. NSatReflectorStrip* ans = new NSatReflectorStrip(1, true); ans->annulus_[0] = annulus; avoidTets.insert(annulus.tet[0]); avoidTets.insert(middle); avoidTets.insert(annulus.tet[1]); return ans; } // Nup. Nothing. return 0; } // If anything, we have a segment of length >= 2. Start following // it around. // Make a list storing the tetrahedra from left to right around the // boundary ring. We must use a list and not a set, since we will // rely on the tetrahedra being stored in a particular order. std::list foundSoFar; foundSoFar.push_back(annulus.tet[0]); foundSoFar.push_back(middle); foundSoFar.push_back(annulus.tet[1]); // Also make a list of tetrahedron vertex roles for the two // tetrahedra in each segment that meet the boundary annuli. std::list rolesSoFar; rolesSoFar.push_back(annulus.roles[0]); rolesSoFar.push_back(annulus.roles[1]); unsigned length = 1; bool twisted = false; NTetrahedron *nextLeft, *nextMiddle, *nextRight; NPerm4 nextLeftRoles, nextMiddleRoles, nextRightRoles; while (1) { // Run off the right hand side looking for the next tetrahedron. nextLeft = foundSoFar.back()->adjacentTetrahedron( rolesSoFar.back()[2]); nextLeftRoles = foundSoFar.back()->adjacentGluing( rolesSoFar.back()[2]) * rolesSoFar.back() * NPerm4(0, 1); if (nextLeft == annulus.tet[0]) { // The right _might_ have completed! if (nextLeftRoles == annulus.roles[0]) { // All good! An untwisted strip. } else if (nextLeftRoles == annulus.roles[0] * NPerm4(0, 1)) { // A complete twisted strip. twisted = true; } else { // Nothing. return 0; } NSatReflectorStrip* ans = new NSatReflectorStrip(length, twisted); std::copy(foundSoFar.begin(), foundSoFar.end(), std::inserter(avoidTets, avoidTets.begin())); std::list::const_iterator tit = foundSoFar.begin(); std::list::const_iterator pit = rolesSoFar.begin(); for (unsigned i = 0; i < length; i++) { ans->annulus_[i].tet[0] = *tit++; tit++; // Skip the middle tetrahedron from each block. ans->annulus_[i].tet[1] = *tit++; ans->annulus_[i].roles[0] = *pit++; ans->annulus_[i].roles[1] = *pit++; } return ans; } // Look for a new adjacent block. if (notUnique(nextLeft) || isBad(nextLeft, avoidTets) || isBad(nextLeft, foundSoFar)) return 0; nextMiddle = nextLeft->adjacentTetrahedron(nextLeftRoles[0]); nextMiddleRoles = nextLeft->adjacentGluing( nextLeftRoles[0]) * nextLeftRoles * NPerm4(3, 1, 0, 2); if (notUnique(nextMiddle, nextLeft) || isBad(nextMiddle, avoidTets) || isBad(nextMiddle, foundSoFar)) return 0; if (nextMiddle != nextLeft->adjacentTetrahedron(nextLeftRoles[1])) return 0; if (nextMiddleRoles != nextLeft->adjacentGluing( nextLeftRoles[1]) * nextLeftRoles * NPerm4(1, 3)) return 0; nextRight = nextMiddle->adjacentTetrahedron(nextMiddleRoles[0]); nextRightRoles = nextMiddle->adjacentGluing( nextMiddleRoles[0]) * nextMiddleRoles * NPerm4(0, 3, 1, 2); if (notUnique(nextRight, nextLeft, nextMiddle) || isBad(nextRight, avoidTets) || isBad(nextRight, foundSoFar)) return 0; if (nextRight != nextMiddle->adjacentTetrahedron(nextMiddleRoles[1])) return 0; if (nextRightRoles != nextMiddle->adjacentGluing( nextMiddleRoles[1]) * nextMiddleRoles * NPerm4(0, 2)) return 0; // Yup, we have a new block. foundSoFar.push_back(nextLeft); foundSoFar.push_back(nextMiddle); foundSoFar.push_back(nextRight); rolesSoFar.push_back(nextLeftRoles); rolesSoFar.push_back(nextRightRoles); length++; } // We should never get out of the loop this way. return 0; } NSatReflectorStrip* NSatReflectorStrip::insertBlock(NTriangulation& tri, unsigned length, bool twisted) { NSatReflectorStrip* ans = new NSatReflectorStrip(length, twisted); const NPerm4 id; NTetrahedron *upper, *lower, *middle; NTetrahedron *prevRight = 0, *firstLeft = 0; for (unsigned i = 0; i < length; i++) { // Create the three tetrahedra behind boundary annulus #i. upper = tri.newTetrahedron(); lower = tri.newTetrahedron(); middle = tri.newTetrahedron(); upper->joinTo(0, middle, NPerm4(2, 1, 3, 0)); lower->joinTo(0, middle, NPerm4(0, 3, 1, 2)); upper->joinTo(1, middle, NPerm4(1, 3)); lower->joinTo(1, middle, NPerm4(0, 2)); if (i == 0) firstLeft = upper; else upper->joinTo(2, prevRight, NPerm4(0, 1)); prevRight = lower; ans->annulus_[i].tet[0] = upper; ans->annulus_[i].tet[1] = lower; ans->annulus_[i].roles[0] = id; ans->annulus_[i].roles[1] = id; } if (twisted) firstLeft->joinTo(2, prevRight, id); else firstLeft->joinTo(2, prevRight, NPerm4(0, 1)); return ans; } void NSatLayering::adjustSFS(NSFSpace& sfs, bool reflect) const { if (overHorizontal_) sfs.insertFibre(1, reflect ? -2 : 2); // Over the diagonal, there is no change at all. } NSatLayering* NSatLayering::isBlockLayering(const NSatAnnulus& annulus, TetList& avoidTets) { // Must be a common usable tetrahedron. if (annulus.tet[0] != annulus.tet[1]) return 0; if (isBad(annulus.tet[0], avoidTets)) return 0; // Is it a layering over the horizontal edge? if (annulus.roles[0][0] == annulus.roles[1][2] && annulus.roles[0][2] == annulus.roles[1][0]) { avoidTets.insert(annulus.tet[0]); NSatLayering* ans = new NSatLayering(true); ans->annulus_[0] = annulus; ans->annulus_[1].tet[0] = ans->annulus_[1].tet[1] = annulus.tet[0]; ans->annulus_[1].roles[0] = annulus.roles[1] * NPerm4(1, 0, 3, 2); ans->annulus_[1].roles[1] = annulus.roles[0] * NPerm4(1, 0, 3, 2); return ans; } // Is it a layering over the diagonal edge? if (annulus.roles[0][1] == annulus.roles[1][2] && annulus.roles[0][2] == annulus.roles[1][1]) { avoidTets.insert(annulus.tet[0]); NSatLayering* ans = new NSatLayering(false); ans->annulus_[0] = annulus; ans->annulus_[1].tet[0] = ans->annulus_[1].tet[1] = annulus.tet[0]; ans->annulus_[1].roles[0] = annulus.roles[1] * NPerm4(1, 0, 3, 2); ans->annulus_[1].roles[1] = annulus.roles[0] * NPerm4(1, 0, 3, 2); return ans; } // No layering. return 0; } } // namespace regina regina-4.95/engine/subcomplex/nsatblocktypes.h000644 000765 000024 00000070253 12234011536 021457 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsatblocktypes.h * \brief Describes several types of saturated blocks within Seifert fibred * space triangulations. */ #ifndef __NSATBLOCKTYPES_H #ifndef __DOXYGEN #define __NSATBLOCKTYPES_H #endif #include "regina-core.h" #include "subcomplex/nsatblock.h" namespace regina { class NLayeredSolidTorus; /** * \weakgroup subcomplex * @{ */ /** * A degenerate zero-tetrahedron saturated block that corresponds to * attaching a Mobius band to a single annulus boundary. * * This is a degenerate case of the layered solid torus (see the class * NSatLST), where instead of joining a solid torus to an annulus * boundary we join a Mobius band. The Mobius band can be thought of as * a zero-tetrahedron solid torus with two boundary triangles, which in fact * are opposite sides of the same triangle. By attaching a zero-tetrahedron * Mobius band to an annulus boundary, we are effectively joining the * two triangles of the annulus together. * * The meridinal disc of this zero-tetrahedron solid torus meets the * three edges of the annulus in 1, 1 and 2 places, so it is in fact * a degenerate (1,1,2) layered solid torus. Note that the weight 2 edge * is the boundary edge of the Mobius strip. */ class REGINA_API NSatMobius : public NSatBlock { private: int position_; /**< Describes how the Mobius band is attached to the boundary annulus. This can take the value 0, 1 or 2. See the position() documentation for further details. */ public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatMobius(const NSatMobius& cloneMe); /** * Describes how the Mobius band is attached to the * boundary annulus. * * The class notes discuss the weight two edge of the Mobius band * (or equivalently the boundary edge of the Mobius band). The * return value of this routine indicates which edge of the * boundary annulus this weight two edge is joined to. * * In the NSatAnnulus class notes, the three edges of the * annulus are denoted vertical, horizontal and boundary, and * the vertices of each triangle are given markings 0, 1 and 2. * * The return value of this routine takes the value 0, 1 or 2 as * follows: * - 0 means that the weight two edge is joined to the diagonal * edge of the annulus (markings 1 and 2); * - 1 means that the weight two edge is joined to the horizontal * edge of the annulus (markings 0 and 2); * - 2 means that the weight two edge is joined to the vertical * edge of the annulus (markings 0 and 1). * * @return the value 0, 1 or 2 as described above. */ int position() const; virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (Mobius band). This routine is * a specific case of NSatBlock::isBlock(); see that routine for * further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatMobius* isBlockMobius(const NSatAnnulus& annulus, TetList& avoidTets); private: /** * Constructs a partially initialised block. The boundary * annulus will remain uninitialised, and must be initialised * before this block can be used. * * @param position indicates which edge of the boundary annulus * meets the weight two edge of the Mobius strip, as described in * the position() member function documentation. This value * must be 0, 1 or 2. */ NSatMobius(int position); }; /** * A saturated block that is a layered solid torus. See the * NLayeredSolidTorus class for details. * * The three boundary edges of the layered solid torus are attached to * the vertical, horizontal and diagonal edges of the boundary annulus; * see the NSatAnnulus class notes for details on precisely what * vertical, horizontal and diagonal mean. */ class REGINA_API NSatLST : public NSatBlock { private: NLayeredSolidTorus* lst_; /**< Contains details of the layered solid torus that this block represents. */ NPerm4 roles_; /**< Describes how the layered solid torus is attached to the boundary annulus. In particular, edge groups \a roles_[0], \a roles_[1] and \a roles_[2] of the layered solid torus are attached to the vertical, horizontal and diagonal edges of the annulus respectively. */ public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatLST(const NSatLST& cloneMe); /** * Destroys this structure and its internal components. */ ~NSatLST(); /** * Returns details of the layered solid torus that this block * represents. * * @return details of the layered solid torus. */ const NLayeredSolidTorus* lst() const; /** * Describes how the layered solid torus is attached to the * boundary annulus. * * The NLayeredSolidTorus class notes describe top-level edge * groups 0, 1 and 2 for a layered solid torus. On the other * hand, the NSatAnnulus class notes define vertical, horizontal * and diagonal edges on the boundary annulus. * * Suppose that the permutation returned by this routine is \a r. * This indicates that: * - edge group \a r[0] is attached to the vertical annulus edges; * - edge group \a r[1] is attached to the horizontal annulus edges; * - edge group \a r[2] is attached to the diagonal annulus edges. * * The image \a r[3] will always be 3. * * @return a description of how the layered solid torus is * attached to the boundary annulus. */ NPerm4 roles() const; virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void transform(const NTriangulation* originalTri, const NIsomorphism* iso, NTriangulation* newTri); virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (layered solid torus). This routine is * a specific case of NSatBlock::isBlock(); see that routine for * further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatLST* isBlockLST(const NSatAnnulus& annulus, TetList& avoidTets); private: /** * Constructs a partially initialised block. The boundary * annulus will remain uninitialised, and must be initialised * before this block can be used. * * @param lst details of the layered solid torus. * @param roles describes how the layered solid torus is * attached to the boundary annulus, as explained in the * \a roles_ data member documentation. */ NSatLST(NLayeredSolidTorus* lst, NPerm4 roles); }; /** * A saturated block that is a three-tetrahedron triangular prism. * * Such a prism may be of major type or of minor type. In a \e major * type prism, the horizontal edges of the boundary annuli are all * major (degree three) edges of the prism. Likewise, in a \e minor * type prism, the horizontal boundary edges are all minor (degree two) * edges of the prism. See the NSatAnnulus class notes for a definition * of "horizontal" and the NTriSolidTorus class notes for further * details regarding "major" and "minor". */ class REGINA_API NSatTriPrism : public NSatBlock { private: bool major_; /**< Is this prism of major type or of minor type? */ public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatTriPrism(const NSatTriPrism& cloneMe); /** * Is this prism of major type or minor type? See the class * notes for further details. * * Note that this routine cannot be called major(), since on * some compilers that name clashes with a macro for isolating * major/minor bytes. * * @return \c true if this prism is of major type, or \c false * if it is of minor type. */ bool isMajor() const; virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (triangular prism). This routine is * a specific case of NSatBlock::isBlock(); see that routine for * further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatTriPrism* isBlockTriPrism(const NSatAnnulus& annulus, TetList& avoidTets); /** * Inserts a new copy of a triangular prism block into the given * triangulation, and returns the corresponding block structure. * * The given triangulation will not be emptied before the new * tetrahedra are inserted. * * @param tri the triangulation into which the new block should * be inserted. * @param major \c true if a block of major type should be inserted, * or \c false if a block of minor type should be inserted. * @return structural details of the newly inserted block. */ static NSatTriPrism* insertBlock(NTriangulation& tri, bool major); protected: /** * Constructs a partially initialised block. The boundary * annuli will remain uninitialised, and must be initialised * before this block can be used. * * @param major \c true if this block is of major type, or * \c false if it is of minor type. */ NSatTriPrism(bool major); private: /** * Implements a special case of isBlockTriPrism() to search for * a block of major type. See isBlockTriPrism() for further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatTriPrism* isBlockTriPrismMajor(const NSatAnnulus& annulus, TetList& avoidTets); }; /** * A saturated block that is a six-tetrahedron cube. * * There are several ways of triangulating a cube with six tetrahedra; * the specific method used here is illustrated in the diagram below * (where the top face of the cube is identified with the bottom). * * \image html cube.png * * Note that none of the four tetrahedra that meet the boundary annuli * touch each other, and that each of these four boundary tetrahedra * meet both central tetrahedra. Note also that (unlike other * triangulations) this cube cannot be split vertically into two * triangular prisms. */ class REGINA_API NSatCube : public NSatBlock { public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatCube(const NSatCube& cloneMe); virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (cube). This routine is a specific case * of NSatBlock::isBlock(); see that routine for further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatCube* isBlockCube(const NSatAnnulus& annulus, TetList& avoidTets); /** * Inserts a new copy of a cube block into the given triangulation, * and returns the corresponding block structure. * * The given triangulation will not be emptied before the new * tetrahedra are inserted. * * @param tri the triangulation into which the new block should * be inserted. * @return structural details of the newly inserted block. */ static NSatCube* insertBlock(NTriangulation& tri); protected: /** * Constructs an uninitialised block. The boundary annuli * must be initialised before this block can be used. */ NSatCube(); }; /** * A saturated block that is a reflector strip. * * A reflector strip is a ring of triangular prisms arranged end-to-end, * as illustrated in the diagram below. The top rectangle of each prism * is identified with the bottom in an orientation-reversing fashion * (the back edge moves to the front and vice versa), and the prisms * are joined in a loop from left to right. The Seifert fibres run * vertically in the diagram, with each saturated boundary annulus shaded * at the rear of each prism. * * \image html reflector.png * * The effect of a reflector strip is to create a reflector boundary in * the base orbifold of the surrounding Seifert fibred space. Each prism * provides a segment of this reflector boundary. * * A reflector strip may have arbitrary length, and it may also include * a twist as the ring of prisms wraps back around to meet itself. Note * that a twisted reflector strip will have a twisted ring of boundary * annuli, as described by NSatBlock::twistedBoundary(). * * The \e length of a reflector strip is defined to be the number of * prisms that are joined together, or equivalently the number of * saturated annuli on the boundary. */ class REGINA_API NSatReflectorStrip : public NSatBlock { public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatReflectorStrip(const NSatReflectorStrip& cloneMe); virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (reflector strip). This routine is a specific * case of NSatBlock::isBlock(); see that routine for further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatReflectorStrip* isBlockReflectorStrip( const NSatAnnulus& annulus, TetList& avoidTets); /** * Inserts a new reflector strip into the given triangulation, * and returns the corresponding block structure. * * The given triangulation will not be emptied before the new * tetrahedra are inserted. * * @param tri the triangulation into which the new block should * be inserted. * @param length the length of the new reflector strip, i.e., * the number of boundary annuli; this must be strictly positive. * @param twisted \c true if the new reflector strip should be twisted * (causing its ring of boundary annuli to be twisted also), or * \c false if the new strip should not be twisted. * @return structural details of the newly inserted block. */ static NSatReflectorStrip* insertBlock(NTriangulation& tri, unsigned length, bool twisted); protected: /** * Constructs a partially initialised block of the given length. * The boundary annuli will remain uninitialised, and must be * initialised before this block can be used. * * @param length the length of the new reflector strip, i.e., * the number of boundary annuli; this must be strictly positive. * @param twisted \c true if the strip should be twisted (giving * a twisted ring of boundary annuli), or \c false if not. */ NSatReflectorStrip(unsigned length, bool twisted); }; /** * A degenerate saturated block that is a single tetrahedron wrapped * around so that two opposite edges touch. This forms a degenerate * one-tetrahedron solid torus that is pinched along a single meridinal * curve. * * The four faces of this tetrahedron form two boundary annuli, and the * tetrahedron is effectively layered onto each boundary annulus. See * the NLayering class notes for more discussion on layerings in general. * * Although this block is degenerate (the fibres are all pinched * together where the opposite edges of the tetrahedron meet), it can be * used without problems as long as the entire Seifert fibred space is * not formed from degenerate blocks. In other words, using such blocks * is fine as long as they eventually meet a real (non-degenerate) block, * which will give room for the fibres to separate so that they are no * longer pinched together. * * The NSatAnnulus class notes describe horizontal and diagonal edges of * a saturated annulus. This block may be one of two types, according * to how the tetrahedron is layered onto the boundary annuli. Either * the tetrahedron can be layered over the horizontal edge of each * annulus (with the fibres pinched together between the two diagonal * edges), or the tetrahedron can be layered over the diagonal edge of * each annulus (with the fibres pinched together between the two * horizontal edges). */ class REGINA_API NSatLayering : public NSatBlock { private: bool overHorizontal_; /**< Do we layer over the horizontal annulus edge, or the diagonal annulus edge? */ public: /** * Constructs a clone of the given block structure. * * @param cloneMe the block structure to clone. */ NSatLayering(const NSatLayering& cloneMe); /** * Does this describe a layering over the horizontal edge of the * boundary annulus, or a layering over the diagonal edge? * * See the NSatAnnulus class notes for definitions of horizontal * and diagonal in this context. */ bool overHorizontal() const; virtual NSatBlock* clone() const; virtual void adjustSFS(NSFSpace& sfs, bool reflect) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeAbbr(std::ostream& out, bool tex = false) const; /** * Determines whether the given annulus is a boundary annulus for * a block of this type (single layering). This routine is * a specific case of NSatBlock::isBlock(); see that routine for * further details. * * @param annulus the proposed boundary annulus that should form * part of the new saturated block. * @param avoidTets the list of tetrahedra that should not be * considered, and to which any new tetrahedra will be added. * @return details of the saturated block if one was found, or * \c null if none was found. */ static NSatLayering* isBlockLayering(const NSatAnnulus& annulus, TetList& avoidTets); protected: /** * Constructs a partially initialised block. The boundary * annuli will remain uninitialised, and must be initialised * before this block can be used. * * @param overHorizontal \c true if this block describes a * layering over the horizontal edge of the boundary annulus, or * \c false if it describes a layering over the diagonal edge. */ NSatLayering(bool overHorizontal); }; /*@}*/ // Inline functions for NSatMobius inline NSatMobius::NSatMobius(const NSatMobius& cloneMe) : NSatBlock(cloneMe), position_(cloneMe.position_) { } inline NSatMobius::NSatMobius(int position) : NSatBlock(1), position_(position) { } inline int NSatMobius::position() const { return position_; } inline NSatBlock* NSatMobius::clone() const { return new NSatMobius(*this); } // Inline functions for NSatLST inline NSatLST::NSatLST(NLayeredSolidTorus* lst, NPerm4 roles) : NSatBlock(1), lst_(lst), roles_(roles) { } inline const NLayeredSolidTorus* NSatLST::lst() const { return lst_; } inline NPerm4 NSatLST::roles() const { return roles_; } inline NSatBlock* NSatLST::clone() const { return new NSatLST(*this); } // Inline functions for NSatTriPrism inline NSatTriPrism::NSatTriPrism(const NSatTriPrism& cloneMe) : NSatBlock(cloneMe), major_(cloneMe.major_) { } inline NSatTriPrism::NSatTriPrism(bool major) : NSatBlock(3), major_(major) { } inline bool NSatTriPrism::isMajor() const { return major_; } inline NSatBlock* NSatTriPrism::clone() const { return new NSatTriPrism(*this); } inline void NSatTriPrism::writeTextShort(std::ostream& out) const { out << "Saturated triangular prism of " << (major_ ? "major" : "minor") << " type"; } inline void NSatTriPrism::writeAbbr(std::ostream& out, bool tex) const { if (tex) out << "\\triangle"; else out << "Tri"; } // Inline functions for NSatCube inline NSatCube::NSatCube(const NSatCube& cloneMe) : NSatBlock(cloneMe) { } inline NSatCube::NSatCube() : NSatBlock(4) { } inline NSatBlock* NSatCube::clone() const { return new NSatCube(*this); } inline void NSatCube::writeTextShort(std::ostream& out) const { out << "Saturated cube"; } inline void NSatCube::writeAbbr(std::ostream& out, bool tex) const { if (tex) out << "\\square"; else out << "Cube"; } // Inline functions for NSatReflectorStrip inline NSatReflectorStrip::NSatReflectorStrip( const NSatReflectorStrip& cloneMe) : NSatBlock(cloneMe) { } inline NSatReflectorStrip::NSatReflectorStrip(unsigned length, bool twisted) : NSatBlock(length, twisted) { } inline NSatBlock* NSatReflectorStrip::clone() const { return new NSatReflectorStrip(*this); } inline void NSatReflectorStrip::writeTextShort(std::ostream& out) const { out << "Saturated reflector strip of length " << nAnnuli(); if (twistedBoundary()) out << " (twisted)"; } inline void NSatReflectorStrip::writeAbbr(std::ostream& out, bool tex) const { if (twistedBoundary()) { if (tex) out << "\\tilde{\\circledash}_" << nAnnuli(); else out << "Ref~(" << nAnnuli() << ')'; } else { if (tex) out << "\\circledash_" << nAnnuli(); else out << "Ref(" << nAnnuli() << ')'; } } // Inline functions for NSatLayering inline NSatLayering::NSatLayering(const NSatLayering& cloneMe) : NSatBlock(cloneMe), overHorizontal_(cloneMe.overHorizontal_) { } inline NSatLayering::NSatLayering(bool overHorizontal) : NSatBlock(2), overHorizontal_(overHorizontal) { } inline bool NSatLayering::overHorizontal() const { return overHorizontal_; } inline NSatBlock* NSatLayering::clone() const { return new NSatLayering(*this); } inline void NSatLayering::writeTextShort(std::ostream& out) const { out << "Saturated single layering over " << (overHorizontal_ ? "horizontal" : "diagonal") << " edge"; } inline void NSatLayering::writeAbbr(std::ostream& out, bool tex) const { if (tex) out << "lozenge"; else out << "Layer"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nsatregion.cpp000644 000765 000024 00000050361 12235500316 021114 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/nsfs.h" #include "subcomplex/nsatblockstarter.h" #include "subcomplex/nsatregion.h" #include "triangulation/nedge.h" #include "triangulation/ntetrahedron.h" #include "utilities/ptrutils.h" #include #include namespace regina { namespace { /** * An anonymous inline boolean xor. I'm always afraid to use ^ with * bool, since I'm never sure if this bitwise operator will do the * right thing on all platforms. */ inline bool regXor(bool a, bool b) { return ((a && ! b) || (b && ! a)); } } NSatRegion::NSatRegion(NSatBlock* starter) : baseEuler_(1), baseOrbl_(true), hasTwist_(false), twistsMatchOrientation_(true), shiftedAnnuli_(0), twistedBlocks_(0), nBdryAnnuli_(starter->nAnnuli()) { blocks_.push_back(NSatBlockSpec(starter, false, false)); if (starter->twistedBoundary()) { hasTwist_ = true; twistsMatchOrientation_ = false; twistedBlocks_ = 1; } } NSatRegion::~NSatRegion() { for (BlockSet::iterator it = blocks_.begin(); it != blocks_.end(); it++) delete it->block; } const NSatAnnulus& NSatRegion::boundaryAnnulus(unsigned long which, bool& blockRefVert, bool& blockRefHoriz) const { unsigned ann; for (BlockSet::const_iterator it = blocks_.begin(); it != blocks_.end(); it++) for (ann = 0; ann < it->block->nAnnuli(); ann++) if (! it->block->hasAdjacentBlock(ann)) { if (which == 0) { blockRefVert = it->refVert; blockRefHoriz = it->refHoriz; return it->block->annulus(ann); } which--; } // Given the precondition, we should never reach this point. // We need to return a reference, so to keep the compiler happy, // create a memory leak. Again, we should never actually reach this point. return *(new NSatAnnulus()); } void NSatRegion::boundaryAnnulus(unsigned long which, NSatBlock*& block, unsigned& annulus, bool& blockRefVert, bool& blockRefHoriz) const { unsigned ann; for (BlockSet::const_iterator it = blocks_.begin(); it != blocks_.end(); it++) for (ann = 0; ann < it->block->nAnnuli(); ann++) if (! it->block->hasAdjacentBlock(ann)) { if (which == 0) { block = it->block; annulus = ann; blockRefVert = it->refVert; blockRefHoriz = it->refHoriz; return; } which--; } // Given the precondition, we should never reach this point. } NSFSpace* NSatRegion::createSFS(bool reflect) const { // Count boundary components. unsigned untwisted, twisted; countBoundaries(untwisted, twisted); // Go ahead and build the Seifert fibred space. NSFSpace::classType baseClass; bool bdry = (twisted || untwisted || twistedBlocks_); if (baseOrbl_) { if (hasTwist_) baseClass = (bdry ? NSFSpace::bo2 : NSFSpace::o2); else baseClass = (bdry ? NSFSpace::bo1 : NSFSpace::o1); } else if (! hasTwist_) baseClass = (bdry ? NSFSpace::bn1 : NSFSpace::n1); else if (twistsMatchOrientation_) baseClass = (bdry ? NSFSpace::bn2 : NSFSpace::n2); else { // In the no-boundary case, we might not be able to distinguish // between n3 and n4. Just call it n3 for now, and if we discover // it might have been n4 instead then we call it off and return 0. baseClass = (bdry ? NSFSpace::bn3 : NSFSpace::n3); } // Recall that baseEuler_ assumes that each block contributes a plain // old disc to the base orbifold (and, in particular, it ignores any // reflector boundaries arising from twistedBlocks_). This lets us // calculate genus just by looking at baseEuler_, orientability and // the number of punctures. NSFSpace* sfs = new NSFSpace(baseClass, (baseOrbl_ ? ((2 - baseEuler_) - twisted - untwisted) / 2 : ((2 - baseEuler_) - twisted - untwisted)), untwisted /* untwisted punctures */, twisted /* twisted punctures */, 0 /* untwisted reflectors */, twistedBlocks_ /* twisted reflectors */); for (BlockSet::const_iterator it = blocks_.begin(); it != blocks_.end(); it++) it->block->adjustSFS(*sfs, ! regXor(reflect, regXor(it->refVert, it->refHoriz))); if (shiftedAnnuli_) sfs->insertFibre(1, reflect ? -shiftedAnnuli_ : shiftedAnnuli_); if ((sfs->baseGenus() >= 3) && (sfs->baseClass() == NSFSpace::n3 || sfs->baseClass() == NSFSpace::n4)) { // Could still be either n3 or n4. // Shrug, give up. delete sfs; return 0; } return sfs; } bool NSatRegion::expand(NSatBlock::TetList& avoidTets, bool stopIfIncomplete) { NSatBlockSpec currBlockSpec; NSatBlock *currBlock, *adjBlock; unsigned ann, adjAnn; unsigned long adjPos; bool adjVert, adjHoriz; bool currTwisted, currNor; unsigned annBdryTriangles; // Try to push past the boundary annuli of all blocks present and future. // We rely on a vector data type for BlockSet here, since this // will keep the loop doing exactly what it should do even if new // blocks are added and blockFound.size() increases. for (unsigned long pos = 0; pos < blocks_.size(); pos++) { currBlockSpec = blocks_[pos]; currBlock = currBlockSpec.block; // Run through each boundary annulus for this block. for (ann = 0; ann < currBlock->nAnnuli(); ann++) { if (currBlock->hasAdjacentBlock(ann)) continue; // Do we have one or two boundary triangles? annBdryTriangles = currBlock->annulus(ann).meetsBoundary(); if (annBdryTriangles == 2) { // The annulus lies completely on the triangulation // boundary. Just skip it. continue; } else if (annBdryTriangles == 1) { // The annulus lies half on the boundary. No chance of // extending it from here, but we have no chance of // filling the entire triangulation. if (stopIfIncomplete) return false; continue; } // We can happily jump to the other side, since we know // there are tetrahedra present. // Is there a new block there? if ((adjBlock = NSatBlock::isBlock( currBlock->annulus(ann).otherSide(), avoidTets))) { // We found a new adjacent block that we haven't seen before. // Note that, since the annuli are not horizontally // reflected, the blocks themselves will be. currBlock->setAdjacent(ann, adjBlock, 0, false, false); blocks_.push_back(NSatBlockSpec(adjBlock, false, ! currBlockSpec.refHoriz)); nBdryAnnuli_ = nBdryAnnuli_ + adjBlock->nAnnuli() - 2; // Note whether the new block has twisted boundary. if (adjBlock->twistedBoundary()) { hasTwist_ = true; twistsMatchOrientation_ = false; twistedBlocks_++; } // On to the next annulus! continue; } // No adjacent block. // Perhaps it's joined to something we've already seen? // Only search forwards from this annulus. if (ann + 1 < currBlock->nAnnuli()) { adjPos = pos; adjAnn = ann + 1; } else { adjPos = pos + 1; adjAnn = 0; } while (adjPos < blocks_.size()) { adjBlock = blocks_[adjPos].block; if ((! adjBlock->hasAdjacentBlock(adjAnn)) && currBlock->annulus(ann).isAdjacent( adjBlock->annulus(adjAnn), &adjVert, &adjHoriz)) { // They match! currBlock->setAdjacent(ann, adjBlock, adjAnn, adjVert, adjHoriz); nBdryAnnuli_ -= 2; // See what kinds of inconsistencies this // rejoining has caused. currNor = regXor(regXor(currBlockSpec.refHoriz, blocks_[adjPos].refHoriz), ! adjHoriz); currTwisted = regXor(regXor(currBlockSpec.refVert, blocks_[adjPos].refVert), adjVert); if (currNor) baseOrbl_ = false; if (currTwisted) hasTwist_ = true; if (regXor(currNor, currTwisted)) twistsMatchOrientation_ = false; // See if we need to add a (1,1) shift before // the annuli can be identified. if (regXor(adjHoriz, adjVert)) { if (regXor(currBlockSpec.refHoriz, currBlockSpec.refVert)) shiftedAnnuli_--; else shiftedAnnuli_++; } break; } if (adjAnn + 1 < adjBlock->nAnnuli()) adjAnn++; else { adjPos++; adjAnn = 0; } } // If we found a match, we're done. Move on to the next annulus. if (adjPos < blocks_.size()) continue; // We couldn't match the annulus to anything. if (stopIfIncomplete) return false; } } // Well, we got as far as we got. calculateBaseEuler(); return true; } long NSatRegion::blockIndex(const NSatBlock* block) const { BlockSet::const_iterator it; unsigned long id; for (id = 0, it = blocks_.begin(); it != blocks_.end(); it++, id++) if (block == it->block) return id; return -1; } void NSatRegion::calculateBaseEuler() { BlockSet::const_iterator it; unsigned ann; long faces = blocks_.size(); long edgesBdry = 0; long edgesInternalDoubled = 0; for (it = blocks_.begin(); it != blocks_.end(); it++) for (ann = 0; ann < it->block->nAnnuli(); ann++) if (it->block->hasAdjacentBlock(ann)) edgesInternalDoubled++; else edgesBdry++; // When counting vertices, don't just count unique edges in the // triangulation -- we could run into strife with edge identifications // outside the region. Count the boundary vertices separately (this // is easy, since it's the same as the number of boundary edges). std::set baseVerticesAll; std::set baseVerticesBdry; NSatAnnulus annData; for (it = blocks_.begin(); it != blocks_.end(); it++) for (ann = 0; ann < it->block->nAnnuli(); ann++) { annData = it->block->annulus(ann); baseVerticesAll.insert(annData.tet[0]->getEdge( NEdge::edgeNumber[annData.roles[0][0]][annData.roles[0][1]])); if (! it->block->hasAdjacentBlock(ann)) { baseVerticesBdry.insert(annData.tet[0]->getEdge( NEdge::edgeNumber[annData.roles[0][0]][annData.roles[0][1]])); baseVerticesBdry.insert(annData.tet[1]->getEdge( NEdge::edgeNumber[annData.roles[1][0]][annData.roles[1][1]])); } } // To summarise what was said above: the internal vertices are // guaranteed to give distinct elements in the baseVertices sets, // but the boundary vertices are not. Thus we calculate internal // vertices via the sets, but boundary vertices via edgesBdry instead. long vertices = baseVerticesAll.size() - baseVerticesBdry.size() + edgesBdry; baseEuler_ = faces - edgesBdry - (edgesInternalDoubled / 2) + vertices; } void NSatRegion::writeBlockAbbrs(std::ostream& out, bool tex) const { typedef std::multiset > OrderedBlockSet; OrderedBlockSet blockOrder; for (BlockSet::const_iterator it = blocks_.begin(); it != blocks_.end(); it++) blockOrder.insert(it->block); for (OrderedBlockSet::const_iterator it = blockOrder.begin(); it != blockOrder.end(); it++) { if (it != blockOrder.begin()) out << ", "; (*it)->writeAbbr(out, tex); } } void NSatRegion::writeDetail(std::ostream& out, const std::string& title) const { out << title << ":\n"; BlockSet::const_iterator it; unsigned long id, nAnnuli, ann; bool ref, back; out << " Blocks:\n"; for (id = 0, it = blocks_.begin(); it != blocks_.end(); it++, id++) { out << " " << id << ". "; it->block->writeTextShort(out); nAnnuli = it->block->nAnnuli(); out << " (" << nAnnuli << (nAnnuli == 1 ? " annulus" : " annuli"); if (it->refVert || it->refHoriz) { out << ", "; if (it->refVert && it->refHoriz) out << "vert./horiz."; else if (it->refVert) out << "vert."; else out << "horiz."; out << " reflection"; } out << ")\n"; } out << " Adjacencies:\n"; for (id = 0, it = blocks_.begin(); it != blocks_.end(); it++, id++) for (ann = 0; ann < it->block->nAnnuli(); ann++) { out << " " << id << '/' << ann << " --> "; if (! it->block->hasAdjacentBlock(ann)) out << "bdry"; else { out << blockIndex(it->block->adjacentBlock(ann)) << '/' << it->block->adjacentAnnulus(ann); ref = it->block->adjacentReflected(ann); back = it->block->adjacentBackwards(ann); if (ref || back) { if (ref && back) out << " (reflected, backwards)"; else if (ref) out << " (reflected)"; else out << " (backwards)"; } } out << "\n"; } } void NSatRegion::writeTextShort(std::ostream& out) const { unsigned long size = blocks_.size(); out << "Saturated region with " << size << (size == 1 ? " block" : " blocks"); } void NSatRegion::countBoundaries(unsigned& untwisted, unsigned& twisted) const { untwisted = twisted = 0; // Just trace around each boundary component in turn. // Note that we are guaranteed that blocks_ is non-empty. unsigned i, j; // Count annuli in each block, and work out how to index all annuli // from all blocks into a single monolithic array. unsigned* nAnnuli = new unsigned[blocks_.size()]; unsigned* indexAnnuliFrom = new unsigned[blocks_.size()]; for (i = 0; i < blocks_.size(); ++i) { nAnnuli[i] = blocks_[i].block->nAnnuli(); indexAnnuliFrom[i] = (i == 0 ? 0 : indexAnnuliFrom[i-1] + nAnnuli[i-1]); } unsigned totAnnuli = indexAnnuliFrom[blocks_.size() - 1] + nAnnuli[blocks_.size() - 1]; // Prepare to keep track of which annuli we've processed. bool* used = new bool[totAnnuli]; std::fill(used, used + totAnnuli, false); // Off we go! NSatBlock *b, *currBlock, *tmpBlock; unsigned currBlockIndex, currAnnulus, tmpAnnulus; bool hTwist, vTwist, tmpHTwist, tmpVTwist; for (i = 0; i < blocks_.size(); ++i) { b = blocks_[i].block; for (j = 0; j < nAnnuli[i]; ++j) { // Here's our next annulus to examine. if (used[indexAnnuliFrom[i] + j]) { // Ignore: we've already processed this before. continue; } if (b->hasAdjacentBlock(j)) { // Ignore: this annulus is internal. used[indexAnnuliFrom[i] + j] = true; continue; } // This annulus is on the boundary, and not yet processed. // Run around the entire boundary component, marking annuli // as processed, and testing whether we close with a twist. currBlock = b; currBlockIndex = i; currAnnulus = j; hTwist = false; vTwist = false; while (true) { used[indexAnnuliFrom[currBlockIndex] + currAnnulus] = true; currBlock->nextBoundaryAnnulus(currAnnulus, tmpBlock, tmpAnnulus, tmpVTwist, tmpHTwist, hTwist); if (tmpVTwist) vTwist = ! vTwist; if (tmpHTwist) hTwist = ! hTwist; currBlock = tmpBlock; currAnnulus = tmpAnnulus; // Gaa. We need a block pointer -> index lookup. // Use a slow search for now. for (currBlockIndex = 0; currBlockIndex < blocks_.size(); ++currBlockIndex) if (blocks_[currBlockIndex].block == currBlock) break; if (currBlockIndex >= blocks_.size()) { std::cerr << "ERROR: Could not index current block." << std::endl; } if (currBlock == b && currAnnulus == j) break; } // See how the boundary component closed itself off. if (hTwist) { std::cerr << "ERROR: Unexpected hTwist in boundary tracing." << std::endl; } if (vTwist) ++twisted; else ++untwisted; } } //std::cout << "Region: " << twisted << " twisted, " << untwisted // << " untwisted." << std::endl; delete[] nAnnuli; delete[] indexAnnuliFrom; delete[] used; } } // namespace regina regina-4.95/engine/subcomplex/nsatregion.h000644 000765 000024 00000074374 12234011536 020573 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsatregion.h * \brief Supports connected regions of saturated blocks in triangulations * of Seifert fibred spaces. */ #ifndef __NSATREGION_H #ifndef __DOXYGEN #define __NSATREGION_H #endif #include "regina-core.h" #include "subcomplex/nsatblock.h" #include namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Describes how a single saturated block forms a part of a larger * saturated region. * * A saturated region consists of several saturated blocks joined * together along their boundary annuli. This is a helper structure * containing a single saturated block along with details of its * orientation within a larger region. * * The ring of saturated annuli around the boundary of the block gives a * natural orientation to the block within the context of the base orbifold, * just as the ring of edges around a polygon would give a natural * orientation to that polygon within the context of a surrounding surface. * Again drawing an analogy with the orientation of polygons within a surface, * each block can be considered to have a correct or reflected orientation * according to whether this ring of annuli runs clockwise or anticlockwise * in the base orbifold. * * The precise orientation of a block is described using two booleans. * A block may be reflected \e horizontally, which preserves the * directions of Seifert fibres but which reverses the * clockwise/anticlockwise orientation as discussed above. A block may * also be reflected \e vertically, which preserves the * clockwise/anticlockwise orientation but which reverses the directions * of the Seifert fibres. A block may of course be reflected both * horizontally and vertically, or it may not be reflected at all. * * This helper structure is small, and can be copied by value if * necessary. Be aware that when this structure is destroyed, the * internal block structure of type NSatBlock is \e not destroyed. * * \ifacespython The data members \a block, \a refVert and \a refHoriz * are read-only. */ struct REGINA_API NSatBlockSpec { NSatBlock* block; /**< Details of the saturated block structure. */ bool refVert; /**< Indicates whether the block is reflected vertically within the larger region. See the class notes for details. */ bool refHoriz; /**< Indicates whether the block is reflected horizontally within the larger region. See the class notes for details. */ /** * Creates a new structure that is completely uninitialised. */ NSatBlockSpec(); /** * Creates a new structure that is initialised to the given set of * values. * * @param useBlock details of the saturated block structure. * @param useRefVert \c true if the block is reflected vertically * within the larger region, or \c false otherwise. * @param useRefHoriz \c true if the block is reflected horizontally * within the larger region, or \c false otherwise. */ NSatBlockSpec(NSatBlock* useBlock, bool useRefVert, bool useRefHoriz); }; /** * A large saturated region in a Seifert fibred space formed by joining * together saturated blocks. * * Like a saturated block (described in the class NSatBlock), a * saturated region is a connected set of tetrahedra built from a subset * of fibres. Unlike a saturated block however, a saturated region has * no constraints on its boundary - it may have several boundary * components or it may have none. For instance, a saturated region * might be an entire closed Seifert fibred space, or it might describe * a Seifert fibred component of a JSJ decomposition. * * A saturated region is formed from a collection of saturated blocks by * joining the boundary annuli of these blocks together in pairs. The * joins must be made so that the fibres are consistent, though it is * allowable to reverse the directions of the fibres. There is no problem * with joining two boundary annuli from the same block to each other. * * Any boundary annulus of a block that is not joined to some other * boundary annulus of a block becomes a boundary annulus of the entire * region. In this way, each boundary component of the region (if there * are any at all) is formed from a ring of boundary annuli, in the same * way that the boundary of a block is. Note that the routine * NSatBlock::nextBoundaryAnnulus() can be used to trace around a region * boundary. Like block boundaries, the boundary of a saturated region * need not be part of the boundary of the larger triangulation (i.e., * there may be adjacent tetrahedra that are not recognised as part of * this saturated structure). * * The NSatRegion class stores a list of its constituent blocks, but it * does not directly store which block boundary annuli are joined to * which. This adjacency information is stored within the blocks * themselves; see the notes regarding adjacency in the NSatBlock class * description. * * Blocks cannot be added to a region by hand. The way a region is * constructed is by locating some initial block within a triangulation * and passing this to the NSatRegion constructor, and then by calling * expand() to locate adjacent blocks and expand the region as far as * possible. For locating initial blocks, the class * NSatBlockStarterSearcher may be of use. * * \warning It is crucial that the adjacency information stored in the * blocks is consistent with the region containing them. All this * requires is that the blocks are not manipulated externally (e.g., * NSatBlock::setAdjacent() is not called on any of the blocks), but * instead all adjacency information is managed by this class. Routines * such as expand() which may add more blocks to the region will update * the block adjacencies accordingly. * * \todo \feature Have this class track the boundary components properly, * with annuli grouped and oriented according to the region boundaries (as * opposed to individual block boundaries). */ class REGINA_API NSatRegion : public ShareableObject { private: typedef std::vector BlockSet; /**< The data structure used to store the list of constituent blocks. */ BlockSet blocks_; /**< The set of blocks from which this region is formed, along with details of how they are oriented within this larger region. */ long baseEuler_; /**< The Euler characteristic of the base orbifold if we assume that each block contributes a trivial disc to the base orbifold. */ bool baseOrbl_; /**< Denotes whether the base orbifold is orientable if we assume that each block contributes a trivial disc to the base orbifold. */ bool hasTwist_; /**< Denotes whether we can find a fibre-reversing path that does not step inside the interior of any constituent blocks. */ bool twistsMatchOrientation_; /**< Denotes whether set of fibre-reversing paths corresponds precisely to the set of orientation-reversing paths on the base orbifold, where we do not allow paths that step inside the interior of any constituent blocks. */ long shiftedAnnuli_; /**< The number of additional (1,1) twists added to the underlying Seifert fibred space due to blocks being sheared up or down as they are joined together. This typically happens when the triangulations of two boundary annuli are not compatible when joined (e.g., they provide opposite diagonal edges) */ unsigned long twistedBlocks_; /**< The number of constituent blocks with twisted boundary. Each such block provides an additional twisted reflector boundary to the base orbifold; see NSatBlock::adjustSFS() for details. */ unsigned long nBdryAnnuli_; /**< The number of saturated annuli forming the boundary components (if any) of this region. */ public: /** * Constructs a new region containing just the given block. * All boundary annuli of the given block will become boundary * annuli of this region. It is guaranteed that this block will * be stored in the region without any kind of reflection (see * NSatBlockSpec for details). * * Typically a region is initialised using this constructor, and * then grown using the expand() routine. For help in finding * an initial starter block, see the NSatBlockStarterSearcher class. * * This region will claim ownership of the given block, and upon * destruction it will destroy this block also. * * \pre The given block has no adjacencies listed. That is, * for every boundary annulus of the given block, * NSatBlock::hasAdjacentBlock() returns \c false. * * @param starter the single block that this region will describe. */ NSatRegion(NSatBlock* starter); /** * Destroys this structure and all of its internal data, * including the individual blocks that make up this region. */ virtual ~NSatRegion(); /** * Returns the number of saturated blocks that come together * to form this saturated region. */ unsigned long numberOfBlocks() const; /** * Returns details of the requested saturated block within this * region. The information will returned will include * structural information for the block, along with details of * how the block is aligned (e.g., reflected vertically or * horizontally) within the larger region. * * @param which indicates which of the constituent blocks should * be returned; this must be between 0 and numberOfBlocks()-1 * inclusive. * @return details of the requested saturated block. */ const NSatBlockSpec& block(unsigned long which) const; /** * Returns the index of the given block within this region. * This index corresponds to the integer parameter that is passed * to the routine block(). * * \warning This routine is slow, since it simply scans through * the blocks in this region one by one until the given block is * found (or until all blocks are exhausted). * * @return the index of the given block (as an integer between * 0 and numberOfBlocks()-1 inclusive), or -1 if the block is * not part of this region. */ long blockIndex(const NSatBlock* block) const; /** * Returns the number of saturated annuli that together form the * boundary components of this region. * * @return the number of boundary annuli. */ unsigned long numberOfBoundaryAnnuli() const; /** * Returns the requested saturated annulus on the boundary of * this region. * * The saturated annuli that together form the boundary * components of this region are numbered from 0 to * numberOfBoundaryAnnuli()-1 inclusive. The argument \a which * specifies which one of these annuli should be returned. * * Currently the annuli are numbered lexicographically by * block and then by annulus number within the block, although * this ordering is subject to change in future versions of Regina. * In particular, the annuli are \e not necessarily numbered in * order around the region boundaries, and each region boundary * component might not even be given a consecutive range of numbers. * * It is guaranteed however that, if the starter block passed to * the NSatRegion constructor provides any boundary annuli for * the overall region, then the first such annulus in the starter * block will be numbered 0 here. * * The structure returned will be the annulus precisely as it * appears within its particular saturated block. As discussed * in the NSatBlockSpec class notes, the block might be * reflected horizontally and/or vertically within the overall * region, which will affect how the annulus is positioned as * part of the overall region boundary (e.g., the annulus might * be positioned upside-down in the overall region boundary, * or it might be positioned with its second triangle appearing before * its first triangle as one walks around the boundary). To account * for this, the two boolean arguments \a blockRefVert and * \a blockRefHoriz will be modified to indicate if and how the * block is reflected. * * \warning This routine is quite slow, since it currently scans * through every annulus of every saturated block. Use it * sparingly! * * \ifacespython Both variants of boundaryAnnulus() are * combined into a single routine, which takes the integer * \a which as its only argument and returns its results as a * tuple. See the alternate version of boundaryAnnulus() for * details on how the return tuple is structured. * * @param which specifies which boundary annulus of this region * to return; this must be between 0 and numberOfBoundaryAnnuli()-1 * inclusive. * @param blockRefVert used to return whether the block * containing the requested annulus is vertically reflected * within this region (see NSatBlockSpec for details). This * will be set to \c true if the block is vertically reflected, * or \c false if not. * @param blockRefHoriz used to return whether the block * containing the requested annulus is horizontally reflected * within this region (see NSatBlockSpec for details). This * will be set to \c true if the block is horizontally reflected, * or \c false if not. * @return details of the requested boundary annulus, precisely * as it appears within its particular saturated block. */ const NSatAnnulus& boundaryAnnulus(unsigned long which, bool& blockRefVert, bool& blockRefHoriz) const; /** * Returns fine details of the requested saturated annulus on * the boundary of this region. * * The argument \a which specifies which one of these * annuli should be returned. See the * boundaryAnnulus(unsigned long, bool&, bool&) documentation * for details on how the boundary annuli are numbered. * * Various details of the requested boundary annulus are * returned in the various arguments, as described below. * * Be aware that the block containing the requested annulus * might be reflected horizontally and/or vertically within the * overall region, as discussed in the NSatBlockSpec class notes. * This will affect how the annulus is positioned as * part of the overall region boundary (e.g., the annulus might * be positioned upside-down in the overall region boundary, * or it might be positioned with its second triangle appearing before * its first triangle as one walks around the boundary). The two * boolean arguments \a blockRefVert and \a blockRefHoriz will * be modified to indicate if and how the block is reflected. * * \warning This routine is quite slow, since it currently scans * through every annulus of every saturated block. Use it * sparingly! * * \ifacespython This routine only takes a single argument (the * integer \a which). The return value is a tuple of four values: * the block returned in \a block, the integer returned in \a annulus, * the boolean returned in \a blockRefVert, and the boolean returned * in \a blockRefHoriz. * * @param which specifies which boundary annulus of this region * to return; this must be between 0 and numberOfBoundaryAnnuli()-1 * inclusive. * @param block used to return the particular saturated block * containing the requested annulus. * @param annulus used to return which annulus number in the * returned block is the requested annulus; this will be between * 0 and block->nAnnuli() inclusive. * @param blockRefVert used to return whether the block * containing the requested annulus is vertically reflected * within this region (see NSatBlockSpec for details). This * will be set to \c true if the block is vertically reflected, * or \c false if not. * @param blockRefHoriz used to return whether the block * containing the requested annulus is horizontally reflected * within this region (see NSatBlockSpec for details). This * will be set to \c true if the block is horizontally reflected, * or \c false if not. */ void boundaryAnnulus(unsigned long which, NSatBlock*& block, unsigned& annulus, bool& blockRefVert, bool& blockRefHoriz) const; /** * Returns details of the Seifert fibred space represented by * this region. * * Each boundary component of this region will be formed from a ring * of saturated annuli, which together form a torus or a Klein bottle. * For torus boundary components, the oriented curves * representing the fibres and base orbifold on the boundary * (see \ref sfsnotation) will be as follows. * * - Consider the 0/1/2 markings on the first and second triangles * of each saturated annulus, as described in the NSatAnnulus * class notes. * - The fibres are represented by the oriented edge joining * markings 1 and 0 on the first triangle (or 0 and 1 on the * second triangle). This is reversed if the block containing the * boundary annulus is vertically reflected. * - The curve representing the base orbifold run along the * oriented edge joining markings 0 and 2 on the first triangle * (or 2 and 0 on the second triangle). This is reversed if the * block containing the boundary annulus is horizontally * reflected. * - See the NSatBlockSpec overview for descriptions of * horizontal and vertical reflection. * * If the argument \a reflect is \c true, the Seifert fibred * space will be created as though the entire region had been * reflected. In particular, each twist or exceptional fibre * will be negated before being added to the Seifert structure. * * For Klein bottle boundary components, these curves must (for * now) be analysed by hand. * * @param reflect \c true if this region is to be reflected * as the Seifert fibred space is created, or \c false if not. * @return the newly created structure of the underlying Seifert * fibred space. */ NSFSpace* createSFS(bool reflect) const; /** * A deprecated version of the routine that returns details of the * Seifert fibred space represented by this region. * * This is an old and now-deprecated version of createSFS(), which * imposed additional preconditions (all boundary components had to be * tori), and required the user to pass the number of boundary * components as the first argument. This first argument is now * ignored, and the preconditions have been relaxed to allow Klein * bottle boundary components also. * * See createSFS(bool), the new version of this routine, for further * details. * * \deprecated This routine simply ignores the first argument, * and passes the second argument through to createSFS(bool). * Code should be changed to call createSFS(bool) directly. * * @param reflect \c true if this region is to be reflected * as the Seifert fibred space is created, or \c false if not. * @return the newly created structure of the underlying Seifert * fibred space. */ NSFSpace* createSFS(long, bool reflect) const; /** * Expands this region as far as possible within the overall * triangulation. This routine will hunt for new saturated * blocks, and will also hunt for new adjacencies between * existing blocks. * * The first argument to this routine is the tetrahedron list * \a avoidTets. This is a list of tetrahedra that will not be * considered when examining potential new blocks. This list * will be modified by this routine; in particular, it will be * expanded to include all tetrahedra for any new blocks that * are found. Before calling this routine it should contain * tetrahedra for blocks already in this region, as discussed in * the preconditions below. * * It may be that you are searching for a region that fills an entire * triangulation component (i.e., every boundary annulus of the * region in fact forms part of the boundary of the triangulation). * In this case you may pass the optional argument * \a stopIfIncomplete as \c true. This means that if this routine * ever discovers an annulus that is not part of the * triangulation boundary and that it cannot match with some * adjacent block, it will exit immediately and return \c false. * Note that the region structure will be incomplete and/or * inconsistent if this happens; in this case the unfinished * region should be destroyed completely and never used. * * For internal purposes, it should be noted that any new blocks * that are discovered will be added to the end of the internal * block list (thus the indices of existing blocks will not change). * * \warning When joining blocks together, it is possible to * create invalid edges (e.g., by joining a one-annulus * untwisted boundary to a one-annulus twisted boundary). * This routine does \e not check for such conditions. It is * recommended that you run NTriangulation::isValid() before * calling this routine. * * \pre If any blocks already belonging to this region have * adjacencies listed in their NSatBlock structures, then these * adjacent blocks belong to this region also. This precondition * is easily satisfied if you let the NSatRegion constructor * and expand() do all of the adjacency handling, as described * in the class notes. * \pre The list \a avoidTets includes all tetrahedra on the * boundaries of any blocks already contained in this region. * * \ifacespython The first argument \a avoidTets is not present. * An empty list will be passed instead. * * @param avoidTets a list of tetrahedra that should not be * considered for new blocks, as discussed above. Note that * this list may be modified by this routine. * @param stopIfIncomplete \c true if you are filling an entire * triangulation component with this region and you wish this * routine to exit early if this is not possible, or \c false * (the default) if you simply wish to expand this region as far * as you can. See above for further discussion. * @return \c false if the optional argument \a stopIfIncomplete * was passed as \c true but expansion did not fill the entire * triangulation component as described above, or \c true in all * other cases. */ bool expand(NSatBlock::TetList& avoidTets, bool stopIfIncomplete = false); /** * Writes an abbreviated list of blocks within this region to * the given output stream. Blocks will be written using their * abbreviated names, and these names will be separated by * commas. See NSatBlock::writeAbbr() for further details. * * The blocks within this region will be sorted before their * abbreviated names are output. The particular method of sorting * is an arbitrary aesthetic decision on the part of the author, * and is subject to change in future versions of Regina. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @param tex \c true if the output should be formatted for TeX, * or \c false if it should be written as plain text. */ void writeBlockAbbrs(std::ostream& out, bool tex = false) const; /** * Writes details of the composition of this region to the given * output stream. * * The output will consist of several lines. The first line * will contain the title string (passed as a separate argument * to this routine), followed by a colon. Following this will be * a number of lines describing the individual blocks that make * up this region and the various adjacencies between them. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @param title the name of this region, to be written on the * first line of output. */ void writeDetail(std::ostream& out, const std::string& title) const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Runs through the region structure and recalculates the * \a baseEuler_ data member. * * No assumptions are made about whether edges of the boundary * annuli become identified due to features outside the region. * That is, this routine is safe to call even when this region * is joined to some other not-yet-understood sections of the * triangulation. */ void calculateBaseEuler(); /** * Each boundary component of this region will be formed from a ring * of saturated annuli, which is either untwisted (forming a * torus), or twisted (forming a Klein bottle). * This routine counts the total number of boundaries of each type. * * @param untwisted returns the number of untwisted (torus) * boundary components. * @param twisted returns the number of twisted (Klein bottle) * boundary components. */ void countBoundaries(unsigned& untwisted, unsigned& twisted) const; }; /*@}*/ // Inline functions for NSatBlockSpec inline NSatBlockSpec::NSatBlockSpec() { } inline NSatBlockSpec::NSatBlockSpec(NSatBlock* useBlock, bool useRefVert, bool useRefHoriz) : block(useBlock), refVert(useRefVert), refHoriz(useRefHoriz) { } // Inline functions for NSatRegion inline unsigned long NSatRegion::numberOfBlocks() const { return blocks_.size(); } inline const NSatBlockSpec& NSatRegion::block(unsigned long which) const { return blocks_[which]; } inline unsigned long NSatRegion::numberOfBoundaryAnnuli() const { return nBdryAnnuli_; } inline NSFSpace* NSatRegion::createSFS(long, bool reflect) const { return createSFS(reflect); } inline void NSatRegion::writeTextLong(std::ostream& out) const { writeDetail(out, "Saturated region"); } } // namespace regina #endif regina-4.95/engine/subcomplex/nsnappeacensustri.cpp000644 000765 000024 00000022152 12234011536 022505 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nsnappeacensusmfd.h" #include "subcomplex/nsnappeacensustri.h" #include "triangulation/ncomponent.h" #include "triangulation/nedge.h" #include "triangulation/ntriangle.h" #include "triangulation/nvertex.h" namespace regina { const char NSnapPeaCensusTri::SEC_5 = 'm'; const char NSnapPeaCensusTri::SEC_6_OR = 's'; const char NSnapPeaCensusTri::SEC_6_NOR = 'x'; const char NSnapPeaCensusTri::SEC_7_OR = 'v'; const char NSnapPeaCensusTri::SEC_7_NOR = 'y'; NSnapPeaCensusTri* NSnapPeaCensusTri::isSmallSnapPeaCensusTri( const NComponent* comp) { // Currently this routine can recognise SnapPea triangulations // m000 -- m004 as well as m129. // Since the triangulations are so small we can use census results // (from a census of all small valid ideal triangulations) to recognise // the triangulations by properties alone. // Before we do any further checks, make sure the number of // tetrahedra is within the supported range. if (comp->getNumberOfTetrahedra() > 4) return 0; // Start with property checks to see if it has a chance of being // in the SnapPea census at all. The component must not be // closed, every edge must be valid and every vertex link must be // either a torus or a Klein bottle. Note that this implies // that there are no boundary triangles. if (comp->isClosed()) return 0; unsigned long nVertices = comp->getNumberOfVertices(); unsigned long nEdges = comp->getNumberOfEdges(); unsigned long i; int link; for (i = 0; i < nVertices; i++) { link = comp->getVertex(i)->getLink(); if (link != NVertex::TORUS && link != NVertex::KLEIN_BOTTLE) return 0; } for (i = 0; i < nEdges; i++) if (! comp->getEdge(i)->isValid()) return 0; // Now search for specific triangulations. if (comp->getNumberOfTetrahedra() == 1) { // At this point it must be m000, since there are no others // that fit these constraints. But test orientability // anyway just to be safe. if (comp->isOrientable()) return 0; return new NSnapPeaCensusTri(SEC_5, 0); } else if (comp->getNumberOfTetrahedra() == 2) { if (comp->isOrientable()) { // Orientable. Looking for m003 or m004. if (comp->getNumberOfVertices() != 1) return 0; if (comp->getNumberOfEdges() != 2) return 0; if (comp->getEdge(0)->getNumberOfEmbeddings() != 6 || comp->getEdge(1)->getNumberOfEmbeddings() != 6) return 0; // Now we know it's either m003 or m004. We distinguish // between them by triangle types, since all of m003's triangles // are Mobius bands and all of m004's triangles are horns. if (comp->getTriangle(0)->getType() == NTriangle::MOBIUS) return new NSnapPeaCensusTri(SEC_5, 3); else return new NSnapPeaCensusTri(SEC_5, 4); } else { // Non-orientable. Looking for m001 or m002. if (comp->getNumberOfVertices() == 1) { // Looking for m001. if (comp->getNumberOfEdges() != 2) return 0; if (! ((comp->getEdge(0)->getNumberOfEmbeddings() == 4 && comp->getEdge(1)->getNumberOfEmbeddings() == 8) || (comp->getEdge(0)->getNumberOfEmbeddings() == 8 && comp->getEdge(1)->getNumberOfEmbeddings() == 4))) return 0; // Census says it's m001 if no triangle forms a dunce hat. for (int i = 0; i < 4; i++) if (comp->getTriangle(i)->getType() == NTriangle::DUNCEHAT) return 0; return new NSnapPeaCensusTri(SEC_5, 1); } else if (comp->getNumberOfVertices() == 2) { // Looking for m002. if (comp->getNumberOfEdges() != 2) return 0; if (comp->getEdge(0)->getNumberOfEmbeddings() != 6 || comp->getEdge(1)->getNumberOfEmbeddings() != 6) return 0; // Census says it's m002 if some triangle forms a dunce hat. for (int i = 0; i < 4; i++) if (comp->getTriangle(i)->getType() == NTriangle::DUNCEHAT) return new NSnapPeaCensusTri(SEC_5, 2); return 0; } } } else if (comp->getNumberOfTetrahedra() == 4) { if (comp->isOrientable()) { // Search for the Whitehead link complement. // Note that this could be done with a smaller set of tests // since some can be deduced from others, but these tests // aren't terribly expensive anyway. if (comp->getNumberOfVertices() != 2) return 0; if (comp->getNumberOfEdges() != 4) return 0; if (comp->getVertex(0)->getLink() != NVertex::TORUS) return 0; if (comp->getVertex(1)->getLink() != NVertex::TORUS) return 0; if (comp->getVertex(0)->getNumberOfEmbeddings() != 8) return 0; if (comp->getVertex(1)->getNumberOfEmbeddings() != 8) return 0; // Census says it's the Whitehead link if some edge has // degree 8. for (int i = 0; i < 4; i++) if (comp->getEdge(i)->getNumberOfEmbeddings() == 8) return new NSnapPeaCensusTri(SEC_5, 129); return 0; } } // Not recognised after all. return 0; } NManifold* NSnapPeaCensusTri::getManifold() const { return new NSnapPeaCensusManifold(section, index); } NAbelianGroup* NSnapPeaCensusTri::getHomologyH1() const { return NSnapPeaCensusManifold(section, index).getHomologyH1(); } std::ostream& NSnapPeaCensusTri::writeName(std::ostream& out) const { out << "SnapPea " << section; // Pad the index with leading zeroes. // All sections are written with three-digit indices, except for // 7-tetrahedron orientable which uses four-digit indices. if (section == SEC_7_OR && index < 1000) out << '0'; if (index < 100) out << '0'; if (index < 10) out << '0'; out << index; return out; } std::ostream& NSnapPeaCensusTri::writeTeXName(std::ostream& out) const { out << section << "_{"; // Pad the index with leading zeroes. // All sections are written with three-digit indices, except for // 7-tetrahedron orientable which uses four-digit indices. if (section == SEC_7_OR && index < 1000) out << '0'; if (index < 100) out << '0'; if (index < 10) out << '0'; out << index << '}'; return out; } } // namespace regina regina-4.95/engine/subcomplex/nsnappeacensustri.h000644 000765 000024 00000021041 12234011536 022146 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsnappeacensustri.h * \brief Deals with 3-manifold triangulations from the SnapPea census. */ #ifndef __NSNAPPEACENSUSTRI_H #ifndef __DOXYGEN #define __NSNAPPEACENSUSTRI_H #endif #include "regina-core.h" #include "nstandardtri.h" namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Represents a 3-manifold triangulation from the SnapPea cusped census. * * The SnapPea cusped census is the census of cusped hyperbolic 3-manifolds * formed from up to seven tetrahedra. This census was tabulated by * Callahan, Hildebrand and Weeks, and is shipped with SnapPea 3.0d3 * (and also with Regina). * * The census is split into five different sections according to number * of tetrahedra and orientability. Each of these sections corresponds * to one of the section constants defined in this class. * * For further details regarding the SnapPea census, see "A census of cusped * hyperbolic 3-manifolds", Patrick J. Callahan, Martin V. Hildebrand and * Jeffrey R. Weeks, Math. Comp. 68 (1999), no. 225, pp. 321--332. * * Note that this class is closely tied to NSnapPeaCensusManifold. * In particular, the section constants defined in NSnapPeaCensusManifold * and NSnapPeaCensusTri are identical, and so may be freely mixed. * Furthermore, the section and index parameters of an NSnapPeaCensusTri * are identical to those of its corresponding NSnapPeaCensusManifold. * * All of the optional NStandardTriangulation routines are implemented * for this class. * * \testpart */ class REGINA_API NSnapPeaCensusTri: public NStandardTriangulation { public: static const char SEC_5; /**< Represents the collection of triangulations formed from five or fewer tetrahedra (both orientable and non-orientable). There are 415 triangulations in this section. */ static const char SEC_6_OR; /**< Represents the collection of orientable triangulations formed from six tetrahedra. There are 962 triangulations in this section. */ static const char SEC_6_NOR; /**< Represents the collection of non-orientable triangulations formed from six tetrahedra. There are 259 triangulations in this section. */ static const char SEC_7_OR; /**< Represents the collection of orientable triangulations formed from seven tetrahedra. There are 3552 triangulations in this section. */ static const char SEC_7_NOR; /**< Represents the collection of non-orientable triangulations formed from seven tetrahedra. There are 887 triangulations in this section. */ private: char section; /**< The section of the SnapPea census to which this triangulation belongs. This must be one of the section constants defined in this class. */ unsigned long index; /**< The index within the given section of this specific triangulation. Note that the first index in each section is zero. */ public: /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NSnapPeaCensusTri* clone() const; /** * Returns the section of the SnapPea census to which this * triangulation belongs. This will be one of the section constants * defined in this class. * * @return the section of the SnapPea census. */ char getSection() const; /** * Returns the index of this triangulation within its particular * section of the SnapPea census. Note that indices for each * section begin counting at zero. * * @return the index of this triangulation within its section. */ unsigned long getIndex() const; /** * Determines whether this and the given structure represent * the same triangulation from the SnapPea census. * * @param compare the structure with which this will be compared. * @return \c true if and only if this and the given structure * represent the same SnapPea census triangulation. */ bool operator == (const NSnapPeaCensusTri& compare) const; /** * Determines if the given triangulation component is one of the * smallest SnapPea census triangulations. * * This routine is able to recognise a small selection of * triangulations from the beginning of the SnapPea census, by * way of hard-coding their structures and properties. * Most triangulations from the census however will not be * recognised by this routine. * * @param comp the triangulation component to examine. * @return a newly created structure representing the small * SnapPea census triangulation, or \c null if the given * component is not one of the few SnapPea census * triangulations recognised by this routine. */ static NSnapPeaCensusTri* isSmallSnapPeaCensusTri( const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; private: /** * Creates a new SnapPea census triangulation with the given * parameters. */ NSnapPeaCensusTri(char newSection, unsigned long newIndex); friend class NSnapPeaCensusManifold; }; /*@}*/ // Inline functions for NSnapPeaCensusTri inline NSnapPeaCensusTri* NSnapPeaCensusTri::clone() const { return new NSnapPeaCensusTri(section, index); } inline char NSnapPeaCensusTri::getSection() const { return section; } inline unsigned long NSnapPeaCensusTri::getIndex() const { return index; } inline bool NSnapPeaCensusTri::operator == (const NSnapPeaCensusTri& compare) const { return (section == compare.section && index == compare.index); } inline NSnapPeaCensusTri::NSnapPeaCensusTri(char newSection, unsigned long newIndex) : section(newSection), index(newIndex) { } } // namespace regina #endif regina-4.95/engine/subcomplex/nsnappedball.cpp000644 000765 000024 00000006541 12234011536 021407 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "triangulation/ntetrahedron.h" #include "subcomplex/nsnappedball.h" namespace regina { NSnappedBall* NSnappedBall::clone() const { NSnappedBall* ans = new NSnappedBall(); ans->tet = tet; ans->equator = equator; return ans; } NSnappedBall* NSnappedBall::formsSnappedBall(NTetrahedron* tet) { int inFace1, inFace2; NPerm4 perm; for (inFace1 = 0; inFace1 < 3; inFace1++) if (tet->adjacentTetrahedron(inFace1) == tet) { perm = tet->adjacentGluing(inFace1); inFace2 = perm[inFace1]; if (perm == NPerm4(inFace1, inFace2)) { // This is it! NSnappedBall* ans = new NSnappedBall(); ans->tet = tet; ans->equator = NEdge::edgeNumber[inFace1][inFace2]; return ans; } } return 0; } NManifold* NSnappedBall::getManifold() const { return new NHandlebody(0, true); } NAbelianGroup* NSnappedBall::getHomologyH1() const { return new NAbelianGroup(); } } // namespace regina regina-4.95/engine/subcomplex/nsnappedball.h000644 000765 000024 00000016021 12234011536 021046 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsnappedball.h * \brief Deals with snapped 3-balls in a triangulation. */ #ifndef __NSNAPPEDBALL_H #ifndef __DOXYGEN #define __NSNAPPEDBALL_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" #include "triangulation/nedge.h" namespace regina { class NTetrahedron; /** * \weakgroup subcomplex * @{ */ /** * Represents a snapped 3-ball in a triangulation. * A snapped 3-ball is a single tetrahedron with two faces glued to each * other to form a 3-ball with a two triangle boundary. * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NSnappedBall : public NStandardTriangulation { private: NTetrahedron* tet; /**< The tetrahedron that forms the snapped ball. */ int equator; /**< The edge that forms the equator on the ball boundary. */ public: /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NSnappedBall* clone() const; /** * Returns the tetrahedron that forms this snapped ball. * * @return the tetrahedron. */ NTetrahedron* getTetrahedron() const; /** * Returns one of the two tetrahedron faces that forms the boundary * of this snapped ball. * * You are guaranteed that index 0 will return a smaller face * number than index 1. * * @param index specifies which of the two boundary faces to return; * this must be either 0 or 1. * @return the corresponding face number in the tetrahedron. */ int getBoundaryFace(int index) const; /** * Returns one of the two tetrahedron faces internal to this snapped * ball. * * You are guaranteed that index 0 will return a smaller face * number than index 1. * * @param index specifies which of the two internal faces to return; * this must be either 0 or 1. * @return the corresponding face number in the tetrahedron. */ int getInternalFace(int index) const; /** * Returns the edge that forms the equator of the boundary sphere * of this ball. * * @return the corresponding edge number in the tetrahedron. */ int getEquatorEdge() const; /** * Returns the edge internal to this snapped ball. * * @return the corresponding edge number in the tetrahedron. */ int getInternalEdge() const; /** * Determines if the given tetrahedron forms a snapped 3-ball * within a triangulation. The ball need not be the entire * triangulation; the boundary triangles may be glued to something * else (or to each other). * * Note that the two boundary triangles of the snapped 3-ball * need not be boundary triangles within the overall * triangulation, i.e., they may be identified with each other * or with triangles of other tetrahedra. * * @param tet the tetrahedron to examine as a potential 3-ball. * @return a newly created structure containing details of the * snapped 3-ball, or \c null if the given tetrahedron is * not a snapped 3-ball. */ static NSnappedBall* formsSnappedBall(NTetrahedron* tet); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NSnappedBall(); }; /*@}*/ // Inline functions for NSnappedBall inline NSnappedBall::NSnappedBall() { } inline NTetrahedron* NSnappedBall::getTetrahedron() const { return tet; } inline int NSnappedBall::getBoundaryFace(int index) const { return index == 0 ? NEdge::edgeVertex[5 - equator][0] : NEdge::edgeVertex[5 - equator][1]; } inline int NSnappedBall::getInternalFace(int index) const { return index == 0 ? NEdge::edgeVertex[equator][0] : NEdge::edgeVertex[equator][1]; } inline int NSnappedBall::getEquatorEdge() const { return equator; } inline int NSnappedBall::getInternalEdge() const { return 5 - equator; } inline std::ostream& NSnappedBall::writeName(std::ostream& out) const { return out << "Snap"; } inline std::ostream& NSnappedBall::writeTeXName(std::ostream& out) const { return out << "\\mathit{Snap}"; } inline void NSnappedBall::writeTextLong(std::ostream& out) const { out << "Snapped 3-ball"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nsnappedtwosphere.cpp000644 000765 000024 00000007250 12234011536 022513 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/ntetrahedron.h" #include "subcomplex/nsnappedtwosphere.h" #include "subcomplex/nsnappedball.h" namespace regina { NSnappedTwoSphere* NSnappedTwoSphere::clone() const { NSnappedTwoSphere* ans = new NSnappedTwoSphere(); ans->ball[0] = ball[0]->clone(); ans->ball[1] = ball[1]->clone(); return ans; } NSnappedTwoSphere* NSnappedTwoSphere::formsSnappedTwoSphere( NTetrahedron* tet1, NTetrahedron* tet2) { NSnappedBall* ball[2]; if (! (ball[0] = NSnappedBall::formsSnappedBall(tet1))) return 0; if (! (ball[1] = NSnappedBall::formsSnappedBall(tet2))) { delete ball[0]; return 0; } if (tet1->getEdge(ball[0]->getEquatorEdge()) != tet2->getEdge(ball[1]->getEquatorEdge())) { delete ball[0]; delete ball[1]; return 0; } // This is it. NSnappedTwoSphere* ans = new NSnappedTwoSphere(); ans->ball[0] = ball[0]; ans->ball[1] = ball[1]; return ans; } NSnappedTwoSphere* NSnappedTwoSphere::formsSnappedTwoSphere( NSnappedBall* ball1, NSnappedBall* ball2) { if (ball1->getTetrahedron()->getEdge(ball1->getEquatorEdge()) != ball2->getTetrahedron()->getEdge(ball2->getEquatorEdge())) return 0; // This is it. NSnappedTwoSphere* ans = new NSnappedTwoSphere(); ans->ball[0] = ball1->clone(); ans->ball[1] = ball2->clone(); return ans; } } // namespace regina regina-4.95/engine/subcomplex/nsnappedtwosphere.h000644 000765 000024 00000014365 12234011536 022165 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nsnappedtwosphere.h * \brief Deals with 2-spheres made from two snapped 3-balls in a * triangulation. */ #ifndef __NSNAPPEDTWOSPHERE_H #ifndef __DOXYGEN #define __NSNAPPEDTWOSPHERE_H #endif #include "regina-core.h" #include "subcomplex/nsnappedball.h" namespace regina { class NTetrahedron; class NTriangulation; class NSnappedBall; /** * \weakgroup subcomplex * @{ */ /** * Represents a 2-sphere made from two snapped 3-balls in a triangulation. * This occurs when two snapped 3-balls are glued together at their * equators (note that this gluing does not have to extend to triangular faces). * Each 3-ball has a central disc (bounded by the 3-ball's equator and bisecting * its internal edge), and these two discs together form an embedded * 2-sphere in the triangulation. * * This 2-sphere can be cut along and the two resulting 2-sphere * boundaries filled in with 3-balls, and the resulting triangulation has * the same number of tetrahedra as the original. If the snapped * 2-sphere was separating, the resulting triangulation will contain the * two terms of the corresponding connected sum. */ class REGINA_API NSnappedTwoSphere : public ShareableObject { private: NSnappedBall* ball[2]; /**< The two snapped 3-balls whose equators are joined. */ public: /** * Destroys this snapped 2-sphere; note that the corresponding * snapped 3-balls will also be destroyed. */ virtual ~NSnappedTwoSphere(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NSnappedTwoSphere* clone() const; /** * Returns one of the two snapped 3-balls whose equators are * joined. * * @param index specifies which of the two 3-balls to return; * this must be either 0 or 1. * @return the corresponding snapped 3-ball. */ const NSnappedBall* getSnappedBall(int index) const; /** * Determines if the two given tetrahedra together form a snapped * 2-sphere. * * \pre The two given tetrahedra are distinct. * * @param tet1 the first tetrahedron to examine. * @param tet2 the second tetrahedron to examine. * @return a newly created structure containing details of the * snapped 2-sphere, or \c null if the given tetrahedra do not * form a snapped 2-sphere. */ static NSnappedTwoSphere* formsSnappedTwoSphere(NTetrahedron* tet1, NTetrahedron* tet2); /** * Determines if the two given snapped 3-balls together form a snapped * 2-sphere. * * If this is the case, the snapped 3-balls stored in * the structure returned will be clones of the * original 3-balls, not the original 3-balls themselves. * * \pre The two given snapped 3-balls use distinct tetrahedra. * * @param ball1 the first snapped 3-ball to examine. * @param ball2 the second snapped 3-ball to examine. * @return a newly created structure containing details of the * snapped 2-sphere, or \c null if the given snapped 3-balls do not * form a snapped 2-sphere. */ static NSnappedTwoSphere* formsSnappedTwoSphere(NSnappedBall* ball1, NSnappedBall* ball2); void writeTextShort(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NSnappedTwoSphere(); }; /*@}*/ // Inline functions for NSnappedTwoSphere inline NSnappedTwoSphere::NSnappedTwoSphere() { } inline NSnappedTwoSphere::~NSnappedTwoSphere() { delete ball[0]; delete ball[1]; } inline const NSnappedBall* NSnappedTwoSphere::getSnappedBall(int index) const { return ball[index]; } inline void NSnappedTwoSphere::writeTextShort(std::ostream& out) const { out << "Snapped 2-sphere"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nspiralsolidtorus.cpp000644 000765 000024 00000015623 12234011536 022545 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "subcomplex/nspiralsolidtorus.h" #include "triangulation/ntriangulation.h" namespace regina { NSpiralSolidTorus* NSpiralSolidTorus::clone() const { NSpiralSolidTorus* ans = new NSpiralSolidTorus(nTet); for (unsigned long i = 0; i < nTet; i++) { ans->tet[i] = tet[i]; ans->vertexRoles[i] = vertexRoles[i]; } return ans; } void NSpiralSolidTorus::reverse() { NTetrahedron** newTet = new NTetrahedron*[nTet]; NPerm4* newRoles = new NPerm4[nTet]; NPerm4 switchPerm(3, 2, 1, 0); for (unsigned long i = 0; i < nTet; i++) { newTet[i] = tet[nTet - 1 - i]; newRoles[i] = vertexRoles[nTet - 1 - i] * switchPerm; } delete[] tet; delete[] vertexRoles; tet = newTet; vertexRoles = newRoles; } void NSpiralSolidTorus::cycle(unsigned long k) { NTetrahedron** newTet = new NTetrahedron*[nTet]; NPerm4* newRoles = new NPerm4[nTet]; for (unsigned long i = 0; i < nTet; i++) { newTet[i] = tet[(i + k) % nTet]; newRoles[i] = vertexRoles[(i + k) % nTet]; } delete[] tet; delete[] vertexRoles; tet = newTet; vertexRoles = newRoles; } bool NSpiralSolidTorus::makeCanonical(const NTriangulation* tri) { unsigned long i, index; unsigned long baseTet = 0; unsigned long baseIndex = tri->tetrahedronIndex(tet[0]); for (i = 1; i < nTet; i++) { index = tri->tetrahedronIndex(tet[i]); if (index < baseIndex) { baseIndex = index; baseTet = i; } } bool reverseAlso = (vertexRoles[baseTet][0] > vertexRoles[baseTet][3]); if (baseTet == 0 && (! reverseAlso)) return false; NTetrahedron** newTet = new NTetrahedron*[nTet]; NPerm4* newRoles = new NPerm4[nTet]; if (reverseAlso) { // Make baseTet into tetrahedron 0 and reverse. NPerm4 switchPerm(3, 2, 1, 0); for (unsigned long i = 0; i < nTet; i++) { newTet[i] = tet[(baseTet + nTet - i) % nTet]; newRoles[i] = vertexRoles[(baseTet + nTet - i) % nTet] * switchPerm; } } else { // Make baseTet into tetrahedron 0 but don't reverse. for (unsigned long i = 0; i < nTet; i++) { newTet[i] = tet[(i + baseTet) % nTet]; newRoles[i] = vertexRoles[(i + baseTet) % nTet]; } } delete[] tet; delete[] vertexRoles; tet = newTet; vertexRoles = newRoles; return true; } bool NSpiralSolidTorus::isCanonical(const NTriangulation* tri) const { if (vertexRoles[0][0] > vertexRoles[0][3]) return false; long baseIndex = tri->tetrahedronIndex(tet[0]); for (unsigned long i = 1; i < nTet; i++) if (tri->tetrahedronIndex(tet[i]) < baseIndex) return false; return true; } NSpiralSolidTorus* NSpiralSolidTorus::formsSpiralSolidTorus(NTetrahedron* tet, NPerm4 useVertexRoles) { NPerm4 invRoleMap(1, 2, 3, 0); // Maps upper roles to lower roles. NTetrahedron* base = tet; NPerm4 baseRoles(useVertexRoles); std::vector tets; std::vector roles; std::set usedTets; tets.push_back(tet); roles.push_back(useVertexRoles); usedTets.insert(tet); NTetrahedron* adjTet; NPerm4 adjRoles; while (1) { // Examine the tetrahedron beyond tet. adjTet = tet->adjacentTetrahedron(useVertexRoles[0]); adjRoles = tet->adjacentGluing(useVertexRoles[0]) * useVertexRoles * invRoleMap; // Check that we haven't hit the boundary. if (adjTet == 0) return 0; if (adjTet == base) { // We're back at the beginning of the loop. // Check that everything is glued up correctly. if (adjRoles != baseRoles) return 0; // Success! break; } if (usedTets.count(adjTet)) return 0; // Move on to the next tetrahedron. tet = adjTet; useVertexRoles = adjRoles; tets.push_back(tet); roles.push_back(useVertexRoles); usedTets.insert(tet); } // We've found a spiralled solid torus. NSpiralSolidTorus* ans = new NSpiralSolidTorus(tets.size()); copy(tets.begin(), tets.end(), ans->tet); copy(roles.begin(), roles.end(), ans->vertexRoles); return ans; } NManifold* NSpiralSolidTorus::getManifold() const { return new NHandlebody(1, true); } NAbelianGroup* NSpiralSolidTorus::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(); return ans; } } // namespace regina regina-4.95/engine/subcomplex/nspiralsolidtorus.h000644 000765 000024 00000030333 12234011536 022205 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nspiralsolidtorus.h * \brief Deals with spiralled solid tori in a triangulation. */ #ifndef __NSPIRALSOLIDTORUS_H #ifndef __DOXYGEN #define __NSPIRALSOLIDTORUS_H #endif #include "regina-core.h" #include "maths/nperm4.h" #include "subcomplex/nstandardtri.h" namespace regina { class NTetrahedron; class NTriangulation; /** * \weakgroup subcomplex * @{ */ /** * Represents a spiralled solid torus in a triangulation. * * A spiralled solid torus is created by placing tetrahedra one upon * another in a spiralling fashion to form a giant loop. * * For each tetrahedron, label the vertices A, B, C and D. Draw the * tetrahedron so that the vertices form an upward spiral in the order * A-B-C-D, with D directly above A. Face BCD is on the top, face ABC * is on the bottom and faces ABD and ACD are both vertical. * * When joining two tetrahedra, face BCD of the lower tetrahedron will * be joined to face ABC of the upper tetrahedron. In this way the * tetrahedra are placed one upon another to form a giant loop (which is * closed up by placing the bottommost tetrahedron above the topmost * tetrahedron in a similar fashion), forming a solid torus overall. * * In each tetrahedron, directed edges AB, BC and CD are major edges, * directed edges AC and BD are minor edges and directed edge AD * is an axis edge. * * The major edges all combined form a single longitude of the solid * torus. Using this directed longitude, using the directed meridinal curve * ACBA and assuming the spiralled solid torus contains \a n tetrahedra, * the minor edges all combined form a (2, n) curve and * the axis edges all combined form a (3, n) curve on the torus * boundary. * * Note that all tetrahedra in the spiralled solid torus must be distinct * and there must be at least one tetrahedron. * * Note also that class NTriSolidTorus represents a spiralled solid * torus with precisely three tetrahedra. A spiralled solid torus with * only one tetrahedron is in fact a (1,2,3) layered solid torus. * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NSpiralSolidTorus : public NStandardTriangulation { private: unsigned long nTet; /**< The number of tetrahedra in this spiralled solid torus. */ NTetrahedron** tet; /**< The tetrahedra that make up this spiralled solid torus. */ NPerm4* vertexRoles; /**< For tetrahedron \a i, vertexRoles[i] is a permutation p chosen so that vertices A, B, C and D above correspond to vertices p[0], p[1], p[2] and p[3]. */ public: /** * Destroys this spiralled solid torus. */ virtual ~NSpiralSolidTorus(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NSpiralSolidTorus* clone() const; /** * Returns the number of tetrahedra in this spiralled solid torus. * * @return the number of tetrahedra. */ unsigned long getNumberOfTetrahedra() const; /** * Returns the requested tetrahedron in this spiralled solid torus. * Tetrahedra are numbered from 0 to getNumberOfTetrahedra()-1 * inclusive, with tetrahedron i+1 being placed above * tetrahedron i. * * @param index specifies which tetrahedron to return; this must * be between 0 and getNumberOfTetrahedra()-1 inclusive. * @return the requested tetrahedron. */ NTetrahedron* getTetrahedron(unsigned long index) const; /** * Returns a permutation represeting the role that each vertex * of the requested tetrahedron plays in the solid torus. * The permutation returned (call this p) maps 0, 1, 2 and * 3 to the four vertices of tetrahedron \a index so that * vertices p[0], p[1], p[2] and * p[3] correspond to vertices A, B, C and D * respectively as described in the general class notes. * * In particular, the directed edge from vertex * p[0] to p[3] is an axis edge, * directed edges p[0] to p[2] and * p[1] to p[3] are minor edges and * the directed path from vertices p[0] to p[1] * to p[2] to p[3] follows the three * major edges. * * See the general class notes for further details. * * @param index specifies which tetrahedron in the solid torus * to examine; this must be between 0 and * getNumberOfTetrahedra()-1 inclusive. * @return a permutation representing the roles of the vertices * of the requested tetrahedron. */ NPerm4 getVertexRoles(unsigned long index) const; /** * Reverses this spiralled solid torus. * Tetrahedra 0, 1, 2, ..., getNumberOfTetrahedra()-1 will * become tetrahedra getNumberOfTetrahedra()-1, ..., 2, 1, 0 * respectively. Note that this operation will change the * vertex roles as well. * * The underlying triangulation is not changed; all that changes * is how this spiralled solid torus is represented. */ void reverse(); /** * Cycles this spiralled solid torus by the given number of * tetrahedra. * Tetrahedra k, k+1, k+2 and so on * will become tetrahedra 0, 1, 2 and so on respectively. * Note that this operation will not change the vertex roles. * * The underlying triangulation is not changed; all that changes * is how this spiralled solid torus is represented. * * @param k the number of tetrahedra through which we should cycle. */ void cycle(unsigned long k); /** * Converts this spiralled solid torus into its canonical * representation. The canonical representation of a spiralled * solid torus is unique in a given triangulation. * * Tetrahedron 0 in the spiralled solid torus will be the * tetrahedron with the lowest index in the triangulation, and * under permutation getVertexRoles(0) the image of 0 * will be less than the image of 3. * * @param tri the triangulation in which this solid torus lives. * @return \c true if and only if the representation of this * spiralled solid torus was actually changed. */ bool makeCanonical(const NTriangulation* tri); /** * Determines whether this spiralled solid torus is in canonical * form. Canonical form is described in detail in the * description for makeCanonical(). * * @param tri the triangulation in which this solid torus lives. * @return \c true if and only if this spiralled solid torus is * in canonical form. */ bool isCanonical(const NTriangulation* tri) const; /** * Determines if the given tetrahedron forms part of a * spiralled solid torus with its vertices * playing the given roles in the solid torus. * * Note that the boundary triangles of the spiralled solid * torus need not be boundary triangles within the overall * triangulation, i.e., they may be identified with each other * or with triangles of other tetrahedra. * * @param tet the tetrahedron to examine. * @param useVertexRoles a permutation describing the role each * tetrahedron vertex must play in the solid torus; this must be * in the same format as the permutation returned by * getVertexRoles(). * @return a newly created structure containing details of the * solid torus with the given tetrahedron as tetrahedron 0, or * \c null if the given tetrahedron is not part of a spiralled * solid torus with the given vertex roles. */ static NSpiralSolidTorus* formsSpiralSolidTorus(NTetrahedron* tet, NPerm4 useVertexRoles); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new partially initialised structure. * Member \a nTet will be initialised and dynamic arrays * \a tet and \a vertexRoles will be created. * * @param newNTet the number of tetrahedra in this spiralled * solid torus; this must be strictly positive. */ NSpiralSolidTorus(unsigned long newNTet); }; /*@}*/ // Inline functions for NSpiralSolidTorus inline NSpiralSolidTorus::NSpiralSolidTorus(unsigned long newNTet) : nTet(newNTet), tet(new NTetrahedron*[newNTet]), vertexRoles(new NPerm4[newNTet]) { } inline NSpiralSolidTorus::~NSpiralSolidTorus() { delete[] tet; delete[] vertexRoles; } inline unsigned long NSpiralSolidTorus::getNumberOfTetrahedra() const { return nTet; } inline NTetrahedron* NSpiralSolidTorus::getTetrahedron(unsigned long index) const { return tet[index]; } inline NPerm4 NSpiralSolidTorus::getVertexRoles(unsigned long index) const { return vertexRoles[index]; } inline std::ostream& NSpiralSolidTorus::writeName(std::ostream& out) const { return out << "Spiral(" << nTet << ')'; } inline std::ostream& NSpiralSolidTorus::writeTeXName(std::ostream& out) const { return out << "\\mathit{Spiral}(" << nTet << ')'; } inline void NSpiralSolidTorus::writeTextLong(std::ostream& out) const { out << nTet << "-tetrahedron spiralled solid torus"; } } // namespace regina #endif regina-4.95/engine/subcomplex/nstandardtri.cpp000644 000765 000024 00000011642 12234011536 021437 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "subcomplex/naugtrisolidtorus.h" #include "subcomplex/nblockedsfs.h" #include "subcomplex/nblockedsfsloop.h" #include "subcomplex/nblockedsfspair.h" #include "subcomplex/nblockedsfstriple.h" #include "subcomplex/nl31pillow.h" #include "subcomplex/nlayeredchainpair.h" #include "subcomplex/nlayeredlensspace.h" #include "subcomplex/nlayeredloop.h" #include "subcomplex/nlayeredsurfacebundle.h" #include "subcomplex/npluggedtorusbundle.h" #include "subcomplex/nplugtrisolidtorus.h" #include "subcomplex/nsnappeacensustri.h" #include "subcomplex/ntrivialtri.h" #include "triangulation/ntriangulation.h" namespace regina { std::string NStandardTriangulation::getName() const { std::ostringstream ans; writeName(ans); return ans.str(); } std::string NStandardTriangulation::getTeXName() const { std::ostringstream ans; writeTeXName(ans); return ans.str(); } NStandardTriangulation* NStandardTriangulation::isStandardTriangulation( NComponent* comp) { NStandardTriangulation* ans; if ((ans = NTrivialTri::isTrivialTriangulation(comp))) return ans; if ((ans = NL31Pillow::isL31Pillow(comp))) return ans; if ((ans = NLayeredLensSpace::isLayeredLensSpace(comp))) return ans; if ((ans = NLayeredLoop::isLayeredLoop(comp))) return ans; if ((ans = NLayeredChainPair::isLayeredChainPair(comp))) return ans; if ((ans = NAugTriSolidTorus::isAugTriSolidTorus(comp))) return ans; if ((ans = NPlugTriSolidTorus::isPlugTriSolidTorus(comp))) return ans; if ((ans = NLayeredSolidTorus::isLayeredSolidTorus(comp))) return ans; if ((ans = NSnapPeaCensusTri::isSmallSnapPeaCensusTri(comp))) return ans; return 0; } NStandardTriangulation* NStandardTriangulation::isStandardTriangulation( NTriangulation* tri) { if (tri->getNumberOfComponents() != 1) return 0; // Do what we can through components. NStandardTriangulation* ans; if ((ans = isStandardTriangulation(tri->getComponent(0)))) return ans; // Run tests that require entire triangulations. if ((ans = NBlockedSFS::isBlockedSFS(tri))) return ans; if ((ans = NLayeredTorusBundle::isLayeredTorusBundle(tri))) return ans; // Save non-geometric graph manifolds until last. if ((ans = NBlockedSFSLoop::isBlockedSFSLoop(tri))) return ans; if ((ans = NBlockedSFSPair::isBlockedSFSPair(tri))) return ans; if ((ans = NBlockedSFSTriple::isBlockedSFSTriple(tri))) return ans; if ((ans = NPluggedTorusBundle::isPluggedTorusBundle(tri))) return ans; // Nup. return 0; } } // namespace regina regina-4.95/engine/subcomplex/nstandardtri.h000644 000765 000024 00000024703 12234011536 021106 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/nstandardtri.h * \brief Deals with triangulations whose structures are well-understood. */ #ifndef __NSTANDARDTRI_H #ifndef __DOXYGEN #define __NSTANDARDTRI_H #endif #include "regina-core.h" #include "shareableobject.h" namespace regina { class NAbelianGroup; class NComponent; class NManifold; class NTriangulation; /** * \addtogroup subcomplex Standard Triangulations and Subcomplexes * Standard triangulations and subcomplexes of triangulations whose * structures are well-understood. * @{ */ /** * Describes a triangulation or subcomplex of a triangulation whose structure * is well-understood. An NStandardTriangulation is generally connected * with a real triangulation, i.e., an NTriangulation object, which it * describes some portion of. * * In general NStandardTriangulation objects cannot be constructed * directly, but are instead created through static identification * routines such as * NStandardTriangulation::isStandardTriangulation(NTriangulation*). * * Subclasses corresponding to different families of triangulations do * not need to override writeTextShort() since this routine is * properly implemented in the base class NStandardTriangulation. * * \testpart */ class REGINA_API NStandardTriangulation : public ShareableObject { public: /** * A destructor that does nothing. */ virtual ~NStandardTriangulation(); /** * Returns the name of this specific triangulation as a * human-readable string. * * @return the name of this triangulation. */ std::string getName() const; /** * Returns the name of this specific triangulation in TeX * format. No leading or trailing dollar signs will be included. * * \warning The behaviour of this routine has changed as of * Regina 4.3; in earlier versions, leading and trailing dollar * signs were provided. * * @return the name of this triangulation in TeX format. */ std::string getTeXName() const; /** * Returns the 3-manifold represented by this triangulation, if * such a recognition routine has been implemented. If the * 3-manifold cannot be recognised then this routine will return 0. * * The details of which standard triangulations have 3-manifold * recognition routines can be found in the notes for the * corresponding subclasses of NStandardTriangulation. The * default implementation of this routine returns 0. * * It is expected that the number of triangulations whose * underlying 3-manifolds can be recognised will grow between * releases. * * The 3-manifold will be newly allocated and must be destroyed * by the caller of this routine. * * @return the underlying 3-manifold. */ virtual NManifold* getManifold() const; /** * Returns the expected first homology group of this triangulation, * if such a routine has been implemented. If the calculation of * homology has not yet been implemented for this triangulation * then this routine will return 0. * * This routine does not work by calling * NTriangulation::getHomologyH1() on the associated real * triangulation. Instead the homology is calculated directly * from the known properties of this standard triangulation. * * The details of which standard triangulations have homology * calculation routines can be found in the notes for the * corresponding subclasses of NStandardTriangulation. The * default implementation of this routine returns 0. * * The homology group will be newly allocated and must be * destroyed by the caller of this routine. * * If this NStandardTriangulation describes an entire NTriangulation * (and not just a part thereof) then the results of this routine * should be identical to the homology group obtained by calling * NTriangulation::getHomologyH1() upon the associated real * triangulation. * * @return the first homology group of this triangulation, or 0 if * the appropriate calculation routine has not yet been implemented. */ virtual NAbelianGroup* getHomologyH1() const; /** * Writes the name of this triangulation as a human-readable * string to the given output stream. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeName(std::ostream& out) const = 0; /** * Writes the name of this triangulation in TeX format * to the given output stream. No leading or trailing dollar * signs will be included. * * \warning The behaviour of this routine has changed as of * Regina 4.3; in earlier versions, leading and trailing dollar * signs were provided. * * \ifacespython The parameter \a out does not exist; standard * output will be used. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeTeXName(std::ostream& out) const = 0; virtual void writeTextShort(std::ostream& out) const; /** * Determines whether the given component represents one of the * standard triangulations understood by Regina. The list of * recognised triangulations is expected to grow between * releases. * * If the standard triangulation returned has boundary triangles * then the given component must have the same corresponding * boundary triangles, i.e., the component cannot have any further * identifications of these boundary triangles with each other. * * Note that the triangulation-based routine * isStandardTriangulation(NTriangulation*) may recognise more * triangulations than this routine, since passing an entire * triangulation allows access to more information. * * @param component the triangulation component under examination. * @return the details of the standard triangulation if the * given component is recognised, or 0 otherwise. */ static NStandardTriangulation* isStandardTriangulation( NComponent* component); /** * Determines whether the given triangulation represents one of the * standard triangulations understood by Regina. The list of * recognised triangulations is expected to grow between * releases. * * If the standard triangulation returned has boundary triangles * then the given triangulation must have the same corresponding * boundary triangles, i.e., the triangulation cannot have any further * identifications of these boundary triangles with each other. * * This routine may recognise more triangulations than the * component-based isStandardTriangulation(NComponent*), * since passing an entire triangulation allows access to * more information. * * @param tri the triangulation under examination. * @return the details of the standard triangualation if the * given triangulation is recognised, or 0 otherwise. */ static NStandardTriangulation* isStandardTriangulation( NTriangulation* tri); }; /*@}*/ // Inline functions for NStandardTriangulation inline NStandardTriangulation::~NStandardTriangulation() { } inline NManifold* NStandardTriangulation::getManifold() const { return 0; } inline NAbelianGroup* NStandardTriangulation::getHomologyH1() const { return 0; } inline void NStandardTriangulation::writeTextShort(std::ostream& out) const { writeName(out); } } // namespace regina #endif regina-4.95/engine/subcomplex/ntrisolidtorus.cpp000644 000765 000024 00000016537 12234011536 022056 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "subcomplex/ntrisolidtorus.h" #include "subcomplex/nlayeredchain.h" #include "triangulation/ntetrahedron.h" namespace regina { NTriSolidTorus* NTriSolidTorus::clone() const { NTriSolidTorus* ans = new NTriSolidTorus(); for (int i = 0; i < 3; i++) { ans->tet[i] = tet[i]; ans->vertexRoles[i] = vertexRoles[i]; } return ans; } bool NTriSolidTorus::isAnnulusSelfIdentified(int index, NPerm4* roleMap) const { int lower = (index + 1) % 3; int upper = (index + 2) % 3; if (tet[lower]->adjacentTetrahedron(vertexRoles[lower][2]) != tet[upper]) return false; if (tet[lower]->adjacentFace(vertexRoles[lower][2]) != vertexRoles[upper][1]) return false; // We have a self-identification. if (roleMap) *roleMap = vertexRoles[upper].inverse() * tet[lower]->adjacentGluing(vertexRoles[lower][2]) * vertexRoles[lower]; return true; } unsigned long NTriSolidTorus::areAnnuliLinkedMajor(int otherAnnulus) const { int right = (otherAnnulus + 1) % 3; int left = (otherAnnulus + 2) % 3; NTetrahedron* adj = tet[right]->adjacentTetrahedron( vertexRoles[right][1]); if (adj != tet[left]->adjacentTetrahedron(vertexRoles[left][2])) return 0; if (adj == tet[0] || adj == tet[1] || adj == tet[2] || adj == 0) return 0; NPerm4 roles = tet[right]->adjacentGluing( vertexRoles[right][1]) * vertexRoles[right] * NPerm4(2, 3, 1, 0); if (roles != tet[left]->adjacentGluing( vertexRoles[left][2]) * vertexRoles[left] * NPerm4(3, 2, 0, 1)) return 0; // We've successfully identified the first tetrahedron of the // layered chain. NLayeredChain chain(adj, roles); chain.extendMaximal(); if (chain.getTop() != tet[otherAnnulus]) return 0; if (chain.getTopVertexRoles() != vertexRoles[otherAnnulus] * NPerm4(0, 1, 2, 3)) return 0; // Success! return chain.getIndex() - 1; } unsigned long NTriSolidTorus::areAnnuliLinkedAxis(int otherAnnulus) const { int right = (otherAnnulus + 1) % 3; int left = (otherAnnulus + 2) % 3; NTetrahedron* adj = tet[right]->adjacentTetrahedron( vertexRoles[right][1]); if (adj != tet[otherAnnulus]->adjacentTetrahedron( vertexRoles[otherAnnulus][2])) return 0; if (adj == tet[0] || adj == tet[1] || adj == tet[2] || adj == 0) return 0; NPerm4 roles = tet[right]->adjacentGluing( vertexRoles[right][1]) * vertexRoles[right] * NPerm4(2, 1, 0, 3); if (roles != tet[otherAnnulus]->adjacentGluing( vertexRoles[otherAnnulus][2]) * vertexRoles[otherAnnulus] * NPerm4(0, 3, 2, 1)) return 0; // We've successfully identified the first tetrahedron of the // layered chain. NLayeredChain chain(adj, roles); chain.extendMaximal(); NTetrahedron* top = chain.getTop(); NPerm4 topRoles(chain.getTopVertexRoles()); if (top->adjacentTetrahedron(topRoles[3]) != tet[left]) return 0; if (top->adjacentTetrahedron(topRoles[0]) != tet[otherAnnulus]) return 0; if (topRoles != tet[left]->adjacentGluing( vertexRoles[left][2]) * vertexRoles[left] * NPerm4(3, 0, 1, 2)) return 0; if (topRoles != tet[otherAnnulus]->adjacentGluing( vertexRoles[otherAnnulus][1]) * vertexRoles[otherAnnulus] * NPerm4(1, 2, 3, 0)) return 0; // Success! return chain.getIndex(); } NTriSolidTorus* NTriSolidTorus::formsTriSolidTorus(NTetrahedron* tet, NPerm4 useVertexRoles) { NTriSolidTorus* ans = new NTriSolidTorus(); ans->tet[0] = tet; ans->vertexRoles[0] = useVertexRoles; // Find the adjacent tetrahedra. ans->tet[1] = tet->adjacentTetrahedron(useVertexRoles[0]); ans->tet[2] = tet->adjacentTetrahedron(useVertexRoles[3]); // Check that we have three distinct tetrahedra. if (ans->tet[1] == 0 || ans->tet[2] == 0 || ans->tet[1] == tet || ans->tet[2] == tet || ans->tet[1] == ans->tet[2]) { delete ans; return 0; } // Find the vertex roles for tetrahedra 1 and 2. ans->vertexRoles[1] = tet->adjacentGluing(useVertexRoles[0]) * useVertexRoles * NPerm4(1, 2, 3, 0); ans->vertexRoles[2] = tet->adjacentGluing(useVertexRoles[3]) * useVertexRoles * NPerm4(3, 0, 1, 2); // Finally, check that tetrahedra 1 and 2 are glued together // properly. NPerm4 roles1 = ans->vertexRoles[1]; if (ans->tet[1]->adjacentTetrahedron(roles1[0]) != ans->tet[2]) { delete ans; return 0; } if (ans->tet[1]->adjacentGluing(roles1[0]) * roles1 * NPerm4(1, 2, 3, 0) != ans->vertexRoles[2]) { delete ans; return 0; } // We have the desired structure! return ans; } NAbelianGroup* NTriSolidTorus::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(); return ans; } NManifold* NTriSolidTorus::getManifold() const { return new NHandlebody(1, true); } } // namespace regina regina-4.95/engine/subcomplex/ntrisolidtorus.h000644 000765 000024 00000034063 12234011536 021515 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/ntrisolidtorus.h * \brief Deals with triangular solid tori in a triangulation. */ #ifndef __NTRISOLIDTORUS_H #ifndef __DOXYGEN #define __NTRISOLIDTORUS_H #endif #include "regina-core.h" #include "maths/nperm4.h" #include "subcomplex/nstandardtri.h" namespace regina { class NTetrahedron; /** * \weakgroup subcomplex * @{ */ /** * Represents a three-tetrahedron triangular solid torus in a triangulation. * A three-tetrahedron triangular solid torus is a three-tetrahedron * triangular prism with its two ends identified. * * The resulting triangular solid torus will have all edges as boundary * edges. Three of these will be axis edges (parallel to the * axis of the solid torus). Between the axis edges will be three * annuli, each with two internal edges. One of these internal edges will * meet all three tetrahedra (the \a major edge) and one of these internal * edges will only meet two of the tetrahedra (the \a minor edge). * * Assume the axis of the layered solid torus is oriented. The three * major edges together form a loop on the boundary torus. This loop can * be oriented to run around the solid torus in the same direction as * the axis; this then induces an orientation on the boundary of a * meridinal disc. Thus, using an axis edge as longitude, the three major * edges will together form a (1,1) curve on the boundary torus. * * We can now orient the minor edges so they also run around the solid torus * in the same direction as the axis, together forming a * (2, -1) curve on the boundary torus. * * Finally, the three tetrahedra can be numbered 0, 1 and 2 in an order * that follows the axis, and the annuli can be numbered 0, 1 and 2 in * an order that follows the meridinal disc boundary so that annulus * \a i does not use any faces from tetrahedron \a i. * * Note that all three tetrahedra in the triangular solid torus must be * distinct. * * All optional NStandardTriangulation routines are implemented for this * class. */ class REGINA_API NTriSolidTorus : public NStandardTriangulation { private: NTetrahedron* tet[3]; /**< The tetrahedra that make up this solid torus. */ NPerm4 vertexRoles[3]; /**< For tetrahedron \a i, vertexRoles[i] is a permutation p chosen so that the axis edge for that tetrahedron runs from vertex p[0] to p[3] and the major edge opposite that axis edge runs from vertex p[1] to p[2]. */ public: /** * Destroys this solid torus. */ virtual ~NTriSolidTorus(); /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NTriSolidTorus* clone() const; /** * Returns the requested tetrahedron in this solid torus. * See the general class notes for further details. * * @param index specifies which tetrahedron in the solid torus * to return; this must be 0, 1 or 2. * @return the requested tetrahedron. */ NTetrahedron* getTetrahedron(int index) const; /** * Returns a permutation represeting the role that each vertex * of the requested tetrahedron plays in the solid torus. * The permutation returned (call this p) maps 0, 1, 2 and * 3 to the four vertices of tetrahedron \a index so that the edge * from p[0] to p[3] is an oriented axis * edge, and the path from vertices p[0] to p[1] * to p[2] to p[3] follows the three oriented * major edges. In particular, the major edge for annulus * \a index will run from vertices p[1] to p[2]. * Edges p[0] to p[2] and p[1] to * p[3] will both be oriented minor edges. * * Note that annulus index+1 uses face p[1] of * the requested tetrahedron and annulus index+2 uses * face p[2] of the requested tetrahedron. Both annuli * use the axis edge p[0] to p[3], and each * annulus uses one other major edge and one other minor edge so * that (according to homology) the axis edge equals the major * edge plus the minor edge. * * See the general class notes for further details. * * @param index specifies which tetrahedron in the solid torus * to examine; this must be 0, 1 or 2. * @return a permutation representing the roles of the vertices * of the requested tetrahedron. */ NPerm4 getVertexRoles(int index) const; /** * Determines whether the two triangles of the requested annulus are * glued to each other. * * If the two triangles are glued, parameter \a roleMap will be * modified to return a permutation describing how the vertex * roles are glued to each other. This will describe directly * how axis edges, major edges and minor edges map to each other * without having to worry about the specific assignment of * tetrahedron vertex numbers. For a discussion of vertex * roles, see getVertexRoles(). * * Note that annulus index uses faces * from tetrahedra index+1 and index+2. * The gluing permutation that maps vertices * of tetrahedron index+1 to vertices of tetrahedron * index+2 will be getVertexRoles(index+2) * roleMap * * getVertexRoles(index+1).inverse(). * * @param index specifies which annulus on the solid torus * boundary to examine; this must be 0, 1 or 2. * @param roleMap a pointer to a permutation that, if this * routine returns \c true, will be modified to describe the gluing * of vertex roles. This parameter may be \c null. * @return \c true if and only if the two triangles of the requested * annulus are glued together. */ bool isAnnulusSelfIdentified(int index, NPerm4* roleMap) const; /** * Determines whether the two given annuli are linked in a * particular fashion by a layered chain. * * In this scenario, both of the given annuli meet one face of * the top tetrahedron and one face of the bottom tetrahedron of * the layered chain. * * To be identified by this routine, the layered chain * (described by NLayeredChain) must be attached as follows. * The two directed major edges of the two annuli should * correspond to the two hinge edges of the layered chain (with * both hinge edges pointing in the same direction around the * solid torus formed by the layered chain). The two directed * diagonals of the layered chain (between the two top faces and * between the two bottom faces, each pointing in the opposite * direction to the hinge edges around the solid torus formed by * the layered chain) should be identified and must correspond * to the (identified) two directed minor edges of the two * annuli. The remaining boundary edges of the layered chain * should correspond to the axis edges of the triangular solid * torus (this correspondence is determined by the previous * identifications). * * @param otherAnnulus the annulus on the solid torus boundary * \a not to be examined; this must be 0, 1 or 2. * @return the number of tetrahedra in the layered chain if the * two annuli are linked as described, or 0 otherwise. */ unsigned long areAnnuliLinkedMajor(int otherAnnulus) const; /** * Determines whether the two given annuli are linked in a * particular fashion by a layered chain. * * In this scenario, one of the given annuli meets both faces of * the top tetrahedron and the other annulus meets both faces of the * bottom tetrahedron of the layered chain. * * To be identified by this routine, the layered chain * (described by NLayeredChain) must be attached as follows. * We shall refer to the two hinge edges of the layered chain as * \e first and \e second. * * The two diagonals of the layered chain (between the two top * faces and between the two bottom faces) should correspond to * the two directed major edges of the two annuli, with the major * edges both pointing from top hinge edge to bottom hinge edge. * The other boundary edges of the layered chain that are not * hinge edges should correspond to the two directed minor edges * of the two annuli, with the minor edges both pointing from * bottom hinge edge to top hinge edge. The hinge edges * themselves should correspond to the axis edges of the * triangular solid torus (this correspondence is determined by * the previous identifications; the axis edge between the two * annuli will be identified to both of the others in reverse). * * @param otherAnnulus the annulus on the solid torus boundary * \a not to be examined; this must be 0, 1 or 2. * @return the number of tetrahedra in the layered chain if the * two annuli are linked as described, or 0 otherwise. */ unsigned long areAnnuliLinkedAxis(int otherAnnulus) const; /** * Determines if the given tetrahedron forms part of a * three-tetrahedron triangular solid torus with its vertices * playing the given roles in the solid torus. * * Note that the six boundary triangles of the triangular solid * torus need not be boundary triangles within the overall * triangulation, i.e., they may be identified with each other * or with faces of other tetrahedra. * * @param tet the tetrahedron to examine. * @param useVertexRoles a permutation describing the role each * tetrahedron vertex must play in the solid torus; this must be * in the same format as the permutation returned by * getVertexRoles(). * @return a newly created structure containing details of the * solid torus with the given tetrahedron as tetrahedron 0, or * \c null if the given tetrahedron is not part of a triangular * solid torus with the given vertex roles. */ static NTriSolidTorus* formsTriSolidTorus(NTetrahedron* tet, NPerm4 useVertexRoles); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new uninitialised structure. */ NTriSolidTorus(); }; /*@}*/ // Inline functions for NTriSolidTorus inline NTriSolidTorus::NTriSolidTorus() { } inline NTriSolidTorus::~NTriSolidTorus() { } inline NTetrahedron* NTriSolidTorus::getTetrahedron(int index) const { return tet[index]; } inline NPerm4 NTriSolidTorus::getVertexRoles(int index) const { return vertexRoles[index]; } inline std::ostream& NTriSolidTorus::writeName(std::ostream& out) const { return out << "TST"; } inline std::ostream& NTriSolidTorus::writeTeXName(std::ostream& out) const { return out << "\\mathop{\\rm TST}"; } inline void NTriSolidTorus::writeTextLong(std::ostream& out) const { out << "3-tetrahedron triangular solid torus"; } } // namespace regina #endif regina-4.95/engine/subcomplex/ntrivialtri.cpp000644 000765 000024 00000020367 12234011536 021315 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "algebra/nabeliangroup.h" #include "manifold/nhandlebody.h" #include "manifold/nlensspace.h" #include "manifold/nsimplesurfacebundle.h" #include "subcomplex/ntrivialtri.h" #include "triangulation/nboundarycomponent.h" #include "triangulation/ncomponent.h" #include "triangulation/nedge.h" #include "triangulation/ntriangle.h" namespace regina { const int NTrivialTri::N2 = 200; const int NTrivialTri::N3_1 = 301; const int NTrivialTri::N3_2 = 302; const int NTrivialTri::SPHERE_4_VERTEX = 5000; const int NTrivialTri::BALL_3_VERTEX = 5100; const int NTrivialTri::BALL_4_VERTEX = 5101; NTrivialTri* NTrivialTri::isTrivialTriangulation(const NComponent* comp) { // Since the triangulations are so small we can use census results // to recognise the triangulations by properties alone. // Are there any boundary components? if (! comp->isClosed()) { if (comp->getNumberOfBoundaryComponents() == 1) { // We have precisely one boundary component. NBoundaryComponent* bc = comp->getBoundaryComponent(0); if (! bc->isIdeal()) { // The boundary component includes boundary triangles. // Look for a one-tetrahedron ball. if (comp->getNumberOfTetrahedra() == 1) { if (bc->getNumberOfTriangles() == 4) return new NTrivialTri(BALL_4_VERTEX); if (bc->getNumberOfTriangles() == 2 && comp->getNumberOfVertices() == 3) return new NTrivialTri(BALL_3_VERTEX); } } } // Not recognised. return 0; } // Otherwise we are dealing with a closed component. // Before we do our validity check, make sure the number of // tetrahedra is in the supported range. if (comp->getNumberOfTetrahedra() > 3) return 0; // Is the triangulation valid? // Since the triangulations is closed we know that the vertices are // valid; all that remains is to check the edges. unsigned long nEdges = comp->getNumberOfEdges(); unsigned long i; for (i = 0; i < nEdges; i++) if (! comp->getEdge(i)->isValid()) return 0; // Test for the specific triangulations that we know about. if (comp->getNumberOfTetrahedra() == 2) { if (comp->isOrientable()) { if (comp->getNumberOfVertices() == 4) { // There's only one closed valid two-tetrahedron // four-vertex orientable triangulation. return new NTrivialTri(SPHERE_4_VERTEX); } } else { // There's only one closed valid two-tetrahedron non-orientable // triangulation. return new NTrivialTri(N2); } return 0; } if (comp->getNumberOfTetrahedra() == 3) { if (! comp->isOrientable()) { // If the triangulation is valid and the edge degrees // are 2,4,4,6 then we have N(3,1) or N(3,2). // All of the vertices are valid since there are no boundary // triangles; we thus only need to check the edges. if (comp->getNumberOfEdges() != 4) return 0; long degree[4]; for (i = 0; i < 4; i++) degree[i] = comp->getEdge(i)->getDegree(); std::sort(degree, degree + 4); if (degree[0] == 2 && degree[1] == 4 && degree[2] == 6 && degree[3] == 6) { // We have N(3,1) or N(3,2)! // Search for Mobius band triangles. unsigned long nTriangles = comp->getNumberOfTriangles(); for (i = 0; i < nTriangles; i++) if (comp->getTriangle(i)->isMobiusBand()) return new NTrivialTri(N3_2); return new NTrivialTri(N3_1); } } } return 0; } NManifold* NTrivialTri::getManifold() const { if (type == SPHERE_4_VERTEX) return new NLensSpace(1, 0); else if (type == BALL_3_VERTEX || type == BALL_4_VERTEX) return new NHandlebody(0, true); else if (type == N2) return new NSimpleSurfaceBundle(NSimpleSurfaceBundle::S2xS1_TWISTED); else if (type == N3_1 || type == N3_2) return new NSimpleSurfaceBundle(NSimpleSurfaceBundle::RP2xS1); return 0; } NAbelianGroup* NTrivialTri::getHomologyH1() const { NAbelianGroup* ans = new NAbelianGroup(); if (type == N2) ans->addRank(); else if (type == N3_1 || type == N3_2) { ans->addRank(); ans->addTorsionElement(2); } return ans; } std::ostream& NTrivialTri::writeName(std::ostream& out) const { if (type == SPHERE_4_VERTEX) out << "S3 (4-vtx)"; else if (type == BALL_3_VERTEX) out << "B3 (3-vtx)"; else if (type == BALL_4_VERTEX) out << "B3 (4-vtx)"; else if (type == N2) out << "N(2)"; else if (type == N3_1) out << "N(3,1)"; else if (type == N3_2) out << "N(3,2)"; return out; } std::ostream& NTrivialTri::writeTeXName(std::ostream& out) const { if (type == SPHERE_4_VERTEX) out << "S^3_{v=4}"; else if (type == BALL_3_VERTEX) out << "B^3_{v=3}"; else if (type == BALL_4_VERTEX) out << "B^3_{v=4}"; else if (type == N2) out << "N_{2}"; else if (type == N3_1) out << "N_{3,1}"; else if (type == N3_2) out << "N_{3,2}"; return out; } void NTrivialTri::writeTextLong(std::ostream& out) const { if (type == SPHERE_4_VERTEX) out << "Two-tetrahedron four-vertex 3-sphere"; else if (type == BALL_3_VERTEX) out << "One-tetrahedron three-vertex ball"; else if (type == BALL_4_VERTEX) out << "One-tetrahedron four-vertex ball"; else if (type == N2) out << "Non-orientable triangulation N(2)"; else if (type == N3_1) out << "Non-orientable triangulation N(3,1)"; else if (type == N3_2) out << "Non-orientable triangulation N(3,2)"; } } // namespace regina regina-4.95/engine/subcomplex/ntrivialtri.h000644 000765 000024 00000013703 12234011536 020756 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/ntrivialtri.h * \brief Deals with a few specific hard-coded trivial triangulations. */ #ifndef __NTRIVIALTRI_H #ifndef __DOXYGEN #define __NTRIVIALTRI_H #endif #include "regina-core.h" #include "subcomplex/nstandardtri.h" namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Represents one of a few particular hard-coded trivial triangulations * that do not belong to any of the other larger families. * * All optional NStandardTriangulation routines are implemented for this * class. * * \testpart */ class REGINA_API NTrivialTri : public NStandardTriangulation { public: /** * Represents the two-tetrahedron four-vertex triangulation of * the 3-sphere. */ static const int SPHERE_4_VERTEX; /** * Represents the one-tetrahedron three-vertex triangulation of * the ball. This is a single tetrahedron with two faces as * boundary and the other two faces folded together. */ static const int BALL_3_VERTEX; /** * Represents the one-tetrahedron four-vertex triangulation of * the ball. This is a single tetrahedron with all four faces * as boundary. */ static const int BALL_4_VERTEX; /** * Represents the two-tetrahedron triangulation N(2) of the * twisted 2-sphere bundle over the circle. */ static const int N2; /** * Represents the three-tetrahedron triangulation N(3,1) of the * projective plane bundle over the circle. This particular * triangulation has no Mobius band triangles. */ static const int N3_1; /** * Represents the three-tetrahedron triangulation N(3,2) of the * projective plane bundle over the circle. This particular * triangulation has two Mobius band triangles. */ static const int N3_2; private: int type; /**< The specific triangulation being represented. This must be one of the triangulation constants defined in this class. */ public: /** * Returns a newly created clone of this structure. * * @return a newly created clone. */ NTrivialTri* clone() const; /** * Returns the specific trivial triangulation being represented. * * @return the specific triangulation. This will be one of the * triangulation constants defined in this class. */ int getType() const; /** * Determines if the given triangulation component is one of the * trivial triangulations recognised by this class. * * @param comp the triangulation component to examine. * @return a newly created structure representing the trivial * triangulation, or \c null if the given component is not one * of the triangulations recognised by this class. */ static NTrivialTri* isTrivialTriangulation(const NComponent* comp); NManifold* getManifold() const; NAbelianGroup* getHomologyH1() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new structure. */ NTrivialTri(int newType); }; /*@}*/ // Inline functions for NTrivialTri inline NTrivialTri::NTrivialTri(int newType) : type(newType) { } inline NTrivialTri* NTrivialTri::clone() const { return new NTrivialTri(type); } inline int NTrivialTri::getType() const { return type; } } // namespace regina #endif regina-4.95/engine/subcomplex/ntxicore.cpp000644 000765 000024 00000013566 12234011536 020604 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "subcomplex/ntxicore.h" #include namespace regina { std::string NTxICore::getName() const { std::ostringstream out; writeName(out); return out.str(); } std::string NTxICore::getTeXName() const { std::ostringstream out; writeTeXName(out); return out.str(); } NTxIDiagonalCore::NTxIDiagonalCore(unsigned long newSize, unsigned long newK) : size_(newSize), k_(newK) { // We'll build the actual triangulation last. Meanwhile, fill in // the remaining bits and pieces. bdryTet_[0][0] = 0; bdryTet_[0][1] = 1; bdryTet_[1][0] = size_ - 2; bdryTet_[1][1] = size_ - 1; // All bdryRoles permutations are identities. // No need to change them here. bdryReln_[0] = NMatrix2(1, 0, 0, 1); bdryReln_[1] = NMatrix2(-1, 0, 0, 1); parallelReln_ = NMatrix2(1, size_ - 6, 0, 1); // Off we go! unsigned i; NTetrahedron** t = new NTetrahedron*[size_]; for (i = 0; i < size_; i++) t[i] = core_.newTetrahedron(); // Glue together the pairs of triangles in the central surface. t[0]->joinTo(0, t[1], NPerm4(0, 2, 1, 3)); t[size_ - 2]->joinTo(0, t[size_ - 1], NPerm4(0, 2, 1, 3)); // Glue together the long diagonal line of quads, and hook the ends // together using the first pair of triangles. t[0]->joinTo(1, t[3], NPerm4(2, 3, 1, 0)); for (i = 3; i < size_ - 3; i++) t[i]->joinTo(0, t[i + 1], NPerm4(0, 3)); t[size_ - 3]->joinTo(0, t[1], NPerm4(1, 0, 2, 3)); // Glue the quadrilateral and double-triangular bulges to their // horizontal neighbours. t[1]->joinTo(2, t[2], NPerm4()); t[2]->joinTo(3, t[0], NPerm4(1, 0, 3, 2)); t[size_ - 1]->joinTo(2, t[size_ - 2 - k_], NPerm4(3, 0, 1, 2)); t[size_ - 2]->joinTo(2, t[size_ - 2 - k_], NPerm4(0, 3, 2, 1)); // Glue in the lower edge of each bulges. if (k_ == size_ - 5) t[2]->joinTo(0, t[size_ - 2], NPerm4(1, 3, 2, 0)); else t[2]->joinTo(0, t[3], NPerm4(2, 1, 3, 0)); if (k_ == 1) t[size_ - 1]->joinTo(1, t[2], NPerm4(2, 1, 3, 0)); else t[size_ - 1]->joinTo(1, t[size_ - 1 - k_], NPerm4(3, 2, 0, 1)); // Glue in the lower edge of each quadrilateral. for (i = 3; i <= size_ - 3; i++) { if (i == size_ - 2 - k_) continue; if (i == size_ - 3) t[i]->joinTo(1, t[2], NPerm4(3, 1, 0, 2)); else if (i == size_ - 3 - k_) t[i]->joinTo(1, t[size_ - 2], NPerm4(0, 1, 3, 2)); else t[i]->joinTo(1, t[i + 1], NPerm4(1, 2)); } delete[] t; } NTxIParallelCore::NTxIParallelCore() { // We'll build the actual triangulation last. Meanwhile, fill in // the remaining bits and pieces. bdryTet_[0][0] = 0; bdryTet_[0][1] = 1; bdryTet_[1][0] = 4; bdryTet_[1][1] = 5; // All bdryRoles permutations are identities. // No need to change them here. bdryReln_[0] = bdryReln_[1] = parallelReln_ = NMatrix2(1, 0, 0, 1); // Off we go! // Just hard-code it. It's only one triangulation, and it's highly // symmetric. unsigned i; NTetrahedron** t = new NTetrahedron*[6]; for (i = 0; i < 6; i++) t[i] = core_.newTetrahedron(); t[0]->joinTo(0, t[1], NPerm4(1, 2)); t[4]->joinTo(0, t[5], NPerm4(1, 2)); t[1]->joinTo(2, t[2], NPerm4()); t[5]->joinTo(2, t[3], NPerm4()); t[0]->joinTo(2, t[2], NPerm4(1, 0, 3, 2)); t[4]->joinTo(2, t[3], NPerm4(1, 0, 3, 2)); t[1]->joinTo(1, t[3], NPerm4(2, 0, 3, 1)); t[5]->joinTo(1, t[2], NPerm4(2, 0, 3, 1)); t[0]->joinTo(1, t[3], NPerm4(0, 3)); t[4]->joinTo(1, t[2], NPerm4(0, 3)); delete[] t; } } // namespace regina regina-4.95/engine/subcomplex/ntxicore.h000644 000765 000024 00000050571 12234011536 020246 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file subcomplex/ntxicore.h * \brief Provides various triangulations of the product of the torus * and the interval. */ #ifndef __NTXICORE_H #ifndef __DOXYGEN #define __NTXICORE_H #endif #include "regina-core.h" #include "maths/nmatrix2.h" #include "triangulation/ntriangulation.h" namespace regina { /** * \weakgroup subcomplex * @{ */ /** * Provides a triangulation of the product T x I (the * product of the torus and the interval). Generally these * triangulations are only one tetrahedron thick (i.e., a "thin I-bundle"), * though this is not a strict requirement of this class. Triangulations of * this type are generally used as components of larger triangulations * (such as layered surface bundles). * * This product has two torus boundaries, called the \a upper and * \a lower boundaries. Each of these boundary tori must be formed from * precisely two triangles. This class tracks the mappings between parallel * curves on the upper and lower boundaries, as well as mappings from * boundary curves to specific tetrahedron edges. * * For each of the two torus boundaries, two curves are chosen as * generators of the fundamental group; these curves are called * \a alpha and \a beta. Note that there is no requirement that the * upper \a alpha and \a beta be parallel to the lower \a alpha and * \a beta. The parallelReln() routine can be called to establish the * precise relationship between these upper and lower curves. * * Every object of this class contains a full copy of the triangulation * that it describes (so you should not create excessive objects of this * class without reason). This triangulation can be accessed through the * core() routine. */ class REGINA_API NTxICore : public ShareableObject { protected: NTriangulation core_; /**< A full copy of the T x I triangulation that is described. */ unsigned bdryTet_[2][2]; /**< The tetrahedra that provide the upper and lower boundary triangles. See bdryTet() for details. */ NPerm4 bdryRoles_[2][2]; /**< Describes which tetrahedron vertices play which roles in the upper and lower boundary triangles. See bdryRoles() for details. */ NMatrix2 bdryReln_[2]; /**< Expresses the \a alpha and \a beta curves for each torus boundary in terms of specific tetrahedron edges and vertices. The elements \a bdryReln_[0] and \a bdryReln_[1] refer to the upper and lower boundaries respectively, and each of these matrices must have determinant +1 or -1. See bdryReln() for further details. */ NMatrix2 parallelReln_; /**< Expresses the lower \a alpha and \a beta curves in terms of the upper \a alpha and \a beta curves. See parallelReln() for details. */ public: /** * Returns a full copy of the T x I triangulation that * this object describes. * * Successive calls to this routine will returns the same * triangulation (i.e., it is not recreated each time). The * triangulation that is returned may not be modified or destroyed. * * @return the full triangulation. */ const NTriangulation& core() const; /** * Determines which tetrahedron provides the requested boundary * triangle. * * Recall that the T x I triangulation has two torus * boundaries, each consisting of two boundary triangles. This * routine returns the specific tetrahedron that provides the * given triangle of the given torus boundary. * * What is returned is the index number of the tetrahedron * within the triangulation. To access the tetrahedron itself, * you may call core().getTetrahedron(bdryTet(...)). * * Note that the same tetrahedron may provide more than one * boundary triangle. * * @param whichBdry 0 if the upper boundary should be examined, * or 1 if the lower boundary should be examined. * @param whichTri 0 if the first boundary triangle should be * examined, or 1 if the second boundary triangle should be examined. */ unsigned bdryTet(unsigned whichBdry, unsigned whichTri) const; /** * Describes which tetrahedron vertices play which roles in the * upper and lower boundary triangles. * * Each boundary torus contains two triangles, whose vertices can be * numbered 0, 1 and 2 according to the following diagram. This * diagram is completely symmetric, in that edges 1-2 are no * more special than edges 0-2 or 0-1. The important * observations are that edges 1-2 and 2-1 of each triangle are * identified, edges 0-2 and 2-0 of each triangle are identified and * edges 0-1 and 1-0 of each triangle are identified. * *
         *           *--->>--*
         *           |0  2 / |
         *   First   |    / 1|  Second
         *  triangle v   /   v triangle
         *           |1 /    |
         *           | / 2  0|
         *           *--->>--*
         * 
* * This routine returns a permutation that maps these integers * 0,1,2 to real tetrahedron vertices. Let \a t be the * tetrahedron returned by bdryTet(\a whichBdry, \a whichTri) * and let \a p be the permutation returned by * bdryRoles(\a whichBdry, \a whichTri). Then vertices * \a p[0], \a p[1] and \a p[2] of tetrahedron \a t correspond to * the markings 0, 1 and 2 respectively in the diagram above (and * therefore the boundary triangle is face \a p[3] of the tetrahedron). * * The arguments to this routine affect whether we examine the * upper or lower boundary and whether we examine the first or * second triangle of this boundary * * @param whichBdry 0 if the upper boundary should be examined, * or 1 if the lower boundary should be examined. * @param whichTri 0 if the first boundary triangle should be * examined, or 1 if the second boundary triangle should be examined. * @return the permutation mapping roles 0, 1 and 2 in the * diagram above to real tetrahedron vertex numbers. */ NPerm4 bdryRoles(unsigned whichBdry, unsigned whichTri) const; /** * Returns a 2-by-2 matrix describing the \a alpha and \a beta curves * on a torus boundary in terms of specific tetrahedron edges. * * Consider the first triangle of the given boundary. Let * \a t be the tetrahedron returned by bdryTet(\a whichBdry, 0) and * let \a p be the permutation returned by bdryRoles(\a whichBdry, 0). * * Let \a edge01 be the directed edge from vertex \a p[0] to \a p[1] * of tetrahedron \a t, and let \a edge02 be the directed edge from * vertex \a p[0] to \a p[2] of tetrahedron \a t. Then the * matrix returned by this routine describes how the directed * edges \a edge01 and \a edge02 relate to the \a alpha and \a beta * curves on the given boundary. Specifically: * *
         *     [ alpha ]                  [ edge01 ]
         *     [       ]  =  bdryReln() * [        ] .
         *     [ beta  ]                  [ edge02 ]
         * 
* * It is guaranteed that this matrix has determinant +1 or -1. * * @param whichBdry 0 if the upper boundary should be examined, * or 1 if the lower boundary should be examined. * @return the relationship between the boundary curves and * tetrahedron edges. */ const NMatrix2& bdryReln(unsigned whichBdry) const; /** * Returns a 2-by-2 matrix describing the parallel relationship * between the upper and lower boundary curves. * * Let \a a_u and \a b_u be the upper \a alpha and \a beta * boundary curves. Suppose that the lower \a alpha is parallel * to \a w.\a a_u + \a x.\a b_u, and that the lower \a beta is * parallel to \a y.\a a_u + \a z.\a b_u. Then the matrix * returned will be * *
         *     [ w  x ]
         *     [      ] .
         *     [ y  z ]
         * 
* * In other words, if \a a_l and \a b_l are the lower \a alpha * and \a beta curves respectively, we have * *
         *     [ a_l ]                      [ a_u ]
         *     [     ]  =  parallelReln() * [     ] .
         *     [ b_l ]                      [ b_u ]
         * 
* * @return the relationship between the upper and lower boundary curves. */ const NMatrix2& parallelReln() const; /** * Returns the name of this specific triangulation of * T x I as a human-readable string. * * @return the name of this triangulation. */ std::string getName() const; /** * Returns the name of this specific triangulation of * T x I in TeX format. No leading or trailing dollar * signs will be included. * * @return the name of this triangulation in TeX format. */ std::string getTeXName() const; /** * Writes the name of this specific triangulation of * T x I to the given output stream. The name will be * written as a human-readable string. * * \ifacespython The argument \a out is missing, and is always * assumed to be standard output. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeName(std::ostream& out) const = 0; /** * Writes the name of this specific triangulation of * T x I in TeX format to the given output stream. * No leading or trailing dollar signs will be written. * * \ifacespython The argument \a out is missing, and is always * assumed to be standard output. * * @param out the output stream to which to write. * @return a reference to the given output stream. */ virtual std::ostream& writeTeXName(std::ostream& out) const = 0; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; protected: /** * Default constructor that performs no initialisation. */ NTxICore(); }; /** * One of a family of thin T x I triangulations that typically * appear at the centres of layered torus bundles. Different * triangulations in this family use different numbers of tetrahedra, * with the larger triangulations producing more complicated * relationships between the upper and lower boundary curves. * * Members of this family are parameterised by their size (the number of * tetrahedra) and an additional integer \a k, where * 1 <= \a k <= \a size - 5. Note that this means we must have * \a size >= 6. The member of this family of size \a n with additional * parameter \a k is labelled T_n:k. * * It is worth noting that T_n:k is isomorphic to * T_n:(n-4-k), so in reality there are only [(\a n-4)/2] * different triangulations for a given size (rounded down). * * A triangulation of this family is most easily defined in terms of its * central torus. Central surfaces are described in detail in * "Structures of small closed non-orientable 3-manifold triangulations", * Benjamin A. Burton, J. Knot Theory Ramifications 16 (2007), 545--574; * in particular, see the section on thin I-bundles. * * The central torus begins with two triangles \a u0 and \a u1 (which * eventually provide the upper torus boundary), with a chain of * quadrilaterals \a q1, ..., \a q(\a n-5) descending diagonally beneath * them as illustrated in the diagram below. * * \image html diaginit.png * * We then distort quadrilateral \a qk and attach two more triangles * \a w0 and \a w1 to its side (these will eventually provide the lower * torus boundary). This is illustrated in the following diagram. * * \image html diagdistort.png * * The entire central torus wraps from left to right (so the lower left * edges of most quadrilaterals \a qi are identified with the upper right * edges of \a q(\a i-1), and the left edge of \a qk is identified with * the right edge of \a w1). As an exception, the two uppermost edges are * identified with the two lowermost edges in a parallel fashion (so the * upper left edge of \a u1 is identified with the lower right edge of \a q1, * and the adjacent edges at right angles to these are also identified). * * The four triangles in the central torus correspond to the four tetrahedra * in the triangulation that provide the boundary triangles. The upper boundary * is coned out from triangles \a u0 and \a u1, and the lower boundary is * coned out from triangles \a w0 and \a w1. In each boundary, \a u0 or * \a w0 gives the first boundary triangle and \a u1 or \a w1 gives the second. * The directions of the corresponding \a alpha and \a beta curves are * illustrated below. * * \image html diagbdry.png * * As a final illustration, the example below shows the central surface in * the case (\a n, \a k) = (9, 2). * * \image html diag92.png */ class REGINA_API NTxIDiagonalCore : public NTxICore { private: unsigned long size_; /**< The number of tetrahedra in this T x I triangulation. */ unsigned long k_; /**< The additional parameter \a k as described in the class notes. */ public: /** * Creates a new T x I triangulation with the given * parameters. * * @param newSize the number of tetrahedra in this * triangulation. This must be at least 6. * @param newK the additional parameter \a k as described in the * class notes. This must be between 1 and (\a newSize - 5) * inclusive. */ NTxIDiagonalCore(unsigned long newSize, unsigned long newK); /** * Returns the total number of tetrahedra in this T x I * triangulation. * * @return the total number of tetrahedra. */ unsigned long size() const; /** * Returns the additional parameter \a k as described in the * class notes. * * @return the additional parameter \a k. */ unsigned long k() const; std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; }; /** * A specific six-tetrahedron NTxICore triangulation that does not fit * neatly into other families. * * This triangulation contains the fewest possible number of tetrahedra * (NTxICore triangulations are not seen below six tetrahedra). It is * referred to as T_6^1 in the paper "Structures of small closed * non-orientable 3-manifold triangulations", Benjamin A. Burton, * J. Knot Theory Ramifications 16 (2007), 545--574. * In Regina it is given the name T_6*, to avoid confusion with * the different NTxIDiagonalCore triangulation T_6:1. * * The triangulations of the upper and lower boundary tori are completely * parallel (and in particular, the upper and lower \a alpha curves are * parallel, as are the upper and lower \a beta curves). * * For reference, the central torus of this triangulation is depicted below. * The left and right sides of the diagram are identified, as are the * top and bottom. The four triangles \a u0, \a u1, \a w0 and \a w1 * provide the boundary triangles of the overall triangulation, with the upper * boundary coned out from triangles \a u0 and \a u1 and the lower boundary * coned out from triangles \a w0 and \a w1. In each boundary, \a u0 or * \a w0 gives the first boundary triangle and \a u1 or \a w1 gives the second. * The directions of the corresponding \a alpha and \a beta curves are * are also included. * * \image html parallel.png */ class REGINA_API NTxIParallelCore : public NTxICore { public: /** * Creates a new copy of this T x I triangulation. */ NTxIParallelCore(); std::ostream& writeName(std::ostream& out) const; std::ostream& writeTeXName(std::ostream& out) const; }; /*@}*/ // Inline functions for NTxICore inline NTxICore::NTxICore() { } inline const NTriangulation& NTxICore::core() const { return core_; } inline unsigned NTxICore::bdryTet(unsigned whichBdry, unsigned whichTri) const { return bdryTet_[whichBdry][whichTri]; } inline NPerm4 NTxICore::bdryRoles(unsigned whichBdry, unsigned whichTri) const { return bdryRoles_[whichBdry][whichTri]; } inline const NMatrix2& NTxICore::bdryReln(unsigned whichBdry) const { return bdryReln_[whichBdry]; } inline const NMatrix2& NTxICore::parallelReln() const { return parallelReln_; } inline void NTxICore::writeTextShort(std::ostream& out) const { writeName(out); } inline void NTxICore::writeTextLong(std::ostream& out) const { out << "TxI core: "; writeName(out); } // Inline functions for NTxIDiagonalCore inline unsigned long NTxIDiagonalCore::size() const { return size_; } inline unsigned long NTxIDiagonalCore::k() const { return k_; } inline std::ostream& NTxIDiagonalCore::writeName(std::ostream& out) const { return out << 'T' << size_ << ':' << k_; } inline std::ostream& NTxIDiagonalCore::writeTeXName(std::ostream& out) const { return out << "T_{" << size_ << ':' << k_ << '}'; } inline std::ostream& NTxIParallelCore::writeName(std::ostream& out) const { return out << "T6*"; } inline std::ostream& NTxIParallelCore::writeTeXName(std::ostream& out) const { return out << "T_{6\\ast}"; } } // namespace regina #endif regina-4.95/engine/subcomplex/parallel.eps000644 000765 000024 00000011177 12234011536 020546 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: parallel.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha5 %%CreationDate: Wed Sep 28 08:50:52 2005 %%For: bab@skaro (Ben Burton,,,) %%BoundingBox: 0 0 74 80 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 80 moveto 0 0 lineto 74 0 lineto 74 80 lineto closepath clip newpath -139.7 305.1 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 2475 3600 m 2475 4500 l 3375 4500 l 3375 3600 l 2475 3600 l 3375 4500 l gs col0 s gr % Polyline n 2925 3600 m 2925 4500 l gs col0 s gr % Polyline n 2475 4050 m 3375 4050 l gs col0 s gr % Polyline gs clippath 2865 4338 m 2865 4170 l 2805 4170 l 2805 4338 l 2805 4338 l 2835 4218 l 2865 4338 l cp eoclip n 2835 4590 m 2835 4185 l gs col0 s gr gr % arrowhead n 2865 4338 m 2835 4218 l 2805 4338 l col0 s % Polyline gs clippath 2637 4170 m 2805 4170 l 2805 4110 l 2637 4110 l 2637 4110 l 2757 4140 l 2637 4170 l cp eoclip n 2385 4140 m 2790 4140 l gs col0 s gr gr % arrowhead n 2637 4170 m 2757 4140 l 2637 4110 l col0 s % Polyline gs clippath 2415 3753 m 2415 3585 l 2355 3585 l 2355 3753 l 2355 3753 l 2385 3633 l 2415 3753 l cp eoclip n 2385 4050 m 2385 3600 l gs col0 s gr gr % arrowhead n 2415 3753 m 2385 3633 l 2355 3753 l col0 s % Polyline gs clippath 3222 4620 m 3390 4620 l 3390 4560 l 3222 4560 l 3222 4560 l 3342 4590 l 3222 4620 l cp eoclip n 2925 4590 m 3375 4590 l gs col0 s gr gr % arrowhead n 3222 4620 m 3342 4590 l 3222 4560 l col0 s /Times-Italic ff 190.50 scf sf 2610 4005 m gs 1 -1 sc (u0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 2790 3780 m gs 1 -1 sc (u1) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 3060 4455 m gs 1 -1 sc (w0) dup sw pop 2 div neg 0 rm col0 sh gr /Times-Italic ff 190.50 scf sf 3240 4230 m gs 1 -1 sc (w1) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 2295 4005 m gs 1 -1 sc (a) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 2295 4275 m gs 1 -1 sc (b) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 2790 4725 m gs 1 -1 sc (a) dup sw pop 2 div neg 0 rm col0 sh gr /Symbol ff 190.50 scf sf 3015 4770 m gs 1 -1 sc (b) dup sw pop 2 div neg 0 rm col0 sh gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/parallel.fig000644 000765 000024 00000002067 12234011536 020522 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 6 2475 3600 2475 4500 3375 4500 3375 3600 2475 3600 3375 4500 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2925 3600 2925 4500 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2475 4050 3375 4050 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2835 4590 2835 4185 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2385 4140 2790 4140 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2385 4050 2385 3600 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 1 0 2 0 0 1.00 60.00 120.00 2925 4590 3375 4590 4 1 0 50 -1 3 12 0.0000 0 150 210 2610 4005 u0\001 4 1 0 50 -1 3 12 0.0000 0 135 210 2790 3780 u1\001 4 1 0 50 -1 3 12 0.0000 0 150 240 3060 4455 w0\001 4 1 0 50 -1 3 12 0.0000 0 135 240 3240 4230 w1\001 4 1 0 50 -1 32 12 0.0000 4 105 120 2295 4005 a\001 4 1 0 50 -1 32 12 0.0000 4 195 105 2295 4275 b\001 4 1 0 50 -1 32 12 0.0000 4 105 120 2790 4725 a\001 4 1 0 50 -1 32 12 0.0000 4 195 105 3015 4770 b\001 regina-4.95/engine/subcomplex/parallel.png000644 000765 000024 00000002620 12234011536 020534 0ustar00babstaff000000 000000 ‰PNG  IHDRbiä © pHYs × ×B(›xtEXtSoftwareESP Ghostscript 815.00yxü:IDAThå™/oäFÆŸ]¸‚‰ y•+­2ýTìí7pÀ©R™MÊmV[*((Ù-+°+•ö´VØy ËÞÛ»þ3cít½¾Ä‰=ž_fÆó>ï3Ù^:¶/NøHŸèoŠM¾ú|3Ý}hF ùÔëA8I€áDEõ5 òÉ,£ø¡¨®ìrŒBý-aÃPËòËS¿fF¾ßL FDþEqúÙÈàŽ;ƒ0@|îX0¤XŽP \ží0ŠøôS¾!9Dûo1ä.Ò{H¹³‘ ´&ªÃ%R˜;O}‡‘!9 éPrÖj÷€è~ó'F.¹ @Ê&sË"àö:h’§…+•Î#Ø$óš‘ï\2È+)7³¦LÛeìCŽ£ƒ#Äl%¶’$–9`NÚ˜‰X—w-…u ÃV»W0¬Ëƒå û d1à !w›x9Ã!÷I‹"Ýmr›lƒ¿€á(¦ÍU¯¿ÿã]KÃ(^YÔ”¿ü8é%°Ê¿²\5¾{bÍ›Ta%Iq¡0Âh™5„ƒèáQ‹13Zf­¿ÜEÕÓsçƒt¼ÿý§÷×·ßö½KU‹êBžòv5 µßlGîõ›ë×ïßÉA›ö8âÈQ´Í ¿,=1LUud¡+ü¤Ÿ¯üV¾""ò]ŸÈ91" ù©I5Qò<KúHqÏ\1Ÿæ* "V8…‡ŽY;¯…ôMô<. €(”<§‰æ‹ 6ÁDOcŒj=by,X׬5ˆœ{Ã×ß"51¤Ë•Û7kÔS×´ÜÝøî›a›RdD™KtðˆèÐtÜR=¡¥€cˆ¯o5mJ‘•Deç»ë©ž²õY~ªÙA, r0€ ;©RÁ ŽÒGq}«Ó"s.©jw•úvr$ßÜiû22jÄSI¥¥àåwwzE51æëE~ ¦OgF…˜a¨ 3«iF…(í’£{f5ɘ?QÐÖr·‰õ ýöÛz‡ƒ®Íe%;¶r»fë­ŒÆDZqÖ¹³!0V!ûˆÚ†°ÏX……ˆ(º†°ÇX7Q¼¨tÎïÂ.c¢¥sCØa¬ü¢N:×3„mÆÊQ¸œùüè ‡ö“3ÃæøÑü àUõqìÓOuâʉÒë\ͨDZnãødïÀFìj*÷à7Õõfšc!õ™ÖÇ?@ÿ“ˆôþ!èhûK,·’y.šV *ÇŒ¶úõ7|õ3[ˆ˜)s%¸¨·ýå2juÀTè#q 2òÂÑFǾ휃Hà–G7Ãá|«tÑ?ÕÄGF”§ukà¦W""*Ñ.Ñ`ÑÇì­§91ž²º3FÁ‘ù([çž ü‚kñ·<0/LÎë "rãÅ­ãöë7­‰•[oC6íä™vÊM¯EX¸éµˆÚ)6á¦/±ñá «›~IêÆ‡?QÿÄ¿éÖ5 vA£AIEND®B`‚regina-4.95/engine/subcomplex/reflector.eps000644 000765 000024 00000017035 12234011536 020736 0ustar00babstaff000000 000000 %!PS-Adobe-2.0 EPSF-2.0 %%Title: reflector.fig %%Creator: fig2dev Version 3.2 Patchlevel 5-alpha7 %%CreationDate: Mon Feb 6 10:17:53 2006 %%BoundingBox: 0 0 590 115 %Magnification: 1.0000 %%EndComments /$F2psDict 200 dict def $F2psDict begin $F2psDict /mtrx matrix put /col-1 {0 setgray} bind def /col0 {0.000 0.000 0.000 srgb} bind def /col1 {0.000 0.000 1.000 srgb} bind def /col2 {0.000 1.000 0.000 srgb} bind def /col3 {0.000 1.000 1.000 srgb} bind def /col4 {1.000 0.000 0.000 srgb} bind def /col5 {1.000 0.000 1.000 srgb} bind def /col6 {1.000 1.000 0.000 srgb} bind def /col7 {1.000 1.000 1.000 srgb} bind def /col8 {0.000 0.000 0.560 srgb} bind def /col9 {0.000 0.000 0.690 srgb} bind def /col10 {0.000 0.000 0.820 srgb} bind def /col11 {0.530 0.810 1.000 srgb} bind def /col12 {0.000 0.560 0.000 srgb} bind def /col13 {0.000 0.690 0.000 srgb} bind def /col14 {0.000 0.820 0.000 srgb} bind def /col15 {0.000 0.560 0.560 srgb} bind def /col16 {0.000 0.690 0.690 srgb} bind def /col17 {0.000 0.820 0.820 srgb} bind def /col18 {0.560 0.000 0.000 srgb} bind def /col19 {0.690 0.000 0.000 srgb} bind def /col20 {0.820 0.000 0.000 srgb} bind def /col21 {0.560 0.000 0.560 srgb} bind def /col22 {0.690 0.000 0.690 srgb} bind def /col23 {0.820 0.000 0.820 srgb} bind def /col24 {0.500 0.190 0.000 srgb} bind def /col25 {0.630 0.250 0.000 srgb} bind def /col26 {0.750 0.380 0.000 srgb} bind def /col27 {1.000 0.500 0.500 srgb} bind def /col28 {1.000 0.630 0.630 srgb} bind def /col29 {1.000 0.750 0.750 srgb} bind def /col30 {1.000 0.880 0.880 srgb} bind def /col31 {1.000 0.840 0.000 srgb} bind def end save newpath 0 115 moveto 0 0 lineto 590 0 lineto 590 115 lineto closepath clip newpath 11.1 199.2 translate 1 -1 scale /cp {closepath} bind def /ef {eofill} bind def /gr {grestore} bind def /gs {gsave} bind def /sa {save} bind def /rs {restore} bind def /l {lineto} bind def /m {moveto} bind def /rm {rmoveto} bind def /n {newpath} bind def /s {stroke} bind def /sh {show} bind def /slc {setlinecap} bind def /slj {setlinejoin} bind def /slw {setlinewidth} bind def /srgb {setrgbcolor} bind def /rot {rotate} bind def /sc {scale} bind def /sd {setdash} bind def /ff {findfont} bind def /sf {setfont} bind def /scf {scalefont} bind def /sw {stringwidth} bind def /tr {translate} bind def /tnt {dup dup currentrgbcolor 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} bind def /shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul 4 -2 roll mul srgb} bind def /$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def /$F2psEnd {$F2psEnteredState restore end} def $F2psBegin 10 setmiterlimit 0 slj 0 slc 0.06299 0.06299 sc % % Fig objects follow % % % here starts figure with depth 55 % Polyline 0 slj 0 slc 15.000 slw [15 68] 68 sd n 2880 2025 m 3600 2025 l gs col0 s gr [] 0 sd % Polyline 0.000 slw n 450 1350 m 2250 1350 l 2250 2700 l 450 2700 l cp gs col7 0.85 shd ef gr % Polyline n 3825 1350 m 5625 1350 l 5625 2700 l 3825 2700 l cp gs col7 0.85 shd ef gr % Polyline n 6300 1350 m 8100 1350 l 8100 2700 l 6300 2700 l cp gs col7 0.85 shd ef gr % here ends figure; % % here starts figure with depth 50 % Polyline 0 slj 0 slc 7.500 slw n 450 1350 m 450 2700 l 2250 2700 l 2700 1800 l 2250 1350 l 450 1350 l 900 1800 l 450 2700 l 2700 1800 l 900 1800 l 2250 1350 l gs col0 s gr % Polyline [45] 0 sd n 2250 2700 m 2250 1350 l 450 2700 l gs col0 s gr [] 0 sd % Polyline n 3825 1350 m 3825 2700 l 5625 2700 l 6075 1800 l 5625 1350 l 3825 1350 l 4275 1800 l 3825 2700 l 6075 1800 l 4275 1800 l 5625 1350 l gs col0 s gr % Polyline [45] 0 sd n 5625 2700 m 5625 1350 l 3825 2700 l gs col0 s gr [] 0 sd % Polyline [45] 0 sd n 8100 2700 m 8100 1350 l 6300 2700 l gs col0 s gr [] 0 sd % here ends figure; % % here starts figure with depth 45 % Polyline 2 slj 0 slc 15.000 slw gs clippath 8903 1894 m 8583 1874 l 8576 1993 l 8895 2014 l 8895 2014 l 8660 1939 l 8903 1894 l cp 373 2054 m 692 2039 l 687 1919 l 367 1934 l 367 1934 l 610 1983 l 373 2054 l cp eoclip n 675 1980 m 632 1982 l 606 1983 l 572 1985 l 533 1988 l 489 1991 l 441 1994 l 392 1998 l 343 2003 l 294 2008 l 248 2014 l 203 2021 l 162 2028 l 124 2036 l 90 2044 l 58 2053 l 30 2063 l 4 2074 l -19 2086 l -40 2099 l -58 2113 l -75 2128 l -90 2145 l -101 2160 l -111 2175 l -121 2192 l -129 2209 l -136 2227 l -142 2246 l -147 2266 l -151 2287 l -153 2309 l -154 2332 l -154 2355 l -152 2379 l -148 2404 l -143 2429 l -137 2454 l -129 2479 l -119 2505 l -107 2530 l -94 2556 l -79 2581 l -63 2605 l -45 2630 l -26 2653 l -5 2677 l 18 2699 l 42 2721 l 67 2742 l 94 2762 l 123 2781 l 153 2800 l 184 2818 l 218 2835 l 241 2846 l 265 2857 l 290 2867 l 316 2877 l 343 2887 l 371 2897 l 401 2907 l 431 2916 l 463 2925 l 496 2934 l 531 2943 l 567 2951 l 604 2960 l 643 2968 l 683 2976 l 724 2983 l 767 2991 l 811 2998 l 857 3005 l 904 3012 l 952 3019 l 1002 3025 l 1052 3031 l 1104 3037 l 1157 3043 l 1212 3048 l 1267 3054 l 1323 3059 l 1380 3064 l 1438 3068 l 1497 3073 l 1556 3077 l 1616 3081 l 1677 3084 l 1738 3088 l 1800 3091 l 1863 3095 l 1926 3098 l 1989 3100 l 2053 3103 l 2118 3106 l 2183 3108 l 2249 3110 l 2315 3112 l 2382 3114 l 2449 3116 l 2518 3118 l 2588 3120 l 2642 3121 l 2697 3123 l 2753 3124 l 2810 3125 l 2868 3126 l 2926 3127 l 2986 3128 l 3046 3129 l 3107 3130 l 3169 3131 l 3232 3132 l 3296 3133 l 3361 3134 l 3427 3135 l 3494 3135 l 3561 3136 l 3630 3137 l 3699 3137 l 3769 3138 l 3840 3138 l 3911 3139 l 3983 3139 l 4056 3140 l 4129 3140 l 4202 3140 l 4276 3140 l 4350 3141 l 4425 3141 l 4500 3141 l 4574 3141 l 4649 3141 l 4724 3141 l 4799 3140 l 4873 3140 l 4947 3140 l 5021 3139 l 5094 3139 l 5167 3139 l 5240 3138 l 5311 3137 l 5382 3137 l 5453 3136 l 5522 3135 l 5591 3135 l 5659 3134 l 5726 3133 l 5792 3132 l 5858 3131 l 5922 3130 l 5985 3129 l 6047 3128 l 6108 3127 l 6169 3126 l 6228 3124 l 6286 3123 l 6343 3122 l 6400 3120 l 6455 3119 l 6509 3117 l 6563 3116 l 6616 3114 l 6667 3113 l 6668 3113 l 6736 3110 l 6804 3108 l 6871 3105 l 6936 3103 l 7001 3100 l 7065 3097 l 7128 3094 l 7190 3091 l 7252 3087 l 7313 3083 l 7373 3079 l 7433 3075 l 7492 3071 l 7550 3067 l 7607 3062 l 7664 3057 l 7719 3051 l 7774 3046 l 7828 3040 l 7880 3034 l 7932 3028 l 7982 3021 l 8032 3015 l 8080 3008 l 8127 3000 l 8172 2993 l 8216 2985 l 8259 2977 l 8300 2969 l 8341 2961 l 8379 2952 l 8416 2943 l 8452 2934 l 8487 2925 l 8520 2915 l 8551 2906 l 8582 2896 l 8611 2886 l 8639 2876 l 8666 2865 l 8691 2855 l 8716 2844 l 8740 2832 l 8762 2821 l 8784 2809 l 8805 2798 l 8836 2779 l 8865 2759 l 8893 2738 l 8919 2717 l 8944 2694 l 8968 2671 l 8991 2647 l 9012 2623 l 9032 2597 l 9050 2571 l 9067 2544 l 9083 2517 l 9097 2490 l 9110 2463 l 9121 2435 l 9130 2408 l 9139 2381 l 9145 2354 l 9150 2328 l 9154 2303 l 9157 2278 l 9158 2254 l 9158 2231 l 9157 2209 l 9154 2189 l 9151 2169 l 9146 2150 l 9141 2132 l 9135 2116 l 9128 2100 l 9116 2080 l 9103 2062 l 9088 2046 l 9071 2032 l 9051 2019 l 9028 2007 l 9001 1997 l 8972 1987 l 8939 1978 l 8903 1970 l 8865 1963 l 8824 1957 l 8783 1952 l 8742 1947 l 8704 1943 l 8670 1940 l 8642 1938 l 8595 1935 l gs col0 s gr gr % arrowhead 0 slj n 373 2054 m 610 1983 l 367 1934 l 373 2054 l cp gs col7 1.00 shd ef gr col0 s % arrowhead n 8903 1894 m 8660 1939 l 8895 2014 l 8903 1894 l cp gs col7 1.00 shd ef gr col0 s % here ends figure; % % here starts figure with depth 40 % Polyline 0 slj 0 slc 7.500 slw n 6300 1350 m 6300 2700 l 8100 2700 l 8550 1800 l 8100 1350 l 6300 1350 l 6750 1800 l 6300 2700 l 8550 1800 l 6750 1800 l 8100 1350 l gs col0 s gr % here ends figure; $F2psEnd rs showpage %%Trailer %EOF regina-4.95/engine/subcomplex/reflector.fig000644 000765 000024 00000002462 12234011536 020712 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 2 1 2 2 0 7 55 -1 -1 4.500 0 0 -1 0 0 2 2880 2025 3600 2025 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 11 450 1350 450 2700 2250 2700 2700 1800 2250 1350 450 1350 900 1800 450 2700 2700 1800 900 1800 2250 1350 2 1 1 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 2250 2700 2250 1350 450 2700 2 2 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 5 450 1350 2250 1350 2250 2700 450 2700 450 1350 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 11 3825 1350 3825 2700 5625 2700 6075 1800 5625 1350 3825 1350 4275 1800 3825 2700 6075 1800 4275 1800 5625 1350 2 1 1 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 5625 2700 5625 1350 3825 2700 2 2 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 5 3825 1350 5625 1350 5625 2700 3825 2700 3825 1350 2 1 0 1 0 7 40 -1 -1 0.000 0 0 -1 0 0 11 6300 1350 6300 2700 8100 2700 8550 1800 8100 1350 6300 1350 6750 1800 6300 2700 8550 1800 6750 1800 8100 1350 2 1 1 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 8100 2700 8100 1350 6300 2700 2 2 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 5 6300 1350 8100 1350 8100 2700 6300 2700 6300 1350 3 0 0 2 0 7 45 -1 -1 6.000 0 1 1 8 1 0 2.00 120.00 240.00 1 0 2.00 120.00 240.00 675 1980 -270 1980 -135 2970 2115 3150 7200 3150 9090 2925 9270 1935 8595 1935 0.000 1.000 1.000 1.000 1.000 1.000 1.000 0.000 regina-4.95/engine/subcomplex/reflector.png000644 000765 000024 00000006566 12234011536 020742 0ustar00babstaff000000 000000 ‰PNG  IHDRšPiìÙ pHYs±±Åa†tIMEÖ œà IDATxÚíœ_ˆ$GÇ¿Ñ Gî`' ½ÎŸŽá ;ÑhÅ„kNÄØ ­!(Èìèƒ>ÌäÁ— Ìø¢"AwaA}Øa!KfçŨ$®‰‚aC Í…Ë,‰r–ÁɃ¡¯—ø 'Ƈî™î®î®?Ý=›S§ánggf§ëWŸúÔ¯ºº¦nú¹Ç3ŸF…Çs|§ü‡¸‡wWX¤w~=Òò_õ~^eü×^²¿©ö7qÐ|÷oݪ 6™Ò;9Õ)ÿAæÓŠŠ4›øoý­-›§noUt.2{ç®5IehöOMºÕpÝμ€òlöíF5\t»ŽžísØì_E«.u[nÛVcóÑä¿öæƒkÏš¥¹üòð³_[›˜ÀÑ“®KÊ~Ü­g×n+Ëåà·Ç_}ôèjo<|¡á–Üø7ŽŽÊ¶ÿùÉŸ¾øåSÏ›ÀÛŸj¯Ê‚i9oB_phx諒Ù3±SʛЗ LWƒ—ïÍ>Á¨”7s_‚øß'è«x#@S†Íœ‹? >âì”g³g¢›—é± §upØì”`³àâ´ç%6"4EÙ̹nÖ×(ÏfÏDQ6 .ÀlÔÖ{Àa³OP”Í‚ €vpÒ› 56B4EØÄ¸`º¨Å?>ŽòlöLbãÿXŸÅŸ‡M¿:›8—Xü3 JljÂw4°ÓUåb/~Û‰ÆAøÁ ü8­»ƒ†"—P]ÓPÁsÚoœÖZE¹ÀÚLü[}ª¬Qò&îKP¶Eàì™àg)oöBÔòÞÄ}a‹t:¬ÃloæñË{“äœì\øSÖ4²lR\Fu;öÛõùkeØÌÑH²Is¶bU÷ÞpØ,â—c“æ2Ôãñßl¨±‘B#Ã&ŘÍWEA®)ÉfF‚MŠ €ÃõøA®ÉcÅ/f“æÌüD ƒ\#ÏFˆM—ùø4Ú2l"46Y\0bžYX“É&?ŸM—tü k$ÙH¢á±ÉâL_m3ÏÌsM)6146™\€á:S[§cõ™f?ŸM&àðˆÿ\ì± Y4yl²¹ÀGºb®ÇßW”MM›.˜¥ë/fM›DüÙlr¸dƳFŠ4š,69\€é¤.Z”kJ°I É`“Ç·SÏE¹&‹M2þ4›<.9ñG¹FŽ<–M.—ìÊXS”M Ã&ŸKN™Ö¤Ø0ñ'ÙäsÉ‹?a4q6<.þ°Y=ñ\S˜ ƒ&ƆË%§;>ÍTa’ ĆË%/þsÌï"6*hæò¸˜œÏ® ëìßaâ Ùp¹È¹ g¬aؤâØp¹pâg¬²QBƒé¤+à‚‰÷J2×d“Bƒ».à⿚W¦d®aÙ¤ãÁpAþ¬N2׈٨¡ÁЯ¯s«n|îëÿº'õfç’Q ¾½ÞXãýÉß_øÊ™ FOÛ®`FÜÚò‡¼®îŠžl+Öú¼ÚÙÜ¢ ×x­$~÷ÝSc ý-T‡†ßhÖ¹³Z^B˜àA«@~ƒv.!LOøw,Žø‚r“P,~wŒŽP~ŸQ«LšéQ‹?ßxO\@T UH3â7Ü™TWšéQ‹ßJŒ¸00HH㸞K¶$Ðd‘+Í«mA%Þ&hEË–æPLNßFÜZxÒøÂø˜0‰*¦Ô¡T#še92Öl;ñN5d¥ñLQÉpùB$ÌIH3­ Wj]Ñc”‘F&~—DÂ,¤tO¿OzÁ(z Õ¡i»–ã¾øÌÚúíQŸ/ÍlÔ×â[¿YsÒLfâ2ù1aÊH#ÿk‘0Q+è ÓqÇ?¼Ïnj}DZ„h¼¿–`°MoýEcÚàK£‹ï‚ŽfŸì%X,W¿n‹¹ŽþýÄ–¸Kã‹ã÷'³û“çŠ2 ![îxðš¸Ïé‹Ñ;‹Æ¥®G=-xÞyiü©o׬‘ȓƊï7Ž`·’×5Ii\¸TÓàxž˜¿æ3WšÌ©_ƪi½}Gòï‹I߇ÅAK?Çi„€Ž7è:Þ°‚6\K\œºžaÍЧ÷•~I"ÓÌDµ0šéí:s]ÃTÃð Füa>š\iŽ»Â¶bv™ëš¢Òˆâ÷'3½Ë\×°Ã3F¯G5Àèõ¶7vIÍxL-u9ÆÀê|H ÏFfC, 3®OgšNðÏ ~FâaGQØ5…Âԙ뚢҈ºŒéá Ú¶‚𹞵y@hÆÛÖVâÍð‰áÙ„?§ øÌIešƒ¶„0€JK39¯KtZ\iÝÛÁÅ×C4ÞE\ —2¥™ê¶”0Á '2<›6Ú2Ân-)Í´aK “Š_0@¬A5ÞFG؆3¥7ä„IÍ¡-OšSN˜Œ94eixñ'„IÍ¡q¥Ð{(@³)&“-MþÈ„†ík—(M~NN ÃΡ‘¦%+ ?͸k„5`›H13ÎÞ­Ë ÃæšeI3B«.)L*רJ“¿ü+- ›kDÒÄéÔà/¡ˆ4ïvå…aúÚeIã릴0l®Q–&/þ,a˜ø©ø6qPࣩKã´ma/?¾\i¦Ç¦)/ à÷O”ÆŸ´laÑ$ŒX.j($Ͱ­$ Ûé.CšYöÔož0Ì¡(Mvü¹Â@~]âÔ-" àòÙj¥‰–‹ðæ’ÒøÃn£„0Aþ°¤¥ì¨8¼²…ÒÌe.(ÌR2Í|ά`†QÊ4óμ 0Š™Æ1 5 ·½+fâ(,Ì<×T'Í|¹daa¹FBš0þâÂÌs¤4ýh:÷æ—î°»Ÿ–ðÎT'M¸\²¤0¸¦ËH³cW ài’Òô-#DƒÝ€RŽ4Áø´œ0a®©N½[V˜y®‘¦[Z˜0×H^Ó8¯cŽÆròêì°‹Zå…©6ÓøC»QZÙL3\k•F%Óxý0¿Üô/X.Èûÿ€‰ic4Óí *gÖ)fï°‹©~<‚iVP¤Ó:ï‹aûWuS}2Ó+ÙTø\_JšM+l-5ÐvûYµvhÌÉÄ®h·ã?W#ÍÈ|wR‰0®½È—f}r¶aಔ4y‘Âo@·3Ö>uÿówW# àÇ?©ù%÷•tëWx ùÆkwéUí |ÿ92X —Ã…µMo#ÍæåW{W++Ú-¹<»üxnUEúÕx¯þî/ÝYݹü{ÕÈDû Æ»FÆÌ vÜ€Eª2ºM«‡4¸›»VLJx¸Ëhc»mx›¤§­*èC;úNrœü‘è¡vIÛpV5ô!ÎCx=y“Ü£†ö±e¬ªéäÚ÷¶ØKKvû gÛè­àœ4˜mÚKçùôÎNÎ6:ÍUuÜ1£—5ËÚt‹ÆÍ&YÕÙ‰ŒÊÆãf'»›ÊÙmì8+:K?Æ®C¬Ü*w«:Ïq\B¬UÞY–.ŽK-Â˼]á:.5ˆµ²§ê²ëRq³ç¢ ðPW#YÍTqx®K]ÈõFB4!fÏ¥ÑYMV…R×ÓˆAd;!94sâžK ÍÐVÉ'Ïõ¨çiÊ [ÍâT”zÔÓ@ +ò$¥páiš¡‘BÛ¿ª£‰$ NN €DûÊü?_ÔSÀõàQP† ¥vä-ކ)“P/ÜR‚Ðÿÿ¯zÀõ¸á’\Q ­M© àb;€Ì.“qC_6¹^êA_°“f‡å„± 4ÜfÔ£‰’kœ¬”ÿeU7UF+«–ÚIÿëÐl³*\âÛåþÐ2÷z"==IEND®B`‚regina-4.95/engine/surfaces/boundaries.fig000644 000765 000024 00000004601 12234011536 020507 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 990 1890 4860 5310 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2925 2025 135 135 2925 2025 3060 2025 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4725 5175 135 135 4725 5175 4860 5175 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 1125 5175 135 135 1125 5175 1260 5175 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 2295 2970 1530 4545 4365 4545 3555 2970 2295 2970 2 1 0 1 0 7 55 -1 -1 0.000 0 0 -1 0 0 2 2295 2970 4365 4545 4 1 0 50 -1 0 14 0.0000 0 150 120 2925 2115 0\001 4 1 0 50 -1 0 14 0.0000 0 150 120 4725 5265 1\001 4 1 0 50 -1 0 14 0.0000 0 150 120 1125 5265 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 4050 4230 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 2655 3150 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3465 3150 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3960 4500 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 1710 4500 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 2340 3240 2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 3465 3600 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 2745 4005 T1\001 -6 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 8325 2025 135 135 8325 2025 8460 2025 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10125 5175 135 135 10125 5175 10260 5175 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 6525 5175 135 135 6525 5175 6660 5175 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 7 7785 2970 7155 4050 7785 5175 8865 5175 9495 4050 8865 2970 7785 2970 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 7785 2970 9495 4050 7785 5175 7785 2970 4 1 0 50 -1 0 14 0.0000 0 150 120 8325 2115 0\001 4 1 0 50 -1 0 14 0.0000 0 150 120 10125 5265 1\001 4 1 0 50 -1 0 14 0.0000 0 150 120 6525 5265 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8190 3150 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8820 3150 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 9225 3825 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 9270 4095 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 9225 4410 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8820 5130 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8145 5130 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7875 5040 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7695 4905 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7290 4095 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7695 3375 2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 7560 4365 T2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 8505 4140 T3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 8775 4815 T1\001 4 1 0 50 -1 0 12 0.0000 0 135 225 8820 3465 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7875 3240 1\001 regina-4.95/engine/surfaces/CMakeLists.txt000644 000765 000024 00000002456 12236247215 020442 0ustar00babstaff000000 000000 # surfaces # Files to compile SET ( FILES components crushandcut enumerator enumfilter links ndisc ndisctype nnormalsurface nnormalsurfacelist nprism nsanstandard nsoriented nsorientedquad nsquad nsquadoct nsstandard nsurfacefilter nsurfacesubset nxmlfilterreader nxmlfilterreaders nxmlsurfacereader orientable quadtostd sfcombination sfproperties spheres stdtoquad ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} surfaces/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES coordregistry.h coordregistry-impl.h filterregistry.h filterregistry-impl.h flavourregistry.h ndisc.h ndisctype.h nnormalsurface.h nnormalsurfacelist.h normalcoords.h normalflags.h normalspec-impl.h normalspec.tcc nprism.h nsanstandard.h nsmirrored.h nsoriented.h nsorientedquad.h nsquad.h nsquadoct.h nsstandard.h nsurfacefilter.h nsurfacesubset.h nxmlfilterreader.h nxmlsurfacereader.h sfcombination.h sfproperties.h surfacefiltertype.h DESTINATION ${INCLUDEDIR}/surfaces COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/surfaces/components.cpp000644 000765 000024 00000025171 12234011536 020563 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "surfaces/ndisc.h" #include "surfaces/nsstandard.h" #include "surfaces/nsanstandard.h" namespace regina { namespace { /** * Stores a connected component ID for a normal disc. */ struct ComponentData { long id; /**< Stores the ID of the connected component that this disc belongs to. Components are numbered from 0 upwards; -1 means that the component ID is unknown. */ /** * Create a new structure with the component ID initialised to -1. */ ComponentData() : id(-1) { } }; /** * Splits the given normal surface into connected components. * * The surface itself will not be changed. Instead, each * connected component will be appended to the end of the * given list \a dest. Note that the list \a dest will \e not * be emptied at the beginning of this routine (i.e., any * surfaces that were in the list beforehand will be left there). * * The components inserted into \a dest will always be in standard * (tri-quad or tri-quad-oct) coordinates, regardless of the * native coordinate system that is used by the given surface. * Any transverse orientations will be lost. * * This routine is slow, since it performs a depth-first search * over the entire set of normal discs. If the surface contains * a very large number of discs (large enough to cause integer * overflows or exhaust memory) then this routine will give up and * return 0. * * The components inserted into \a dest will be newly created. It is * the responsibility of the caller of this routine to deallocate them. * * \pre The given normal surface is compact (has finitely many discs) * and is also embedded. * * \todo \prob Check for absurdly large numbers of discs and bail * accordingly. * * @param s the surface to split into components. * @param dest the vector into which individual components will * be inserted. * @return the number of connected components. */ unsigned splitIntoComponents(const NNormalSurface& s, std::vector& dest) { // Shamelessly copied from my orientation/two-sidedness code from // years earlier. Some day I will need to make a generic structure // for a depth-first search over normal discs. Not today. // If the precondition (compactness) does not hold, things will get // nasty (read: infinite). if (! s.isCompact()) return 0; // TODO: First check that there aren't too many discs! // All right. Off we go. NDiscSetSurfaceData components(s); // Stores the component ID for each disc. std::queue discQueue; // A queue of discs whose component IDs must be propagated. NDiscSpecIterator it(components); // Runs through the discs whose component IDs might not have yet // been determined. NDiscSpec use; // The disc that currently holds our interest. int nGluingArcs; // The number of arcs on the current disc to // which an adjacent disc might may be glued. NDiscSpec* adjDisc; // The disc to which the current disc is glued. NPerm4 arc[8]; // Holds each gluing arc for the current disc. NPerm4 adjArc; // Represents the corresponding gluing arc on the // adjacent disc. long compID = 0; // The current working component ID. long i; while (true) { // If there's no discs to propagate from, choose the next // one without a component label. while (discQueue.empty() && (! it.done())) { if (components.data(*it).id == -1) { components.data(*it).id = compID++; discQueue.push(*it); } ++it; } if (discQueue.empty()) break; // At the head of the queue is the next already-labelled disc // whose component ID must be propagated. use = discQueue.front(); discQueue.pop(); // Determine along which arcs we may glue other discs. if (use.type < 4) { // Current disc is a triangle. nGluingArcs = 3; for (i = 0; i < 3; i++) arc[i] = regina::triDiscArcs(use.type, i); } else if (use.type < 7) { // Current disc is a quad. nGluingArcs = 4; for (i = 0; i < 4; i++) arc[i] = regina::quadDiscArcs(use.type - 4, i); } else { // Current disc is an octagon. nGluingArcs = 8; for (i = 0; i < 8; i++) arc[i] = regina::octDiscArcs(use.type - 7, i); } // Process any discs that might be adjacent to each of these // gluing arcs. for (i = 0; i < nGluingArcs; ++i) { // Establish which is the adjacent disc. adjDisc = components.adjacentDisc(use, arc[i], adjArc); if (adjDisc == 0) continue; // There is actually a disc glued along this arc. // Propagate the component ID. if (components.data(*adjDisc).id == -1) { components.data(*adjDisc).id = components.data(use).id; discQueue.push(*adjDisc); } // Tidy up. delete adjDisc; } } // Were there any discs at all? if (compID == 0) return 0; // Create the set of normal surfaces! // Note that all vectors are automagically initialised to zero. NTriangulation* tri = s.getTriangulation(); NNormalSurfaceVector** ans = new NNormalSurfaceVector*[compID]; NNormalSurfaceVector* vec; long coord; if (s.rawVector()->allowsAlmostNormal()) { for (i = 0; i < compID; ++i) ans[i] = new NNormalSurfaceVectorANStandard( 10 * tri->getNumberOfTetrahedra()); for (it.init(components); ! it.done(); ++it) { vec = ans[components.data(*it).id]; coord = 10 * (*it).tetIndex + (*it).type; vec->setElement(coord, (*vec)[coord] + 1); } } else { for (i = 0; i < compID; ++i) ans[i] = new NNormalSurfaceVectorStandard( 7 * tri->getNumberOfTetrahedra()); for (it.init(components); ! it.done(); ++it) { vec = ans[components.data(*it).id]; coord = 7 * (*it).tetIndex + (*it).type; vec->setElement(coord, (*vec)[coord] + 1); } } for (i = 0; i < compID; ++i) dest.push_back(new NNormalSurface(tri, ans[i])); delete[] ans; // All done! return compID; } } // anonymous namespace bool NNormalSurface::disjoint(const NNormalSurface& other) const { // Some sanity tests before we begin. // These should all pass if the user has adhered to the preconditions. if (! (isCompact() && other.isCompact())) return false; if (! (isConnected() && other.isConnected())) return false; // Begin with a local compatibility test. if (! locallyCompatible(other)) return false; // Now we know that the sum of both surfaces is an embedded surface. // Form the sum, pull it apart into connected components, and see // whether we get our original two surfaces back. NNormalSurfaceVector* v = static_cast(vector->clone()); (*v) += *(other.vector); NNormalSurface* sum = new NNormalSurface(triangulation, v); typedef std::vector CompVector; CompVector bits; splitIntoComponents(*sum, bits); bool ans = false; if (bits.size() == 2) for (int c = 0; c < 2; ++c) if (sameSurface(*bits[c])) { ans = true; break; } for (CompVector::iterator it = bits.begin(); it != bits.end(); ++it) delete *it; delete sum; return ans; } } // namespace regina regina-4.95/engine/surfaces/coordregistry-impl.h000644 000765 000024 00000010534 12234011536 021676 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/coordregistry-impl.h * \brief Contains the registry of all normal coordinate systems that can * be used to create and store normal surfaces in 3-manifold triangulations. * * Each time a new coordinate system is created, this registry must be * updated to: * * - add a #include line for the corresponding vector subclass; * - add a corresponding case to each implementation of forCoords(). * * See coordregistry.h for how other routines can use this registry. */ #ifndef __FLAVOURREGISTRY_IMPL_H #ifndef __DOXYGEN #define __FLAVOURREGISTRY_IMPL_H #endif #include "surfaces/coordregistry.h" #include "surfaces/nsstandard.h" #include "surfaces/nsanstandard.h" #include "surfaces/nsquad.h" #include "surfaces/nsquadoct.h" #include "surfaces/nsoriented.h" #include "surfaces/nsorientedquad.h" namespace regina { template inline typename FunctionObject::ReturnType forCoords( NormalCoords coords, FunctionObject func, typename FunctionObject::ReturnType defaultReturn) { switch (coords) { case NS_STANDARD : return func(NormalInfo()); case NS_AN_STANDARD : return func(NormalInfo()); case NS_QUAD : return func(NormalInfo()); case NS_AN_QUAD_OCT : return func(NormalInfo()); case NS_ORIENTED : return func(NormalInfo()); case NS_ORIENTED_QUAD : return func(NormalInfo()); default: return defaultReturn; } } template inline void forCoords(NormalCoords coords, VoidFunctionObject func) { switch (coords) { case NS_STANDARD : func(NormalInfo()); break; case NS_AN_STANDARD : func(NormalInfo()); break; case NS_QUAD : func(NormalInfo()); break; case NS_AN_QUAD_OCT : func(NormalInfo()); break; case NS_ORIENTED : func(NormalInfo()); break; case NS_ORIENTED_QUAD : func(NormalInfo()); break; default: break; } } } // namespace regina #endif regina-4.95/engine/surfaces/coordregistry.h000644 000765 000024 00000020617 12234011536 020742 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/coordregistry.h * \brief Provides access to a registry of all normal coordinate systems * that can be used to create and store normal surfaces in 3-manifold * triangulations. * * Each time a new coordinate system is created, the file * coordregistry-impl.h must be updated to include it. Instructions on * how to do this are included in coordregistry-impl.h. * * External routines can access the registry by calling one of the * forCoords() template functions defined in coordregistry.h. * * \warning You should not include this header unless it is necessary, * since it will automatically import every header for every coordinate * system in the registry. */ // The old registry macros will silently compile but do nothing. // This could lead to nasty surprises, so throw an error if it looks like // people are still trying to use them. #ifdef __FLAVOUR_REGISTRY_BODY #error "The old REGISTER_FLAVOUR macros have been removed. Use forCoords() instead." #endif #ifndef __FLAVOURREGISTRY_H #ifndef __DOXYGEN #define __FLAVOURREGISTRY_H #endif #include "surfaces/normalcoords.h" #include "utilities/registryutils.h" namespace regina { class NNormalSurfaceVector; // For the deprecated NewNormalSurfaceVector. /** * \weakgroup surfaces * @{ */ /** * Allows the user to call a template function whose template parameter * matches a given value of NormalCoords, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible coordinate sytems. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new coordinate system is added then only a small amount of code * needs to be extended to incorporate it. * * This function can only work with coordinate systems in which * you can create and store normal surfaces. All other coordinate systems are * considered invalid for our purposes here. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(NormalInfo) is * defined for any valid NormalCoords enum value \a c. Then, * when the user calls forCoords(coords, func, defaultReturn), * this routine will call func(NormalInfo) and pass back * the corresponding return value. If \a coords does not denote a valid * coordinate system as described above, then forCoords() will pass back * \a defaultReturn instead. * * There is also a two-argument variant of forCoords() that works with * void functions. * * \pre The function object must have a typedef \a ReturnType indicating * the return type of the corresponding templated unary bracket operator. * Inheriting from Returns<...> is a convenient way to ensure this. * * \ifacespython Not present. * * @param coords the given normal coordinate system. * @param func the function object whose unary bracket operator we will * call with a NormalInfo object. * @param defaultReturn the value to return if the given * coordinate system is invalid. * @return the return value from the corresponding unary bracket * operator of \a func, or \a defaultReturn if the given * coordinate system is invalid. */ template typename FunctionObject::ReturnType forCoords( NormalCoords coords, FunctionObject func, typename FunctionObject::ReturnType defaultReturn); /** * Allows the user to call a template function whose template parameter * matches a given value of NormalCoords, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible coordinate sytems. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new coordinate system is added then only a small amount of code * needs to be extended to incorporate it. * * This function can only work with coordinate systems in which * you can create and store normal surfaces. All other coordinate systems are * considered invalid for our purposes here. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(NormalInfo) is * defined for any valid NormalCoords enum value \a c. Then, * when the user calls forCoords(coords, func), * this routine will call func(NormalInfo) in turn. * If \a coords does not denote a valid coordinate system as described above, * then forCoords() will do nothing. * * There is also a three-argument variant of forCoords() that works with * functions with return values. * * \ifacespython Not present. * * @param coords the given normal coordinate system. * @param func the function object whose unary bracket operator we will * call with a NormalInfo object. */ template void forCoords(NormalCoords coords, VoidFunctionObject func); /** * A legacy typedef provided for backward compatibility only. * * \deprecated The old NewNormalSurfaceVector class has been redesigned * as the more general template class NewFunction1, and moved into the * header registryutils.h. This typedef is provided for backward * compatibility, and will be removed in some future version of Regina. */ typedef NewFunction1 NewNormalSurfaceVector; /** * A deprecated alias for the registry-based template function forCoords(). * See forCoords() for further details. */ template inline typename FunctionObject::ReturnType forFlavour( NormalCoords coords, FunctionObject func, typename FunctionObject::ReturnType defaultReturn) { return forCoords(coords, func, defaultReturn); } /** * A deprecated alias for the registry-based template function forCoords(). * See forCoords() for further details. */ template inline void forFlavour(NormalCoords coords, VoidFunctionObject func) { forCoords(coords, func); } /*@}*/ } // namespace regina // Import template implementations: #include "surfaces/coordregistry-impl.h" #endif regina-4.95/engine/surfaces/crushandcut.cpp000644 000765 000024 00000176757 12234011536 020742 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "enumerate/ntreetraversal.h" #include "surfaces/nnormalsurface.h" #include "surfaces/nprism.h" #include "triangulation/ntriangulation.h" #include "utilities/nthread.h" namespace regina { /** * The bulk of this file contains the implementation for cutAlong(), * which cuts along a normal surface. * * The way this routine operates is as follows: * * - We add an extra set of vertex links to the original normal surface. We * refer to the regions inside these vertex links as "vertex neighbourhoods". * These neighbourhoods are typically balls (though around ideal vertices * they are cones over the corresponding boundary surfaces). * * - If we cut along the new normal surface, each tetrahedron falls * apart into the following types of blocks: * * + Triangular prisms, represented by the class TriPrism. There are * four types of triangular prism, corresponding to the four triangular * normal disc types that bound them. * * + Quadrilateral prisms, represented by the class QuadPrism. There * are three types of quadrilateral prism, corresponding to the * three quadrilateral normal disc types that bound them. * * + Tetrahedra truncated at all four vertices, represented by the * class TruncTet. There is only one type of truncated tetrahedron. * * + Truncated half-tetrahedra, obtained by slicing a truncated * tetrahedron along a quadrilateral normal disc and keeping one of * the two halves that results. This is represented by the class * TruncHalfTet. There are six types of truncated half-tetrahedra, * corresponding to the three choices of "slicing quadrilateral" and * the two choices of which half to keep. * * The reason we add the extra vertex links is to keep this list of * block types small; otherwise we must also deal with \e partially * truncated tetrahedra and half-tetrahedra. * * - We triangulate each of the blocks. There are two types of boundary * for each block: (i) boundary faces that run along the normal * surface, and (ii) boundary faces that run along the joins between * adjacent tetrahedra. Faces (i) can be left alone (they will become * the boundary of the final triangulation); faces (ii) need to be * joined together according to how the original tetrahedra were * joined together. Note that a handful of type (i) boundary faces * run along the extra vertex links, and so these will be glued back * onto the missing vertex neighbourhoods at the end of the cutting * procedure. * * - For each block, we organise the boundaries of type (ii) into * quadrilaterals and hexagons (each of which is the intersection of * the block with a single face of the enclosing tetrahedron). These * are represented by the classes BdryQuad and BdryHex respectively. * * - The overall cutting algorithm then works as follows: * * + Triangulate each block. The class TetBlockSet represents a full * set of triangulated blocks within a single tetrahedron of the * original triangulation. * * + Glue together the type (ii) boundaries between adjacent blocks, * using layerings as needed to make the triangulated quadrilaterals * and hexagons compatible. * * + Construct the missing vertex neighbourhoods and glue them back onto * the appropriate type (i) block boundaries. * * See the individual classes for further details. */ // ------------------------------------------------------------------------ // Supporting classes for cutAlong() // ------------------------------------------------------------------------ namespace { class Bdry; /** * A single triangulated block within a single tetrahedron of the * original triangulation. */ class Block { protected: NTetrahedron* outerTet_; /**< The "outer tetrahedron". This is the tetrahedron of the original triangulation that contains this block. */ NTetrahedron** innerTet_; /**< The "inner tetrahedra". These are the tetrahedra used to triangulate this block, and also to perform any necessary boundary layerings. */ unsigned nInnerTet_; /**< The number of inner tetrahedra. */ Bdry* bdry_[4]; /**< The four quadrilateral / hexagonal type (ii) boundaries of this block. These are boundaries that meet faces of the outer tetrahedron (not boundaries that run along the original normal surface). Specifically, bdry_[i] is the boundary on face i of the outer tetrahedron (or 0 if this block does not actually meet face i of the outer tetrahedron). */ NTetrahedron* link_[4]; /**< Indicates which inner tetrahedra in this block (if any) face the vertices of the outer tetrahedron. Specifically, if this block contains a triangle on its boundary surrounding vertex i of the outer tetrahedron, and if this triangle is facing vertex i (so the block lies on the side of the triangle away from vertex i, not towards vertex i), then link_[i] is the inner tetrahedron containing this triangle. Otherwise, link_[i] is null. */ NPerm4 linkVertices_[4]; /**< If link_[i] is non-zero, then linkVertices_[i] is a mapping from vertices of the inner tetrahedron \a link_[i] to vertices of the outer tetrahedron \a outerTet. Specifically, if we let V denote vertex i of the outer tetrahedron, then this mapping sends the three vertices of the inner vertex linking triangle surrounding V to the three "parallel" vertices of the triangular face opposite V in the outer tetrahedron. */ public: /** * Destroys the four boundaries, but none of the inner * tetrahedra or the outer tetrahedron. */ virtual ~Block(); /** * Returns the outer tetrahedron. */ NTetrahedron* outerTet(); /** * Glues this block to the given adjacent block. This * involves taking the quadrilateral or hexagon boundary of * this block that sits on the given face of this block's * outer tetrahedron, and gluing it (using layerings if need * be) to the corresponding quadrilateral or hexagon of the * adjacent block. */ void joinTo(int face, Block* other); /** * Creates a new inner tetrahedron within this block. * It is assumed that this tetrahedron is to be used for * layering on the block boundary. However, this layering * will not be performed by this routine (so the new tetrahedron * that is returned will be isolated). * * This routine assumes that the innerTet_ array has enough * space for a new tetrahedron (which should be true if the * correct arguments were passed to the Block constructor). * * The new tetrahedron will be automatically added to the * same triangulation as the previous tetrahedra in this block. * * \pre This block already contains at least one inner tetrahedron. */ NTetrahedron* layeringTetrahedron(); /** * Attaches the triangle described by link_[vertex] to the * given "small tetrahedron" that forms part of the corresponding * vertex neighbourhood. It is assumed that the small tetrahedron * in the neighbourhood will have its vertices numbered in a * way that represents a "shrunk-down" version of the outer * tetrahedron (where "shrunk-down" means dilation about the * given outer tetrahedron vertex). */ void attachVertexNbd(NTetrahedron* nbd, int vertex); protected: /** * Creates a new block within the given outer tetrahedron. * This constructor creates \a initialNumTet inner tetrahedra, * but also leaves enough extra room in the inner tetrahedron * array for up to \a maxLayerings layerings on the boundaries. * * All new inner tetrahedra created now and in subsequent * layerings will be automatically inserted into the given * triangulation. */ Block(NTetrahedron* outerTet, unsigned initialNumTet, unsigned maxLayerings, NTriangulation* insertInto); }; /** * A triangular prism, triangulated using three inner tetrahedra. * * See cut-triprism.fig for details of the triangulation. * In this diagram, inner tetrahedra are numbered T0, T1, ..., and * vertices of the inner tetrahedra are indicated using plain integers. * For a block of type 0 (see the constructor for details), vertices * of the outer tetrahedron are indicated using integers in circles. * For blocks of other types, vertex 0 is swapped with vertex \a type * in the outer tetrahedron. */ class TriPrism : public Block { public: /** * Creates a new triangular prism within the given outer * tetrahedron. * * The given block type is an integer between 0 and 3 * inclusive, describing which triangle type in the * outer tetrahedron supplies the two ends of the prism. * * Equivalently, the block type describes which vertex of * the outer tetrahedron this triangular prism surrounds. * * All new inner tetrahedra will be automatically * inserted into the given triangulation. */ TriPrism(NTetrahedron *outerTet, int type, NTriangulation* insertInto); }; /** * A quadrilateral prism, triangulated using five inner tetrahedra. * * See cut-quadprism.fig for details of the triangulation. * In this diagram, inner tetrahedra are numbered T0, T1, ..., and * vertices of the inner tetrahedra are indicated using plain integers. * For a block of type 1 (see the constructor for details), vertices * of the outer tetrahedron are indicated using integers in circles. * For blocks of other types, the vertices of the outer tetrahedron * are permuted accordingly. */ class QuadPrism : public Block { public: /** * Creates a new quadrilateral prism within the given outer * tetrahedron. * * The given block type is an integer between 0 and 2 * inclusive, describing which quadrilateral type in the * outer tetrahedron supplies the two ends of the prism. * * All new inner tetrahedra will be automatically * inserted into the given triangulation. */ QuadPrism(NTetrahedron *outerTet, int type, NTriangulation* insertInto); }; /** * A truncated half-tetrahedron, triangulated using eight inner tetrahedra. * * See cut-trunchalftet.fig for details of the triangulation. * In this diagram, inner tetrahedra are numbered T0, T1, ..., and * vertices of the inner tetrahedra are indicated using plain integers. * For a block of type 0 (see the constructor for details), vertices * of the outer tetrahedron are indicated using integers in circles. * For blocks of other types, the vertices of the outer tetrahedron * are permuted accordingly. */ class TruncHalfTet : public Block { public: /** * Creates a new truncated half-tetrahedron within the given * outer tetrahedron. * * The given block type is an integer between 0 and 5 * inclusive, describing which edge of the outer tetrahedron * this half-tetrahedron does not meet at all. * * All new inner tetrahedra will be automatically * inserted into the given triangulation. */ TruncHalfTet(NTetrahedron *outerTet, int type, NTriangulation* insertInto); }; /** * A truncated tetrahedron, triangulated using eleven inner tetrahedra. * * See cut-trunctet.fig for details of the triangulation. * In this diagram, inner tetrahedra are numbered T0, T1, ..., * vertices of the inner tetrahedra are indicated using plain integers, * and vertices of the outer tetrahedron are indicated using integers in * circles. */ class TruncTet : public Block { public: /** * Creates a new truncated tetrahedron within the given outer * tetrahedron. * * All new inner tetrahedra will be automatically * inserted into the given triangulation. */ TruncTet(NTetrahedron *outerTet, NTriangulation* insertInto); }; /** * Represents a quadrilateral or hexagonal piece of a block boundary. * This is the intersection of a block with a single face of its * outer tetrahedron. * * For each such quadrilateral or hexagon, we number the faces from * 0 to 1 (for a quadrilateral) or 0 to 3 (for a hexagon); these are * called the \e inner boundary faces. The enclosing face of the * outer tetrahedron is called the \e outer boundary face. * * See boundaries.fig for details of how each quadrilateral or * hexagon is triangulated. The inner boundary faces are numbered * T0, T1, ..., the vertices of each inner boundary face are * numbered using plain integers (these are the \e inner vertex * numbers), and the vertices of the outer boundary face are numbered * using integers in circles (these are the \e outer vertex numbers). */ class Bdry { protected: Block* block_; /**< The block whose boundary this is a piece of. */ NPerm4 outerVertices_; /**< A mapping from the outer vertex numbers 0, 1 and 2 to the corresponding vertex numbers in the outer tetrahedron (block_->outerTet). */ public: /** * A virtual destructor that does nothing. */ virtual ~Bdry(); /** * Identifies (i.e., glues together) this piece of boundary * and the given piece of boundary, performing layerings if * required to make sure that the boundaries are compatible. * * This routine assumes that this and the given piece of * boundary are the same shape (i.e., both quadrilaterals or * both hexagons). */ virtual void joinTo(Bdry* other) = 0; /* PRE: other is same shape */ protected: /** * Initialises a new object with the given block and the * given mapping from outer vertex numbers to vertices of * the outer tetrahedron. */ Bdry(Block* block, NPerm4 outerVertices); }; /** * A piece of block boundary that is a triangulated quadrilateral. * * See boundaries.fig for details of how the quadrilateral is * triangulated, and see the Bdry class notes for what all the * numbers on this diagram actually mean. */ class BdryQuad : public Bdry { private: NTetrahedron* innerTet_[2]; /**< The two inner tetrahedra of the block that supply the two inner boundary faces for this quadrilateral. */ NPerm4 innerVertices_[2]; /**< For the ith inner boundary face, the permutation innerVertices_[i] maps the inner vertex numbers 0, 1 and 2 to the corresponding vertex numbers in the inner tetrahedron innerTet_[i]. */ public: /** * See Bdry::joinTo() for details. */ virtual void joinTo(Bdry* other); private: /** * Initialises a new object with the given block and the * given mapping from outer vertex numbers to vertices of * the outer tetrahedron. */ BdryQuad(Block* block, NPerm4 outerVertices); /** * Layers a new tetrahedron upon the quadrilateral boundary, so * that the triangulated quadrilateral becomes a reflection of * itself. As a result, the diagram in boundaries.fig will * likewise become reflected, and so the faces and vertex numbers * within this diagram will now refer to different tetrahedra * and vertices within the underlying block. */ void reflect(); friend class TriPrism; friend class QuadPrism; friend class TruncHalfTet; }; /** * A piece of block boundary that is a triangulated hexagon. * * See boundaries.fig for details of how the hexagon is * triangulated, and see the Bdry class notes for what all the * numbers on this diagram actually mean. */ class BdryHex : public Bdry { private: NTetrahedron* innerTet_[4]; /**< The four inner tetrahedra of the block that supply the four inner boundary faces for this quadrilateral. */ NPerm4 innerVertices_[4]; /**< For the ith inner boundary face, the permutation innerVertices_[i] maps the inner vertex numbers 0, 1 and 2 to the corresponding vertex numbers in the inner tetrahedron innerTet_[i]. */ public: /** * See Bdry::joinTo() for details. */ virtual void joinTo(Bdry* other); private: /** * Initialises a new object with the given block and the * given mapping from outer vertex numbers to vertices of * the outer tetrahedron. */ BdryHex(Block* block, NPerm4 outerVertices); /** * Layers four new tetrahedra upon the hexagon boundary, so * that the triangulated hexagon becomes a reflection of * itself. As a result, the diagram in boundaries.fig will * likewise become reflected, and so the faces and vertex numbers * within this diagram will now refer to different tetrahedra * and vertices within the underlying block. */ void reflect(); /** * Rotates the diagram from boundaries.fig by a one-third turn, * so that the faces and vertex numbers in boundaries.fig * correspond to different tetrahedra and vertex numbers in * the underlying block. * * This is simply a relabelling operation; no layerings are * performed, and no changes are made to the triangulation * of the block itself. */ void rotate(); friend class TruncHalfTet; friend class TruncTet; }; /** * Stores a full set of triangulated blocks within a single * "outer" tetrahedron of the original triangulation, as formed by * cutting along some normal surface within this original triangulation. */ class TetBlockSet { private: unsigned long triCount_[4]; /**< The number of triangular normal discs of each type within this outer tetrahedron. This does \e not include the "extra" vertex links that we add to slice off a neighbourhood of each vertex of the original triangulation. */ unsigned long quadCount_; /**< The number of quadrilateral normal discs (of any type) within this outer tetrahedron. The \e type of these quadrilaterals is stored in the separate data member \a quadType_. */ int quadType_; /**< The unique quadrilateral normal disc \e type that appears within this outer tetrahedron. This will be 0, 1 or 2 if there are indeed quadrilateral discs (i.e., quadCount_ is positive), or -1 if this outer tetrahedron contains no quadrilateral discs at all (i.e., quadCount_ is zero). */ Block** triPrism_[4]; /**< The array triPrism_[i] contains all of the triangular prism blocks surrounding vertex \a i of the outer tetrahedron, or is null if there are no such blocks. Such blocks exist if and only if the normal surface contains at least one triangular disc of type \a i. If these blocks do exist, they are stored in order moving \e away from vertex \a i of the outer tetrahedron (or equivalently, moving in towards the centre of the outer tetrahedron). */ Block** quadPrism_; /**< An array containing all of the quadrilateral prism blocks, or null if there are no such blocks within this outer tetrahedron. These blocks exist if and only if the normal surface contains two or more quadrilateral discs. If these blocks do exist, they are stored in order moving \e away from vertex 0 of the outer tetrahedron. */ Block* truncHalfTet_[2]; /**< The two truncated half-tetrahedron blocks, or null if there are no such blocks within this outer tetrahedron. These blocks exist if and only if the normal surface contains one or more quadrilateral discs. In this case, the block truncHalfTet_[0] is closer to vertex 0 of the outer tetrahedron, and the block truncHalfTet_[1] is further away. */ Block* truncTet_; /**< The unique truncated tetrahedron block, or null if there is no such block within this outer tetrahedron. This block exists if and only if the normal surface contains no quadrilateral discs. */ NTetrahedron* vertexNbd_[4]; /**< The four small tetrahedra that contribute to the vertex neighbourhoods surrounding the four vertices of the outer tetrahedron. The vertices of each small tetrahedron are numbered in a way that matches the outer tetrahedron (so the small tetrahedron vertexNbd_[i] looks like the outer tetrahedron, shrunk down using a dilation about vertex \a i of the outer tetrahedron). */ public: /** * Creates a full set of triangulated blocks within the given * outer tetrahedron, as formed by cutting along the given normal * surface. * * This contructor also creates the four small tetrahedra in * the vertex neighbourhoods, and glues them to the four * blocks closest to the outer tetrahedron vertices. * * All new inner tetrahedra (that is, the inner tetrahedra * from the triangulated blocks and also the small * tetrahedra in the vertex neighbourhoods) will be automatically * inserted into the given triangulation. */ TetBlockSet(const NNormalSurface* s, unsigned long tetIndex, NTriangulation* insertInto); /** * Destroys all block and boundary structures within this outer * tetrahedron. * * Note that the inner tetrahedra that make up the triangulated * blocks are \e not destroyed (since presumably we are keeping * these inner tetrahedra for the new sliced-open triangulation * that we plan to give back to the user). */ ~TetBlockSet(); /** * Returns the number of blocks that provide quadrilateral * boundaries on the given face of the outer tetrahedron, * surrounding the given vertex of the outer tetrahedron. * * It is assumed that \a face and \a fromVertex are not equal. */ unsigned long numQuadBlocks(int face, int fromVertex); /** * Returns the requested block that provides a quadrilateral * boundary on some particular face of the outer tetrahedron, * surrounding the given vertex of the outer tetrahedron. * * Ordinarily the face number would be passed; however, * it is omitted because it is not actually necessary. * Nevertheless, the choice of face number affects how \e many * such blocks are available; see numQuadBlocks() for details. * * Blocks are numbered 0,1,... outwards from the given vertex of * the outer tetrahedron, in towards the centre of the outer * tetrahedron. The argument \a whichBlock indicates which of * these blocks should be returned. * * It is assumed that \a whichBlock is strictly less than * numQuadBlocks(\a face, \a fromVertex), where \a face is * the relevant face of the outer tetrahedron. */ Block* quadBlock(int fromVertex, unsigned long whichBlock); /** * Returns the (unique) block that provides a hexagon * boundary on the given face of the outer tetrahedron, or * null if there is no such block. */ Block* hexBlock(int face); /** * Returns the small tetrahedron that contributes to the * vertex neighbourhood surrounding the given vertex of the * outer tetrahedron. * * See the data member \a vertexNbd_ for further details. */ NTetrahedron* vertexNbd(int vertex); }; inline Block::~Block() { for (unsigned i = 0; i < 4; ++i) delete bdry_[i]; delete[] innerTet_; } inline NTetrahedron* Block::outerTet() { return outerTet_; } inline void Block::joinTo(int face, Block* other) { bdry_[face]->joinTo(other->bdry_[outerTet_->adjacentFace(face)]); } inline NTetrahedron* Block::layeringTetrahedron() { return (innerTet_[nInnerTet_++] = innerTet_[0]->getTriangulation()->newTetrahedron()); } inline void Block::attachVertexNbd(NTetrahedron* nbd, int vertex) { link_[vertex]->joinTo(linkVertices_[vertex].preImageOf(vertex), nbd, linkVertices_[vertex]); } inline Block::Block(NTetrahedron *outerTet, unsigned initialNumTet, unsigned maxLayerings, NTriangulation* insertInto) : outerTet_(outerTet), innerTet_(new NTetrahedron*[initialNumTet + maxLayerings]), nInnerTet_(initialNumTet) { unsigned i; for (i = 0; i < nInnerTet_; ++i) innerTet_[i] = insertInto->newTetrahedron(); std::fill(link_, link_ + 4, static_cast(0)); } TriPrism::TriPrism(NTetrahedron *outerTet, int type, NTriangulation* insertInto) : Block(outerTet, 3, 3, insertInto) { innerTet_[1]->joinTo(1, innerTet_[0], NPerm4()); innerTet_[1]->joinTo(3, innerTet_[2], NPerm4()); NPerm4 vertices = NPerm4(0, type); BdryQuad* q; bdry_[vertices[0]] = 0; q = new BdryQuad(this, vertices * NPerm4(0, 2, 3, 1)); q->innerTet_[0] = innerTet_[1]; q->innerTet_[1] = innerTet_[2]; q->innerVertices_[0] = NPerm4(2, 3, 1, 0); q->innerVertices_[1] = NPerm4(1, 3, 2, 0); bdry_[vertices[1]] = q; q = new BdryQuad(this, vertices * NPerm4(2, 3)); q->innerTet_[0] = innerTet_[0]; q->innerTet_[1] = innerTet_[2]; q->innerVertices_[0] = NPerm4(2, 1, 0, 3); q->innerVertices_[1] = NPerm4(0, 3, 2, 1); bdry_[vertices[2]] = q; q = new BdryQuad(this, vertices); q->innerTet_[0] = innerTet_[0]; q->innerTet_[1] = innerTet_[1]; q->innerVertices_[0] = NPerm4(3, 1, 0, 2); q->innerVertices_[1] = NPerm4(0, 1, 3, 2); bdry_[vertices[3]] = q; link_[vertices[0]] = innerTet_[0]; linkVertices_[vertices[0]] = vertices * NPerm4(0, 1, 3, 2); } QuadPrism::QuadPrism(NTetrahedron *outerTet, int type, NTriangulation* insertInto) : Block(outerTet, 5, 4, insertInto) { innerTet_[4]->joinTo(2, innerTet_[0], NPerm4()); innerTet_[4]->joinTo(3, innerTet_[1], NPerm4()); innerTet_[4]->joinTo(0, innerTet_[2], NPerm4()); innerTet_[4]->joinTo(1, innerTet_[3], NPerm4()); NPerm4 vertices( regina::vertexSplitDefn[type][0], regina::vertexSplitDefn[type][2], regina::vertexSplitDefn[type][1], regina::vertexSplitDefn[type][3]); BdryQuad* q; q = new BdryQuad(this, vertices * NPerm4(2, 3, 1, 0)); q->innerTet_[0] = innerTet_[2]; q->innerTet_[1] = innerTet_[1]; q->innerVertices_[0] = NPerm4(1, 0, 2, 3); q->innerVertices_[1] = NPerm4(2, 3, 1, 0); bdry_[vertices[0]] = q; q = new BdryQuad(this, vertices * NPerm4(3, 0, 2, 1)); q->innerTet_[0] = innerTet_[3]; q->innerTet_[1] = innerTet_[2]; q->innerVertices_[0] = NPerm4(2, 1, 3, 0); q->innerVertices_[1] = NPerm4(3, 0, 2, 1); bdry_[vertices[1]] = q; q = new BdryQuad(this, vertices * NPerm4(0, 1, 3, 2)); q->innerTet_[0] = innerTet_[0]; q->innerTet_[1] = innerTet_[3]; q->innerVertices_[0] = NPerm4(3, 2, 0, 1); q->innerVertices_[1] = NPerm4(0, 1, 3, 2); bdry_[vertices[2]] = q; q = new BdryQuad(this, vertices * NPerm4(1, 2, 0, 3)); q->innerTet_[0] = innerTet_[1]; q->innerTet_[1] = innerTet_[0]; q->innerVertices_[0] = NPerm4(0, 3, 1, 2); q->innerVertices_[1] = NPerm4(1, 2, 0, 3); bdry_[vertices[3]] = q; } TruncHalfTet::TruncHalfTet(NTetrahedron *outerTet, int type, NTriangulation* insertInto): Block(outerTet, 8, 10, insertInto) { innerTet_[1]->joinTo(2, innerTet_[0], NPerm4()); innerTet_[1]->joinTo(1, innerTet_[2], NPerm4()); innerTet_[1]->joinTo(0, innerTet_[3], NPerm4()); innerTet_[2]->joinTo(0, innerTet_[4], NPerm4()); innerTet_[3]->joinTo(1, innerTet_[4], NPerm4()); innerTet_[3]->joinTo(3, innerTet_[5], NPerm4()); innerTet_[5]->joinTo(2, innerTet_[6], NPerm4()); innerTet_[4]->joinTo(2, innerTet_[7], NPerm4()); NPerm4 vertices( NEdge::edgeVertex[type][0], NEdge::edgeVertex[type][1], NEdge::edgeVertex[5 - type][0], NEdge::edgeVertex[5 - type][1]); BdryQuad* q; BdryHex* h; h = new BdryHex(this, vertices * NPerm4(1, 3, 2, 0)); h->innerTet_[0] = innerTet_[2]; h->innerTet_[1] = innerTet_[7]; h->innerTet_[2] = innerTet_[5]; h->innerTet_[3] = innerTet_[4]; h->innerVertices_[0] = NPerm4(2, 0, 1, 3); h->innerVertices_[1] = NPerm4(1, 2, 0, 3); h->innerVertices_[2] = NPerm4(0, 3, 2, 1); h->innerVertices_[3] = NPerm4(0, 2, 1, 3); bdry_[vertices[0]] = h; h = new BdryHex(this, vertices * NPerm4(0, 3, 2, 1)); h->innerTet_[0] = innerTet_[0]; h->innerTet_[1] = innerTet_[7]; h->innerTet_[2] = innerTet_[6]; h->innerTet_[3] = innerTet_[3]; h->innerVertices_[0] = NPerm4(1, 2, 3, 0); h->innerVertices_[1] = NPerm4(3, 2, 0, 1); h->innerVertices_[2] = NPerm4(0, 2, 1, 3); h->innerVertices_[3] = NPerm4(0, 1, 3, 2); bdry_[vertices[1]] = h; q = new BdryQuad(this, vertices * NPerm4(3, 1, 0, 2)); q->innerTet_[0] = innerTet_[2]; q->innerTet_[1] = innerTet_[0]; q->innerVertices_[0] = NPerm4(3, 1, 0, 2); q->innerVertices_[1] = NPerm4(0, 2, 3, 1); bdry_[vertices[2]] = q; q = new BdryQuad(this, vertices * NPerm4(2, 0, 1, 3)); q->innerTet_[0] = innerTet_[6]; q->innerTet_[1] = innerTet_[5]; q->innerVertices_[0] = NPerm4(3, 2, 1, 0); q->innerVertices_[1] = NPerm4(1, 2, 3, 0); bdry_[vertices[3]] = q; link_[vertices[2]] = innerTet_[6]; linkVertices_[vertices[2]] = vertices * NPerm4(3, 2, 0, 1); link_[vertices[3]] = innerTet_[7]; linkVertices_[vertices[3]] = vertices * NPerm4(3, 1, 2, 0); } TruncTet::TruncTet(NTetrahedron *outerTet, NTriangulation* insertInto) : Block(outerTet, 11, 16, insertInto) { innerTet_[0]->joinTo(2, innerTet_[4], NPerm4()); innerTet_[1]->joinTo(3, innerTet_[7], NPerm4()); innerTet_[2]->joinTo(0, innerTet_[6], NPerm4()); innerTet_[3]->joinTo(1, innerTet_[9], NPerm4()); innerTet_[5]->joinTo(3, innerTet_[4], NPerm4()); innerTet_[5]->joinTo(1, innerTet_[6], NPerm4()); innerTet_[8]->joinTo(0, innerTet_[7], NPerm4()); innerTet_[8]->joinTo(2, innerTet_[9], NPerm4()); innerTet_[4]->joinTo(1, innerTet_[10], NPerm4()); innerTet_[6]->joinTo(3, innerTet_[10], NPerm4()); innerTet_[7]->joinTo(2, innerTet_[10], NPerm4()); innerTet_[9]->joinTo(0, innerTet_[10], NPerm4()); BdryHex* h; h = new BdryHex(this, NPerm4(2, 1, 3, 0)); h->innerTet_[0] = innerTet_[2]; h->innerTet_[1] = innerTet_[8]; h->innerTet_[2] = innerTet_[3]; h->innerTet_[3] = innerTet_[9]; h->innerVertices_[0] = NPerm4(2, 0, 1, 3); h->innerVertices_[1] = NPerm4(1, 2, 0, 3); h->innerVertices_[2] = NPerm4(0, 1, 2, 3); h->innerVertices_[3] = NPerm4(0, 2, 1, 3); bdry_[0] = h; h = new BdryHex(this, NPerm4(3, 2, 0, 1)); h->innerTet_[0] = innerTet_[3]; h->innerTet_[1] = innerTet_[5]; h->innerTet_[2] = innerTet_[0]; h->innerTet_[3] = innerTet_[4]; h->innerVertices_[0] = NPerm4(3, 1, 2, 0); h->innerVertices_[1] = NPerm4(2, 3, 1, 0); h->innerVertices_[2] = NPerm4(1, 2, 3, 0); h->innerVertices_[3] = NPerm4(1, 3, 2, 0); bdry_[1] = h; h = new BdryHex(this, NPerm4(0, 3, 1, 2)); h->innerTet_[0] = innerTet_[0]; h->innerTet_[1] = innerTet_[8]; h->innerTet_[2] = innerTet_[1]; h->innerTet_[3] = innerTet_[7]; h->innerVertices_[0] = NPerm4(0, 2, 3, 1); h->innerVertices_[1] = NPerm4(3, 0, 2, 1); h->innerVertices_[2] = NPerm4(2, 3, 0, 1); h->innerVertices_[3] = NPerm4(2, 0, 3, 1); bdry_[2] = h; h = new BdryHex(this, NPerm4(1, 0, 2, 3)); h->innerTet_[0] = innerTet_[1]; h->innerTet_[1] = innerTet_[5]; h->innerTet_[2] = innerTet_[2]; h->innerTet_[3] = innerTet_[6]; h->innerVertices_[0] = NPerm4(1, 3, 0, 2); h->innerVertices_[1] = NPerm4(0, 1, 3, 2); h->innerVertices_[2] = NPerm4(3, 0, 1, 2); h->innerVertices_[3] = NPerm4(3, 1, 0, 2); bdry_[3] = h; link_[0] = innerTet_[0]; linkVertices_[0] = NPerm4(1, 2, 3, 0); link_[1] = innerTet_[1]; linkVertices_[1] = NPerm4(1, 2, 3, 0); link_[2] = innerTet_[2]; linkVertices_[2] = NPerm4(1, 2, 3, 0); link_[3] = innerTet_[3]; linkVertices_[3] = NPerm4(1, 2, 3, 0); } inline Bdry::~Bdry() { // Empty virtual destructor. } inline Bdry::Bdry(Block* block, NPerm4 outerVertices) : block_(block), outerVertices_(outerVertices) { } void BdryQuad::joinTo(Bdry* other) { // Assume other is a BdryQuad. BdryQuad* dest = static_cast(other); // Get the map from *this* 012 to *dest* tetrahedron vertices. NPerm4 destMap = block_->outerTet()-> adjacentGluing(outerVertices_[3]) * outerVertices_; if (destMap != dest->outerVertices_) { // A reflection is our only recourse. dest->reflect(); if (destMap != dest->outerVertices_) { // This should never happen. std::cerr << "ERROR: Cannot match up BdryQuad pair." << std::endl; ::exit(1); } } // Now we match up perfectly. for (int i = 0; i < 2; ++i) innerTet_[i]->joinTo(innerVertices_[i][3], dest->innerTet_[i], dest->innerVertices_[i] * innerVertices_[i].inverse()); } inline BdryQuad::BdryQuad(Block* block, NPerm4 outerVertices) : Bdry(block, outerVertices) { } void BdryQuad::reflect() { NTetrahedron* layering = block_->layeringTetrahedron(); layering->joinTo(0, innerTet_[1], innerVertices_[1] * NPerm4(3, 2, 1, 0)); layering->joinTo(2, innerTet_[0], innerVertices_[0] * NPerm4(1, 0, 3, 2)); innerTet_[0] = innerTet_[1] = layering; innerVertices_[0] = NPerm4(); innerVertices_[1] = NPerm4(2, 3, 0, 1); outerVertices_ = outerVertices_ * NPerm4(1, 2); } void BdryHex::joinTo(Bdry* other) { // Assume other is a BdryQuad. BdryHex* dest = static_cast(other); // Get the map from *this* 012 to *dest* tetrahedron vertices. NPerm4 destMap = block_->outerTet()-> adjacentGluing(outerVertices_[3]) * outerVertices_; if (destMap.sign() != dest->outerVertices_.sign()) dest->reflect(); while (destMap != dest->outerVertices_) dest->rotate(); // Now we match up perfectly. for (int i = 0; i < 4; ++i) innerTet_[i]->joinTo(innerVertices_[i][3], dest->innerTet_[i], dest->innerVertices_[i] * innerVertices_[i].inverse()); } inline BdryHex::BdryHex(Block* block, NPerm4 outerVertices) : Bdry(block, outerVertices) { } void BdryHex::reflect() { NTetrahedron* layering0 = block_->layeringTetrahedron(); NTetrahedron* layering1 = block_->layeringTetrahedron(); NTetrahedron* layering2 = block_->layeringTetrahedron(); NTetrahedron* layering3 = block_->layeringTetrahedron(); layering0->joinTo(1, innerTet_[3], innerVertices_[3] * NPerm4(1, 3)); layering0->joinTo(2, innerTet_[2], innerVertices_[2] * NPerm4(2, 3)); layering1->joinTo(3, layering0, NPerm4()); layering1->joinTo(1, innerTet_[1], innerVertices_[1] * NPerm4(2, 3, 0, 1)); layering2->joinTo(0, layering0, NPerm4()); layering2->joinTo(1, innerTet_[0], innerVertices_[0] * NPerm4(1, 3, 2, 0)); layering3->joinTo(0, layering1, NPerm4()); layering3->joinTo(3, layering2, NPerm4()); innerTet_[0] = layering2; innerTet_[1] = layering1; innerTet_[2] = layering3; innerTet_[3] = layering3; innerVertices_[0] = NPerm4(0, 3, 1, 2); innerVertices_[1] = NPerm4(1, 0, 3, 2); innerVertices_[2] = NPerm4(3, 2, 0, 1); innerVertices_[3] = NPerm4(3, 0, 1, 2); outerVertices_ = outerVertices_ * NPerm4(1, 2); } void BdryHex::rotate() { NTetrahedron* t = innerTet_[0]; innerTet_[0] = innerTet_[1]; innerTet_[1] = innerTet_[2]; innerTet_[2] = t; NPerm4 p = innerVertices_[0]; innerVertices_[0] = innerVertices_[1]; innerVertices_[1] = innerVertices_[2]; innerVertices_[2] = p; innerVertices_[3] = innerVertices_[3] * NPerm4(1, 2, 0, 3); outerVertices_ = outerVertices_ * NPerm4(1, 2, 0, 3); } TetBlockSet::TetBlockSet(const NNormalSurface* s, unsigned long tetIndex, NTriangulation* insertInto) { unsigned long i, j; for (i = 0; i < 4; ++i) triCount_[i] = s->getTriangleCoord(tetIndex, i).longValue(); NLargeInteger coord; if ((coord = s->getQuadCoord(tetIndex, 0)) > 0) { quadCount_ = coord.longValue(); quadType_ = 0; } else if ((coord = s->getQuadCoord(tetIndex, 1)) > 0) { quadCount_ = coord.longValue(); quadType_ = 1; } else if ((coord = s->getQuadCoord(tetIndex, 2)) > 0) { quadCount_ = coord.longValue(); quadType_ = 2; } else { quadCount_ = 0; quadType_ = -1; } NTetrahedron* tet = s->getTriangulation()->getTetrahedron(tetIndex); // Build the blocks. // Note in all of this that we insert an extra "fake" triangle at each // vertex (i.e., the entire surface gains a fake set of extra vertex // links). for (i = 0; i < 4; ++i) { if (triCount_[i] == 0) triPrism_[i] = 0; else { triPrism_[i] = new Block*[triCount_[i]]; for (j = 0; j < triCount_[i]; ++j) triPrism_[i][j] = new TriPrism(tet, i, insertInto); } } if (quadCount_ == 0) { quadPrism_ = 0; truncHalfTet_[0] = truncHalfTet_[1] = 0; truncTet_ = new TruncTet(tet, insertInto); } else { if (quadCount_ > 1) { quadPrism_ = new Block*[quadCount_ - 1]; for (j = 0; j < quadCount_ - 1; ++j) quadPrism_[j] = new QuadPrism(tet, quadType_, insertInto); } else quadPrism_ = 0; truncHalfTet_[0] = new TruncHalfTet(tet, 5 - quadType_, insertInto); truncHalfTet_[1] = new TruncHalfTet(tet, quadType_, insertInto); truncTet_ = 0; } for (i = 0; i < 4; ++i) { vertexNbd_[i] = insertInto->newTetrahedron(); if (triCount_[i] > 0) triPrism_[i][0]->attachVertexNbd(vertexNbd_[i], i); else if (quadCount_ == 0) truncTet_->attachVertexNbd(vertexNbd_[i], i); else if (i == 0 || static_cast(i) == NEdge::edgeVertex[quadType_][1]) truncHalfTet_[0]->attachVertexNbd(vertexNbd_[i], i); else truncHalfTet_[1]->attachVertexNbd(vertexNbd_[i], i); } } TetBlockSet::~TetBlockSet() { unsigned long i, j; for (i = 0; i < 4; ++i) if (triPrism_[i]) { for (j = 0; j < triCount_[i]; ++j) delete triPrism_[i][j]; delete[] triPrism_[i]; } if (quadCount_ == 0) { delete truncTet_; } else { if (quadPrism_) { for (j = 0; j < quadCount_ - 1; ++j) delete quadPrism_[j]; delete[] quadPrism_; } delete truncHalfTet_[0]; delete truncHalfTet_[1]; } } unsigned long TetBlockSet::numQuadBlocks(int face, int fromVertex) { // We see all triangular discs surrounding fromVertex. unsigned long ans = triCount_[fromVertex]; if (quadType_ == regina::vertexSplit[face][fromVertex]) { // We also see the quadrilateral discs. ans += quadCount_; } return ans; } Block* TetBlockSet::quadBlock(int fromVertex, unsigned long whichBlock) { // First come the triangular prisms. if (whichBlock < triCount_[fromVertex]) return triPrism_[fromVertex][whichBlock]; // Next comes the truncated half-tetrahedron. if (whichBlock == triCount_[fromVertex]) { if (fromVertex == 0 || fromVertex == NEdge::edgeVertex[quadType_][1]) return truncHalfTet_[0]; else return truncHalfTet_[1]; } // Finally we have the quad prisms. if (fromVertex == 0 || fromVertex == NEdge::edgeVertex[quadType_][1]) return quadPrism_[whichBlock - triCount_[fromVertex] - 1]; else return quadPrism_[ quadCount_ - (whichBlock - triCount_[fromVertex]) - 1]; } Block* TetBlockSet::hexBlock(int face) { if (quadCount_ == 0) return truncTet_; if (face == 0 || face == NEdge::edgeVertex[quadType_][1]) return truncHalfTet_[1]; return truncHalfTet_[0]; } inline NTetrahedron* TetBlockSet::vertexNbd(int vertex) { return vertexNbd_[vertex]; } } // ------------------------------------------------------------------------ // Implementation of cutAlong() // ------------------------------------------------------------------------ NTriangulation* NNormalSurface::cutAlong() const { NTriangulation* ans = new NTriangulation(); NPacket::ChangeEventSpan span(ans); unsigned long nTet = getTriangulation()->getNumberOfTetrahedra(); if (nTet == 0) return ans; unsigned long i; TetBlockSet** sets = new TetBlockSet*[nTet]; for (i = 0; i < nTet; ++i) sets[i] = new TetBlockSet(this, i, ans); NTriangulation::TriangleIterator fit; NTriangle* f; unsigned long tet0, tet1; int face0, face1; int fromVertex0, fromVertex1; NPerm4 gluing; unsigned long quadBlocks; for (fit = getTriangulation()->getTriangles().begin(); fit != getTriangulation()->getTriangles().end(); ++fit) { f = *fit; if (f->isBoundary()) continue; tet0 = f->getEmbedding(0).getTetrahedron()->markedIndex(); tet1 = f->getEmbedding(1).getTetrahedron()->markedIndex(); face0 = f->getEmbedding(0).getTriangle(); face1 = f->getEmbedding(1).getTriangle(); gluing = f->getEmbedding(0).getTetrahedron()->adjacentGluing(face0); for (fromVertex0 = 0; fromVertex0 < 4; ++fromVertex0) { if (fromVertex0 == face0) continue; fromVertex1 = gluing[fromVertex0]; quadBlocks = sets[tet0]->numQuadBlocks(face0, fromVertex0); for (i = 0; i < quadBlocks; ++i) sets[tet0]->quadBlock(fromVertex0, i)->joinTo( face0, sets[tet1]->quadBlock(fromVertex1, i)); sets[tet0]->vertexNbd(fromVertex0)->joinTo( face0, sets[tet1]->vertexNbd(fromVertex1), gluing); } sets[tet0]->hexBlock(face0)->joinTo(face0, sets[tet1]->hexBlock(face1)); } // All done! Clean up. for (i = 0; i < nTet; ++i) delete sets[i]; delete[] sets; return ans; } // ------------------------------------------------------------------------ // Implementation of crush() // ------------------------------------------------------------------------ NTriangulation* NNormalSurface::crush() const { NTriangulation* ans = new NTriangulation(*triangulation); unsigned long nTet = ans->getNumberOfTetrahedra(); if (nTet == 0) return ans; // Work out which tetrahedra contain which quad types. int* quads = new int[nTet]; long whichTet = 0; for (whichTet = 0; whichTet < static_cast(nTet); whichTet++) { if (getQuadCoord(whichTet, 0) != 0) quads[whichTet] = 0; else if (getQuadCoord(whichTet, 1) != 0) quads[whichTet] = 1; else if (getQuadCoord(whichTet, 2) != 0) quads[whichTet] = 2; else quads[whichTet] = -1; } // Run through and fix the tetrahedron gluings. NTetrahedron* tet; NTetrahedron* adj; int adjQuads; NPerm4 adjPerm; NPerm4 swap; int face, adjFace; for (whichTet = 0; whichTet < static_cast(nTet); whichTet++) if (quads[whichTet] == -1) { // We want to keep this tetrahedron, so make sure it's glued // up correctly. tet = ans->getTetrahedron(whichTet); for (face = 0; face < 4; face++) { adj = tet->adjacentTetrahedron(face); if (! adj) continue; adjQuads = quads[ans->tetrahedronIndex(adj)]; if (adjQuads == -1) continue; // We're glued to a bad tetrahedron. Follow around // until we reach a good tetrahedron or a boundary. adjPerm = tet->adjacentGluing(face); adjFace = adjPerm[face]; while (adj && (adjQuads >= 0)) { swap = NPerm4(adjFace, vertexSplitPartner[adjQuads][adjFace]); adjFace = swap[adjFace]; adjPerm = adj->adjacentGluing(adjFace) * swap * adjPerm; adj = adj->adjacentTetrahedron(adjFace); adjFace = adjPerm[face]; if (adj) adjQuads = quads[ans->tetrahedronIndex(adj)]; } // Reglue the tetrahedron face accordingly. tet->unjoin(face); if (! adj) continue; // We haven't yet unglued the face of adj since there is // at least one bad tetrahedron between tet and adj. adj->unjoin(adjFace); tet->joinTo(face, adj, adjPerm); } } // Delete unwanted tetrahedra. for (whichTet = nTet - 1; whichTet >= 0; whichTet--) if (quads[whichTet] >= 0) ans->removeTetrahedronAt(whichTet); delete[] quads; return ans; } bool NNormalSurface::isCompressingDisc(bool knownConnected) const { // Is it even a disc? if (! hasRealBoundary()) return false; if (getEulerCharacteristic() != 1) return false; if (! knownConnected) { if (! isConnected()) return false; } // Yep, it's a disc (and hence two-sided). // Count the number of boundary spheres that our triangulation has // to begin with. unsigned long origSphereCount = 0; NTriangulation::BoundaryComponentIterator bit; for (bit = getTriangulation()->getBoundaryComponents().begin(); bit != getTriangulation()->getBoundaryComponents().end(); ++bit) if ((*bit)->getEulerCharacteristic() == 2) ++origSphereCount; // Now cut along the disc, and see if we get an extra sphere as a // result. If not, the disc boundary is non-trivial and so the disc // is compressing. std::auto_ptr cut(cutAlong()); if (cut->getNumberOfBoundaryComponents() == getTriangulation()->getNumberOfBoundaryComponents()) { // The boundary of the disc is not a separating curve in the // boundary of the triangulation. Therefore we might end up // converting a torus boundary into a sphere boundary, but the // disc is compressing regardless. return true; } unsigned long newSphereCount = 0; for (bit = cut->getBoundaryComponents().begin(); bit != cut->getBoundaryComponents().end(); ++bit) if ((*bit)->getEulerCharacteristic() == 2) ++newSphereCount; if (newSphereCount == origSphereCount) return true; else return false; } /** * Supporting classes for isIncompressible(). */ namespace { class CompressionTest; /** * Manages two parallel searches for compressing discs. * If one search reports that it has found a compressing disc, * then it will cancel the other. * * Each individual search is run through a CompressingTest object * (a subclass of NThread). Before starting, each test thread should * register itself via registerTest(), and if it finds a compressing * disc then it should call hasFound() to cancel the other thread. */ class SharedSearch { private: NMutex mutex_; bool found_; CompressionTest* ct_[2]; public: inline SharedSearch() : found_(false) { ct_[0] = ct_[1] = 0; } inline void registerTest(CompressionTest* ct) { if (! ct_[0]) ct_[0] = ct; else ct_[1] = ct; } inline bool hasFound() const { NMutex::MutexLock lock(mutex_); return found_; } void markFound(); }; /** * A thread class whose task is to locate a compressing disc in a * single connected triangulation with boundary. * * IMPORTANT: A side-effect of run() is that it will always delete * the underlying triangulation. */ class CompressionTest : public NThread { private: NTriangulation* t_; SharedSearch& ss_; NTreeSingleSoln* currSearch_; NMutex searchMutex_; public: inline CompressionTest(NTriangulation* t, SharedSearch& ss) : t_(t), ss_(ss), currSearch_(0) { ss_.registerTest(this); } inline void cancel() { NMutex::MutexLock lock(searchMutex_); if (currSearch_) currSearch_->cancel(); } void* run(void*) { // Remember: run() must delete t_. if (ss_.hasFound()) { delete t_; return 0; } t_->intelligentSimplify(); if (ss_.hasFound()) { delete t_; return 0; } // Try for a simple answer first. if (t_->hasSimpleCompressingDisc()) { ss_.markFound(); delete t_; return 0; } if (ss_.hasFound()) { delete t_; return 0; } // The LP-and-crush method is only suitable for // orientable triangulations with a single boundary component. if (t_->getNumberOfBoundaryComponents() > 1 || ! t_->isOrientable()) { // Fall back to the slow and non-cancellable method. if (t_->hasCompressingDisc()) ss_.markFound(); delete t_; return 0; } // Compute the Euler characteristic of the boundary component. long ec = t_->getBoundaryComponent(0)->getEulerCharacteristic(); // Look for a normal disc or sphere to crush. NNormalSurface* ans; NMatrixInt* eqns; NTriangulation* crush; unsigned nComp; bool found; while (true) { t_->intelligentSimplify(); // The LP-and-crushing method only works for // 1-vertex triangulations (at present). if (t_->getNumberOfVertices() > 1) { // Try harder. t_->barycentricSubdivision(); t_->intelligentSimplify(); if (t_->getNumberOfVertices() > 1) { // Fall back to the old (slow and uncancellable) // method. if (t_->hasCompressingDisc()) ss_.markFound(); delete t_; return 0; } } if (ss_.hasFound()) { delete t_; return 0; } NTreeSingleSoln search(t_, NNormalSurfaceList::STANDARD); { NMutex::MutexLock lock(searchMutex_); currSearch_ = &search; } found = search.find(); { NMutex::MutexLock lock(searchMutex_); currSearch_ = 0; } if (ss_.hasFound()) { delete t_; return 0; } if (! found) { // No discs or spheres. // In particular, no compressing disc. delete t_; return 0; } // NTreeSingleSoln guarantees that our solution is // connected, and so it (or its double) is a sphere or // a disc. ans = search.buildSurface(); crush = ans->crush(); delete ans; delete t_; // Find the piece in the crushed triangulation with the // right Euler characteristic on the boundary, if it exists. nComp = crush->splitIntoComponents(); t_ = static_cast( crush->getFirstTreeChild()); while (t_) { if (t_->getNumberOfBoundaryComponents() == 1 && t_->getBoundaryComponent(0)-> getEulerCharacteristic() == ec) { // Found it. t_->makeOrphan(); break; } t_ = static_cast( t_->getNextTreeSibling()); } delete crush; if (! t_) { // No boundary component with the right Euler // characteristic. We must have compressed. ss_.markFound(); return 0; } // We now have a triangulation with fewer tetrahedra, // which contains a compressing disc iff the original did. // Around we go again! } } }; inline void SharedSearch::markFound() { NMutex::MutexLock lock(mutex_); found_ = true; if (ct_[0]) ct_[0]->cancel(); if (ct_[1]) ct_[1]->cancel(); } } // anonymous namespace bool NNormalSurface::isIncompressible() const { // We don't bother making the surface two-sided. This is because // cutting along the two-sided surface will produce (i) exactly what // you obtain from cutting along the one-sided surface, plus // (ii) a twisted I-bundle over a surface that will not contain any // compressing discs. // Rule out spheres. // From the preconditions, we can assume this surface to be // closed, compact and connected. if (getEulerCharacteristic() == 2 || ((! isTwoSided()) && getEulerCharacteristic() == 1)) return false; if (isThinEdgeLink().first) { // Since the manifold is closed and this surface is not a // sphere, the edge it links must be a loop and the surface must // surround a solid torus or Klein bottle. return false; } // Time for the heavy machinery. NTriangulation* cut = cutAlong(); cut->intelligentSimplify(); bool result; NTriangulation* side[2]; side[0] = side[1] = 0; cut->splitIntoComponents(); int which = 0; for (NPacket* comp = cut->getFirstTreeChild(); comp; comp = comp->getNextTreeSibling()) if (static_cast(comp)->hasBoundaryTriangles()) { if (which == 2) { // We have more than two components with boundary. // This should never happen. std::cerr << "ERROR: isCompressible() sliced to give " "more than two components with boundary." << std::endl; delete cut; return false; } side[which++] = static_cast(comp); } // Detach from parents so we don't run into multithreading problems // (e.g., when both triangulations try to delete themselves at the // same time). side[0]->makeOrphan(); if (side[1]) side[1]->makeOrphan(); delete cut; SharedSearch ss; if (! side[1]) { CompressionTest c(side[0], ss); c.run(0); } else { // Test both sides for compressing discs in parallel, // so we can terminate early if one side finds such a disc. CompressionTest c1(side[0], ss); CompressionTest c2(side[1], ss); c1.start(); c2.start(); c1.join(); c2.join(); } return ! ss.hasFound(); } } // namespace regina regina-4.95/engine/surfaces/cut-quadprism.fig000644 000765 000024 00000005134 12234011536 021154 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 990 -585 5760 3735 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2475 -450 135 135 2475 -450 2610 -450 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 5625 450 135 135 5625 450 5760 450 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 5175 3150 135 135 5175 3150 5310 3150 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 1125 3600 135 135 1125 3600 1260 3600 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 1440 2610 2160 540 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 5265 2475 5490 1125 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3150 450 4500 2250 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 2340 2745 4500 1215 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 2160 540 3150 450 5490 1125 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5 1440 2610 4500 2250 5265 2475 2340 2745 1440 2610 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 2160 540 4500 1215 5490 1125 2 1 0 0 0 7 55 -1 15 2.000 0 0 -1 0 0 4 1440 2610 2340 2745 5265 2475 4500 2250 2 1 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 4 2160 540 3150 450 5490 1125 4455 1215 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 1440 2610 3150 450 5265 2475 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 5265 2475 4500 1215 1440 2610 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 3150 450 4500 1215 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 1440 2610 5265 2475 4 1 0 50 -1 0 14 0.0000 0 150 120 5625 540 0\001 4 1 0 50 -1 0 14 0.0000 0 150 120 5175 3240 1\001 4 1 0 50 -1 0 14 0.0000 0 150 120 2475 -360 2\001 4 1 0 50 -1 0 14 0.0000 0 150 120 1125 3690 3\001 4 1 0 50 -1 0 12 0.0000 0 135 105 2070 540 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3195 405 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 5580 1170 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 5355 2610 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 2295 2925 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 1350 2700 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 4500 1170 3\001 4 1 0 50 -1 0 12 0.0000 0 135 105 4545 2205 3\001 -6 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 7065 2610 8775 450 10890 2475 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 3 10890 2475 10125 1215 7065 2610 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 8775 450 10125 1215 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7065 2610 10890 2475 4 1 0 50 -1 0 12 0.0000 0 135 105 8820 405 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 10980 2610 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 6975 2700 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 10125 1170 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 8910 1530 T4\001 4 1 0 50 -1 0 12 0.0000 0 135 225 4140 2205 T1\001 4 1 0 50 -1 0 12 0.0000 0 135 225 2385 990 T2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 5040 1485 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 2790 2925 T3\001 regina-4.95/engine/surfaces/cut-triprism.fig000644 000765 000024 00000003365 12234011536 021024 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 8100 -450 135 135 8100 -450 8235 -450 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 11250 450 135 135 11250 450 11385 450 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 10800 3150 135 135 10800 3150 10935 3150 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 6750 3600 135 135 6750 3600 6885 3600 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 7785 540 8820 495 8910 -225 7785 540 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 7065 2610 10125 2250 10485 225 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 8910 -225 10485 225 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 7065 2610 7785 540 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 8820 495 10125 2250 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7065 2610 10485 225 2 1 0 0 0 7 55 -1 15 1.500 0 0 -1 0 0 3 7785 540 8910 -225 8820 495 2 1 0 0 0 7 55 -1 17 1.500 0 0 -1 0 0 3 10485 225 7065 2610 10125 2250 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7785 540 10125 2250 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 8820 495 10485 225 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 10485 225 7785 540 4 1 0 50 -1 0 14 0.0000 0 150 120 8100 -360 0\001 4 1 0 50 -1 0 14 0.0000 0 150 120 11250 540 1\001 4 1 0 50 -1 0 14 0.0000 0 150 120 10800 3240 2\001 4 1 0 50 -1 0 14 0.0000 0 150 120 6750 3690 3\001 4 1 0 50 -1 0 12 0.0000 0 135 105 10575 270 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8955 -270 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7695 585 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8775 675 3\001 4 1 0 50 -1 0 12 0.0000 0 135 105 10215 2340 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 6975 2700 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 8100 1530 T2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 9135 180 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 9315 810 T1\001 regina-4.95/engine/surfaces/cut-trunchalftet.fig000644 000765 000024 00000011661 12234011536 021654 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 315 2115 5085 6435 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 1800 2250 135 135 1800 2250 1935 2250 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4950 3150 135 135 4950 3150 5085 3150 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4500 5850 135 135 4500 5850 4635 5850 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 450 6300 135 135 450 6300 585 6300 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 1485 3240 2475 3150 4815 3825 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 4 3510 5985 3825 4950 4590 5175 3510 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 765 5310 1440 6210 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 1440 6210 1665 5445 765 5310 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 1485 3240 3825 3915 4815 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 765 5310 1485 3240 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 1440 6210 3510 5985 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4590 5175 4815 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2475 3150 3825 4950 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 1665 5445 3825 3915 2 1 0 0 0 7 55 -1 15 2.000 0 0 -1 0 0 3 3825 4950 3510 5985 4590 5175 2 1 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 3 765 5310 1440 6210 1665 5445 2 1 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 4 1485 3240 2475 3150 4815 3825 3825 3915 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 765 5310 3510 5985 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 1665 5445 3510 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3825 4950 4815 3825 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 4815 3825 3510 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 2475 3150 3510 5985 4 1 0 50 -1 0 14 0.0000 0 150 120 450 6390 3\001 4 1 0 50 -1 0 14 0.0000 0 150 120 4500 5940 2\001 4 1 0 50 -1 0 14 0.0000 0 150 120 1800 2340 1\001 4 1 0 50 -1 0 14 0.0000 0 150 120 4950 3240 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 1350 6345 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 720 5355 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 1395 3285 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 2520 3105 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 4905 3870 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 4680 5265 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3870 4095 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3555 6165 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 1620 5400 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 1935 5940 T7\001 4 1 0 50 -1 0 12 0.0000 0 135 105 3825 4860 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 4410 4995 T6\001 4 1 0 50 -1 0 12 0.0000 0 135 225 3870 4455 T5\001 -6 6 6255 2970 10620 6165 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 7110 3240 8100 3150 10440 3825 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 7110 3240 9450 3915 10440 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 6390 5310 7110 3240 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7290 5445 9450 3915 2 1 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 4 7110 3240 8100 3150 10440 3825 9450 3915 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 6390 5310 9135 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 8100 3150 9135 5985 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 6390 5310 7290 5445 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7290 5445 9135 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 10440 3825 9135 5985 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 8100 3150 6390 5310 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 7290 5445 8100 3150 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 7290 5445 10440 3825 4 1 0 50 -1 0 12 0.0000 0 135 105 6345 5355 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7020 3285 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 8145 3105 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 10530 3870 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 9495 4095 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 9180 6165 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 7245 5400 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 7920 4680 T4\001 4 1 0 50 -1 0 12 0.0000 0 135 225 9225 4905 T3\001 -6 6 11655 2970 16020 5445 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 3 12510 3240 13500 3150 15840 3825 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 12510 3240 14850 3915 15840 3825 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 11790 5310 12510 3240 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 12690 5445 14850 3915 2 1 0 0 0 7 55 -1 17 2.000 0 0 -1 0 0 4 12510 3240 13500 3150 15840 3825 14850 3915 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 11790 5310 12690 5445 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 13500 3150 11790 5310 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 12690 5445 15840 3825 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 15840 3825 12510 3240 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 12690 5445 12510 3240 2 1 3 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 12690 5445 13500 3150 4 1 0 50 -1 0 12 0.0000 0 135 105 11745 5355 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 12420 3285 0\001 4 1 0 50 -1 0 12 0.0000 0 135 105 13545 3105 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 15930 3870 1\001 4 1 0 50 -1 0 12 0.0000 0 135 105 14895 4095 2\001 4 1 0 50 -1 0 12 0.0000 0 135 105 12645 5400 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 12375 4320 T2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 14400 4050 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 13275 3105 T1\001 -6 regina-4.95/engine/surfaces/cut-trunctet.fig000644 000765 000024 00000016700 12234011536 021020 0ustar00babstaff000000 000000 #FIG 3.2 Produced by xfig version 3.2.5-alpha5 Landscape Center Metric A4 100.00 Single -2 1200 2 6 810 5670 5220 9495 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 990 8685 2700 6525 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 990 8685 3735 9360 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 2700 6525 4050 8325 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 4050 8325 5040 7200 2 1 0 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 5040 7200 2835 5850 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 990 8685 2835 5850 2700 6525 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 4050 8325 3735 9360 5040 7200 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3735 9360 2700 6525 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 4050 8325 2835 5850 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3735 9360 2835 5850 4 1 0 55 -1 0 12 0.0000 0 135 105 900 8730 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 3825 9495 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 5130 7245 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 2790 5805 0\001 4 1 0 55 -1 0 12 0.0000 0 135 105 2790 6570 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 3960 8415 3\001 4 1 0 50 -1 0 12 0.0000 0 135 225 2430 8055 T4\001 4 1 0 50 -1 0 12 0.0000 0 135 225 4185 7380 T6\001 4 1 0 50 -1 0 12 0.0000 0 135 225 3645 8235 T5\001 -6 6 5760 5670 10170 9495 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 5940 8685 8685 9360 2 1 0 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 9990 7200 7785 5850 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 5940 8685 9990 7200 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 8685 9360 9990 7200 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7785 5850 5940 8685 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 7785 5850 8685 9360 4 1 0 55 -1 0 12 0.0000 0 135 105 5850 8730 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 8775 9495 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 10080 7245 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 7740 5805 0\001 4 1 0 50 -1 0 12 0.0000 0 135 330 7785 7695 T10\001 -6 6 10710 5670 15120 9495 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 11790 8820 13635 9360 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 10890 8685 13635 9360 2 1 2 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 11790 8820 13950 7290 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 13950 7290 12735 5850 2 1 0 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 14940 7200 12735 5850 2 1 2 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 11790 8820 10890 8685 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 13950 7290 14940 7200 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 10890 8685 13950 7290 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 11790 8820 14940 7200 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 10890 8685 14940 7200 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 13635 9360 14940 7200 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 12735 5850 10890 8685 4 1 0 55 -1 0 12 0.0000 0 135 105 10800 8730 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 13725 9495 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 15030 7245 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 12690 5805 0\001 4 1 0 55 -1 0 12 0.0000 0 135 105 13995 7470 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 11745 8775 0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 12915 7200 T7\001 4 1 0 50 -1 0 12 0.0000 0 135 225 12150 8460 T8\001 4 1 0 50 -1 0 12 0.0000 0 135 225 13455 8640 T9\001 -6 6 2790 540 7560 4860 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 4275 675 135 135 4275 675 4410 675 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 7425 1575 135 135 7425 1575 7560 1575 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 6975 4275 135 135 6975 4275 7110 4275 1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 2925 4725 135 135 2925 4725 3060 4725 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3240 3735 4950 1575 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 5085 900 3240 3735 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 4140 3870 5985 4410 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3240 3735 5985 4410 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 3960 1665 4950 1575 5085 900 3960 1665 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 5085 900 6660 1350 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3240 3735 3960 1665 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 3915 4635 5985 4410 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 7065 3600 7290 2250 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 4950 1575 6300 3375 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 4140 3870 6300 2340 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 4 6300 3375 5985 4410 7065 3600 6300 3375 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 6660 1350 6300 2340 7290 2250 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 3240 3735 4140 3870 3915 4635 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 3240 3735 3915 4635 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 6660 1350 7290 2250 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 6300 3375 7290 2250 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 5985 4410 7290 2250 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 6300 2340 5085 900 2 1 0 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 7290 2250 5085 900 2 1 0 0 0 7 55 -1 15 1.500 0 0 -1 0 0 3 3960 1665 5085 900 4950 1575 2 1 0 0 0 7 55 -1 15 1.500 0 0 -1 0 0 3 5985 4410 6300 3375 7065 3600 2 1 0 0 0 7 55 -1 17 1.500 0 0 -1 0 0 3 3240 3735 3915 4635 4140 3870 2 1 0 0 0 7 55 -1 17 1.500 0 0 -1 0 0 3 6660 1350 6300 2340 7290 2250 4 1 0 55 -1 0 14 0.0000 0 150 120 4275 765 0\001 4 1 0 55 -1 0 14 0.0000 0 150 120 7425 1665 1\001 4 1 0 55 -1 0 14 0.0000 0 150 120 6975 4365 2\001 4 1 0 55 -1 0 14 0.0000 0 150 120 2925 4815 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 3870 1665 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 3150 3780 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 3825 4770 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 6075 4545 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 7155 3735 0\001 4 1 0 55 -1 0 12 0.0000 0 135 105 7380 2295 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 6750 1350 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 5040 855 0\001 4 1 0 55 -1 0 12 0.0000 0 135 105 5040 1620 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 6345 2520 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 6210 3465 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 4095 3825 0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 4095 2025 T0\001 4 1 0 50 -1 0 12 0.0000 0 135 225 6345 1530 T1\001 4 1 0 50 -1 0 12 0.0000 0 135 225 6885 3420 T2\001 4 1 0 50 -1 0 12 0.0000 0 135 225 4365 4365 T3\001 -6 6 8910 720 13320 4545 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 9090 3735 10800 1575 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 9990 3870 11835 4410 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 9090 3735 11835 4410 2 1 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 2 10800 1575 12150 3375 2 1 2 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 9990 3870 12150 2340 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 12150 3375 13140 2250 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 12150 2340 10935 900 2 1 0 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 13140 2250 10935 900 2 1 2 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 9990 3870 9090 3735 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 9090 3735 10935 900 10800 1575 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 3 12150 3375 11835 4410 13140 2250 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 12150 2340 13140 2250 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 11835 4410 10800 1575 2 1 0 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 12150 3375 10935 900 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 9090 3735 12150 2340 2 1 2 1 0 7 50 -1 -1 3.000 0 0 -1 0 0 2 9990 3870 13140 2250 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 11835 4410 10935 900 2 1 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 2 9090 3735 13140 2250 4 1 0 55 -1 0 12 0.0000 0 135 105 9000 3780 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 11925 4545 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 13230 2295 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 10890 855 0\001 4 1 0 55 -1 0 12 0.0000 0 135 105 10890 1620 1\001 4 1 0 55 -1 0 12 0.0000 0 135 105 12195 2520 2\001 4 1 0 55 -1 0 12 0.0000 0 135 105 12060 3465 3\001 4 1 0 55 -1 0 12 0.0000 0 135 105 9945 3825 0\001 -6 regina-4.95/engine/surfaces/enumerator.cpp000644 000765 000024 00000060355 12236247215 020571 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "regina-config.h" // For EXCLUDE_NORMALIZ. #include #include "enumerate/ndoubledescription.h" #include "enumerate/nhilbertcd.h" #include "enumerate/nhilbertdual.h" #ifndef EXCLUDE_NORMALIZ #include "enumerate/nhilbertprimal.h" #endif #include "enumerate/ntreetraversal.h" #include "maths/matrixops.h" #include "maths/nmatrixint.h" #include "progress/nprogresstracker.h" #include "surfaces/coordregistry.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" namespace regina { #ifdef INT128_AVAILABLE /** * The largest possible signed 128-bit integer, */ NInteger maxSigned128(NNativeInteger<16>(~(IntOfSize<16>::type(1) << 127))); #endif NNormalSurfaceList* NNormalSurfaceList::enumerate( NTriangulation* owner, NormalCoords coords, NormalList which, NormalAlg algHints, NProgressTracker* tracker) { NNormalSurfaceList* list = new NNormalSurfaceList( coords, which, algHints); Enumerator* e = new Enumerator(list, owner, tracker); if (tracker) { if (! e->start(0, true)) { delete list; list = 0; } } else { e->run(0); delete e; } return list; } void* NNormalSurfaceList::Enumerator::run(void*) { forCoords(list_->coords_, *this); return 0; } template void NNormalSurfaceList::Enumerator::operator() (Coords) { // Clean up the "type of list" flag. list_->which_ &= ( NS_EMBEDDED_ONLY | NS_IMMERSED_SINGULAR | NS_VERTEX | NS_FUNDAMENTAL); list_->which_.ensureOne(NS_VERTEX, NS_FUNDAMENTAL); list_->which_.ensureOne(NS_EMBEDDED_ONLY, NS_IMMERSED_SINGULAR); // Farm out the real work to list-type-specific routines. if (list_->which_.has(NS_VERTEX)) fillVertex(); else fillFundamental(); // Insert the results into the packet tree, but only once they are ready. if (! (tracker_ && tracker_->isCancelled())) triang_->insertChildLast(list_); if (tracker_) tracker_->setFinished(); } template void NNormalSurfaceList::Enumerator::fillVertex() { // ----- Decide which algorithm to use ----- // Here we will set the algorithm_ flag to precisely what we plan to do. // First clear out all irrelevant options. list_->algorithm_ &= ( NS_VERTEX_VIA_REDUCED | NS_VERTEX_STD_DIRECT | NS_VERTEX_TREE | NS_VERTEX_DD); // For standard normal / almost normal coordinates, choose between // standard-direct vs standard-via-reduced. if (list_->coords_ == NS_STANDARD || list_->coords_ == NS_AN_STANDARD) { list_->algorithm_.ensureOne( NS_VERTEX_VIA_REDUCED, NS_VERTEX_STD_DIRECT); // If we've chosen via-reduced, check that this is actually available. // If not, switch back to standard-direct. if (list_->algorithm_.has(NS_VERTEX_VIA_REDUCED) && ! (list_->which_.has(NS_EMBEDDED_ONLY) && triang_->isValid() && (! triang_->isIdeal()))) list_->algorithm_ ^= (NS_VERTEX_VIA_REDUCED | NS_VERTEX_STD_DIRECT); } else { // Standard-direct vs standard-via-reduced is not relevant here. list_->algorithm_.clear(NS_VERTEX_VIA_REDUCED | NS_VERTEX_STD_DIRECT); } // Choose between double description and tree traversal. // Which is the default will depend upon the underlying coordinate system. if (list_->algorithm_.has(NS_VERTEX_STD_DIRECT)) { // Tree traversal is at its best when every coordinate is involved // in branching decisions (i.e., we are in quad or quad-oct // coordinates). It can be slower when working with triangles, // so default to the older double description method. list_->algorithm_.ensureOne(NS_VERTEX_DD, NS_VERTEX_TREE); } else { // Use the new technology. list_->algorithm_.ensureOne(NS_VERTEX_TREE, NS_VERTEX_DD); } // Check whether tree traversal supports our enumeration arguments. // If not, switch back to double description. // The integer template argument for NTreeTraversal::supported() // is unimportant here; we just use NInteger. if (list_->algorithm_.has(NS_VERTEX_TREE) && ! (list_->which_.has(NS_EMBEDDED_ONLY) && NTreeTraversal::supported( list_->coords_))) list_->algorithm_ ^= (NS_VERTEX_TREE | NS_VERTEX_DD); // ----- Run the enumeration algorithm ----- if (triang_->getNumberOfTetrahedra() == 0) { // Handle the empty triangulation separately. list_->algorithm_ = NS_VERTEX_DD; /* shrug */ // Nothing to do. } else if (! list_->algorithm_.has(NS_VERTEX_VIA_REDUCED)) { // A direct enumeration in the chosen coordinate system. if (list_->algorithm_.has(NS_VERTEX_TREE)) { if (tracker_) tracker_->newStage( "Enumerating vertex surfaces\n(tree traversal method)"); fillVertexTree(); } else { if (tracker_) tracker_->newStage( "Enumerating vertex surfaces\n(double description method)"); fillVertexDD(); } } else { // Enumerate in the reduced coordinate system, and then convert // the solution set to the standard coordinate system. // Since there are currently only two systems in which we can do // this (NS_STANDARD and NS_AN_STANDARD), we hard-code these // cases in if() statements to avoid generating template code // for other, unsupported coordinate systems. // Enumerate in reduced (quad / quad-oct) form. Enumerator e(new NNormalSurfaceList( (list_->coords_ == NS_STANDARD ? NS_QUAD : NS_AN_QUAD_OCT), list_->which_, list_->algorithm_ ^ NS_VERTEX_VIA_REDUCED), triang_, tracker_); if (list_->algorithm_.has(NS_VERTEX_TREE)) { if (tracker_) tracker_->newStage("Enumerating reduced solution set\n" "(tree traversal method)", 0.9); e.fillVertexTree(); } else { if (tracker_) tracker_->newStage("Enumerating reduced solution set\n" "(double description method)", 0.9); e.fillVertexDD(); } if (tracker_ && tracker_->isCancelled()) { delete e.list_; return; } // Expand to the standard the solution set. if (tracker_) tracker_->newStage("Expanding to standard solution set", 0.1); if (list_->coords_ == NS_STANDARD) list_->buildStandardFromReduced(triang_, e.list_->surfaces, tracker_); else list_->buildStandardFromReduced(triang_, e.list_->surfaces, tracker_); // Clean up. delete e.list_; } } template void NNormalSurfaceList::Enumerator::fillVertexDD() { NMatrixInt* eqns = makeMatchingEquations(triang_, list_->coords_); NEnumConstraintList* constraints = (list_->which_.has(NS_EMBEDDED_ONLY) ? makeEmbeddedConstraints(triang_, list_->coords_) : 0); NDoubleDescription::enumerateExtremalRays( SurfaceInserter(*list_, triang_), *eqns, constraints, tracker_); delete constraints; delete eqns; } template void NNormalSurfaceList::Enumerator::fillVertexTree() { // We can always do this with the arbitrary-precision NInteger, // but it will be much faster if we can get away with native // integers instead. To do this, though, we need to be able to // guarantee that all intermediate integers that could appear in the // algorithm are sufficiently small in magnitude. // // Here we compute an upper bound on the magnitude of the integers that // could appear in a vanilla NTreeEnumeration // algorithm. For details on how these arguments work, see // section 4 of the tree traversal algorithm paper (Burton & Ozlen, // Algorithmica, 2013). // // All "maximum" quantities in the calculations below refer to // maximum absolute value, and are always non-negative. // Here we use the fact that the coordinate system is known to be // supported by the tree traversal algorithm, and therefore is one // of NS_STANDARD, NS_QUAD, NS_AN_STANDARD or NS_AN_QUAD. // The matching equation matrix that will be used by the tree traversal // tableaux, which is always based on NS_STANDARD or NSQUAD (even // for almost normal surfaces): NMatrixInt* eqns; // The maximum number of columns in the tableaux that could be added // to form the right hand side, as a consequence of either // LPData::constrainPositive() or LPData::constrainOct(): unsigned long maxColsRHS; switch (list_->coords_) { case NS_STANDARD: eqns = makeMatchingEquations(triang_, NS_STANDARD); maxColsRHS = triang_->getNumberOfTetrahedra() * 5; break; case NS_QUAD: eqns = makeMatchingEquations(triang_, NS_QUAD); maxColsRHS = triang_->getNumberOfTetrahedra(); break; case NS_AN_STANDARD: eqns = makeMatchingEquations(triang_, NS_STANDARD); maxColsRHS = triang_->getNumberOfTetrahedra() * 5 + 1; break; case NS_AN_QUAD_OCT: eqns = makeMatchingEquations(triang_, NS_QUAD); maxColsRHS = triang_->getNumberOfTetrahedra() + 1; break; default: // We shouldn't be here.. just use arbitrary precision arithmetic. fillVertexTreeWith(); return; } size_t i, j; NInteger tmp; // The rank of the matching equation matrix: unsigned long rank = rowBasis(*eqns); // The maximum entry in the matching equation matrix: NInteger maxEqnEntry = 0; for (i = 0; i < rank; ++i) for (j = 0; j < eqns->columns(); ++j) { tmp = eqns->entry(i, j).abs(); if (tmp > maxEqnEntry) maxEqnEntry = tmp; } // The maximum integer that can appear on the RHS of the original // tableaux, after all calls to constrainPositive() and/or constrainOct(): NInteger maxOrigRHS = maxEqnEntry * maxColsRHS; // The maximum sum of absolute values of entries in a single column // of the original tableaux (noting that for almost normal surfaces, // the octagon column will be the sum of two original columns): NInteger maxOrigColSum = 0; for (i = 0; i < eqns->columns(); ++i) { tmp = 0; for (j = 0; j < rank; ++j) tmp += NInteger(eqns->entry(j, i).abs()); if (tmp > maxOrigColSum) maxOrigColSum = tmp; } if (Coords::almostNormal) maxOrigColSum *= 2; // The square of the Hadamard bound for the original tableaux: NInteger hadamardSquare = 1; NInteger* colNorm = new NInteger[eqns->columns()]; for (i = 0; i < eqns->columns(); ++i) { colNorm[i] = 0; for (j = 0; j < rank; ++j) colNorm[i] += NInteger(eqns->entry(j, i) * eqns->entry(j, i)); } std::sort(colNorm, colNorm + eqns->columns()); for (i = 0; i < rank; ++i) hadamardSquare *= colNorm[eqns->columns() - 1 - i]; delete[] colNorm; delete eqns; // We are done with the matching equations now. if (Coords::almostNormal) { // The octagon column is the sum of two quadrilateral columns. // This is no worse than doubling the Euclidean norm of the // largest column. hadamardSquare *= 4; } // The maximum entry in the tableaux, at any stage of the algorithm, // is hadamard * maxOrigColSum. Call this X. // The maximum entry on the RHS, at any stage of the algorithm, // is hadamard * rank * maxOrigRHS. Call this Y. // Assume nTetrahedra >= 2, since with 1 tetrahedron, all enumerations // easily fit into small native integers. // Then: // maxOrigColSum <= rank * maxEqnEntry * 2 // <= rank * maxEqnEntry * nTetrahedra // <= rank * maxEqnEntry * maxColsRHS // = rank * maxOrigRHS. // So X <= Y. // The worst computations we have to perform are // (X * X + X * X) in the tableaux, and (X * Y + X * Y) on the RHS. // Therefore the largest integer we have to deal with is: // 2XY = 2 * hadamardSquare * maxOrigColSum * rank * maxOrigRHS. NInteger worst = hadamardSquare; worst *= 2; worst *= maxOrigColSum; worst *= rank; worst *= maxOrigRHS; // Bridge builders add safety margins, and we can add one too. worst *= 4; // TODO: Rework this calculation so that maxOrigRHS is computed from // row sums in the matching equation matrix (don't forget to double // the highest term for almost normal surfaces). This may mean that // we need to take max(X, Y), since it will no longer be clear that // X <= Y. // Now we can select an appropriate integer type. if (worst <= LONG_MAX) { // std::cerr << "Using NNativeLong." << std::endl; fillVertexTreeWith(); #ifdef INT128_AVAILABLE } else if (worst <= maxSigned128) { // std::cerr << "Using NNativeInteger<16>." << std::endl; fillVertexTreeWith >(); #endif } else { // std::cerr << "Using the fallback NInteger." << std::endl; fillVertexTreeWith(); } } template void NNormalSurfaceList::Enumerator::fillVertexTreeWith() { NTreeEnumeration search( triang_, list_->coords_); while (search.next(tracker_)) { list_->surfaces.push_back(search.buildSurface()); if (tracker_ && tracker_->isCancelled()) break; } } template void NNormalSurfaceList::Enumerator::fillFundamental() { // Get the empty triangulation out of the way separately. if (triang_->getNumberOfTetrahedra() == 0) { list_->algorithm_ = NS_HILBERT_DUAL; /* shrug */ return; } // ----- Decide upon and run an appropriate algorithm ----- // This is where we make the "default" decision for the user. if (list_->which_.has(NS_IMMERSED_SINGULAR)) { // The primal method makes no sense without the quadrilateral // constraints. list_->algorithm_.ensureOne(NS_HILBERT_DUAL, NS_HILBERT_FULLCONE, NS_HILBERT_PRIMAL, NS_HILBERT_CD); } else { list_->algorithm_.ensureOne(NS_HILBERT_PRIMAL, NS_HILBERT_DUAL, NS_HILBERT_FULLCONE, NS_HILBERT_CD); } // Run the chosen algorithm. if (list_->algorithm_.has(NS_HILBERT_PRIMAL)) fillFundamentalPrimal(); else if (list_->algorithm_.has(NS_HILBERT_DUAL)) fillFundamentalDual(); else if (list_->algorithm_.has(NS_HILBERT_CD)) fillFundamentalCD(); else fillFundamentalFullCone(); } template void NNormalSurfaceList::Enumerator::fillFundamentalDual() { list_->algorithm_ = NS_HILBERT_DUAL; if (tracker_) tracker_->newStage("Enumerating Hilbert basis\n(dual method)"); NMatrixInt* eqns = makeMatchingEquations(triang_, list_->coords_); NEnumConstraintList* constraints = (list_->which_.has(NS_EMBEDDED_ONLY) ? makeEmbeddedConstraints(triang_, list_->coords_) : 0); NHilbertDual::enumerateHilbertBasis( SurfaceInserter(*list_, triang_), *eqns, constraints, tracker_); delete constraints; delete eqns; } template void NNormalSurfaceList::Enumerator::fillFundamentalCD() { list_->algorithm_ = NS_HILBERT_CD; if (tracker_) tracker_->newStage( "Enumerating Hilbert basis\n(Contejean-Devie method)"); NMatrixInt* eqns = makeMatchingEquations(triang_, list_->coords_); NEnumConstraintList* constraints = (list_->which_.has(NS_EMBEDDED_ONLY) ? makeEmbeddedConstraints(triang_, list_->coords_) : 0); NHilbertCD::enumerateHilbertBasis( SurfaceInserter(*list_, triang_), *eqns, constraints); delete constraints; delete eqns; } #ifdef EXCLUDE_NORMALIZ template void NNormalSurfaceList::Enumerator::fillFundamentalPrimal() { // This build of Regina does not include normaliz. // Fall back to some other option. fillFundamentalDual(); } template void NNormalSurfaceList::Enumerator::fillFundamentalFullCone() { // This build of Regina does not include normaliz. // Fall back to some other option. fillFundamentalDual(); } #else template void NNormalSurfaceList::Enumerator::fillFundamentalPrimal() { // We will not set algorithm_ until after the extremal ray // enumeration has finished (since we might want pass additional flags // to and/or from that routine). if (tracker_) tracker_->newStage("Initialising Hilbert basis enumeration", 0.1); // Fetch validity constraints from the registry. NEnumConstraintList* constraints = (list_->which_.has(NS_EMBEDDED_ONLY) ? makeEmbeddedConstraints(triang_, list_->coords_) : 0); // Enumerate all vertex normal surfaces. if (tracker_) tracker_->newStage("Enumerating extremal rays", 0.4); NNormalSurfaceList* vtx = new NNormalSurfaceList(list_->coords_, NS_VERTEX | (list_->which_.has(NS_EMBEDDED_ONLY) ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), list_->algorithm_ /* passes through any vertex enumeration flags */); Enumerator e(vtx, triang_, 0 /* Don't set another progress tracker */); e.fillVertex(); // Finalise the algorithm flags for this list: combine NS_HILBERT_PRIMAL // with whatever vertex enumeration flags were used. list_->algorithm_ = e.list_->algorithm_ | NS_HILBERT_PRIMAL; // Expand this list to a full Hilbert basis. if (tracker_) tracker_->newStage("Expanding to Hilbert basis", 0.5); NHilbertPrimal::enumerateHilbertBasis( SurfaceInserter(*list_, triang_), vtx->beginVectors(), vtx->endVectors(), constraints, tracker_); delete vtx; delete constraints; } template void NNormalSurfaceList::Enumerator::fillFundamentalFullCone() { list_->algorithm_ = NS_HILBERT_FULLCONE; if (tracker_) tracker_->newStage( "Enumerating Hilbert basis of full cone", 0.8); NMatrixInt* eqns = makeMatchingEquations(triang_, list_->coords_); unsigned rank = rowBasis(*eqns); unsigned long dim = eqns->columns(); std::vector > input; unsigned r, c; input.reserve(rank); for (r = 0; r < rank; ++r) { input.push_back(std::vector()); std::vector& v(input.back()); v.reserve(eqns->columns()); for (c = 0; c < eqns->columns(); ++c) { NLargeInteger& entry(eqns->entry(r, c)); if (entry.isNative()) v.push_back(mpz_class(entry.longValue())); else v.push_back(mpz_class(entry.rawData())); } } delete eqns; libnormaliz::Cone cone(input, libnormaliz::Type::equations); libnormaliz::ConeProperties wanted( libnormaliz::ConeProperty::HilbertBasis); cone.compute(wanted); if (! cone.isComputed(libnormaliz::ConeProperty::HilbertBasis)) { // Something has gone wrong inside Normaliz. // Return an empty list. } else { if (tracker_) tracker_->newStage("Extracting relevant solutions", 0.2); // Fetch validity constraints from the registry. NEnumConstraintList* constraints = (list_->which_.has(NS_EMBEDDED_ONLY) ? makeEmbeddedConstraints(triang_, list_->coords_) : 0); bool broken; int nonZero; int i; std::vector >::const_iterator hlit; NEnumConstraintList::const_iterator eit; std::set::const_iterator sit; NNormalSurfaceVector* v; NLargeInteger tmpInt; NewFunction1 newVec(dim); const std::vector > basis = cone.getHilbertBasis(); for (hlit = basis.begin(); hlit != basis.end(); ++hlit) { broken = false; if (constraints) { for (eit = constraints->begin(); eit != constraints->end(); ++eit) { nonZero = 0; for (sit = eit->begin(); sit != eit->end(); ++sit) { if ((*hlit)[*sit] != 0) { if (++nonZero > 1) break; } } if (nonZero > 1) { broken = true; break; } } } if (! broken) { // Insert a new surface. v = forCoords(list_->coords_, newVec, 0); if (! v) { // Coordinate system not recognised. // Return an empty list to indicate that something broke. list_->surfaces.clear(); break; } for (i = 0; i < dim; ++i) { // Inefficiency: We make two copies of the GMP integer // here instead of one, since NVector/NRay does not give // us direct non-const access to its elements. tmpInt.setRaw((*hlit)[i].get_mpz_t()); tmpInt.tryReduce(); v->setElement(i, tmpInt); } list_->surfaces.push_back(new NNormalSurface(triang_, v)); } } delete constraints; } } #endif // EXCLUDE_NORMALIZ } // namespace regina regina-4.95/engine/surfaces/enumfilter.cpp000644 000765 000024 00000013331 12234011536 020543 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" namespace regina { NNormalSurfaceList* NNormalSurfaceList::filterForLocallyCompatiblePairs() const { // Sanity check: if (! isEmbeddedOnly()) return 0; NNormalSurfaceList* ans = new NNormalSurfaceList( coords_, NS_CUSTOM | NS_EMBEDDED_ONLY, NS_ALG_CUSTOM); // Find all surfaces that have a compatible partner. std::vector::const_iterator first, second; for (first = surfaces.begin(); first != surfaces.end(); ++first) { for (second = surfaces.begin(); second != surfaces.end(); ++second) { if (second == first) continue; if ((*first)->locallyCompatible(**second)) { ans->surfaces.push_back((*first)->clone()); break; } } } getTriangulation()->insertChildLast(ans); return ans; } NNormalSurfaceList* NNormalSurfaceList::filterForDisjointPairs() const { // Sanity check: if (! isEmbeddedOnly()) return 0; NNormalSurfaceList* ans = new NNormalSurfaceList( coords_, NS_CUSTOM | NS_EMBEDDED_ONLY, NS_ALG_CUSTOM); // Collect all the surfaces that we might care about. // This means non-empty, connected and compact. std::vector interesting; for (std::vector::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) { if ((*it)->isEmpty()) continue; if (! (*it)->isCompact()) continue; if (! (*it)->isConnected()) continue; interesting.push_back(*it); } // Find all surfaces that have a disjoint partner. std::vector::iterator first, second; for (first = interesting.begin(); first != interesting.end(); ++first) { for (second = interesting.begin(); second != interesting.end(); ++second) { if (second == first) continue; if ((*first)->disjoint(**second)) { ans->surfaces.push_back((*first)->clone()); break; } } } getTriangulation()->insertChildLast(ans); return ans; } NNormalSurfaceList* NNormalSurfaceList::filterForPotentiallyIncompressible() const { // Sanity check: if (! isEmbeddedOnly()) return 0; NNormalSurfaceList* ans = new NNormalSurfaceList( coords_, NS_CUSTOM | NS_EMBEDDED_ONLY, NS_ALG_CUSTOM); NTriangulation* t; #ifdef DEBUG int which = 0; #endif for (std::vector::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) { #ifdef DEBUG std::cout << "Processing surface " << which++ << "..." << std::endl; #endif if ((*it)->isVertexLinking()) continue; if ((*it)->isThinEdgeLink().first) continue; // If we have a one-sided surface, don't worry about taking the // two-sided double cover. If the complement of the one-sided // surface has a compressing disc, then the complement of the // double cover has the same compressing disc, and this surface // can happily be tossed away. t = (*it)->cutAlong(); if (! t->hasSimpleCompressingDisc()) ans->surfaces.push_back((*it)->clone()); delete t; } getTriangulation()->insertChildLast(ans); return ans; } } // namespace regina regina-4.95/engine/surfaces/filterregistry-impl.h000644 000765 000024 00000007774 12234011536 022071 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/filterregistry-impl.h * \brief Contains the registry of all normal surface filter classes that can * be used to filter lists of normal surfaces in 3-manifold triangulations. * * Each time a new filter is created, this registry must be updated to: * * - add a #include line for the corresponding filter class; * - add a corresponding case to each implementation of forFilter(). * * See filterregistry.h for how other routines can use this registry. */ #ifndef __FILTERREGISTRY_IMPL_H #ifndef __DOXYGEN #define __FILTERREGISTRY_IMPL_H #endif #include "surfaces/nsurfacefilter.h" #include "surfaces/sfproperties.h" #include "surfaces/sfcombination.h" namespace regina { template inline typename FunctionObject::ReturnType forFilter( SurfaceFilterType filter, FunctionObject func, typename FunctionObject::ReturnType defaultReturn) { switch (filter) { case NS_FILTER_DEFAULT : return func(SurfaceFilterInfo()); case NS_FILTER_PROPERTIES : return func(SurfaceFilterInfo()); case NS_FILTER_COMBINATION : return func(SurfaceFilterInfo()); default: return defaultReturn; } } template inline void forFilter(SurfaceFilterType filter, VoidFunctionObject func) { switch (filter) { case NS_FILTER_DEFAULT : func(SurfaceFilterInfo()); break; case NS_FILTER_PROPERTIES : func(SurfaceFilterInfo()); break; case NS_FILTER_COMBINATION : func(SurfaceFilterInfo()); break; default: break; } } } // namespace regina #endif regina-4.95/engine/surfaces/filterregistry.h000644 000765 000024 00000015606 12234011536 021123 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/filterregistry.h * \brief Provides access to a registry of all normal surface filter classes * that can be used to filter lists of normal surfaces in 3-manifold * triangulations. * * Each time a new filter is created, the file filterregistry-impl.h must be * updated to include it. Instructions on how to do this are included in * filterregistry-impl.h. * * External routines can access the registry by calling one of the * forFilter() template functions defined in filterregistry.h. * * \warning You should not include this header unless it is necessary, * since it will automatically import every header for every filter class * in the registry. */ // The old registry macros will silently compile but do nothing. // This could lead to nasty surprises, so throw an error if it looks like // people are still trying to use them. #ifdef __FILTER_REGISTRY_BODY #error "The old REGISTER_FILTER macros have been removed. Use forFilter() instead." #endif #ifndef __FILTERREGISTRY_H #ifndef __DOXYGEN #define __FILTERREGISTRY_H #endif #include "surfaces/surfacefiltertype.h" #include "utilities/registryutils.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Allows the user to call a template function whose template parameter * matches a given value of SurfaceFilterType, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible normal surface filter types. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new filter type is added then only a small amount of code * needs to be extended to incorporate it. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(SurfaceFilterInfo) is * defined for any valid SurfaceFilterType enum value \a t. Then, * when the user calls forFilter(filter, func, defaultReturn), * this routine will call func(SurfaceFilterInfo) and pass * back the corresponding return value. If \a filter does not denote a valid * filter type, then forFilter() will pass back \a defaultReturn instead. * * There is also a two-argument variant of forFilter() that works with * void functions. * * \pre The function object must have a typedef \a ReturnType indicating * the return type of the corresponding templated unary bracket operator. * Inheriting from Returns<...> is a convenient way to ensure this. * * \ifacespython Not present. * * @param filter the given type of normal surface filter. * @param func the function object whose unary bracket operator we will * call with a SurfaceFilterInfo object. * @param defaultReturn the value to return if the given filter type * is not valid. * @return the return value from the corresponding unary bracket * operator of \a func, or \a defaultReturn if the given filter type * is not valid. */ template typename FunctionObject::ReturnType forFilter( SurfaceFilterType filter, FunctionObject func, typename FunctionObject::ReturnType defaultReturn); /** * Allows the user to call a template function whose template parameter * matches a given value of SurfaceFilterType, which is not known * until runtime. In essence, this routine contains a switch/case statement * that runs through all possible normal surface filter types. * * The advantages of this routine are that (i) the user does not need to * repeatedly type such switch/case statements themselves; and (ii) if * a new filter type is added then only a small amount of code * needs to be extended to incorporate it. * * In detail: the function object \a func must define a templated * unary bracket operator, so that func(SurfaceFilterInfo) is * defined for any valid SurfaceFilterType enum value \a t. Then, * when the user calls forFilter(filter, func), * this routine will call func(SurfaceFilterInfo) in turn. * If \a filter does not denote a valid filter type, then forFilter() * will do nothing. * * There is also a three-argument variant of forFilter() that works with * functions with return values. * * \ifacespython Not present. * * @param filter the given type of normal surface filter. * @param func the function object whose unary bracket operator we will * call with a SurfaceFilterInfo object. */ template void forFilter(SurfaceFilterType filter, VoidFunctionObject func); /*@}*/ } // namespace regina // Import template implementations: #include "surfaces/filterregistry-impl.h" #endif regina-4.95/engine/surfaces/flavourregistry.h000644 000765 000024 00000004661 12234011536 021313 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/flavourregistry.h * \brief Deprecated header. */ #warning This header is deprecated; please use coordregistry.h instead. #include "surfaces/coordregistry.h" regina-4.95/engine/surfaces/links.cpp000644 000765 000024 00000030463 12234011536 017516 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nnormalsurface.h" #include "triangulation/ntriangulation.h" #include #define NO_EDGES std::make_pair(static_cast(0), static_cast(0)) namespace regina { bool NNormalSurfaceVector::isVertexLinking(NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); unsigned long tet; int type; for (tet = 0; tet < nTets; tet++) { for (type = 0; type < 3; type++) if (getQuadCoord(tet, type, triang) != 0) return false; } if (allowsAlmostNormal()) for (tet = 0; tet < nTets; tet++) for (type = 0; type < 3; type++) if (getOctCoord(tet, type, triang) != 0) return false; return true; } const NVertex* NNormalSurfaceVector::isVertexLink(NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); unsigned long tet; int type; // Check that there are no quad/oct discs. for (tet = 0; tet < nTets; tet++) { for (type = 0; type < 3; type++) if (getQuadCoord(tet, type, triang) != 0) return 0; } if (allowsAlmostNormal()) for (tet = 0; tet < nTets; tet++) for (type = 0; type < 3; type++) if (getOctCoord(tet, type, triang) != 0) return 0; // Now examine the triangle to see if we link only a single vertex. std::set notAns; /**< We will ignore notAns once ans != 0. */ NVertex* ans = 0; NLargeInteger ansMult; NTetrahedron* t; NVertex* v; NLargeInteger coord; for (tet = 0; tet < nTets; tet++) { t = triang->getTetrahedron(tet); for (type = 0; type < 4; type++) { v = t->getVertex(type); coord = getTriangleCoord(tet, type, triang); if (coord == 0) { // No discs in this coordinate. // Do we have a candidate vertex yet? if (! ans) { // Still haven't found a candidate. notAns.insert(v); } else if (ans == v) { // This is our only candidate vertex. return 0; } } if (coord != 0) { // Some discs in this coordinate. // Do we have a candidate vertex? if (! ans) { // We've found our first and only possible candidate. if (notAns.count(v)) return 0; ans = v; ansMult = coord; } else if (ans == v) { // This vertex matches our current candidate. if (ansMult != coord) return 0; } else { // This vertex does not match our current candidate. return 0; } } else { // No discs in this coordinate. if (ans == v) return 0; } } } return ans; } std::pair NNormalSurfaceVector::isThinEdgeLink( NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); unsigned long tet; int type; // Check that there are no octagonal discs. if (allowsAlmostNormal()) for (tet = 0; tet < nTets; tet++) for (type = 0; type < 3; type++) if (getOctCoord(tet, type, triang) != 0) return NO_EDGES; // Run through the quadrilateral discs and work out if there are any // valid candidates. std::set notAns; /**< We will ignore notAns once ans != 0. */ bool foundQuads = false; const NEdge* ans[2]; NLargeInteger ansMultDouble; NTetrahedron* t; NEdge* e[6]; // { 2*link, 4*intersect } NLargeInteger coord; int i; for (tet = 0; tet < nTets; tet++) { t = triang->getTetrahedron(tet); for (type = 0; type < 3; type++) { coord = getQuadCoord(tet, type, triang); e[0] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][0]] [vertexSplitDefn[type][1]]); e[1] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][2]] [vertexSplitDefn[type][3]]); e[2] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][0]] [vertexSplitDefn[type][2]]); e[3] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][0]] [vertexSplitDefn[type][3]]); e[4] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][1]] [vertexSplitDefn[type][2]]); e[5] = t->getEdge(NEdge::edgeNumber[vertexSplitDefn[type][1]] [vertexSplitDefn[type][3]]); if (coord == 0) { // No discs in this coordinate. // Do we have any candidate edges yet? if (foundQuads) { // Rule out any candidates that should have discs here. for (i = 0; i < 2; i++) if (ans[i] == e[0] || ans[i] == e[1]) ans[i] = 0; } else { // Still haven't found any candidates. notAns.insert(e[0]); notAns.insert(e[1]); } } else { // Some discs in this coordinate. // Do we have any candidate edges yet? if (foundQuads) { // Check consistency with our candidates. if (e[0] == e[1]) { // Same edge on both sides of the quad. // Note that there can only be one candidate now. if (e[0] == ans[0]) ans[1] = 0; else if (e[0] == ans[1]) { ans[0] = ans[1]; ans[1] = 0; } else return NO_EDGES; // The only possible candidate is ans[0]. if (ans[0] == 0 || ansMultDouble != coord) return NO_EDGES; } else { // Different edges on both sides of the quad. // Check each candidate in turn. for (i = 0; i < 2; i++) if (ans[i] != e[0] && ans[i] != e[1]) ans[i] = 0; if (ansMultDouble != coord * 2) return NO_EDGES; } } else { // We've found our first and only possible candidates. if (e[0] == e[1]) { // Same edge on both sides of the quad. if (notAns.count(e[0])) return NO_EDGES; ans[0] = e[0]; ans[1] = 0; ansMultDouble = coord; } else { // Different edges on both sides of the quad. for (i = 0; i < 2; i++) { if (notAns.count(e[i])) ans[i] = 0; else { ans[i] = e[i]; ansMultDouble = coord; ansMultDouble *= 2; } } } foundQuads = true; } // We now absolutely have candidates (or have exhausted // them all). Check that these candidates don't // intersect the new quads. for (i = 2; i < 6; i++) { if (ans[0] == e[i]) ans[0] = 0; if (ans[1] == e[i]) ans[1] = 0; } } // Have we ruled out all the candidates we ever had? if (foundQuads && (! ans[0]) && (! ans[1])) return NO_EDGES; } } // So did we actually find anything? if (! foundQuads) return NO_EDGES; if ((! ans[0]) && (! ans[1])) return NO_EDGES; // Finally check the triangular discs. NVertex* v; bool expectZero[2]; int j; for (tet = 0; tet < nTets; tet++) { t = triang->getTetrahedron(tet); for (type = 0; type < 4; type++) { v = t->getVertex(type); coord = getTriangleCoord(tet, type, triang); // Should we actually see any discs? for (i = 0; i < 2; i++) { if (! ans[i]) continue; // If the candidate edge does not touch this vertex, the // triangular coordinate should be 0. expectZero[i] = (v != ans[i]->getVertex(0) && v != ans[i]->getVertex(1)); // If this triangular disc type intersects the candidate // edge, the coordinate should also be 0. if (! expectZero[i]) for (j = 0; j < 3; j++) if (t->getEdge(NEdge::edgeNumber[type][(type+j+1) % 4]) == ans[i]) { expectZero[i] = true; break; } // So did we get the right triangular coordinate? if (expectZero[i]) { if (coord != 0) ans[i] = 0; } else { if (ansMultDouble != coord * 2) ans[i] = 0; } } // Have we ruled out all possibilities? if ((! ans[0]) && (! ans[1])) return NO_EDGES; } } // Return whatever candidates have survived. if (ans[0]) return std::make_pair(ans[0], ans[1]); else return std::make_pair(ans[1], ans[0]); } } // namespace regina regina-4.95/engine/surfaces/ndisc.cpp000644 000765 000024 00000020500 12234011536 017465 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nnormalsurface.h" #include "surfaces/ndisc.h" namespace regina { std::ostream& operator << (std::ostream& out, const NDiscSpec& spec) { out << '(' << spec.tetIndex << ", " << spec.type << ", " << spec.number << ')'; return out; } bool numberDiscsAwayFromVertex(int discType, int vertex) { if (discType < 4) return (vertex == discType); return (vertex == 0 || vertex == vertexSplitPartner[(discType - 1) % 3][0]); } bool discOrientationFollowsEdge(int discType, int vertex, int edgeStart, int edgeEnd) { NPerm4 forwards(vertex, edgeStart, edgeEnd, 6-vertex-edgeStart-edgeEnd); NPerm4 reverse(vertex, edgeEnd, edgeStart, 6-vertex-edgeStart-edgeEnd); if (discType < 4) { for (int i = 0; i < 3; i++) { if (triDiscArcs(discType, i) == forwards) return true; if (triDiscArcs(discType, i) == reverse) return false; } } else if (discType < 7) { for (int i = 0; i < 4; i++) { if (quadDiscArcs(discType - 4, i) == forwards) return true; if (quadDiscArcs(discType - 4, i) == reverse) return false; } } else { for (int i = 0; i < 8; i++) { if (octDiscArcs(discType - 7, i) == forwards) return true; if (octDiscArcs(discType - 7, i) == reverse) return false; } } return false; } NDiscSetTet::NDiscSetTet(const NNormalSurface& surface, unsigned long tetIndex) { int i; for (i=0; i<4; i++) internalNDiscs[i] = surface.getTriangleCoord(tetIndex, i).longValue(); for (i=4; i<7; i++) internalNDiscs[i] = surface.getQuadCoord(tetIndex, i - 4).longValue(); for (i=7; i<10; i++) internalNDiscs[i] = surface.getOctCoord(tetIndex, i - 7).longValue(); } NDiscSetTet::NDiscSetTet(unsigned long tri0, unsigned long tri1, unsigned long tri2, unsigned long tri3, unsigned long quad0, unsigned long quad1, unsigned long quad2, unsigned long oct0, unsigned long oct1, unsigned long oct2) { internalNDiscs[0] = tri0; internalNDiscs[1] = tri1; internalNDiscs[2] = tri2; internalNDiscs[3] = tri3; internalNDiscs[4] = quad0; internalNDiscs[5] = quad1; internalNDiscs[6] = quad2; internalNDiscs[7] = oct0; internalNDiscs[8] = oct1; internalNDiscs[9] = oct2; } unsigned long NDiscSetTet::arcFromDisc(int /* arcFace */, int arcVertex, int discType, unsigned long discNumber) const { // Is it a triangle? if (discType < 4) return discNumber; // It's a quad or an octagon. // Note that there is at most one octagonal or quad type present // (since the surface must be embedded), so this must be it. if (arcVertex == 0 || arcVertex == vertexSplitPartner[(discType - 1) % 3][0]) return internalNDiscs[arcVertex] + discNumber; else return internalNDiscs[arcVertex] + internalNDiscs[discType] - discNumber - 1; } void NDiscSetTet::discFromArc(int arcFace, int arcVertex, unsigned long arcNumber, int& discType, unsigned long& discNumber) const { // Is it a triangle? if (arcNumber < internalNDiscs[arcVertex]) { discType = arcVertex; discNumber = arcNumber; return; } // It's a quad or an octagon. // Note that there is at most one octagonal or quad type present // (since the surface must be embedded), so this must be it. if (internalNDiscs[vertexSplit[arcVertex][arcFace] + 4] > 0) discType = vertexSplit[arcVertex][arcFace] + 4; else if (internalNDiscs[vertexSplitMeeting[arcVertex][arcFace][0] + 7] > 0) discType = vertexSplitMeeting[arcVertex][arcFace][0] + 7; else discType = vertexSplitMeeting[arcVertex][arcFace][1] + 7; if (arcVertex == 0 || arcVertex == vertexSplitPartner[(discType - 1) % 3][0]) discNumber = arcNumber - internalNDiscs[arcVertex]; else discNumber = internalNDiscs[discType] - (arcNumber - internalNDiscs[arcVertex]) - 1; } NDiscSetSurface::NDiscSetSurface(const NNormalSurface& surface, bool) : triangulation(surface.getTriangulation()) { unsigned long tot = triangulation->getNumberOfTetrahedra(); if (tot == 0) discSets = 0; else discSets = new NDiscSetTet*[tot]; } NDiscSetSurface::NDiscSetSurface(const NNormalSurface& surface) : triangulation(surface.getTriangulation()) { unsigned long tot = triangulation->getNumberOfTetrahedra(); if (tot == 0) discSets = 0; else { discSets = new NDiscSetTet*[tot]; for (unsigned long index = 0; index < tot; index++) discSets[index] = new NDiscSetTet(surface, index); } } NDiscSetSurface::~NDiscSetSurface() { if (discSets) { unsigned long tot = nTets(); for (unsigned long index = 0; index < tot; index++) delete discSets[index]; delete[] discSets; } } NDiscSpec* NDiscSetSurface::adjacentDisc(const NDiscSpec& disc, NPerm4 arc, NPerm4& adjArc) const { NTetrahedron* tet = triangulation->getTetrahedron(disc.tetIndex); int arcFace = arc[3]; if (tet->adjacentTetrahedron(arcFace) == 0) return 0; NDiscSpec* ans = new NDiscSpec; ans->tetIndex = triangulation->tetrahedronIndex( tet->adjacentTetrahedron(arcFace)); adjArc = tet->adjacentGluing(arcFace) * arc; unsigned long arcNumber = discSets[disc.tetIndex]->arcFromDisc( arcFace, arc[0], disc.type, disc.number); discSets[ans->tetIndex]->discFromArc(adjArc[3], adjArc[0], arcNumber, ans->type, ans->number); return ans; } void NDiscSpecIterator::makeValid() { while (current.number == internalDiscSet->nDiscs(current.tetIndex, current.type)) { current.number = 0; current.type++; if (current.type == 10) { current.type = 0; current.tetIndex++; if (current.tetIndex == internalDiscSet->nTets()) break; } } } } // namespace regina regina-4.95/engine/surfaces/ndisc.h000644 000765 000024 00000101257 12234011536 017143 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/ndisc.h * \brief Deals with individual normal discs and sets of normal discs * in a normal surface. */ #ifndef __NDISC_H #ifndef __DOXYGEN #define __NDISC_H #endif #include #include "regina-core.h" #include "surfaces/nnormalsurface.h" #include "triangulation/ntriangulation.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Specifies a single normal disc in a normal surface. * * There are 10 disc types. Types 0-3 represent triangles 0-3, * types 4-6 represent quads 0-2 and types 7-9 represent * octagons 0-2. * * Discs of a specific type are assigned numbers from 0 upwards. * Triangular discs are numbered outwards from the vertex they surround. * Quad discs and octagonal discs are numbered outwards away from vertex 0 * of the tetrahedron. * * Note that, unlike NDiscType in which the meaning of NDiscType::type is * flexible, the meaning of NDiscSpec::type is fixed as described above. * * \warning This class converts the indices of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. * \pre This class should only be used with \a embedded * normal surfaces. */ struct REGINA_API NDiscSpec { unsigned long tetIndex; /**< The index in the triangulation of the tetrahedron containing the disc. */ int type; /**< The disc type; this is between 0 and 9 inclusive, as described in the \a NDiscSpec class notes. */ unsigned long number; /**< Specifies which disc of the particular type in the particular tetrahedron is being referred to; discs are numbered as described in the \a NDiscSpec class notes. */ /** * Creates a new uninitialised disc specifier. */ NDiscSpec(); /** * Creates a new disc specifier containing the given values. * * @param newTetIndex the index in the triangulation of the tetrahedron * containing the disc. * @param newType the disc type; this is between 0 and 9 inclusive, * as described in the \a NDiscSpec class notes. * @param newNumber specifies which disc of the particular type in the * particular tetrahedron is being referred to; discs are numbered * as described in the \a NDiscSpec class notes. */ NDiscSpec(unsigned long newTetIndex, int newType, unsigned long newNumber); /** * Creates a new disc specifier that is a clone of the given specifier. * * @param cloneMe the disc specifier to clone. */ NDiscSpec(const NDiscSpec& cloneMe); /** * Copies the values from the given disc specifier into this specifier. * * @param cloneMe the disc specifier whose values should be copied. * @return a reference to this disc specifier. */ NDiscSpec& operator = (const NDiscSpec& cloneMe); /** * Determines if this and the given disc specifier contain identical * information. * * @param other the disc specifier to compare with this. * @return \c true if and only if this and the given disc specifier * contain identical information. */ bool operator == (const NDiscSpec& other) const; friend std::ostream& operator << (std::ostream& out, const NDiscSpec& spec); }; /** * Writes the given disc specifier to the given output stream. * The disc specifier will be written as a triple * (tetIndex, type, number). * * @param out the output stream to which to write. * @param spec the disc specifier to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NDiscSpec& spec); /** * Determines whether or not normal discs of the given type are * numbered away from the given vertex. * * @param discType the normal disc type under consideration; this * should be between 0 and 9 inclusive, as described by the NDiscSpec * class notes. * @param vertex the vertex under consideration; this should be * between 0 and 3 inclusive. * @return \c true if normal discs of the given type are * numbered away from the given vertex, or \c false if they are * numbered towards the given vertex. */ REGINA_API bool numberDiscsAwayFromVertex(int discType, int vertex); /** * Determines whether or not the natural boundary orientation of a normal * disc of the given type follows the given directed normal arc. * Natural boundary orientation is defined by arrays ::triDiscArcs, * ::quadDiscArcs and ::octDiscArcs. * * \pre The given normal arc lies on a normal disc of the given type. * * @param discType the normal disc type under consideration; this should * be between 0 and 9 inclusive, as described by the NDiscSpec class * notes. * @param vertex the vertex about which the normal arc runs. * @param edgeStart the start vertex of the edge to which the normal arc * is parallel. * @param edgeEnd the end vertex of the edge to which the normal arc is * parallel. */ REGINA_API bool discOrientationFollowsEdge(int discType, int vertex, int edgeStart, int edgeEnd); /** * Represents a set of normal discs inside a single tetrahedron. * The numbers of discs of each type are stored in this structure, so * querying them is fast regardless of the underlying normal surface * coordinate system used. * * \warning This class converts the number of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. * \pre This class should only be used with \a embedded normal surfaces. * * \todo \problong Have some error flag so we can barf politely if the number * of normal discs of a given type does not fit into an unsigned * long. See how this affects NDiscSetTetData also. */ class REGINA_API NDiscSetTet { protected: unsigned long internalNDiscs[10]; /**< The number of discs of each type. */ public: /** * Creates a new set of normal discs corresponding to the discs * of the given normal surface that lie within the given * tetrahedron. * * @param surface the normal surface whose discs we shall use. * @param tetIndex the index in the triangulation of the * tetrahedron that our discs must lie in; this must be between * 0 and tri.getNumberOfTetrahedra()-1 inclusive, where * tri is the triangulation containing the given normal * surface. */ NDiscSetTet(const NNormalSurface& surface, unsigned long tetIndex); /** * Creates a new set of normal discs where the number of discs of * each type is explicitly given. * * @param tri0 the number of triangular discs surrounding vertex 0. * @param tri1 the number of triangular discs surrounding vertex 1. * @param tri2 the number of triangular discs surrounding vertex 2. * @param tri3 the number of triangular discs surrounding vertex 3. * @param quad0 the number of quadrilateral discs of type 0. * @param quad1 the number of quadrilateral discs of type 1. * @param quad2 the number of quadrilateral discs of type 2. * @param oct0 the number of octahedral discs of type 0. * @param oct1 the number of octahedral discs of type 1. * @param oct2 the number of octahedral discs of type 2. */ NDiscSetTet(unsigned long tri0, unsigned long tri1, unsigned long tri2, unsigned long tri3, unsigned long quad0, unsigned long quad1, unsigned long quad2, unsigned long oct0 = 0, unsigned long oct1 = 0, unsigned long oct2 = 0); /** * Destroys this disc set. */ virtual ~NDiscSetTet(); /** * Determines the number of discs of the given type inside this * tetrahedron. * * @param type the disc type to examine; this should be between * 0 and 9 inclusive. Disc types are outlined in * the NDiscSpec class notes. * @return the number of discs of the given type inside this * tetrahedron. */ unsigned long nDiscs(int type) const; /** * Determines which normal arc of a given type on a given face * of this tetrahedron corresponds to the given normal disc. * * \pre The given normal disc actually meets a * normal arc of the given type on the given face. * * @param arcFace the face of this tetrahedron containing the * normal arc (between 0 and 3 inclusive). * @param arcVertex the vertex of this tetrahedron about which the * normal arc runs (between 0 and 3 inclusive); \a arcFace and * \a arcVertex should not be the same. * @param discType the disc type of the given normal disc; * this should be between 0 and 9 inclusive, as described in the * NDiscSpec class notes. * @param discNumber indicates which normal disc of the given disc * type is referred to (between 0 and nDiscs(discType)-1 * inclusive). * @return the number of the normal arc of the given type that belongs * to the given normal disc. * Arcs of a given type (where \a arcFace and \a arcVertex * together define the arc type) are numbered starting at 0 from the * tetrahedron vertex outwards. */ unsigned long arcFromDisc(int arcFace, int arcVertex, int discType, unsigned long discNumber) const; /** * Determines which normal disc in this tetrahedron meets * the given normal arc on the given face. * * \pre The given normal arc * actually exists in the normal surface with which this * \a NDiscSetTet object was created. * * @param arcFace the face of this tetrahedron containing the * normal arc (between 0 and 3 inclusive). * @param arcVertex the vertex of this tetrahedron about which the * normal arc runs (between 0 and 3 inclusive); \a arcFace and * \a arcVertex should not be the same. * @param arcNumber indicates which normal arc of the given type * is referred to. * Arcs of a given type (where \a arcFace and \a arcVertex * together define the arc type) are numbered starting at 0 from the * tetrahedron vertex outwards. * @param discType returns the disc type of the normal disc that * meets the given normal arc; this will be between 0 and 9 * inclusive, as described in the NDiscSpec class notes. * Any value may be initially passed. * @param discNumber returns a number that indicates which * normal disc of the returned disc type (discType) * meets the given normal arc; this will be between 0 and * nDiscs(discType)-1 inclusive. Any value may be * initially passed. */ void discFromArc(int arcFace, int arcVertex, unsigned long arcNumber, int& discType, unsigned long& discNumber) const; }; /** * Stores data of type \c T for every normal disc inside a single * tetrahedron. * * \warning This class converts the number of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. * \pre This class should only be used with \a embedded * normal surfaces. * \pre Type T has a default constructor and an * assignment operator. That is, if \c a and \c b are of type T, then * \c a can be declared with no parameters and can then receive the * value of \c b using a=b. * * \ifacespython Not present. */ template class NDiscSetTetData : public NDiscSetTet { public: typedef T* DataPtr; /**< A type that is a pointer to the data stored with each * disc. */ protected: DataPtr internalData[10]; /**< Stores the data corresponding to each normal disc. */ public: /** * Creates a new disc set corresponding to the discs of the * given normal surface that lie within the given tetrahedron. * The data for each disc will remain uninitialised. * * @param surface the normal surface whose discs we shall use. * @param tetIndex the index in the triangulation of the * tetrahedron that our discs must lie in; this must be between * 0 and tri.getNumberOfTetrahedra()-1 inclusive, where * tri is the triangulation containing the given normal * surface. */ NDiscSetTetData(const NNormalSurface& surface, unsigned long tetIndex) : NDiscSetTet(surface, tetIndex) { for (int i=0; i<10; i++) if (internalNDiscs[i]) internalData[i] = new T[internalNDiscs[i]]; else internalData[i] = 0; } /** * Creates a new disc set corresponding to the discs of the * given normal surface that lie within the given tetrahedron. * The data for each disc will be initialised to the given * value. * * @param surface the normal surface whose discs we shall use. * @param tetIndex the index in the triangulation of the * tetrahedron that our discs must lie in; this must be between * 0 and tri.getNumberOfTetrahedra()-1 inclusive, where * tri is the triangulation containing the given normal * surface. * @param initValue the value with which to initialise the data * corresponding to each disc. */ NDiscSetTetData(const NNormalSurface& surface, unsigned long tetIndex, const T& initValue) : NDiscSetTet(surface, tetIndex) { unsigned long disc; for (int i=0; i<10; i++) if (internalNDiscs[i]) { internalData[i] = new T[internalNDiscs[i]]; for (disc = 0; disc < internalNDiscs[i]; disc++) internalData[i][disc] = initValue; } else internalData[i] = 0; } /** * Creates a new disc set where the number of discs of each type * is explicitly given. The data for each disc will remain * uninitialised. * * @param tri0 the number of triangular discs surrounding vertex 0. * @param tri1 the number of triangular discs surrounding vertex 1. * @param tri2 the number of triangular discs surrounding vertex 2. * @param tri3 the number of triangular discs surrounding vertex 3. * @param quad0 the number of quadrilateral discs of type 0. * @param quad1 the number of quadrilateral discs of type 1. * @param quad2 the number of quadrilateral discs of type 2. * @param oct0 the number of octahedral discs of type 0. * @param oct1 the number of octahedral discs of type 1. * @param oct2 the number of octahedral discs of type 2. */ NDiscSetTetData(unsigned long tri0, unsigned long tri1, unsigned long tri2, unsigned long tri3, unsigned long quad0, unsigned long quad1, unsigned long quad2, unsigned long oct0 = 0, unsigned long oct1 = 0, unsigned long oct2 = 0) : NDiscSetTet(tri0, tri1, tri2, tri3, quad0, quad1, quad2, oct0, oct1, oct2) { for (int i=0; i<10; i++) if (internalNDiscs[i]) internalData[i] = new T[internalNDiscs[i]]; else internalData[i] = 0; } /** * Destroys this disc set and deallocates all data arrays. * Note that no assumption is made about type \c T, so if data * elements are pointers to dynamically allocated objects, these * will not be destroyed. */ virtual ~NDiscSetTetData() { for (int i=0; i<10; i++) if (internalData[i]) delete[] internalData[i]; } /** * Retrieves a reference to the data corresponding to the given * normal disc. * * @param discType the disc type of the given normal disc; * this should be between 0 and 9 inclusive, as described in the * NDiscSpec class notes. * @param discNumber indicates which normal disc of the given disc * type is referred to; this should be between 0 and * nDiscs(discType)-1 inclusive. * @return a reference to the data corresponding to the given * normal disc. */ T& data(int discType, unsigned long discNumber) { assert(0 <= discType && discType < 10); assert(discNumber < internalNDiscs[discType]); return internalData[discType][discNumber]; } }; /** * Represents the set of all normal discs forming a normal surface. * These are stored as an array of NDiscSetTet objects, one for each * tetrahedron. * * \warning This class converts the number of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. * \pre This class should only be used with \a embedded * normal surfaces. */ class REGINA_API NDiscSetSurface { protected: NDiscSetTet** discSets; /**< The disc sets corresponding to each tetrahedron. */ NTriangulation* triangulation; /**< The triangulation in which the normal surface lives. */ protected: /** * Creates a new disc set corresponding to the discs of the * given normal surface. The array of tetrahedron disc set * pointers will be created but the NDiscSetTet objects themselves * will not be created. * * This constructor should be called from constructors of subclasses * who wish to use objects of a subclass of NDiscSetTet, which * this constructor allows them to create for themselves. * * \warning After calling this constructor, each * NDiscSetTet object * in the \a discSets array must be created, since the * \a NDiscSetSurface destructor will attempt to destroy them! * The \a discSets array will have size * surface.getTriangulation()->getNumberOfTetrahedra(). * * @param surface the normal surface whose discs we shall use. * @param b this parameter is ignored. */ NDiscSetSurface(const NNormalSurface& surface, bool b); public: /** * Creates a new disc set corresponding to the discs of the * given normal surface. * * @param surface the normal surface whose discs we shall use. */ NDiscSetSurface(const NNormalSurface& surface); /** * Destroys this set of discs and deallocates all associated memory. */ virtual ~NDiscSetSurface(); /** * Returns the number of tetrahedra in the underlying * triangulation. * * @return the number of tetrahedra. */ unsigned long nTets() const; /** * Determines the number of discs of the given type inside the * given tetrahedron. * * @param tetIndex the index in the triangulation of the * tetrahedron to examine. * @param type the disc type to examine; this should be between * 0 and 9 inclusive. Disc types are outlined in * the NDiscSpec class notes. * @return the number of discs of the given type inside the * given tetrahedron. */ unsigned long nDiscs(unsigned long tetIndex, int type) const; /** * Returns the specific set of discs living inside the given * tetrahedron. * * @param tetIndex the index in the triangulation of the given * tetrahedron. * @return the set of discs inside the given tetrahedron. */ NDiscSetTet& tetDiscs(unsigned long tetIndex) const; /** * Determines which normal disc is adjacent to the given normal disc * along the given directed normal arc in the surface described by * this disc set. * * A directed normal arc will be specified by a permutation * p, where the arc runs around vertex p[0] * parallel to the directed edge from vertex p[1] to * p[2]. * * @param disc the given normal disc; this must be a disc in this * disc set. * @param arc the given normal arc; this must actually be an arc * on the boundary of the given normal disc (although it may run * in either direction). * @param adjArc returns the same directed normal arc that was * passed, but expressed in terms of the vertices of the * adjacent tetrahedron. Any value may be initially passed. If * there is no adjacent disc/tetrahedron, this permutation will * remain unchanged. * @return the normal disc adjacent to the given disc along the * given arc, or 0 if there is no adjacent disc. This disc * specifier will be newly created, and it is up to the caller * of this routine to dispose of it. */ NDiscSpec* adjacentDisc(const NDiscSpec& disc, NPerm4 arc, NPerm4& adjArc) const; }; /** * Stores data of type \c T for every normal disc within a particular * normal surface. * This data is stored using an array of NDiscSetTetData objects, * one for each tetrahedron (thus the inherited member function * tetDiscs() will return an object of class NDiscSetTetData). * * \warning This class converts the number of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. * \pre This class should only be used with \a embedded normal surfaces. * \pre Type T has a default constructor and an * assignment operator. That is, if \c a and \c b are of type T, then * \c a can be declared with no parameters and can then receive the * value of \c b using a=b. * * \ifacespython Not present. */ template class NDiscSetSurfaceData : public NDiscSetSurface { public: /** * Creates a new disc set corresponding to the discs of the * given normal surface. * The data for each disc will remain uninitialised. * * @param surface the normal surface whose discs we shall use. */ NDiscSetSurfaceData(const NNormalSurface& surface) : NDiscSetSurface(surface, true) { unsigned long tot = triangulation->getNumberOfTetrahedra(); if (tot) for (unsigned long index = 0; index < tot; index++) discSets[index] = new NDiscSetTetData(surface, index); } /** * Creates a new disc set corresponding to the discs of the * given normal surface. * The data for each disc will be initialised to the given * value. * * @param surface the normal surface whose discs we shall use. * @param initValue the value with which to initialise the data * corresponding to each disc. */ NDiscSetSurfaceData(const NNormalSurface& surface, const T& initValue) : NDiscSetSurface(surface, true) { unsigned long tot = triangulation->getNumberOfTetrahedra(); if (tot) for (unsigned long index = 0; index < tot; index++) discSets[index] = new NDiscSetTetData(surface, index, initValue); } /** * Retrieves a reference to the data corresponding to the given * normal disc. * * @param disc the disc whose data we require; this must refer * to a disc within this disc set. * @return a reference to the data corresponding to the given * normal disc. */ T& data(const NDiscSpec& disc) { return dynamic_cast*>(discSets[disc.tetIndex])-> data(disc.type, disc.number); } }; /** * An iterator used for running through all normal discs in a normal * surface. * * \warning This class converts the indices of normal discs of a * given type from NLargeInteger to unsigned long. See the * precondition below. * * \pre The number of normal discs of a particular type * in a particular tetrahedron can be represented by a long integer. */ class REGINA_API NDiscSpecIterator { protected: const NDiscSetSurface* internalDiscSet; /**< The disc set through which we are iterating. */ NDiscSpec current; /**< The disc currently pointed to. */ public: /** * Creates a new uninitialised iterator. * This iterator cannot be used or queried until init() is called. */ NDiscSpecIterator(); /** * Creates a new iterator pointing to the first disc in the * given disc set. * * @param discSet the disc set used to initialise this iterator. */ NDiscSpecIterator(const NDiscSetSurface& discSet); /** * Points this iterator to the first disc in the given disc set. * * @param discSet the disc set used to reinitialise this iterator. */ void init(const NDiscSetSurface& discSet); /** * Points this iterator to the next disc, or makes it * past-the-end if there is no next disc. * * Unlike most standard increment operators, this operator returns * \c void. One consequence of this is that the preincrement * and postincrement operators for this class are identical. This * interface will need to be made more standard some day. * * \pre This iterator is not past-the-end. * * \ifacespython This routine is called inc(), since Python does * not support the increment operator. */ void operator++(); /** * Points this iterator to the next disc, or makes it * past-the-end if there is no next disc. * * Unlike most standard increment operators, this operator returns * \c void. One consequence of this is that the preincrement * and postincrement operators for this class are identical. This * interface will need to be made more standard some day. * * \pre This iterator is not past-the-end. * * \ifacespython This routine is called inc(), since Python does * not support the increment operator. */ void operator++(int); /** * Returns a reference to the disc pointed to by this iterator. * * \pre This iterator is not past-the-end. * * \ifacespython This routine is called deref(), since Python does * not support the dereference operator. * * @return a reference to the disc pointed to by this iterator. */ const NDiscSpec& operator *() const; /** * Determines if this iterator is past-the-end. * * @return \c true if and only if this iterator is past-the-end. */ bool done() const; private: /** * Ensures the data member \a current points to a real disc and * not a virtual disc (in which the disc number exceeds the * number of discs of the corresponding type). * * \pre This iterator is not yet past-the-end * (although it may be in the middle of an increment operation * that will put it past-the-end). */ void makeValid(); }; /*@}*/ // Inline functions for NDiscSpec inline NDiscSpec::NDiscSpec() { } inline NDiscSpec::NDiscSpec(unsigned long newTetIndex, int newType, unsigned long newNumber) : tetIndex(newTetIndex), type(newType), number(newNumber) { } inline NDiscSpec::NDiscSpec(const NDiscSpec& cloneMe) : tetIndex(cloneMe.tetIndex), type(cloneMe.type), number(cloneMe.number) { } inline NDiscSpec& NDiscSpec::operator = (const NDiscSpec& cloneMe) { tetIndex = cloneMe.tetIndex; type = cloneMe.type; number = cloneMe.number; return *this; } inline bool NDiscSpec::operator == (const NDiscSpec& other) const { return (tetIndex == other.tetIndex && type == other.type && number == other.number); } // Inline functions for NDiscSetTet inline NDiscSetTet::~NDiscSetTet() { } inline unsigned long NDiscSetTet::nDiscs(int type) const { return internalNDiscs[type]; } // Inline functions for NDiscSetSurface inline unsigned long NDiscSetSurface::nTets() const { return triangulation->getNumberOfTetrahedra(); } inline unsigned long NDiscSetSurface::nDiscs(unsigned long tetIndex, int type) const { return discSets[tetIndex]->nDiscs(type); } inline NDiscSetTet& NDiscSetSurface::tetDiscs(unsigned long tetIndex) const { return *(discSets[tetIndex]); } // Inline functions for NDiscSpecIterator inline NDiscSpecIterator::NDiscSpecIterator() { } inline NDiscSpecIterator::NDiscSpecIterator(const NDiscSetSurface& discSet) : internalDiscSet(&discSet), current(0, 0, 0) { makeValid(); } inline void NDiscSpecIterator::init(const NDiscSetSurface& discSet) { internalDiscSet = &discSet; current.tetIndex = 0; current.type = 0; current.number = 0; makeValid(); } inline void NDiscSpecIterator::operator++() { current.number++; makeValid(); } inline void NDiscSpecIterator::operator++(int) { current.number++; makeValid(); } inline const NDiscSpec& NDiscSpecIterator::operator *() const { return current; } inline bool NDiscSpecIterator::done() const { return (current.tetIndex == internalDiscSet->nTets()); } } // namespace regina #endif regina-4.95/engine/surfaces/ndisctype.cpp000644 000765 000024 00000004551 12234011536 020377 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/ndisctype.h" namespace regina { const NDiscType NDiscType::NONE; } // namespace regina regina-4.95/engine/surfaces/ndisctype.h000644 000765 000024 00000017266 12234011536 020053 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/ndisctype.h * \brief Deals with normal and almost normal disc types. */ #ifndef __NDISCTYPE_H #ifndef __DOXYGEN #define __NDISCTYPE_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Identifies a single normal or almost normal disc type within a * triangulation. * * A disc type is identified by a tetrahedron index (the data member * \a tetIndex), and a disc type within that tetrahedron (the data * member \a type). The latter could mean any number of things according * to the application at hand. For instance, if we are tracking quad types * then \a type might be an integer between 0 and 2 inclusive, or if we * are tracking all normal discs in standard coordinates then \a type * might be an integer between 0 and 6 inclusive. Ultimately, the * specific meaning of \a type is left to the user. * * It is however assumed that \a type will always be non-negative for * "meaningful" disc types; this is to ensure that the constant NONE * does not clash with any meaningful values. * * Note that this class tracks disc \a types, not discs themselves. * To track individual normal discs, see the NDiscSpec class instead. */ struct REGINA_API NDiscType { static const NDiscType NONE; /**< Represents a "null" disc type. Here the \a type member is negative, to distinguish it from "meaningful" disc types in which \a type is always zero or positive. */ unsigned long tetIndex; /**< The index within the triangulation of the tetrahedron containing this disc type. This must be between 0 and NTriangulation::getNumberOfTetrahedra()-1 inclusive. */ int type; /**< Identifies the disc type within the specified tetrahedron. The precise meaning of this member is left up to the user, though it must be non-negative for "meaningful" disc types. See the NDiscType class notes for details. */ /** * Creates a new disc type initialised to NONE. */ NDiscType(); /** * Creates a new disc type initialised with the given values. * * @param newTetIndex the index within the triangulation of the * tetrahedron containing this disc type. * @param newType the specific disc type within the given * tetrahedron; see the class notes for the meaning of this field. */ NDiscType(unsigned long newTetIndex, int newType); /** * Creates a copy of the given disc type. * * @param cloneMe the disc type to clone. */ NDiscType(const NDiscType& cloneMe); /** * Sets this to a copy of the given disc type. * * @param cloneMe the disc type to clone. * @return a reference to this disc type. */ NDiscType& operator = (const NDiscType& cloneMe); /** * Determines if this and the given disc type are identical. * * Note that NONE is considered identical to NONE, and that NONE will * not be equal to any "meaningful" disc type (specifically, a disc type * for which \a type is non-negative). * * @return \c true if this and the given disc type are identical, or * \c false if they are different. */ bool operator == (const NDiscType& compare) const; /** * Determines if this and the given disc type are different. * * This is the negation of the equality test; see operator == for * further details. * * @return \c true if this and the given disc type are different, or * \c false if they are identical. */ bool operator != (const NDiscType& compare) const; /** * Provides an ordering of disc types. Types are ordered first by * \a tetrahedron and then by \a type. NONE is considered less than * all "meaningful" disc types. * * @return \c true if this disc type appears before the given disc type * in the ordering, or \c false if not. */ bool operator < (const NDiscType& compare) const; }; /** * Writes the given disc type to the given output stream. * The disc type will be written as a pair (tetIndex, type). * * @param out the output stream to which to write. * @param type the disc type to write. * @return a reference to the given output stream. */ REGINA_API std::ostream& operator << (std::ostream& out, const NDiscType& type); /*@}*/ // Inline functions for NDiscType inline NDiscType::NDiscType() : tetIndex(0), type(-1) { } inline NDiscType::NDiscType(unsigned long newTetIndex, int newType) : tetIndex(newTetIndex), type(newType) { } inline NDiscType::NDiscType(const NDiscType& cloneMe) : tetIndex(cloneMe.tetIndex), type(cloneMe.type) { } inline NDiscType& NDiscType::operator = (const NDiscType& cloneMe) { tetIndex = cloneMe.tetIndex; type = cloneMe.type; return *this; } inline bool NDiscType::operator == (const NDiscType& compare) const { return (tetIndex == compare.tetIndex && type == compare.type); } inline bool NDiscType::operator != (const NDiscType& compare) const { return (tetIndex != compare.tetIndex || type != compare.type); } inline bool NDiscType::operator < (const NDiscType& compare) const { return (tetIndex < compare.tetIndex || (tetIndex == compare.tetIndex && type < compare.type)); } inline std::ostream& operator << (std::ostream& out, const NDiscType& type) { return out << '(' << type.tetIndex << ", " << type.type << ')'; } } // namespace regina #endif regina-4.95/engine/surfaces/nnormalsurface.cpp000644 000765 000024 00000042642 12234011536 021417 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nmatrixint.h" #ifndef EXCLUDE_SNAPPEA #include "snappea/nsnappeatriangulation.h" #endif #include "surfaces/nnormalsurface.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" #include "utilities/xmlutils.h" // Property IDs: #define PROPID_EULERCHARACTERISTIC 1 #define PROPID_REALBOUNDARY 5 #define PROPID_COMPACT 6 #define PROPID_ORIENTABILITY 7 #define PROPID_TWOSIDEDNESS 8 #define PROPID_CONNECTEDNESS 9 #define PROPID_CANCRUSH 10 #define PROPID_SURFACENAME 100 namespace regina { const int vertexSplit[4][4] = { { -1, 0, 1, 2 }, { 0,-1, 2, 1 }, { 1, 2,-1, 0 }, { 2, 1, 0,-1 } }; const int vertexSplitMeeting[4][4][2] = { { {-1,-1}, { 1, 2}, { 0, 2}, { 0, 1} }, { { 1, 2}, {-1,-1}, { 0, 1}, { 0, 2} }, { { 0, 2}, { 0, 1}, {-1,-1}, { 1, 2} }, { { 0, 1}, { 0, 2}, { 1, 2}, {-1,-1} } }; const int vertexSplitDefn[3][4] = { { 0, 1, 2, 3 }, { 0, 2, 1, 3 }, { 0, 3, 1, 2 } }; const int vertexSplitPartner[3][4] = { { 1, 0, 3, 2}, { 2, 3, 0, 1}, { 3, 2, 1, 0} }; const char vertexSplitString[3][6] = { "01/23", "02/13", "03/12" }; // The following three arrays cannot be made 2-D because of a g++-2.95 bug. const NPerm4 __triDiscArcs[12] = { NPerm4(0,1,2,3), NPerm4(0,2,3,1), NPerm4(0,3,1,2), NPerm4(1,0,3,2), NPerm4(1,3,2,0), NPerm4(1,2,0,3), NPerm4(2,3,0,1), NPerm4(2,0,1,3), NPerm4(2,1,3,0), NPerm4(3,2,1,0), NPerm4(3,1,0,2), NPerm4(3,0,2,1) }; const NPerm4 __quadDiscArcs[12] = { NPerm4(0,2,3,1), NPerm4(3,0,1,2), NPerm4(1,3,2,0), NPerm4(2,1,0,3), NPerm4(0,3,1,2), NPerm4(1,0,2,3), NPerm4(2,1,3,0), NPerm4(3,2,0,1), NPerm4(0,1,2,3), NPerm4(2,0,3,1), NPerm4(3,2,1,0), NPerm4(1,3,0,2) }; const NPerm4 __octDiscArcs[24] = { NPerm4(0,3,1,2), NPerm4(0,1,2,3), NPerm4(2,0,3,1), NPerm4(2,3,1,0), NPerm4(1,2,0,3), NPerm4(1,0,3,2), NPerm4(3,1,2,0), NPerm4(3,2,0,1), NPerm4(0,1,2,3), NPerm4(0,2,3,1), NPerm4(3,0,1,2), NPerm4(3,1,2,0), NPerm4(2,3,0,1), NPerm4(2,0,1,3), NPerm4(1,2,3,0), NPerm4(1,3,0,2), NPerm4(0,2,3,1), NPerm4(0,3,1,2), NPerm4(1,0,2,3), NPerm4(1,2,3,0), NPerm4(3,1,0,2), NPerm4(3,0,2,1), NPerm4(2,3,1,0), NPerm4(2,1,0,3) }; NNormalSurface* NNormalSurface::clone() const { NNormalSurface* ans = new NNormalSurface(triangulation, dynamic_cast(vector->clone())); ans->eulerChar = eulerChar; ans->orientable = orientable; ans->twoSided = twoSided; ans->connected = connected; ans->realBoundary = realBoundary; ans->compact = compact; return ans; } NNormalSurface* NNormalSurface::doubleSurface() const { NNormalSurface* ans = new NNormalSurface(triangulation, dynamic_cast(vector->clone())); (*(ans->vector)) *= 2; // Some properties can be copied straight across. ans->realBoundary = realBoundary; ans->compact = compact; if (eulerChar.known()) ans->eulerChar = eulerChar.value() * 2; // The following three properties can be used together to deduce how // they change in the clone. However, until we sit down and check // through all possible cases we'll just leave them marked unknown. // TODO: ans->orientable, ans->twoSided, ans->connected // And some other properties are best left recalculated. return ans; } NNormalSurface::NNormalSurface(NTriangulation* triang, NNormalSurfaceVector* newVector) : vector(newVector), triangulation(triang) { } void NNormalSurface::writeTextShort(std::ostream& out) const { unsigned long nTets = triangulation->getNumberOfTetrahedra(); unsigned long tet; unsigned j; bool almostNormal = vector->allowsAlmostNormal(); for (tet=0; tet 0) out << " || "; for (j=0; j<4; j++) out << getTriangleCoord(tet, j) << ' '; out << ';'; for (j=0; j<3; j++) out << ' ' << getQuadCoord(tet, j); if (almostNormal) { out << " ;"; for (j=0; j<3; j++) out << ' ' << getOctCoord(tet, j); } } } bool NNormalSurfaceVector::hasMultipleOctDiscs(NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); int oct; NLargeInteger coord; for (unsigned long tet=0; tetgetNumberOfTetrahedra(); unsigned long tet; int type; for (tet = 0; tet < nTets; tet++) { for (type = 0; type < 4; type++) if (getTriangleCoord(tet, type, triang).isInfinite()) return false; for (type = 0; type < 3; type++) if (getQuadCoord(tet, type, triang).isInfinite()) return false; } if (allowsAlmostNormal()) for (tet = 0; tet < nTets; tet++) for (type = 0; type < 3; type++) if (getOctCoord(tet, type, triang).isInfinite()) return false; return true; } bool NNormalSurfaceVector::isSplitting(NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); unsigned long tet; int type; NLargeInteger tot; for (tet = 0; tet < nTets; tet++) { for (type = 0; type < 4; type++) if (getTriangleCoord(tet, type, triang) != 0) return false; tot = 0L; for (type = 0; type < 3; type++) tot += getQuadCoord(tet, type, triang); if (tot != 1) return false; } if (allowsAlmostNormal()) for (tet = 0; tet < nTets; tet++) for (type = 0; type < 3; type++) if (getOctCoord(tet, type, triang) != 0) return false; return true; } NLargeInteger NNormalSurfaceVector::isCentral(NTriangulation* triang) const { unsigned long nTets = triang->getNumberOfTetrahedra(); unsigned long tet; int type; NLargeInteger tot, tetTot; for (tet = 0; tet < nTets; tet++) { tetTot = 0L; for (type = 0; type < 4; type++) tetTot += getTriangleCoord(tet, type, triang); for (type = 0; type < 3; type++) tetTot += getQuadCoord(tet, type, triang); for (type = 0; type < 3; type++) tetTot += getOctCoord(tet, type, triang); if (tetTot > 1) return NLargeInteger::zero; tot += tetTot; } return tot; } bool NNormalSurface::isEmpty() const { unsigned long nTet = triangulation->getNumberOfTetrahedra(); bool checkAlmostNormal = vector->allowsAlmostNormal(); unsigned long t; int i; for (t = 0; t < nTet; ++t) { for (i = 0; i < 4; ++i) if (getTriangleCoord(t, i) != 0) return false; for (i = 0; i < 3; ++i) if (getQuadCoord(t, i) != 0) return false; if (checkAlmostNormal) for (i = 0; i < 3; ++i) if (getOctCoord(t, i) != 0) return false; } return true; } bool NNormalSurface::sameSurface(const NNormalSurface& other) const { unsigned long nTet = triangulation->getNumberOfTetrahedra(); bool checkAlmostNormal = (vector->allowsAlmostNormal() || other.vector->allowsAlmostNormal()); unsigned long t; int i; for (t = 0; t < nTet; ++t) { for (i = 0; i < 4; ++i) if (getTriangleCoord(t, i) != other.getTriangleCoord(t, i)) return false; for (i = 0; i < 3; ++i) if (getQuadCoord(t, i) != other.getQuadCoord(t, i)) return false; if (checkAlmostNormal) for (i = 0; i < 3; ++i) if (getOctCoord(t, i) != other.getOctCoord(t, i)) return false; } return true; } bool NNormalSurface::locallyCompatible(const NNormalSurface& other) const { unsigned long nTets = triangulation->getNumberOfTetrahedra(); int type; int found; for (unsigned long tet = 0; tet < nTets; ++tet) { found = 0; for (type = 0; type < 3; ++type) if (getQuadCoord(tet, type) > 0 || other.getQuadCoord(tet, type) > 0) ++found; for (type = 0; type < 3; ++type) if (getOctCoord(tet, type) > 0 || other.getOctCoord(tet, type) > 0) ++found; if (found > 1) return false; } return true; } void NNormalSurface::calculateOctPosition() const { if (! vector->allowsAlmostNormal()) { octPosition = NDiscType::NONE; return; } unsigned long tetIndex; int type; for (tetIndex = 0; tetIndex < triangulation->getNumberOfTetrahedra(); ++tetIndex) for (type = 0; type < 3; ++type) if (getOctCoord(tetIndex, type) != 0) { octPosition = NDiscType(tetIndex, type); return; } octPosition = NDiscType::NONE; return; } void NNormalSurface::calculateEulerCharacteristic() const { unsigned long index, tot; int type; NLargeInteger ans = NLargeInteger::zero; // Add vertices. tot = triangulation->getNumberOfEdges(); for (index = 0; index < tot; index++) ans += getEdgeWeight(index); // Subtract edges. tot = triangulation->getNumberOfTriangles(); for (index = 0; index < tot; index++) for (type = 0; type < 3; type++) ans -= getTriangleArcs(index, type); // Add faces. tot = triangulation->getNumberOfTetrahedra(); for (index = 0; index < tot; index++) { for (type=0; type<4; type++) ans += getTriangleCoord(index, type); for (type=0; type<3; type++) ans += getQuadCoord(index, type); for (type=0; type<3; type++) ans += getOctCoord(index, type); } // Done! eulerChar = ans; } void NNormalSurface::calculateRealBoundary() const { if (triangulation->isClosed()) { realBoundary = false; return; } unsigned long index; unsigned long tot = triangulation->getNumberOfTetrahedra(); NTetrahedron* tet; int type, face; for (index = 0; index < tot; index++) { tet = triangulation->getTetrahedron(index); if (tet->hasBoundary()) { // Check for disk types with boundary for (type=0; type<3; type++) { if (getQuadCoord(index, type) > 0) { realBoundary = true; return; } } for (type=0; type<3; type++) { if (getOctCoord(index, type) > 0) { realBoundary = true; return; } } for (type=0; type<4; type++) if (getTriangleCoord(index, type) > 0) { // Make sure the triangle actually hits the // boundary. for (face=0; face<4; face++) { if (face == type) continue; if (tet->adjacentTetrahedron(face) == 0) { realBoundary = true; return; } } } } } realBoundary = false; } #ifndef EXCLUDE_SNAPPEA NMatrixInt* NNormalSurface::boundarySlopes() const { NTriangulation *tri = getTriangulation(); // Check the preconditions. if (! tri->isOriented()) return 0; if (vector->allowsAlmostNormal()) return 0; for (NTriangulation::VertexIterator it = tri->getVertices().begin(); it != tri->getVertices().end(); ++it) { if (! (*it)->isIdeal()) return 0; if (! (*it)->isLinkOrientable()) return 0; if ((*it)->getLinkEulerCharacteristic() != 0) return 0; } NSnapPeaTriangulation snapPea(*tri, false); NMatrixInt* equations = snapPea.slopeEquations(); if (! equations) return 0; // Check that snappea hasn't changed the triangulation. // It shouldn't, but if it does then the matrix we obtain cannot be used. if (! snapPea.verifyTriangulation(*tri)) { delete equations; return 0; } unsigned long cusps = equations->rows() / 2; unsigned long numTet = tri->getNumberOfTetrahedra(); NMatrixInt* slopes = new NMatrixInt(cusps, 2); for(unsigned int i=0; i < cusps; i++) { NLargeInteger meridian; // constructor sets this to 0 NLargeInteger longitude; // constructor sets this to 0 for(unsigned int j=0; j < numTet; j++) { meridian += equations->entry(2*i, 3*j)*getQuadCoord(j,vertexSplit[0][1]) + equations->entry(2*i, 3*j+1)*getQuadCoord(j,vertexSplit[0][2]) + equations->entry(2*i, 3*j+2)*getQuadCoord(j,vertexSplit[0][3]); longitude += equations->entry(2*i+1, 3*j)*getQuadCoord(j,vertexSplit[0][1]) + equations->entry(2*i+1, 3*j+1)*getQuadCoord(j,vertexSplit[0][2]) + equations->entry(2*i+1, 3*j+2)*getQuadCoord(j,vertexSplit[0][3]); } slopes->entry(i,0) = meridian; slopes->entry(i,1) = longitude; } delete equations; return slopes; } #endif // EXCLUDE_SNAPPEA void NNormalSurface::writeXMLData(std::ostream& out) const { using regina::xml::xmlEncodeSpecialChars; using regina::xml::xmlValueTag; // Write the opening tag including vector length. size_t vecLen = vector->size(); out << " "; // Write all non-zero entries. NLargeInteger entry; for (size_t i = 0; i < vecLen; i++) { entry = (*vector)[i]; if (entry != 0) out << ' ' << i << ' ' << entry; } // Write properties. if (eulerChar.known()) out << "\n\t" << xmlValueTag("euler", eulerChar.value()); if (orientable.known()) out << "\n\t" << xmlValueTag("orbl", orientable.value()); if (twoSided.known()) out << "\n\t" << xmlValueTag("twosided", twoSided.value()); if (connected.known()) out << "\n\t" << xmlValueTag("connected", connected.value()); if (realBoundary.known()) out << "\n\t" << xmlValueTag("realbdry", realBoundary.value()); if (compact.known()) out << "\n\t" << xmlValueTag("compact", compact.value()); // Write the closing tag. out << " \n"; } // Default implementations for oriented surfaces. Returns zero as any // coordinate system which supports orientation should override these. NLargeInteger NNormalSurfaceVector::getOrientedTriangleCoord( unsigned long, int, NTriangulation*, bool) const { return NLargeInteger::zero; }; NLargeInteger NNormalSurfaceVector::getOrientedQuadCoord( unsigned long, int, NTriangulation*, bool) const { return NLargeInteger::zero; }; } // namespace regina regina-4.95/engine/surfaces/nnormalsurface.h000644 000765 000024 00000237716 12237710474 021106 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nnormalsurface.h * \brief Deals with an individual normal surface in a 3-manifold * triangulation. */ #ifndef __NNORMALSURFACE_H #ifndef __DOXYGEN #define __NNORMALSURFACE_H #endif #include "regina-config.h" // For EXCLUDE_SNAPPEA #include #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" #include "maths/nray.h" #include "surfaces/ndisctype.h" #include "surfaces/normalcoords.h" #include "utilities/nbooleans.h" #include "utilities/nproperty.h" namespace regina { /** * \addtogroup surfaces Normal Surfaces * Normal surfaces in 3-manifold triangulations. * @{ */ /** * Lists which vertex splits separate which pairs of vertices. * There are three vertex splits, numbered 0,1,2. Each vertex * split separates the four tetrahedron vertices 0,1,2,3 into two pairs. * vertexSplit[i][j] is the number of the vertex split that * keeps vertices i and j together. * * It is guaranteed that vertex split \a i will keep the vertices of * edge \a i together (and will therefore also keep the vertices of edge * \a 5-i together). */ REGINA_API extern const int vertexSplit[4][4]; /** * Lists which vertex splits meet which edges. * See ::vertexSplit for details on what a vertex split is. * vertexSplitMeeting[i][j][0,1] are the numbers of the two * vertex splits that meet the edge joining tetrahedron vertices * i and j. */ REGINA_API extern const int vertexSplitMeeting[4][4][2]; /** * Lists the vertices which each vertex split splits. * See ::vertexSplit for details on what a vertex split is. * Vertex split number \c i splits the vertex pairs * vertexSplitDefn[i][0,1] and * vertexSplitDefn[i][2,3]. * * It is guaranteed that: * * - vertexSplitDefn[i][0] < vertexSplitDefn[i][1]; * * - vertexSplitDefn[i][2] < vertexSplitDefn[i][3]; * * - vertexSplitDefn[i][0] < vertexSplitDefn[i][2]. */ REGINA_API extern const int vertexSplitDefn[3][4]; /** * Lists the second vertex with which each vertex is paired under each * vertex split. * See ::vertexSplit for details on what a vertex split is. * Vertex split number \c i pairs vertex \c v with * vertex vertexSplitPartner[i][v]. */ REGINA_API extern const int vertexSplitPartner[3][4]; /** * Contains strings describing which vertices each vertex split splits. * See ::vertexSplit for details on what a vertex split is. * The string describing vertex split number \c i is * vertexSplitString[i] and is of the form 02/13, * which in this case is the vertex split that splits vertices 0,2 from * vertices 1,3. */ REGINA_API extern const char vertexSplitString[3][6]; /** * Lists in consecutive order the directed normal arcs that form the * boundary of each type of triangular normal disc. Each permutation \a p * represents an arc about vertex p[0] parallel to the directed * edge from p[1] to p[2]. * * Array triDiscArcs[i] lists the boundary arcs of the * triangular disc of type i. See NNormalSurface::getTriangleCoord() * for further details. * * Note that every permutation in this array is even. * * \ifacescpp This array is replaced by a macro * triDiscArcs(discType, arcIndex) that essentially looks up * the corresponding array. This is necessary because of a bug in gcc 2.95. */ #ifdef __DOXYGEN REGINA_API extern const NPerm4 triDiscArcs[4][3]; #else REGINA_API extern const NPerm4 __triDiscArcs[12]; #define triDiscArcs(i, j) __triDiscArcs[(3 * (i)) + (j)] #endif /** * Lists in consecutive order the directed normal arcs that form the * boundary of each type of quadrilateral normal disc. Each permutation \a p * represents an arc about vertex p[0] parallel to the directed * edge from p[1] to p[2]. * * Array quadDiscArcs[i] lists the boundary arcs of the * quadrilateral disc of type i. See NNormalSurface::getQuadCoord() * for further details. * * Note that permutation quadDiscArcs[i][j] will be even * precisely when j is even. * * \ifacescpp This array is replaced by a macro * quadDiscArcs(discType, arcIndex) that essentially looks up * the corresponding array. This is necessary because of a bug in gcc 2.95. */ #ifdef __DOXYGEN REGINA_API extern const NPerm4 quadDiscArcs[3][4]; #else REGINA_API extern const NPerm4 __quadDiscArcs[12]; #define quadDiscArcs(i, j) __quadDiscArcs[(4 * (i)) + (j)] #endif /** * Lists in consecutive order the directed normal arcs that form the * boundary of each type of octagonal normal disc. Each permutation \a p * represents an arc about vertex p[0] parallel to the directed * edge from p[1] to p[2]. * * Array octDiscArcs[i] lists the boundary arcs of the * octagonal disc of type i. See NNormalSurface::getOctCoord() * for further details. * * Note that permutation octDiscArcs[i][j] will be even * precisely when j is 0, 1, 4 or 5. * * \ifacescpp This array is replaced by a macro * octDiscArcs(discType, arcIndex) that essentially looks up * the corresponding array. This is necessary because of a bug in gcc 2.95. */ #ifdef __DOXYGEN REGINA_API extern const NPerm4 octDiscArcs[3][8]; #else REGINA_API extern const NPerm4 __octDiscArcs[24]; #define octDiscArcs(i, j) __octDiscArcs[(8 * (i)) + (j)] #endif class NEnumConstraintList; class NTriangulation; class NEdge; class NVertex; class NXMLNormalSurfaceReader; class NMatrixInt; /** * A template that stores information about a particular * normal coordinate system. Much of this information is given in the * form of compile-time constants and types. * * To iterate through cases for a given value of NormalCoords that is not * known until runtime, see the various forCoords() routines defined in * coordregistry.h. * * This NormalInfo template should only be defined for \a coordType * arguments that represent coordinate systems in which you can create and * store normal surfaces. * * At a bare minimum, each specialisation of this template must provide: * * - a typedef \a Class that represents the corresponding * NNormalSurfaceVector subclass; * - typedefs \a Standard and \a Reduced that identify NormalInfo templates * for the corresponding coordinate systems with and without triangles * (if this is not meaningful then both typedefs should just identify this * system); * - enum constants \a almostNormal, \a spun and \a oriented, which indicate * whether the coordinate system allows almost normal, spun and/or * transversely oriented surfaces; * - a static function name() that returns a C-style string giving the * human-readable name of the coordinate system. * * \ifacespython Not present. */ template struct NormalInfo; /** * Defines various constants, types and virtual functions for a subclass * of NNormalSurfaceVector. * * Every subclass of NNormalSurfaceVector \a must include * REGINA_NORMAL_SURFACE_FLAVOUR at the beginning of the class definition. * * This macro provides the class with: * * - a compile-time enum constant \a coordType, which is equal to the * corresponding NormalCoords constant; * - a typedef \a Info, which refers to the corresponding specialisation * of the NormalInfo<> template; * - declarations and implementations of the virtual functions * NNormalSurfaceVector::clone(), * NNormalSurfaceVector::allowsAlmostNormal(), * NNormalSurfaceVector::allowsSpun(), and * NNormalSurfaceVector::allowsOriented(). * * @param class_ the name of this subclass of NNormalSurfaceVector. * @param id the corresponding NNormalCoords constant. */ #define REGINA_NORMAL_SURFACE_FLAVOUR(class_, id) \ public: \ typedef NormalInfo Info; \ enum { coordType = id }; \ inline virtual NNormalSurfaceVector* clone() const { \ return new class_(*this); \ } \ inline virtual bool allowsAlmostNormal() const { \ return Info::almostNormal; \ } \ inline virtual bool allowsSpun() const { \ return Info::spun; \ } \ inline virtual bool allowsOriented() const { \ return Info::oriented; \ } /** * Stores the vector of a single normal surface in a 3-manifold. * The different subclasses of NNormalSurfaceVector use different * underlying coordinate systems for the normal solution space. * However, the various coordinate retrieval routines will return values * that are independent of the underlying coordinate system. Thus the * coordinates of the normal surface in any coordinate system can be * determined without knowledge of the specific underlying coordinate * system being used. * * Note that if a mirrored vector class is being used (see * NNormalSurfaceVectorMirrored), the vector may not change once * the first coordinate lookup routine (such as getTriangleCoord() and * the like) has been called. See * NNormalSurfaceVectorMirrored for further explanation. * * Note that non-compact surfaces (surfaces with infinitely many discs, * such as spun-normal surfaces) are allowed; in these cases, the * corresponding coordinate lookup routines should return * NLargeInteger::infinity where appropriate. * * All subclasses of NNormalSurfaceVector must have the following * properties: * * - Normal surfaces can be enumerated by intersecting the non-negative * orthant of the underlying vector space with some linear subspace; * * - Multiplying a normal surface by \a k corresponds to multiplying * the underlying vector by \a k for any non-negative integer \a k. * * When deriving classes from NNormalSurfaceVector: *
    *
  • A new value must must be added to the NormalCoords enum in * normalcoords.h to represent the new coordinate system.
  • *
  • The file coordregistry-impl.h must be updated to reflect the new * coordinate system (the file itself contains instructions * on how to do this).
  • *
  • A corresponding specialisation of NormalInfo<> must be * defined, typically in the same header as the new vector subclass.
  • *
  • The macro REGINA_NORMAL_SURFACE_FLAVOUR must be added to the * beginning of the new vector subclass. This will declare and define * various constants, typedefs and virtual functions (see the * REGINA_NORMAL_SURFACE_FLAVOUR macro documentation for details).
  • *
  • Constructors class(size_t length) and * class(const NVector& cloneMe) must be * declared and implemented; these will usually just call the * corresponding superclass constructors.
  • *
  • All abstract functions must be implemented, except for those * already provided by REGINA_NORMAL_SURFACE_FLAVOUR. * Note that coordinate functions such as getTriangleCoord() must return the * \e total number of discs of the requested type; if your new coordinate * system adorns discs with extra information (such as orientation) then * your implementation must compute the appropriate sum.
  • *
  • The orientation-specific coordinate functions * getOrientedTriangleCoord() and getOrientedQuadCoord() must be * implemented if your coordinate system supports transverse orientation. * Otherwise you can use the default implementations (which returns zero). *
  • Static public functions void * makeZeroVector(const NTriangulation*), * NMatrixInt* makeMatchingEquations(NTriangulation*) and * makeEmbeddedConstraints(NTriangulation*) must be * declared and implemented.
  • *
* * \testpart * * \todo \optlong Investigate using sparse vectors for storage. * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVector : public NRay { public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVector(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVector(const NVector& cloneMe); /** * A virtual destructor. This is required because here we introduce * virtual functions into the NRay hierarchy. */ virtual ~NNormalSurfaceVector(); /** * Creates a newly allocated clone of this vector. * The clone will be of the same subclass of NNormalSurfaceVector * as this vector. */ virtual NNormalSurfaceVector* clone() const = 0; /** * Determines if the specific underlying coordinate system * allows for almost normal surfaces, that is, allows for * octagonal discs. * * Note that this has nothing to do with whether or not this * specific surface \e contains octagonal discs. * * @return \c true if and only if almost normal surfaces are allowed. */ virtual bool allowsAlmostNormal() const = 0; /** * Determines if the specific underlying coordinate system * allows for spun-normal surfaces; that is, surfaces with * infinitely many triangles. * * Note that this has nothing to do with whether or not this * specific surface \e contains infinitely many triangles. * * @return \c true if and only if spun-normal surfaces are allowed. */ virtual bool allowsSpun() const = 0; /** * Determines if the specific underlying coordinate system * allows for transversely oriented normal surfaces. * * @return \c true if and only if transverse orientations are * supported. */ virtual bool allowsOriented() const = 0; /** * Determines if this normal surface has more than one * octagonal disc. It may be assumed that at most one * octagonal disc \e type exists in this surface. This routine will * return \c true if an octagonal type does exist and its * coordinate is greater than one. * * The default implementation for this routine simply calculates * all the octagonal coordinates and returns as soon as a * positive or negative result can be established. Subclasses * of NNormalSurfaceVector should override this if they can provide a * faster implementation. * * If a subclass does not allow for almost normal surfaces, this * routine will never be called and thus does not need to be * overwritten. * * \pre At most one octagonal disc \e type exists in this surface. * \pre This normal surface vector is using a * coordinate system that allows for almost normal surfaces. * * @param triang the triangulation in which this normal surface lives. * @return \c true if and only if there is an octagonal disc type * present and its coordinate is greater than one. */ virtual bool hasMultipleOctDiscs(NTriangulation* triang) const; /** * Determines if the normal surface represented is compact (has * finitely many discs). * * The default implementation for this routine simply runs * through every disc type until a disc type with infinite disc * count is found or all disc types have been examined. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return \c true if and only if the normal surface represented * is compact. */ virtual bool isCompact(NTriangulation* triang) const; /** * Determines if the normal surface represented is vertex * linking. A vertex linking surface contains only * triangles. * * The default implementation for this routine simply runs * through every non-triangular disc type ensuring that each * has no corresponding discs. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return \c true if and only if the normal surface represented * is vertex linking. */ virtual bool isVertexLinking(NTriangulation* triang) const; /** * Determines if a rational multiple of the normal surface represented * is the link of a single vertex. * * The default implementation for this routine involves counting the * number of discs of every type. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return the vertex linked by this surface, or 0 if this * surface is not the link of a single vertex. */ virtual const NVertex* isVertexLink(NTriangulation* triang) const; /** * Determines if a rational multiple of the normal surface represented * is the thin link of a single edge. * * If there are two different edges e1 and e2 for * which the surface could be expressed as the thin link of * either e1 or e2, the pair * (e1,e2) will be returned. * If the surface is the thin link of only one edge e, * the pair (e,0) will be returned. * If the surface is not the thin link of any edges, the pair * (0,0) will be returned. * * The default implementation for this routine involves counting the * number of discs of every type. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return a pair containing the edge(s) linked by this surface, * as described above. */ virtual std::pair isThinEdgeLink( NTriangulation* triang) const; /** * Determines if the normal surface represented is a splitting * surface in the given triangulation. A \a splitting surface * is a compact surface containing precisely * one quad per tetrahedron and no other normal (or almost * normal) discs. * * The default implementation for this routine simply runs * through and checks the count for each disc type. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return \c true if and only if the normal surface represented * is a splitting surface. */ virtual bool isSplitting(NTriangulation* triang) const; /** * Determines if the normal surface represented is a central * surface in the given triangulation. A \a central surface * is a compact surface containing at most one normal or almost * normal disc per tetrahedron. If the surface is central, the * number of tetrahedra it meets (i.e., the number of discs in * the surface) will be returned. * * The default implementation for this routine simply runs * through and checks the count for each disc type. * Subclasses of NNormalSurfaceVector should override this if * they can provide a faster implementation. * * @param triang the triangulation in which this normal surface lives. * @return the number of tetrahedra that the surface meets if it * is a central surface, or 0 if it is not a central surface. */ virtual NLargeInteger isCentral(NTriangulation* triang) const; /** * Returns the number of triangular discs of the given type in * this normal surface. * See NNormalSurface::getTriangleCoord() for further details. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested triangles reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param vertex the vertex of the given tetrahedron around * which the requested triangles lie; this should be between 0 * and 3 inclusive. * @param triang the triangulation in which this normal surface lives. * @return the number of triangular discs of the given type. */ virtual NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang) const = 0; /** * Returns the number of oriented triangular discs of the given type in * this normal surface. * See NNormalSurface::getOrientedTriangleCoord() for further details. * * The default implementation of this routine returns zero, * which is suitable for coordinate systems that do not support * transverse orientation. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested triangles reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param vertex the vertex of the given tetrahedron around * which the requested triangles lie; this should be between 0 * and 3 inclusive. * @param triang the triangulation in which this normal surface lives. * @param orientation the orientation of the normal discs. * @return the number of triangular discs of the given type. */ virtual NLargeInteger getOrientedTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang, bool orientation) const; /** * Returns the number of quadrilateral discs of the given type * in this normal surface. * See NNormalSurface::getQuadCoord() for further details. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested quadrilaterals reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param quadType the number of the vertex splitting that this * quad type represents; this should be between 0 and 2 * inclusive. * @param triang the triangulation in which this normal surface lives. * @return the number of quadrilateral discs of the given type. */ virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const = 0; /** * Returns the number of oriented quadrilateral discs of the given type * in this normal surface. * See NNormalSurface::getOrientedQuadCoord() for further details. * * The default implementation of this routine returns zero, * which is suitable for coordinate systems that do not support * transverse orientation. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested quadrilaterals reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param quadType the number of the vertex splitting that this * quad type represents; this should be between 0 and 2 * inclusive. * @param triang the triangulation in which this normal surface lives. * @param orientation the orientation of the normal discs. * @return the number of quadrilateral discs of the given type. */ virtual NLargeInteger getOrientedQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang, bool orientation) const; /** * Returns the number of octagonal discs of the given type * in this normal surface. * See NNormalSurface::getOctCoord() for further details. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested octagons reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param octType the number of the vertex splitting that this * octagon type represents; this should be between 0 and 2 * inclusive. * @param triang the triangulation in which this normal surface lives. * @return the number of octagonal discs of the given type. */ virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const = 0; /** * Returns the number of times this normal surface crosses the * given edge. * See NNormalSurface::getEdgeWeight() for further details. * * @param edgeIndex the index in the triangulation of the edge * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfEdges()-1 inclusive. * @param triang the triangulation in which this normal surface lives. * @return the number of times this normal surface crosses the * given edge. */ virtual NLargeInteger getEdgeWeight(unsigned long edgeIndex, NTriangulation* triang) const = 0; /** * Returns the number of arcs in which this normal surface * intersects the given triangle in the given direction. * See NNormalSurface::getTriangleArcs() for further details. * * @param triIndex the index in the triangulation of the triangle * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfTriangles()-1 inclusive. * @param triVertex the vertex of the triangle (0, 1 or 2) around * which the arcs of intersection that we are interested in lie; * only these arcs will be counted. * @param triang the triangulation in which this normal surface lives. * @return the number of times this normal surface intersect the * given triangle with the given arc type. */ virtual NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const = 0; /** * A deprecated alias for getTriangleArcs(). * * This routine returns the number of arcs in which this normal * surface intersects the given triangle in the given direction. * See getTriangleArcs() for further details. * * Since this is an alias only, it is non-virtual and cannot be * overridden. Its implementation simply calls getTriangleArcs(). * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangleArcs() instead. * * @param triIndex the index in the triangulation of the triangle * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfTriangles()-1 inclusive. * @param triVertex the vertex of the triangle (0, 1 or 2) around * which the arcs of intersection that we are interested in lie; * only these arcs will be counted. * @param triang the triangulation in which this normal surface lives. * @return the number of times this normal surface intersect the * given triangle with the given arc type. */ NLargeInteger getFaceArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const; /** * Returns a new normal surface vector of the appropriate length * for the given triangulation and for the coordinate * system corresponding to this subclass of NNormalSurfaceVector. * All elements of the new vector will be initialised to zero. * * See ::makeZeroVector() for further details. * * @param triangulation the triangulation upon which the * underlying coordinate system is based. * @return a new zero vector of the correct class and length. */ #ifdef __DOXYGEN static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); #endif /** * Creates a new set of normal surface matching equations for * the given triangulation using the coordinate * system corresponding to this particular subclass of * NNormalSurfaceVector. * * See ::makeMatchingEquations() for further details. * * @param triangulation the triangulation upon which these * matching equations will be based. * @return a newly allocated set of matching equations. */ #ifdef __DOXYGEN static NMatrixInt* makeMatchingEquations( NTriangulation* triangulation); #endif /** * Creates a new set of validity constraints representing * the condition that normal surfaces be embedded. The * validity constraints will be expressed relative to the * coordinate system corresponding to this particular * subclass of NNormalSurfaceVector. * * @param triangulation the triangulation upon which these * validity constraints will be based. * @return a newly allocated set of constraints. */ #ifdef __DOXYGEN static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); #endif }; /** * Represents a single normal surface in a 3-manifold. * Once the underlying triangulation changes, this normal surface object * is no longer valid. * * The information provided by the various query methods is independent * of the underlying coordinate system being used. * See the NNormalSurfaceVector class notes for details of what to do * when introducing a new coordinate system. * * Note that non-compact surfaces (surfaces with infinitely many discs, * such as spun-normal surfaces) are allowed; in these cases, the * corresponding coordinate lookup routines will return * NLargeInteger::infinity where appropriate. * * \testpart * * \todo \feature Calculation of Euler characteristic and orientability * for non-compact surfaces. * \todo \featurelong Determine which faces in the solution space a * normal surface belongs to. */ class REGINA_API NNormalSurface : public ShareableObject { protected: NNormalSurfaceVector* vector; /**< Contains the coordinates of the normal surface in whichever * space is appropriate. */ NTriangulation* triangulation; /**< The triangulation in which this normal surface resides. */ std::string name; /**< An optional name associated with this surface. */ mutable NProperty octPosition; /**< The position of the first non-zero octagonal coordinate, or NDiscType::NONE if there is no non-zero octagonal coordinate. Here NDiscType::type is an octagon type between 0 and 2 inclusive. */ mutable NProperty eulerChar; /**< The Euler characteristic of this surface. */ mutable NProperty orientable; /**< Is this surface orientable? */ mutable NProperty twoSided; /**< Is this surface two-sided? */ mutable NProperty connected; /**< Is this surface connected? */ mutable NProperty realBoundary; /**< Does this surface have real boundary (i.e. does it meet * any boundary triangles)? */ mutable NProperty compact; /**< Is this surface compact (i.e. does it only contain * finitely many discs)? */ public: /** * Creates a new normal surface inside the given triangulation * with the given coordinate vector. * * \pre The given coordinate vector represents a * normal surface inside the given triangulation. * \pre The given coordinate vector cannot be the null pointer. * * \ifacespython Not present. * * @param triang the triangulation in which this normal surface resides. * @param newVector a vector containing the coordinates of the * normal surface in whichever space is appropriate. */ NNormalSurface(NTriangulation* triang, NNormalSurfaceVector* newVector); /** * A Python-only routine that creates a new normal surface * inside the given triangulation with the given coordinate vector. * * \pre The given coordinate system is one in which Regina is * able to enumerate and store normal surfaces (not a system * like ::NS_EDGE_WEIGHT, which is for viewing purposes only). * \pre The given coordinate vector represents a normal surface * inside the given triangulation (in particular, it satisfies the * relevant system of matching equations). This will not be checked, * and things \e will go wrong if you break it. * * \ifacescpp Not available; this routine is for Python only. * * @param triang the triangulation in which this normal surface resides. * @param coordSystem the coordinate system used by this normal surface. * @param allCoords the corresponding vector of normal coordinates, * expressed as a Python list. The list elements will be * converted internally to NLargeInteger objects. */ #ifdef __DOXYGEN NNormalSurface(NTriangulation* triang, NormalCoords coordSystem, List allCoords); #endif /** * Destroys this normal surface. * The underlying vector of coordinates will also be * deallocated. */ virtual ~NNormalSurface(); /** * Creates a newly allocated clone of this normal surface. * * The name of the normal surface will \e not be copied to the * clone; instead the clone will have an empty name. * * @return a clone of this normal surface. */ NNormalSurface* clone() const; /** * Creates a newly allocated surface that is the double of this * surface. * * @return the double of this normal surface. */ NNormalSurface* doubleSurface() const; /** * Returns the number of triangular discs of the given type in * this normal surface. * A triangular disc type is identified by specifying a * tetrahedron and a vertex of that tetrahedron that the * triangle surrounds. * * If you are using a coordinate system that adorns discs with * additional information (such as orientation), this routine * returns the \e total number of triangles in the given * tetrahedron of the given type. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested triangles reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param vertex the vertex of the given tetrahedron around * which the requested triangles lie; this should be between 0 * and 3 inclusive. * @return the number of triangular discs of the given type. */ NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex) const; /** * Returns the number of oriented triangular discs of the given type * in this normal surface. * * This routine is for coordinate systems that support * transversely oriented normal surfaces; for details see * "The Thurston norm via normal surfaces", Stephan Tillmann and * Daryl Cooper, Pacific Journal of Mathematics 239 (2009), 1-15. * * An oriented triangular disc type is identified by specifying a * tetrahedron, a vertex of that tetrahedron that the * triangle surrounds, and a boolean orientation. The \c true * orientation indicates a transverse orientation pointing to the * nearby vertex, and the \c false orientation indicates a * transverse orientation pointing to the opposite face. * * If the underlying coordinate system does not support transverse * orientation, this routine will simply return zero. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested triangles reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param vertex the vertex of the given tetrahedron around * which the requested triangles lie; this should be between 0 * and 3 inclusive. * @param orientation the orientation of the triangle * @return the number of triangular discs of the given type. */ NLargeInteger getOrientedTriangleCoord(unsigned long tetIndex, int vertex, bool orientation) const; /** * Returns the number of oriented quadrilateral discs of the given * type in this normal surface. * A quadrilateral disc type is identified by specifying a * tetrahedron and a vertex splitting of that tetrahedron that * describes how the quadrilateral partitions the tetrahedron * vertices. See ::vertexSplit for more details on vertex splittings. * * If you are using a coordinate system that adorns discs with * additional information (such as orientation), this routine * returns the \e total number of quadrilaterals in the given * tetrahedron of the given type. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested quadrilaterals reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param quadType the number of the vertex splitting that this * quad type represents; this should be between 0 and 2 * inclusive. * @return the number of quadrilateral discs of the given type. */ NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType) const; /** * Returns the number of oriented quadrilateral discs of the given type * in this normal surface. * * This routine is for coordinate systems that support * transversely oriented normal surfaces; for details see * "The Thurston norm via normal surfaces", Stephan Tillmann and * Daryl Cooper, Pacific Journal of Mathematics 239 (2009), 1-15. * * An oriented triangular disc type is identified by specifying a * tetrahedron, a vertex of that tetrahedron that the * triangle surrounds, and a boolean orientation. The \c true * orientation indicates a triangle whose "transverse" orientation * points to the nearby vertex, and the \c false orientation * indicates a triangle whose "transverse" orientation points to * the opposite face. * * An oriented quadrilateral disc type is identified by specifying a * tetrahedron, a vertex splitting of that tetrahedron as * described in getQuadCoord(), and a boolean orientation. * The \c true orientation indicates a transverse orientation * pointing to the edge containing vertex 0 of the tetrahedron, * and the \c false orientation indicates a transverse * orientation pointing to the opposite edge. * * If the underlying coordinate system does not support transverse * orientation, this routine will simply return zero. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested quadrilaterals reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param quadType the number of the vertex splitting that this * quad type represents; this should be between 0 and 2 * inclusive. * @param orientation the orientation of the quadrilateral disc * @return the number of quadrilateral discs of the given type. */ NLargeInteger getOrientedQuadCoord(unsigned long tetIndex, int quadType, bool orientation) const; /** * Returns the number of octagonal discs of the given type * in this normal surface. * An octagonal disc type is identified by specifying a * tetrahedron and a vertex splitting of that tetrahedron that * describes how the octagon partitions the tetrahedron * vertices. See ::vertexSplit for more details on vertex splittings. * * If you are using a coordinate system that adorns discs with * additional information (such as orientation), this routine * returns the \e total number of octagons in the given * tetrahedron of the given type. * * @param tetIndex the index in the triangulation of the * tetrahedron in which the requested octagons reside; * this should be between 0 and * NTriangulation::getNumberOfTetrahedra()-1 inclusive. * @param octType the number of the vertex splitting that this * octagon type represents; this should be between 0 and 2 * inclusive. * @return the number of octagonal discs of the given type. */ NLargeInteger getOctCoord(unsigned long tetIndex, int octType) const; /** * Returns the number of times this normal surface crosses the * given edge. * * @param edgeIndex the index in the triangulation of the edge * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfEdges()-1 inclusive. * @return the number of times this normal surface crosses the * given edge. */ NLargeInteger getEdgeWeight(unsigned long edgeIndex) const; /** * Returns the number of arcs in which this normal surface * intersects the given triangle in the given direction. * * @param triIndex the index in the triangulation of the triangle * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfTriangles()-1 inclusive. * @param triVertex the vertex of the triangle (0, 1 or 2) around * which the arcs of intersection that we are interested in lie; * only these arcs will be counted. * @return the number of times this normal surface intersect the * given triangle with the given arc type. */ NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex) const; /** * A deprecated alias for getTriangleArcs(). * * This routine returns the number of arcs in which this normal * surface intersects the given triangle in the given direction. * See getTriangleArcs() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangleArcs() instead. * * @param triIndex the index in the triangulation of the triangle * in which we are interested; this should be between 0 and * NTriangulation::getNumberOfTriangles()-1 inclusive. * @param triVertex the vertex of the triangle (0, 1 or 2) around * which the arcs of intersection that we are interested in lie; * only these arcs will be counted. * @return the number of times this normal surface intersect the * given triangle with the given arc type. */ NLargeInteger getFaceArcs(unsigned long triIndex, int triVertex) const; /** * Determines the first coordinate position at which this surface * has a non-zero octagonal coordinate. In other words, if this * routine returns the disc type \a t, then the octagonal * coordinate returned by getOctCoord(t.tetIndex, t.type) is non-zero. * Here NDiscType::type represents an octagon type within a * tetrahedron, and takes values between 0 and 2 inclusive. * * If this surface does not contain any octagons, this routine * returns NDiscType::NONE instead. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. Moreover, if the underlying coordinate * system does not support almost normal surfaces, then even the * first call is fast (it returns NDiscType::NONE immediately). * * @return the position of the first non-zero octagonal coordinate, * or NDiscType::NONE if there is no such coordinate. */ NDiscType getOctPosition() const; /** * Returns the number of coordinates in the specific underlying * coordinate system being used. * * @return the number of coordinates. */ size_t getNumberOfCoords() const; /** * Returns the triangulation in which this normal surface * resides. * * @return the underlying triangulation. */ NTriangulation* getTriangulation() const; /** * Returns the name associated with this normal surface. * Names are optional and need not be unique. * The default name for a surface is the empty string. * * @return the name of associated with this surface. */ const std::string& getName() const; /** * Sets the name associated with this normal surface. * Names are optional and need not be unique. * The default name for a surface is the empty string. * * @param newName the new name to associate with this surface. */ void setName(const std::string& newName); /** * The text representation will be in standard triangle-quad-oct * coordinates. Octagonal coordinates will only be written if * the surface is stored using a coordinate system that supports * almost normal surfaces. * * \ifacespython The paramater \a out does not exist, and is * taken to be standard output. */ void writeTextShort(std::ostream& out) const; /** * Writes the underlying coordinate vector to the given output * stream in text format. * No indication will be given as to which coordinate * system is being used or what each coordinate means. * No newline will be written. * * \ifacespython The paramater \a out does not exist, and is * taken to be standard output. * * @param out the output stream to which to write. */ void writeRawVector(std::ostream& out) const; /** * Writes a chunk of XML containing this normal surface and all * of its properties. This routine will be called from within * NNormalSurfaceList::writeXMLPacketData(). * * \ifacespython Not present. * * @param out the output stream to which the XML should be written. */ virtual void writeXMLData(std::ostream& out) const; /** * Determines if this normal surface is empty (has no discs * whatsoever). */ bool isEmpty() const; /** * Determines if this normal surface is compact (has * finitely many discs). * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * @return \c true if and only if this normal surface is compact. */ bool isCompact() const; /** * Returns the Euler characteristic of this surface. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * \pre This normal surface is compact (has finitely many discs). * * @return the Euler characteristic. */ NLargeInteger getEulerCharacteristic() const; /** * Returns whether or not this surface is orientable. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * \pre This normal surface is compact (has finitely many discs). * * \warning This routine explicitly builds the normal discs, * and so may run out of memory if the normal coordinates * are extremely large. * * @return \c true if this surface is orientable, or \c false if * this surface is non-orientable. */ bool isOrientable() const; /** * Returns whether or not this surface is two-sided. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * \pre This normal surface is compact (has finitely many discs). * * \warning This routine explicitly builds the normal discs, * and so may run out of memory if the normal coordinates * are extremely large. * * @return \c true if this surface is two-sided, or \c false if * this surface is one-sided. */ bool isTwoSided() const; /** * Returns whether or not this surface is connected. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * \pre This normal surface is compact (has finitely many discs). * * \warning This routine explicitly builds the normal discs, * and so may run out of memory if the normal coordinates * are extremely large. * * @return \c true if this surface is connected, or \c false if * this surface is disconnected. */ bool isConnected() const; /** * Determines if this surface has any real boundary, that is, * whether it meets any boundary triangles of the triangulation. * * This routine caches its results, which means that once it has * been called for a particular surface, subsequent calls return * the answer immediately. * * @return \c true if and only if this surface has real boundary. */ bool hasRealBoundary() const; /** * Determines whether or not this surface is vertex linking. * A vertex linking surface contains only triangles. * * Note that the results of this routine are not cached. * Thus the results will be reevaluated every time this routine is * called. * * \todo \opt Cache results. * * @return \c true if and only if this surface is vertex linking. */ bool isVertexLinking() const; /** * Determines whether or not a rational multiple of this surface * is the link of a single vertex. * * Note that the results of this routine are not cached. * Thus the results will be reevaluated every time this routine is * called. * * \todo \opt Cache results. * * @return the vertex linked by this surface, or 0 if this * surface is not the link of a single vertex. */ virtual const NVertex* isVertexLink() const; /** * Determines whether or not a rational multiple of this surface * is the thin link of a single edge. * * If there are two different edges e1 and e2 for * which this surface could be expressed as the thin link of * either e1 or e2, the pair * (e1,e2) will be returned. * If this surface is the thin link of only one edge e, * the pair (e,0) will be returned. * If this surface is not the thin link of any edges, the pair * (0,0) will be returned. * * Note that the results of this routine are not cached. * Thus the results will be reevaluated every time this routine is * called. * * \todo \opt Cache results. * * \ifacespython This routine returns a tuple of size 2. * * @return a pair containing the edge(s) linked by this surface, * as described above. */ virtual std::pair isThinEdgeLink() const; /** * Determines whether or not this surface is a splitting surface. * A \a splitting surface is a compact surface containing * precisely one quad per tetrahedron and no other normal (or * almost normal) discs. * * Note that the results of this routine are not cached. * Thus the results will be reevaluated every time this routine is * called. * * \todo \opt Cache results. * * @return \c true if and only if this is a splitting surface. */ bool isSplitting() const; /** * Determines whether or not this surface is a central surface. * A \a central surface is a compact surface containing * at most one normal or almost normal disc per tetrahedron. * If this surface is central, the number of tetrahedra that it meets * (i.e., the number of discs in the surface) will be returned. * * Note that the results of this routine are not cached. * Thus the results will be reevaluated every time this routine is * called. * * \todo \opt Cache results. * * @return the number of tetrahedra that this surface meets if it * is a central surface, or 0 if it is not a central surface. */ NLargeInteger isCentral() const; /** * Determines whether this surface represents a compressing disc * in the underlying 3-manifold. * * Let this surface be \a D and let the underlying 3-manifold * be \a M with boundary \a B. To be a compressing disc, \a D must * be a properly embedded disc in \a M, and the boundary of \a D * must not bound a disc in \a B. * * The implementation of this routine is somewhat inefficient at * present, since it cuts along the disc, retriangulates and then * examines the resulting boundary components. * * \pre This normal surface is compact and embedded. * \pre This normal surface contains no octagonal discs. * * \todo \opt Reimplement this to avoid cutting along surfaces. * \todo \prob Check for absurdly large numbers of discs and bail * accordingly. * * \warning This routine might cut along the surface and * retriangulate, and so may run out of memory if the normal * coordinates are extremely large. * * @param knownConnected \c true if this normal surface is * already known to be connected (for instance, if it came from * an enumeration of vertex normal surfaces), or \c false if * we should not assume any such information about this surface. * @return \c true if this surface is a compressing disc, or \c false if * this surface is not a compressing disc. */ bool isCompressingDisc(bool knownConnected = false) const; /** * Determines whether this is an incompressible surface within * the surrounding 3-manifold. At present, this routine is only * implemented for surfaces embedded within \e closed and * \e irreducible 3-manifold triangulations. * * Let \a D be some disc embedded in the underlying 3-manifold, * and let \a B be the boundary of \a D. We call \a D a * compressing disc for this surface if (i) the intersection * of \a D with this surface is the boundary \a B, and * (ii) although \a B bounds a disc within the 3-manifold, it * does not bound a disc within this surface. * * We declare this surface to be \e incompressible if there are * no such compressing discs. For our purposes, spheres are never * considered incompressible (so if this surface is a sphere then * this routine will always return \c false). * * This test is designed exclusively for two-sided surfaces. * If this surface is one-sided, the incompressibility test will * be run on its two-sided double cover. * * \warning This routine may in some circumstances be extremely slow. * This is because the underlying algorithm cuts along this surface, * retriangulates (possibly using a very large number of tetrahedra), * and then searches for a normal compressing disc in each * component of the cut-open triangulation. * * \pre The underlying triangulation is valid and closed, and * represents an irreducible 3-manifold. * \pre This normal surface is compact, embedded and connected, * and contains no octagonal discs. * * @return \c true if this surface is incompressible, or \c false if * this surface is not incompressible (or if it is a sphere). */ bool isIncompressible() const; /** * Cuts the associated triangulation along this surface and * returns a newly created resulting triangulation. * The original triangulation is not changed. * * Note that, unlike crushing a surface to a point, this * operation will not change the topology of the underlying * 3-manifold beyond simply slicing along this surface. * * \warning The number of tetrahedra in the new triangulation * can be very large. * * \pre This normal surface is compact and embedded. * \pre This normal surface contains no octagonal discs. * * @return a pointer to the newly allocated resulting * triangulation. */ NTriangulation* cutAlong() const; /** * Crushes this surface to a point in the associated * triangulation and returns a newly created resulting * triangulation. The original triangulation is not changed. * * Crushing the surface will produce a number of tetrahedra, * triangular pillows and/or footballs. The pillows and * footballs will then be flattened to triangles and edges * respectively (resulting in the possible * changes mentioned below) to produce a proper triangulation. * * Note that the new triangulation will have at most the same * number of tetrahedra as the old triangulation, and will have * strictly fewer tetrahedra if this surface is not vertex * linking. * * The act of flattening pillows and footballs as described * above can lead to unintended topological side-effects, beyond * the effects of merely cutting along this surface and * identifying the new boundary surface(s) to points. * Examples of these unintended side-effects can include * connected sum decompositions, removal of 3-spheres and * small Lens spaces and so on; a full list of possible changes * is beyond the scope of this API documentation. * * \warning This routine can have unintended topological * side-effects, as described above. * \warning In exceptional cases with non-orientable * 3-manifolds, these side-effects might lead to invalid edges * (edges whose midpoints are projective plane cusps). * * \pre This normal surface is compact and embedded. * \pre This normal surface contains no octagonal discs. * * @return a pointer to the newly allocated resulting * triangulation. */ NTriangulation* crush() const; /** * Determines whether this and the given surface in fact * represent the same normal (or almost normal) surface. * * Specifically, this routine examines (or computes) the number of * normal or almost normal discs of each type, and returns \c true * if and only if these counts are the same for both surfaces. * * It does not matter what coordinate systems the two surfaces * use. In particular, it does not matter if this and the * given surface use different coordinate systems, and it * does not matter if one surface uses an almost normal * coordinate system and the other does not. * * \pre Both this and the given normal surface live within the * same 3-manifold triangulation. * * @param other the surface to be compared with this surface. * @return \c true if both surfaces represent the same normal or * almost normal surface, or \c false if not. */ bool sameSurface(const NNormalSurface& other) const; /** * Determines whether this and the given surface are locally compatible. * Local compatibility means that, within each individual tetrahedron * of the triangulation, it is possible to arrange the normal * discs of both surfaces so that none intersect. * * This is a local constraint, not a global constraint. That is, * we do not insist that we can avoid intersections within all * tetrahedra \e simultaneously. To test the global constraint, * see the (much slower) routine disjoint() instead. * * Local compatibility can be formulated in terms of normal disc types. * Two normal (or almost normal) surfaces are locally compatible if * and only if they together have at most one quadrilateral or * octagonal disc type per tetrahedron. * * Note again that this is a local constraint only. In particular, * for almost normal surfaces, it does \e not insist that there is * at most one octagonal disc type anywhere within the triangulation. * * If one of the two surfaces breaks the local compatibility * constraints on its own (for instance, it contains two different * quadrilateral disc types within the same tetrahedron), then this * routine will return \c false regardless of what the other surface * contains. * * \pre Both this and the given normal surface live within the * same 3-manifold triangulation. * * @param other the other surface to test for local compatibility with * this surface. * @return \c true if the two surfaces are locally compatible, or * \c false if they are not. */ bool locallyCompatible(const NNormalSurface& other) const; /** * Determines whether this and the given surface can be placed * within the surrounding triangulation so that they do not intersect * anywhere at all, without changing either normal isotopy class. * * This is a global constraint, and therefore gives a stronger test * than locallyCompatible(). However, this global constraint is * also much slower to test; the running time is proportional to * the total number of normal discs in both surfaces. * * Note that this routine has a number of preconditions. Most * importantly, it will only work if both this and the given * surface use the \e same coordinate system. * Running this test over two surfaces with different coordinate * systems could give unpredictable results, and might * crash the program entirely. * * \pre Both this and the given normal surface live within the * same 3-manifold triangulation. * \pre Both this and the given normal surface are stored using * the same coordinate system (i.e., the same * subclass of NNormalSurfaceVector). * \pre Both this and the given surface are compact (have * finitely many discs), embedded, non-empty and connected. * * @param other the other surface to test alongside this surface * for potential intersections. * @return \c true if both surfaces can be embedded without * intersecting anywhere, or \c false if this and the given * surface are forced to intersect at some point. */ bool disjoint(const NNormalSurface& other) const; #ifndef EXCLUDE_SNAPPEA /** * Computes the boundary slopes of this surface at each cusp of * the triangulation. This is for use with spun-normal surfaces * (for closed surfaces all boundary slopes are zero). * * The results are returned in a matrix with \a V rows and two * columns, where \a V is the number of vertices in the triangulation. * If row \a i of the matrix contains the integers * \a M and \a L, this indicates that at the ith cusp, * the boundary curves have algebraic intersection number \a M * with the meridian and \a L with the longitude. Equivalently, * the boundary curves pass \a L times around the meridian and * -M times around the longitude. * The rational boundary slope is therefore -L/M, and * there are gcd(L,M) boundary curves with this slope. * * This code makes use of the \e SnapPy kernel, and the choice of * meridian and longitude on each cusp follows \e SnapPy's conventions. * In particular, we use the orientations for meridian and longitude * from \e SnapPy. The orientations of the boundary curves of a * spun-normal surface are chosen so that \e if meridian and * longitude are a positive basis as vieved from the cusp, then * as one travels along an oriented boundary curve, the * spun-normal surface spirals into the cusp to one's right and * down into the manifold to one's left. * * If this triangulation contains more than one vertex, the * rows in the resulting matrix are ordered by vertex number in the * triangulation. * * At present, Regina can only compute boundary slopes if the * triangulation is oriented, if every vertex link in the * triangulation is a torus, and if the underlying coordinate system * is for normal surfaces (not almost normal surfaces). If these * conditions are not met, this routine will return 0. * * @author William Pettersson and Stephan Tillmann * * @return a newly allocated matrix with \a number_of_vertices * rows and two columns as described above, or 0 if the boundary * slopes cannot be computed. */ NMatrixInt* boundarySlopes() const; #endif // EXCLUDE_SNAPPEA /** * Gives read-only access to the raw vector that sits beneath this * normal surface. * * Generally users should not need this function. However, it is * provided here in case the need should arise (e.g., for reasons * of efficiency). * * \warning An NNormalSurface does not know what underlying * coordinate system its raw vector uses. Unless you already know * the coordinate system in advance (i.e., you created the surface * yourself), it is best to keep to the coordinate-system-agnostic * access functions such as NNormalSurfaceVector::getTriangleCoord() * and NNormalSurfaceVector::getQuadCoord(). * * \ifacespython Not present. * * @return the underlying raw vector. */ const NNormalSurfaceVector* rawVector() const; /** * Searches for a non-vertex-linking normal 2-sphere within the * given triangulation. If a non-vertex linking normal 2-sphere * exists anywhere at all within the triangulation, then this routine * is guaranteed to find one. * * Note that the surface returned (if any) depends upon the * triangulation, and so must be destroyed before the triangulation * itself. * * \deprecated This routine will be removed in a future version * of Regina. Use NTriangulation::hasNonTrivialSphereOrDisc() * instead. * * @param tri the triangulation in which to search. * @return a newly allocated non-vertex-linking normal sphere * within the given triangulation, or 0 if no such sphere exists. */ static NNormalSurface* findNonTrivialSphere(NTriangulation* tri); /** * Searches the list of vertex octagonal almost normal surfaces for * an almost normal 2-sphere within the given triangulation. This * means that tubed almost normal 2-spheres or non-vertex octagonal * almost normal 2-spheres will not be found. * * This search can be done either in standard almost normal * coordinates (with triangles, quadrilaterals and octagons), or * in quadrilateral-octagon coordinates. This choice of coordinate * system affects how we define "vertex". The default is to use * standard coordinates (where the set of vertex surfaces is larger). * * For "sufficiently nice" triangulations, if this routine fails * to find an almost normal 2-sphere then we can be certain that * the triangulation contains no almost normal 2-spheres at all. * In particular, this is true for closed orientable one-vertex * 0-efficient triangulations. For a proof in standard coordinates, * see "0-efficient triangulations of 3-manifolds", William Jaco * and J. Hyam Rubinstein, J. Differential Geom. 65 (2003), * no. 1, 61--168. For a proof in quadrilateral-octagon coordinates, * see "Quadrilateral-octagon coordinates for almost normal surfaces", * Benjamin A. Burton, Experiment. Math. 19 (2010), 285-315. * * Note that the surface that this routine returns (if any) depends * upon the triangulation, and so this surface must be destroyed * before the triangulation is destroyed. * * \warning Currently this routine can be quite slow since it * performs a full enumeration of vertex almost normal surfaces. * * \deprecated This routine will be removed in a future version * of Regina. Use NTriangulation::hasOctagonalAlmostNormalSphere() * instead, which offers significant optimisations over this routine. * * @param tri the triangulation in which to search. * @param quadOct \c true if we should search for vertex * surfaces in quadrilateral-octagon coordiantes, or \c false * (the default) if we should search for surfaces in standard * almost normal coordinates. * @return a newly allocated vertex octagonal almost normal sphere * within the given triangulation, or 0 if no such sphere exists. */ static NNormalSurface* findVtxOctAlmostNormalSphere( NTriangulation* tri, bool quadOct = false); protected: /** * Calculates the position of the first non-zero octagon * coordinate and stores it as a property. */ void calculateOctPosition() const; /** * Calculates the Euler characteristic of this surface and * stores it as a property. * * \pre This normal surface is compact (has finitely many discs). */ void calculateEulerCharacteristic() const; /** * Calculates whether this surface is orientable and/or * two-sided and stores the results as properties. * * \pre This normal surface is compact (has finitely many discs). */ void calculateOrientable() const; /** * Calculates whether this surface has any real boundary and * stores the result as a property. */ void calculateRealBoundary() const; friend class regina::NXMLNormalSurfaceReader; }; /*@}*/ // Inline functions for NNormalSurfaceVector inline NNormalSurfaceVector::NNormalSurfaceVector(size_t length) : NRay(length) { } inline NNormalSurfaceVector::NNormalSurfaceVector( const NVector& cloneMe) : NRay(cloneMe) { } inline NNormalSurfaceVector::~NNormalSurfaceVector() { } inline NLargeInteger NNormalSurfaceVector::getFaceArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const { return getTriangleArcs(triIndex, triVertex, triang); } // Inline functions for NNormalSurface inline NNormalSurface::~NNormalSurface() { delete vector; } inline NLargeInteger NNormalSurface::getTriangleCoord(unsigned long tetIndex, int vertex) const { return vector->getTriangleCoord(tetIndex, vertex, triangulation); } inline NLargeInteger NNormalSurface::getOrientedTriangleCoord( unsigned long tetIndex, int vertex, bool oriented) const { return vector->getOrientedTriangleCoord(tetIndex, vertex, triangulation, oriented); } inline NLargeInteger NNormalSurface::getQuadCoord(unsigned long tetIndex, int quadType) const { return vector->getQuadCoord(tetIndex, quadType, triangulation); } inline NLargeInteger NNormalSurface::getOrientedQuadCoord( unsigned long tetIndex, int quadType, bool oriented) const { return vector->getOrientedQuadCoord(tetIndex, quadType, triangulation, oriented); } inline NLargeInteger NNormalSurface::getOctCoord(unsigned long tetIndex, int octType) const { return vector->getOctCoord(tetIndex, octType, triangulation); } inline NLargeInteger NNormalSurface::getEdgeWeight(unsigned long edgeIndex) const { return vector->getEdgeWeight(edgeIndex, triangulation); } inline NLargeInteger NNormalSurface::getTriangleArcs(unsigned long triIndex, int triVertex) const { return vector->getTriangleArcs(triIndex, triVertex, triangulation); } inline NLargeInteger NNormalSurface::getFaceArcs(unsigned long triIndex, int triVertex) const { return vector->getTriangleArcs(triIndex, triVertex, triangulation); } inline NDiscType NNormalSurface::getOctPosition() const { if (! octPosition.known()) calculateOctPosition(); return octPosition.value(); } inline size_t NNormalSurface::getNumberOfCoords() const { return vector->size(); } inline NTriangulation* NNormalSurface::getTriangulation() const { return triangulation; } inline const std::string& NNormalSurface::getName() const { return name; } inline void NNormalSurface::setName(const std::string& newName) { name = newName; } inline void NNormalSurface::writeRawVector(std::ostream& out) const { out << *vector; } inline bool NNormalSurface::isCompact() const { if (! compact.known()) compact = vector->isCompact(triangulation); return compact.value(); } inline NLargeInteger NNormalSurface::getEulerCharacteristic() const { if (! eulerChar.known()) calculateEulerCharacteristic(); return eulerChar.value(); } inline bool NNormalSurface::isOrientable() const { if (! orientable.known()) calculateOrientable(); return orientable.value(); } inline bool NNormalSurface::isTwoSided() const { if (! twoSided.known()) calculateOrientable(); return twoSided.value(); } inline bool NNormalSurface::isConnected() const { if (! connected.known()) calculateOrientable(); return connected.value(); } inline bool NNormalSurface::hasRealBoundary() const { if (! realBoundary.known()) calculateRealBoundary(); return realBoundary.value(); } inline bool NNormalSurface::isVertexLinking() const { return vector->isVertexLinking(triangulation); } inline const NVertex* NNormalSurface::isVertexLink() const { return vector->isVertexLink(triangulation); } inline std::pair NNormalSurface::isThinEdgeLink() const { return vector->isThinEdgeLink(triangulation); } inline bool NNormalSurface::isSplitting() const { return vector->isSplitting(triangulation); } inline NLargeInteger NNormalSurface::isCentral() const { return vector->isCentral(triangulation); } inline const NNormalSurfaceVector* NNormalSurface::rawVector() const { return vector; } } // namespace regina #endif regina-4.95/engine/surfaces/nnormalsurfacelist.cpp000644 000765 000024 00000022160 12234011536 022304 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/coordregistry.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" #include "utilities/xmlutils.h" namespace regina { namespace { // Since legacy coordinate systems don't appear in the coordinate system // registry, give them a consistent name here. const char* AN_LEGACY_NAME = "Legacy standard almost normal (pruned tri-quad-oct)"; } const NormalCoords NNormalSurfaceList::STANDARD = NS_STANDARD;; const NormalCoords NNormalSurfaceList::QUAD = NS_QUAD; const NormalCoords NNormalSurfaceList::AN_LEGACY = NS_AN_LEGACY; const NormalCoords NNormalSurfaceList::AN_QUAD_OCT = NS_AN_QUAD_OCT; const NormalCoords NNormalSurfaceList::AN_STANDARD = NS_AN_STANDARD; const NormalCoords NNormalSurfaceList::EDGE_WEIGHT = NS_EDGE_WEIGHT; const NormalCoords NNormalSurfaceList::FACE_ARCS = NS_TRIANGLE_ARCS; const NormalCoords NNormalSurfaceList::ORIENTED = NS_ORIENTED; const NormalCoords NNormalSurfaceList::ORIENTED_QUAD = NS_ORIENTED_QUAD; void NNormalSurfaceList::writeAllSurfaces(std::ostream& out) const { unsigned long n = getNumberOfSurfaces(); out << "Number of surfaces is " << n << '\n'; for (unsigned long i = 0; i < n; i++) { getSurface(i)->writeTextShort(out); out << '\n'; } } namespace { struct ZeroVector : public Returns { const NTriangulation* tri_; ZeroVector(const NTriangulation* tri) : tri_(tri) {} template inline NNormalSurfaceVector* operator() (Coords) { return Coords::Class::makeZeroVector(tri_); } }; } NNormalSurfaceVector* makeZeroVector(const NTriangulation* triangulation, NormalCoords coords) { return forCoords(coords, ZeroVector(triangulation), 0); } namespace { struct MatchingEquations : public Returns { NTriangulation* tri_; MatchingEquations(NTriangulation* tri) : tri_(tri) {} template inline NMatrixInt* operator() (Coords) { return Coords::Class::makeMatchingEquations(tri_); } }; } NMatrixInt* makeMatchingEquations(NTriangulation* triangulation, NormalCoords coords) { return forCoords(coords, MatchingEquations(triangulation), 0); } namespace { struct EmbeddedConstraints : public Returns { NTriangulation* tri_; EmbeddedConstraints(NTriangulation* tri) : tri_(tri) {} template inline NEnumConstraintList* operator() (Coords) { return Coords::Class::makeEmbeddedConstraints(tri_); } }; } NEnumConstraintList* makeEmbeddedConstraints(NTriangulation* triangulation, NormalCoords coords) { return forCoords(coords, EmbeddedConstraints(triangulation), 0); } NTriangulation* NNormalSurfaceList::getTriangulation() const { return dynamic_cast(getTreeParent()); } namespace { struct AlmostNormalFunction : public Returns { template inline bool operator() (Coords f) { return f.almostNormal; } }; } bool NNormalSurfaceList::allowsAlmostNormal() const { if (coords_ == NS_AN_LEGACY) return true; else return forCoords(coords_, AlmostNormalFunction(), false); } namespace { struct SpunFunction : public Returns { template inline bool operator() (Coords f) { return f.spun; } }; } bool NNormalSurfaceList::allowsSpun() const { // Both the default and the NS_AN_LEGACY cases should return false. return forCoords(coords_, SpunFunction(), false); } namespace { struct OrientedFunction : public Returns { template inline bool operator() (Coords f) { return f.oriented; } }; } bool NNormalSurfaceList::allowsOriented() const { // Both the default and the NS_AN_LEGACY cases should return false. return forCoords(coords_, OrientedFunction(), false); } namespace { struct NameFunction : public Returns { template inline const char* operator() (Coords f) { return f.name(); } }; } void NNormalSurfaceList::writeTextShort(std::ostream& out) const { out << surfaces.size(); if (which_.has(regina::NS_EMBEDDED_ONLY)) out << " embedded,"; else if (which_.has(regina::NS_IMMERSED_SINGULAR)) out << " embedded / immersed / singular,"; else out << " unknown,"; if (which_.has(regina::NS_VERTEX)) out << " vertex"; else if (which_.has(regina::NS_FUNDAMENTAL)) out << " fundamental"; else if (which_.has(regina::NS_CUSTOM)) out << " custom"; else if (which_.has(regina::NS_LEGACY)) out << " legacy"; else out << " unknown"; out << " surface"; if (surfaces.size() != 1) out << 's'; out << " ("; if (coords_ == NS_AN_LEGACY) out << AN_LEGACY_NAME; else out << forCoords(coords_, NameFunction(), "Unknown"); out << ')'; } void NNormalSurfaceList::writeTextLong(std::ostream& out) const { if (which_.has(regina::NS_EMBEDDED_ONLY)) out << "Embedded,"; else if (which_.has(regina::NS_IMMERSED_SINGULAR)) out << "Embedded / immersed / singular,"; else out << "Unknown,"; if (which_.has(regina::NS_VERTEX)) out << " vertex"; else if (which_.has(regina::NS_FUNDAMENTAL)) out << " fundamental"; else if (which_.has(regina::NS_CUSTOM)) out << " custom"; else if (which_.has(regina::NS_LEGACY)) out << " legacy"; else out << " unknown"; out << " surfaces\n"; out << "Coordinates: "; if (coords_ == NS_AN_LEGACY) out << AN_LEGACY_NAME << '\n'; else out << forCoords(coords_, NameFunction(), "Unknown") << '\n'; writeAllSurfaces(out); } void NNormalSurfaceList::writeXMLPacketData(std::ostream& out) const { // Write the surface list parameters. out << " \n"; // Write the individual surfaces. std::vector::const_iterator it; for (it = surfaces.begin(); it != surfaces.end(); it++) (*it)->writeXMLData(out); } NPacket* NNormalSurfaceList::internalClonePacket(NPacket* /* parent */) const { NNormalSurfaceList* ans = new NNormalSurfaceList( coords_, which_, algorithm_); transform(surfaces.begin(), surfaces.end(), back_inserter(ans->surfaces), FuncNewClonePtr()); return ans; } } // namespace regina regina-4.95/engine/surfaces/nnormalsurfacelist.h000644 000765 000024 00000241355 12236546232 021772 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nnormalsurfacelist.h * \brief Contains a packet representing a collection of normal * surfaces in a 3-manifold. */ #ifndef __NNORMALSURFACELIST_H #ifndef __DOXYGEN #define __NNORMALSURFACELIST_H #endif #include #include #include #include "regina-core.h" #include "packet/npacket.h" #include "surfaces/nnormalsurface.h" #include "surfaces/normalflags.h" #include "surfaces/normalcoords.h" #include "utilities/memutils.h" #include "utilities/nthread.h" namespace regina { class NTriangulation; class NMatrixInt; class NNormalSurfaceList; class NProgressTracker; class NXMLPacketReader; class NXMLNormalSurfaceListReader; /** * \weakgroup surfaces * @{ */ /** * Stores information about the normal surface list packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NNormalSurfaceList Class; inline static const char* name() { return "Normal Surface List"; } }; /** * A packet representing a collection of normal surfaces in a 3-manifold. * Such a packet must always be a child packet of the triangulation from * which the surfaces were obtained. If this triangulation changes, the * information contained in this packet will become invalid. * * See the NNormalSurfaceVector class notes for details of what to do * when introducing a new coordinate system. * * Normal surface lists should be created using the routine enumerate(), * which is new as of Regina 3.95. * * \testpart * * \todo \feature Allow custom matching equations. * \todo \feature Allow enumeration with some coordinates explicitly set * to zero. * \todo \feature Allow generating only closed surfaces. * \todo \feature Generate facets of the solution space representing * embedded surfaces. */ class REGINA_API NNormalSurfaceList : public NPacket { REGINA_PACKET(NNormalSurfaceList, PACKET_NORMALSURFACELIST) public: /** * Represents standard triangle-quadrilateral coordinates for * normal surfaces. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_STANDARD directly. */ static const NormalCoords STANDARD; /** * Represents standard triangle-quadrilateral-octagon coordinates * for octagonal almost normal surfaces. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_AN_STANDARD directly. */ static const NormalCoords AN_STANDARD; /** * Represents quadrilateral coordinates for normal surfaces. * For details, see "Normal surface Q-theory", Jeffrey L. Tollefson, * Pacific J. Math. 183 (1998), no. 2, 359--374. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_QUAD directly. */ static const NormalCoords QUAD; /** * Represents quadrilateral-octagon coordinates for octagonal * almost normal surfaces. For details, see * "Quadrilateral-octagon coordinates for almost normal surfaces", * Benjamin A. Burton, Experiment. Math. 19 (2010), 285-315. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_AN_QUAD_OCT directly. */ static const NormalCoords AN_QUAD_OCT; /** * Represents edge weight coordinates for normal surfaces. * This coordinate system is for representation only; surface * vectors and lists cannot be created in this coordinate system. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_EDGE_WEIGHT directly. */ static const NormalCoords EDGE_WEIGHT; /** * Represents triangle arc coordinates for normal surfaces. * This coordinate system is for representation only; surface * vectors and lists cannot be created in this coordinate system. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_TRIANGLE_ARCS. */ static const NormalCoords FACE_ARCS; /** * Indicates that a list of almost normal surfaces was created * using Regina 4.5.1 or earlier, where surfaces with more than * one octagon of the same type were stripped out of the final * solution set. As of Regina 4.6 such surfaces are now * included in the solution set, since we need them if we * wish to enumerate \e all almost normal surfaces (not just * the \e vertex almost normal surfaces). * * This coordinate system is only used with legacy data files; new * vectors and lists cannot be created in this coordinate system. * The underlying coordinates are identical to those of AN_STANDARD. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_AN_LEGACY directly. */ static const NormalCoords AN_LEGACY; /** * Represents standard triangle-quadrilateral coordinates for * transversely oriented normal surfaces. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_ORIENTED directly. */ static const NormalCoords ORIENTED; /** * Represents quadrilateral coordinates for transversely oriented * normal surfaces. * * \deprecated Instead of this class constant, you should use * the NormalCoords enum value NS_ORIENTED_QUAD directly. */ static const NormalCoords ORIENTED_QUAD; class VectorIterator; protected: std::vector surfaces; /**< Contains the normal surfaces stored in this packet. */ NormalCoords coords_; /**< Stores which coordinate system is being used by the normal surfaces in this packet. */ NormalList which_; /**< Indicates which normal surfaces these represent within the underlying triangulation. */ NormalAlg algorithm_; /**< Stores the details of the enumeration algorithm that was used to generate this list. This might not be the same as the \a algorithmHints flag passed to the corresponding enumeration routine (e.g., if invalid or inappropriate flags were passed). */ public: /** * Destroys this list and all the surfaces within. */ virtual ~NNormalSurfaceList(); /** * A unified routine for enumerating various classes of normal * surfaces within a given triangulation. * * The NormalCoords argument allows you to specify an underlying * coordinate system (e.g., standard coordinates, * quadrilateral coordinates or almost normal coordinates). * * The NormalList argument is a combination of flags that * allows you to specify exactly which normal surfaces you require. * This includes (i) whether you want all vertex surfaces * or all fundamental surfaces, which defaults to NS_VERTEX * if you specify neither or both; and (ii) whether you want only * properly embedded surfaces or you also wish to include * immersed and/or singular surfaces, which defaults to * NS_EMBEDDED_ONLY if you specify neither or both. * * The NormalAlg argument is a combination of flags that allows * you to control the underlying enumeration algorithm. These * flags are treated as hints only: if your selection of * algorithm is invalid, unavailable or unsupported then Regina * will choose something more appropriate. Unless you have * some specialised need, the default NS_ALG_DEFAULT (which * makes no hints at all) will allow Regina to choose what it * thinks will be the most efficient method. * * The enumerated surfaces will be stored in a new normal * surface list, and their representations will be scaled down * to use the smallest possible integer coordinates. * This normal surface list will be inserted into the packet tree as * the last child of the given triangulation. This triangulation * \b must remain the parent of this normal surface list, and must not * change while this normal surface list remains in existence. * * If a progress tracker is passed, the normal surface * enumeration will take place in a new thread and this routine * will return immediately. If the user cancels the operation * from another thread, then the normal surface list will \e not * be inserted into the packet tree (but the caller of this * routine will still need to delete it). Regarding progress tracking, * this routine will declare and work through a series of stages * whose combined weights sum to 1; typically this means that the * given tracker must not have been used before. * * If no progress tracker is passed, the enumeration will run * in the current thread and this routine will return only when * the enumeration is complete. Note that this enumeration can * be extremely slow for larger triangulations. * * @param owner the triangulation upon which this list of normal * surfaces will be based. * @param coords the coordinate system to be used. * @param which indicates which normal surfaces should be enumerated. * @param algHints passes requests to Regina for which specific * enumeration algorithm should be used. * @param tracker a progress tracker through which progress will * be reported, or 0 if no progress reporting is required. * @return the newly created normal surface list. Note that if * a progress tracker is passed then this list may not be completely * filled when this routine returns. If a progress tracker is * passed and a new thread could not be started, this routine * returns 0 (and no normal surface list is created). */ static NNormalSurfaceList* enumerate(NTriangulation* owner, NormalCoords coords, NormalList which = NS_LIST_DEFAULT, NormalAlg algHints = NS_ALG_DEFAULT, NProgressTracker* tracker = 0); /** * Deprecated method for enumerating all vertex normal surfaces * using the given coordinate system. * * Users should now call enumerate(NTriangulation*, NormalCoords, * NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details, * including all arguments, returns values, preconditions and * postconditions. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, coords, NS_EMBEDDED_ONLY, * NS_ALG_DEFAULT, tracker) if \a embeddedOnly is \c true, or * enumerate(owner, coords, NS_IMMERSED_SINGULAR, * NS_ALG_DEFAULT, tracker) if \a embeddedOnly is \c false. */ static NNormalSurfaceList* enumerate(NTriangulation* owner, NormalCoords coords, bool embeddedOnly, NProgressTracker* tracker = 0); /** * Deprecated method that uses a slow-but-direct procedure to * enumerate all embedded vertex normal surfaces in standard * coordinates, without using the faster procedure that works * via quadrilateral coordinates. * * Users can still access this slower procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, NS_STANDARD, NS_LIST_DEFAULT, * NS_VERTEX_STD_DIRECT). * * \warning This routine is slow, and users will not want to * call it unless they have some specialised need. * * @param owner the triangulation upon which this list of * surfaces will be based. * @return the newly created normal surface list. */ static NNormalSurfaceList* enumerateStandardDirect( NTriangulation* owner); /** * Deprecated method that uses a slow-but-direct procedure to * enumerate all embedded vertex almost normal surfaces in standard * almost normal coordinates, without using the faster procedure * that works via quadrilateral-octagon coordinates. * * Users can still access this slower procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, NS_AN_STANDARD, NS_LIST_DEFAULT, * NS_VERTEX_STD_DIRECT). * * \warning This routine is slow, and users will not want to * call it unless they have some specialised need. * * @param owner the triangulation upon which this list of * surfaces will be based. * @return the newly created normal surface list. */ static NNormalSurfaceList* enumerateStandardANDirect( NTriangulation* owner); /** * Deprecated method that enumerates all fundamental normal surfaces * in the given triangulation using the primal Hilbert basis algorithm. * For details of the algorithm, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * Users can still access this procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \warning As of Regina 4.94, the \a vtxSurfaces argument is ignored. * Future versions of Regina will automatically search existing surface * lists in the packet tree for a ready-made list of vertex normal * surfaces that can be used. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, coords, NS_FUNDAMENTAL | NS_EMBEDDED_ONLY, * NS_HILBERT_PRIMAL, tracker) if \a embeddedOnly is \c true, or * enumerate(owner, coords, NS_FUNDAMENTAL | NS_IMMERSED_SINGULAR, * NS_HILBERT_PRIMAL, tracker) if \a embeddedOnly is \c false. * * @param owner the triangulation upon which this list of normal * surfaces will be based. * @param coords the coordinate system to be used. * @param embeddedOnly \c true if only embedded normal surfaces * are to be produced, or \c false if immersed and singular * normal surfaces are also to be produced; this defaults to \c true. * @param vtxSurfaces the set of all \e vertex normal surfaces * as enumerated under the same coordinate system and * constraints as given here; this may be 0 if unknown. * @param tracker a progress tracker through which progress will * be reported, or 0 if no progress reporting is required. * @return the newly created normal surface list. Note that if * a progress tracker is passed then this list may not be completely * filled when this routine returns. If a progress tracker is * passed and a new thread could not be started, this routine * returns 0 (and no normal surface list is created). */ static NNormalSurfaceList* enumerateFundPrimal( NTriangulation* owner, NormalCoords coords, bool embeddedOnly = true, NNormalSurfaceList* vtxSurfaces = 0, NProgressTracker* tracker = 0); /** * Deprecated method that enumerates all fundamental normal surfaces * in the given triangulation using the dual Hilbert basis algorithm. * For details of the algorithm, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * Users can still access this procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, coords, NS_FUNDAMENTAL | NS_EMBEDDED_ONLY, * NS_HILBERT_DUAL, tracker) if \a embeddedOnly is \c true, or * enumerate(owner, coords, NS_FUNDAMENTAL | NS_IMMERSED_SINGULAR, * NS_HILBERT_DUAL, tracker) if \a embeddedOnly is \c false. * * @param owner the triangulation upon which this list of normal * surfaces will be based. * @param coords the coordinate system to be used. * @param embeddedOnly \c true if only embedded normal surfaces * are to be produced, or \c false if immersed and singular * normal surfaces are also to be produced; this defaults to \c true. * @param tracker a progress tracker through which progress will * be reported, or 0 if no progress reporting is required. * @return the newly created normal surface list. Note that if * a progress tracker is passed then this list may not be completely * filled when this routine returns. If a progress tracker is * passed and a new thread could not be started, this routine * returns 0 (and no normal surface list is created). */ static NNormalSurfaceList* enumerateFundDual( NTriangulation* owner, NormalCoords coords, bool embeddedOnly = true, NProgressTracker* tracker = 0); /** * Deprecated method that uses an extremely slow procedure to enumerate * all embedded fundamental surfaces in the given triangulation, * by running Normaliz over the full (and typically very large) * solution cone, and only enforcing embedded constraints (such as * the quadrilateral constraints) afterwards. * * Users can still access this slower procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, coords, NS_FUNDAMENTAL | NS_EMBEDDED_ONLY, * NS_HILBERT_FULLCONE) if \a embeddedOnly is \c true, or * enumerate(owner, coords, NS_FUNDAMENTAL | NS_IMMERSED_SINGULAR, * NS_HILBERT_FULLCONE) if \a embeddedOnly is \c false. * * \warning This routine is extremely slow, and users will not want to * call it unless they have some specialised need. * * @param owner the triangulation upon which this list of normal * surfaces will be based. * @param coords the coordinate system to be used. * @param embeddedOnly \c true if only embedded normal surfaces * are to be produced, or \c false if immersed and singular * normal surfaces are also to be produced; this defaults to \c true. * @return the newly created normal surface list. */ static NNormalSurfaceList* enumerateFundFullCone( NTriangulation* owner, NormalCoords coords, bool embeddedOnly = true); /** * Deprecated method that uses an extremely slow modified * Contejean-Devie procedure to enumerate all embedded fundamental * surfaces in the given triangulation. For details of the * algorithm, see B. A. Burton, "Fundamental normal surfaces and the * enumeration of Hilbert bases", arXiv:1111.7055v1, Nov 2011. * * Users can still access this slower procedure if they need to; * however, they should do this via enumerate(NTriangulation*, * NormalCoords, NormalList, NormalAlg, NProgressTracker*) instead. * See the documentation for that routine for further details. * * \deprecated The correct way to access this procedure is to call * enumerate(owner, coords, NS_FUNDAMENTAL | NS_EMBEDDED_ONLY, * NS_HILBERT_CD) if \a embeddedOnly is \c true, or * enumerate(owner, coords, NS_FUNDAMENTAL | NS_IMMERSED_SINGULAR, * NS_HILBERT_CD) if \a embeddedOnly is \c false. * * \warning This routine is extremely slow, and users will not want to * call it unless they have some specialised need. * * @param owner the triangulation upon which this list of normal * surfaces will be based. * @param coords the coordinate system to be used. * @param embeddedOnly \c true if only embedded normal surfaces * are to be produced, or \c false if immersed and singular * normal surfaces are also to be produced; this defaults to \c true. * @return the newly created normal surface list. */ static NNormalSurfaceList* enumerateFundCD( NTriangulation* owner, NormalCoords coords, bool embeddedOnly = true); /** * Deprecated routine to return the coordinate system * being used by the surfaces stored in this set. * * \deprecated Users should switch to the identical routine * coords() instead. * * @return the coordinate system used. */ NormalCoords getFlavour() const; /** * Deprecated routine to return the coordinate system being used by the * surfaces stored in this set. * * \deprecated Users should switch to the identical routine * coords() instead. * * @return the coordinate system used. */ NormalCoords flavour() const; /** * Returns the coordinate system being used by the * surfaces stored in this set. * * @return the coordinate system used. */ NormalCoords coords() const; /** * Returns details of which normal surfaces this list represents * within the underlying triangulation. * * This may not be the same NormalList that was passed to * enumerate(). In particular, default values will have be * explicitly filled in (such as NS_VERTEX and/or NS_EMBEDDED_ONLY), * and invalid and/or redundant values will have been removed. * * @return details of what this list represents. */ NormalList which() const; /** * Returns details of the algorithm that was used to enumerate * this list. * * These may not be the same NormalAlg flags that were passed to * enumerate(). In particular, default values will have be * explicitly filled in, invalid and/or redundant values will have * been removed, and unavailable and/or unsupported combinations * of algorithm flags will be replaced with whatever algorithm was * actually used. * * @return details of the algorithm used to enumerate this list. */ NormalAlg algorithm() const; /** * Determines if the coordinate system being used * allows for almost normal surfaces, that is, allows for * octagonal discs. * * @return \c true if and only if almost normal surfaces are * allowed. */ bool allowsAlmostNormal() const; /** * Determines if the coordinate system being used * allows for spun normal surfaces. * * @return \c true if and only if spun normal surface are * supported. */ bool allowsSpun() const; /** * Determines if the coordinate system being used * allows for transversely oriented normal surfaces. * * @return \c true if and only if transverse orientations are * supported. */ bool allowsOriented() const; /** * Returns whether this list was constructed to contain only * properly embedded surfaces. * * If this returns \c false, it does not guarantee that immersed * and/or singular surfaces are present; it merely indicates * that they were not deliberately excluded (for instance, the * quadrilateral constraints were not enforced). * * @return \c true if this list was constructed to contain only * properly embedded surfaces, or \c false otherwise. */ bool isEmbeddedOnly() const; /** * Returns the triangulation in which these normal surfaces live. * * @return the triangulation in which these surfaces live. */ NTriangulation* getTriangulation() const; /** * Returns the number of surfaces stored in this set. * * @return the number of surfaces. */ unsigned long getNumberOfSurfaces() const; /** * Returns the surface at the requested index in this set. * * @param index the index of the requested surface in this set; * this must be between 0 and getNumberOfSurfaces()-1 inclusive. * * @return the normal surface at the requested index in this set. */ const NNormalSurface* getSurface(unsigned long index) const; /** * Writes the number of surfaces in this set followed by the * details of each surface to the given output stream. Output * will be over many lines. * * \ifacespython Parameter \a out is not present and is assumed * to be standard output. * * @param out the output stream to which to write. */ void writeAllSurfaces(std::ostream& out) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; /** * Converts the set of all embedded vertex normal surfaces in * quadrilateral space to the set of all embedded vertex normal * surfaces in standard (tri-quad) space. The initial list in * quadrilateral space is taken to be this normal surface list; * the final list in standard space will be inserted as a new * child packet of the underlying triangulation (specifically, as * the final child). As a convenience, the final list will also * be returned from this routine. * * This routine can only be used with normal surfaces, not almost * normal surfaces. For almost normal surfaces, see the similar * routine quadOctToStandardAN(). * * This procedure is available for any triangulation whose vertex * links are all spheres and/or discs, and is \e much faster than * enumerating surfaces directly in standard tri-quad coordinates. * The underlying algorithm is described in detail in "Converting * between quadrilateral and standard solution sets in normal * surface theory", Benjamin A. Burton, Algebr. Geom. Topol. 9 (2009), * 2121-2174. * * Typically users do not need to call this routine directly, * since the standard enumerate() routine will use it implicitly * where possible. That is, when asked for standard vertex surfaces, * enumerate() will first find all \e quadrilateral vertex surfaces * and then use this procedure to convert them to standard vertex * surfaces; this is generally orders of magnitude faster than * enumerating surfaces directly in standard coordinates. * * Nevertheless, this standalone routine is provided as a convenience * for users who already have a set of quadrilateral vertex surfaces, * and who simply wish to convert them to a set of standard * vertex surfaces without the cost of implicitly enumerating the * quadrilateral vertex surfaces again. * * It should be noted that this routine does \e not simply convert * vectors from one form to another; instead it converts a full * solution set of vertex surfaces in quadrilateral coordinates to a * full solution set of vertex surfaces in standard coordinates. * Typically there are many more vertex surfaces in standard * coordinates (all of which this routine will find). * * This routine will run some very basic sanity checks before * starting. Specifically, it will check the validity and vertex * links of the underlying triangulation, and will verify that * the coordinate system and embedded-only flag are set to * NS_QUAD and \c true respectively. If any of * these checks fails, this routine will do nothing and return 0. * * \pre The underlying triangulation (the parent packet of this * normal surface list) is valid, and the link of every vertex * is either a sphere or a disc. * \pre This normal surface list is precisely the set of all * embedded vertex normal surfaces in quadrilateral space; no more, * no less. Moreover, these vectors are stored using quadrilateral * coordinates. Typically this means that it was obtained through * enumerate(), with the coordinate system set to NS_QUAD and * with \a embeddedOnly set to \c true. * * @return a full list of vertex normal surfaces in standard (tri-quad) * coordinates, or 0 if any of the basic sanity checks failed. */ NNormalSurfaceList* quadToStandard() const; /** * Converts the set of all embedded vertex almost normal surfaces in * quadrilateral-octagon space to the set of all embedded vertex * almost normal surfaces in the standard tri-quad-oct space. * * This routine is the almost normal analogue to the * quadToStandard() conversion routine; see the quadToStandard() * documentation for further information. * * \pre The underlying triangulation (the parent packet of this * normal surface list) is valid, and the link of every vertex * is either a sphere or a disc. * \pre This surface list is precisely the set of all embedded vertex * almost normal surfaces in quadrilateral-octagon space; no more, * no less. Moreover, these vectors are stored using * quadrilateral-octagon coordinates. Typically this means that it * was obtained through enumerate(), with the coordinate system set to * NS_AN_QUAD_OCT and with \a embeddedOnly set to \c true. * * @return a full list of vertex almost normal surfaces in standard * tri-quad-oct coordinates, or 0 if any of the basic sanity checks * failed. */ NNormalSurfaceList* quadOctToStandardAN() const; /** * Converts the set of all embedded vertex normal surfaces in * standard (tri-quad) space to the set of all embedded vertex * normal surfaces in quadrilateral space. The initial list in * standard space is taken to be this normal surface list; * the final list in quadrilateral space will be inserted as a new * child packet of the underlying triangulation (specifically, as * the final child). As a convenience, the final list will also * be returned from this routine. * * This routine can only be used with normal surfaces, not almost * normal surfaces. For almost normal surfaces, see the similar * routine standardANToQuadOct(). * * This procedure is available for any triangulation whose vertex * links are all spheres and/or discs. The underlying algorithm * is described in detail in "Converting between quadrilateral and * standard solution sets in normal surface theory", * Benjamin A. Burton, Algebr. Geom. Topol. 9 (2009), 2121-2174. * * It should be noted that this routine does \e not simply convert * vectors from one form to another; instead it converts a full * solution set of vertex surfaces in standard coordinates to a * full solution set of vertex surfaces in quadrilateral coordinates. * Typically there are far fewer vertex surfaces in quadrilateral * coordinates (all of which this routine will find). * * This routine will run some very basic sanity checks before * starting. Specifically, it will check the validity and vertex * links of the underlying triangulation, and will verify that * the coordinate system and embedded-only flag are set to * NS_STANDARD and \c true respectively. If any of * these checks fails, this routine will do nothing and return 0. * * \pre The underlying triangulation (the parent packet of this * normal surface list) is valid, and the link of every vertex * is either a sphere or a disc. * \pre This normal surface list is precisely the set of all * embedded vertex normal surfaces in standard (tri-quad) space; * no more, no less. Moreover, these vectors are stored using * standard coordinates. Typically this means that this list was * obtained through enumerate(), with the coordinate system set to * NS_STANDARD and with \a embeddedOnly set to \c true. * * @return a full list of vertex normal surfaces in quadrilateral * coordinates, or 0 if any of the basic sanity checks failed. */ NNormalSurfaceList* standardToQuad() const; /** * Converts the set of all embedded vertex almost normal surfaces in * standard tri-quad-oct space to the set of all embedded vertex * almost normal surfaces in the smaller quadrilateral-octagon space. * * This routine is the almost normal analogue to the * standardToQuad() conversion routine; see the standardToQuad() * documentation for further information. * * \pre The underlying triangulation (the parent packet of this * normal surface list) is valid, and the link of every vertex * is either a sphere or a disc. * \pre This normal surface list is precisely the set of all * embedded vertex almost normal surfaces in standard tri-quad-oct * space; no more, no less. Typically this means that it was obtained * through enumerate(), with the coordinate system set to * NS_AN_STANDARD and with \a embeddedOnly set to \c true. * * @return a full list of vertex almost normal surfaces in * quadrilateral-octagon coordinates, or 0 if any of the basic * sanity checks failed. */ NNormalSurfaceList* standardANToQuadOct() const; /** * Creates a new list filled with the surfaces from this list * that have at least one locally compatible partner. * In other words, a surface \a S from this list will be placed * in the new list if and only if there is some other surface \a T * in this list for which \a S and \a T are locally compatible. * See NNormalSurface::locallyCompatible() for further details on * compatibility testing. * * The new list will be inserted as a new child packet of the * underlying triangulation (specifically, as the final child). As a * convenience, the new list will also be returned from this routine. * * This original list is not altered in any way. Likewise, * the surfaces in the new list are deep copies of the originals * (so they can be altered without affecting the original surfaces). * * \pre This list contains only embedded normal surfaces. More * precisely, isEmbeddedOnly() must return \c true. * * \warning If this list contains a vertex link (plus at least * one other surface), then the new list will be identical to * the old (i.e., every surface will be copied across). * * @return the new list, which will also have been inserted as * a new child packet of the underlying triangulation. */ NNormalSurfaceList* filterForLocallyCompatiblePairs() const; /** * Creates a new list filled with the surfaces from this list * that have at least one disjoint partner. * In other words, a surface \a S from this list will be placed * in the new list if and only if there is some other surface \a T * in this list for which \a S and \a T can be made to intersect * nowhere at all, without changing either normal isotopy class. * See NNormalSurface::disjoint() for further details on disjointness * testing. * * This routine cannot deal with empty, disconnected or * non-compact surfaces. Such surfaces will be silently * ignored, and will not be used in any disjointness tests (in * particular, they will never be considered as a "disjoint partner" * for any other surface). * * The new list will be inserted as a new child packet of the * underlying triangulation (specifically, as the final child). As a * convenience, the new list will also be returned from this routine. * * This original list is not altered in any way. Likewise, * the surfaces in the new list are deep copies of the originals * (so they can be altered without affecting the original surfaces). * * \pre This list contains only embedded normal surfaces. More * precisely, isEmbeddedOnly() must return \c true. * \pre All surfaces within this list are stored using the same * coordinate system (i.e., the same subclass of NNormalSurfaceVector). * * \warning If this list contains a vertex link (plus at least * one other surface), then the new list will be identical to * the old (i.e., every surface will be copied across). * * \todo Deal properly with surfaces that are too large to handle. * * @return the new list, which will also have been inserted as * a new child packet of the underlying triangulation. */ NNormalSurfaceList* filterForDisjointPairs() const; /** * Creates a new list filled with only the surfaces from this list * that "might" represent two-sided incompressible surfaces. * More precisely, we consider all two-sided surfaces in this list, * as well as the two-sided double covers of all one-sided surfaces * in this list (see below for details on how one-sided surfaces * are handled). Each of these surfaces is examined using * relatively fast heuristic tests for incompressibility. Any * surface that is definitely \e not incompressible is thrown * away, and all other surfaces are placed in the new list. * * Therefore, it is guaranteed that every incompressible surface * from the old list will be placed in the new list. However, * it is not known whether any given surface in the new list is * indeed incompressible. * * See NNormalSurface::isIncompressible() for the definition of * incompressibility that is used here. Note in particular that * spheres are \e never considered incompressible. * * As indicated above, this filter works exclusively with two-sided * surfaces. If a surface in this list is one-sided, the heuristic * incompressibility tests will be run on its two-sided double cover. * Nevertheless, if the tests pass, the original one-sided surface * (not the double cover) will be added to the new list. * * The new list will be inserted as a new child packet of the * underlying triangulation (specifically, as the final child). As a * convenience, the new list will also be returned from this routine. * * This original list is not altered in any way. Likewise, * the surfaces in the new list are deep copies of the originals * (so they can be altered without affecting the original surfaces). * * Currently the heuristic tests include (i) throwing away * all vertex links and thin edge links, and then * (ii) cutting along the remaining surfaces and running * NTriangulation::hasSimpleCompressingDisc() on the resulting * bounded triangulations. For more details on these tests * see "The Weber-Seifert dodecahedral space is non-Haken", * Benjamin A. Burton, J. Hyam Rubinstein and Stephan Tillmann, * Trans. Amer. Math. Soc. 364:2 (2012), pp. 911-932. * * \pre The underlying 3-manifold triangulation is valid and closed. * In particular, it has no ideal vertices. * \pre This list contains only embedded normal surfaces. More * precisely, isEmbeddedOnly() must return \c true. * \pre This list contains only compact, connected normal surfaces. * \pre No surfaces in this list contain any octagonal discs. * * \warning The behaviour of this routine is subject to change * in future versions of Regina, since additional tests may be * added to improve the power of this filtering. * * \todo Add progress tracking. * * @return the new list, which will also have been inserted as * a new child packet of the underlying triangulation. */ NNormalSurfaceList* filterForPotentiallyIncompressible() const; /** * Returns a newly created matrix containing the matching * equations that were used to create this normal surface list. * The destruction of this matrix is the responsibility of the * caller of this routine. Multiple calls to this routine will * result in the construction of multiple matrices. This * routine in fact merely calls makeMatchingEquations() with the * appropriate parameters. * * The format of the matrix is identical to that returned by * makeMatchingEquations(). * * @return the matching equations used to create this normal * surface list. */ NMatrixInt* recreateMatchingEquations() const; /** * An iterator that gives access to the raw vectors for surfaces in * this list, pointing to the beginning of this surface list. * * \ifacespython Not present. * * @return an iterator at the beginning of this surface list. */ VectorIterator beginVectors() const; /** * An iterator that gives access to the raw vectors for surfaces in * this list, pointing past the end of this surface list. * This iterator is not dereferenceable. * * \ifacespython Not present. * * @return an iterator past the end of this surface list. */ VectorIterator endVectors() const; /** * A bidirectional iterator that runs through the raw vectors for * surfaces in this list. * * \ifacespython Not present. */ class VectorIterator : public std::iterator< std::bidirectional_iterator_tag, const NNormalSurfaceVector*> { private: std::vector::const_iterator it_; /**< An iterator into the underlying list of surfaces. */ public: /** * Creates a new uninitialised iterator. */ VectorIterator(); /** * Creates a copy of the given iterator. * * @param cloneMe the iterator to clone. */ VectorIterator(const VectorIterator& cloneMe); /** * Makes this a copy of the given iterator. * * @param cloneMe the iterator to clone. * @return a reference to this iterator. */ VectorIterator& operator = (const VectorIterator& cloneMe); /** * Compares this with the given operator for equality. * * @param other the iterator to compare this with. * @return \c true if the iterators point to the same * element of the same normal surface list, or \c false * if they do not. */ bool operator == (const VectorIterator& other) const; /** * Compares this with the given operator for inequality. * * @param other the iterator to compare this with. * @return \c false if the iterators point to the same * element of the same normal surface list, or \c true * if they do not. */ bool operator != (const VectorIterator& other) const; /** * Returns the raw vector for the normal surface that this * iterator is currently pointing to. * * \pre This iterator is dereferenceable (in particular, * it is not past-the-end). * * @return the corresponding normal surface vector. */ const NNormalSurfaceVector* operator *() const; /** * The preincrement operator. * * @return a reference to this iterator after the increment. */ VectorIterator& operator ++(); /** * The postincrement operator. * * @return a copy of this iterator before the * increment took place. */ VectorIterator operator ++(int); /** * The predecrement operator. * * @return a reference to this iterator after the decrement. */ VectorIterator& operator --(); /** * The postdecrement operator. * * @return a copy of this iterator before the * decrement took place. */ VectorIterator operator --(int); private: /** * Initialise a new vector iterator using an iterator for * the internal list of normal surfaces. */ VectorIterator( const std::vector::const_iterator& i); friend class NNormalSurfaceList; }; protected: /** * Creates an empty list of normal surfaces with the given * parameters. * * @param coords the coordinate system to be used * for filling this list. * @param which indicates which normal surfaces these will * represent within the underlying triangulation. * @param algorithm details of the enumeration algorithm that * will be used to fill this list. */ NNormalSurfaceList(NormalCoords coords, NormalList which, NormalAlg algorithm); virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; /** * An output iterator used to insert surfaces into an * NNormalSurfaceList. * * Objects of type NNormalSurface* and * NNormalSurfaceVector* can be assigned to this * iterator. In the latter case, a surrounding NNormalSurface * will be automatically created. * * \warning The behaviour of this class has changed! * As of Regina 4.6, this class happily inserts every surface or * vector that it is given. In previous versions it checked * almost normal surface vectors for multiple octagonal discs; * this check has been removed to support conversions between * quad-oct space and standard almost normal space, and to support * the enumeration of \e all almost normal surfaces (as opposed to * just vertex surfaces). Such checks are now left to the user * interface (and indeed are now optional, at the user's discretion). */ struct SurfaceInserter : public std::iterator< std::output_iterator_tag, NNormalSurfaceVector*> { NNormalSurfaceList* list; /**< The list into which surfaces will be inserted. */ NTriangulation* owner; /**< The triangulation in which the surfaces to be * inserted are contained. */ /** * Creates a new output iterator. The member variables of * this iterator will be initialised according to the * parameters passed to this constructor. * * @param newList the list into which surfaces will be * inserted. * @param newOwner the triangulation in which the surfaces * to be inserted are contained. */ SurfaceInserter(NNormalSurfaceList& newList, NTriangulation* newOwner); /** * Creates a new output iterator that is a clone of the * given iterator. * * @param cloneMe the output iterator to clone. */ SurfaceInserter(const SurfaceInserter& cloneMe); /** * Sets this iterator to be a clone of the given output iterator. * * @param cloneMe the output iterator to clone. * @return this output iterator. */ SurfaceInserter& operator =(const SurfaceInserter& cloneMe); /** * Appends a normal surface to the end of the appropriate * surface list. * * The given surface will be deallocated with the other * surfaces in this list when the list is eventually destroyed. * * @param surface the normal surface to insert. * @return this output iterator. */ SurfaceInserter& operator =(NNormalSurface* surface); /** * Appends the normal surface corresponding to the given * vector to the end of the appropriate surface list. * * The given vector will be owned by the newly created * normal surface and will be deallocated with the other * surfaces in this list when the list is eventually destroyed. * * \warning The behaviour of this routine has changed! * As of Regina 4.6, this routine no longer checks for * multiple octagonal discs. See the SurfaceInserter * class notes for details. * * @param vector the vector of the normal surface to insert. * @return this output iterator. */ SurfaceInserter& operator =(NNormalSurfaceVector* vector); /** * Returns a reference to this output iterator. * * @return this output iterator. */ SurfaceInserter& operator *(); /** * Returns a reference to this output iterator. * * @return this output iterator. */ SurfaceInserter& operator ++(); /** * Returns a reference to this output iterator. * * @return this output iterator. */ SurfaceInserter& operator ++(int); }; private: /** * A helper class containing constants, typedefs and operations * for working with normal (as opposed to almost normal) surfaces. * * This class and its partner AlmostNormalSpec can be used to * write generic template code that works with both normal * \e and almost normal surfaces. * * The full definition of this class is in the file normalspec-impl.h . */ struct NormalSpec; /** * A helper class containing constants, typedefs and operations * for working with almost normal (as opposed to normal) surfaces. * * This class and its partner NormalSpec can be used to * write generic template code that works with both normal * \e and almost normal surfaces. * * The full definition of this class is in the file normalspec-impl.h . */ struct AlmostNormalSpec; /** * Converts a set of embedded vertex normal surfaces in * (quad or quad-oct) space to a set of embedded vertex normal * surfaces in (standard normal or standard almost normal) space. * The original (quad or quad-oct) space surfaces are passed in * the argument \a quadList, and the resulting (standard normal * or standard almost normal) space surfaces will be inserted * directly into this list. * * See quadToStandard() and quadOctToStandardAN() for full details * and preconditions for this procedure. * * This routine is designed to work with surface lists that are * still under construction. As such, it ignores the packet * tree completely. The parent packet is ignored (and not changed); * instead the underlying triangulation is passed explicitly as * the argument \a owner. * * Although this routine takes a vector of non-const pointers, it * guarantees not to modify (or destroy) any of the contents. * * An optional progress tracker may be passed. If so, this routine * will update the percentage progress and poll for cancellation * requests. It will be assumed that an appropriate stage has already * been declared via NProgressTracker::newStage() before this routine * is called, and that NProgressTracker::setFinished() will be * called after this routine returns. * * Although this is a template function, it is a private * template function and so is only defined in the one .cpp * file that needs it. * * \pre The template argument \a Variant is either NormalSpec * or AlmostNormalSpec, according to whether we are doing the * work for quadToStandard() or quadOctToStandardAN() respectively. * \pre The coordinate system for this surface list is set to * NS_STANDARD or NS_AN_STANDARD, according to whether we are doing * the work for quadToStandard() or quadOctToStandardAN() respectively, * and the embedded-only flag is set to \c true. * \pre The given triangulation is valid and non-empty, and the link * of every vertex is either a sphere or a disc. * * @param owner the triangulation upon which this list of * surfaces is to be based. * @param reducedList a full list of vertex surfaces in * (quad or quad-oct) coordinates for the given triangulation. * @param tracker a progress tracker to be used for progress reporting * and cancellation requests, or 0 if this is not required. */ template void buildStandardFromReduced(NTriangulation* owner, const std::vector& reducedList, NProgressTracker* tracker = 0); /** * Implements the one-template-argument version of * buildStandardFromReduced() using the specified bitmask type * to store zero sets. See the one-template-argument * buildStandardFromReduced() for further information on this routine, * including important preconditions. * * The one-template-argument buildStandardFromReduced() simply * chooses an appropriate bitmask type and then calls this * routine, which does the real work. * * \pre The template argument \a BitmaskType can support * bitmasks of size 7 \a n (if we are using normal surfaces) or size * 10 \a n (if we are using almost normal surfaces), where \a n is * the number of tetrahedra in the given triangulation. */ template void buildStandardFromReducedUsing(NTriangulation* owner, const std::vector& reducedList, NProgressTracker* tracker); /** * Converts a set of embedded vertex surfaces in (quad or quad-oct) * space to a set of embedded vertex surfaces in (standard normal or * standard almost normal) space. * * This is a generic implementation that performs the real work * for both quadToStandard() and quadOctToStandardAN(). See each * of those routines for further details as well as relevant * preconditions and postconditions. * * \pre The template argument \a Variant is either NormalSpec * or AlmostNormalSpec, according to whether we are implementing * quadToStandard() or quadOctToStandardAN() accordingly. */ template NNormalSurfaceList* internalReducedToStandard() const; /** * Converts a set of embedded vertex surfaces in * (standard normal or standard almost normal) space to a set of * embedded vertex surfaces in (quad or quad-oct) space. * * This is a generic implementation that performs the real work * for both standardToQuad() and standardANToQuadOct(). See each * of those routines for further details as well as relevant * preconditions and postconditions. * * \pre The template argument \a Variant is either NormalSpec * or AlmostNormalSpec, according to whether we are implementing * standardToQuad() or standardANToQuadOct() accordingly. */ template NNormalSurfaceList* internalStandardToReduced() const; /** * A thread class that performs all normal surface enumeration. * * The "real work" is in operator(), where the coordinate system * becomes a compile-time constant. The run() routine simply * farms out the real work to some instantiation of operator(). */ class Enumerator : public NThread { private: NNormalSurfaceList* list_; /**< The surface list to be filled. */ NTriangulation* triang_; /**< The triangulation in which these surfaces lie. */ NProgressTracker* tracker_; /**< The progress tracker through which progress is reported and cancellation requests are accepted, or 0 if no progress tracker is in use. */ public: /** * Creates a new enumerator thread with the given * parameters. * * @param list the surface list to be filled. * @param triang the triangulation in which these surfaces lie. * @param tracker the progress tracker to use for progress * reporting and cancellation polling, or 0 if these * capabilities are not required. */ Enumerator(NNormalSurfaceList* list, NTriangulation* triang, NProgressTracker* tracker); void* run(void*); /** * Performs the real enumeration work, in a setting * where the underlying coordinate system is * a compile-time constant. * * We assume here that neither list_->which_ nor * list_->algorithm_ have been sanity-checked. * * This routine fills \a list_ with surfaces, and then once * this is finished it inserts \a list_ into the packet * tree as a child of \a triang_. */ template void operator() (Coords); private: /** * The enumeration code for enumerating vertex surfaces. * This is internal to operator(). * * We assume that the flag set which_ is set correctly, * and we do not alter it here. * We make no assumptions about the state of algorithm_, * and we set this during the course of this routine. * * This routine only fills \a list_ with surfaces. * It does not make any adjustments to the structure of * the packet tree. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). */ template void fillVertex(); /** * The enumeration code for enumerating fundamental surfaces. * This is internal to operator(). * * We assume that the flag set which_ is set correctly, * and we do not alter it here. * We make no assumptions about the state of algorithm_, * and we set this during the course of this routine. * * This routine only fills \a list_ with surfaces. * It does not make any adjustments to the structure of * the packet tree. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). */ template void fillFundamental(); /** * The enumeration code for enumerating vertex surfaces * using the double description method. * This is internal to fillVertex(). * * This routine assumes that \a algorithm_ has been set * correctly, and does not alter it. * * If \a tracker_ is non-null, this routine assumes that * an appropriate tracker stage has already been * declared, and works through that stage only. * * \pre The underlying triangulation is non-empty. */ template void fillVertexDD(); /** * The enumeration code for enumerating vertex surfaces * using the tree traversal method. * This is internal to fillVertex(). * * This routine assumes that \a algorithm_ has been set * correctly, and does not alter it. * * If \a tracker_ is non-null, this routine assumes that * an appropriate tracker stage has already been * declared, and works through that stage only. * * \pre We are enumerating embedded surfaces only. * \pre The underlying triangulation is non-empty. */ template void fillVertexTree(); /** * Internal code for fillVertexTree() in which the underlying * integer type for the tree traversal method is fixed. * * This does all of the work for fillVertexTree(), aside * from the initial selection of an integer type. See * the nodes for fillVertexTree() for further details. * * \pre We are enumerating embedded surfaces only. * \pre The underlying triangulation is non-empty. * \pre The given integer type is known to be sufficient * (i.e., will not overflow) for the enumeration problem * under consideration. */ template void fillVertexTreeWith(); /** * The enumeration code for enumerating fundamental surfaces * using the primal method. * This is internal to fillFundamental(). * * This routine assumes nothing about the state of the * \a algorithm_ flag set, and sets it appropriately. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). * * \pre The underlying triangulation is non-empty. */ template void fillFundamentalPrimal(); /** * The enumeration code for enumerating fundamental surfaces * using the dual method. * This is internal to fillFundamental(). * * This routine assumes nothing about the state of the * \a algorithm_ flag set, and sets it appropriately. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). * * \pre The underlying triangulation is non-empty. */ template void fillFundamentalDual(); /** * The enumeration code for enumerating fundamental surfaces * using a slow Contejean-Devie method. * This is internal to fillFundamental(). * * This routine assumes nothing about the state of the * \a algorithm_ flag set, and sets it appropriately. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). * * \pre The underlying triangulation is non-empty. */ template void fillFundamentalCD(); /** * The enumeration code for enumerating fundamental surfaces * using a slow full cone enumeration. * This is internal to fillFundamental(). * * This routine assumes nothing about the state of the * \a algorithm_ flag set, and sets it appropriately. * * If \a tracker_ is non-null, this routine declare and * work through a series of tracker stages whose * combined weights sum to 1. It will not, however, * call NProgressTracker::setFinished(). * * \pre The underlying triangulation is non-empty. */ template void fillFundamentalFullCone(); }; friend class regina::NXMLNormalSurfaceListReader; }; /** * Returns a new normal surface vector of the appropriate length for the * given triangulation and the given coordinate system. * All elements of this vector will be initialised to zero. * * The new vector will be of the subclass of NNormalSurfaceVector * corresponding to the given coordinate system. The caller * of this routine is responsible for destroying the new vector. * * \ifacespython Not present. * * @param triangulation the triangulation upon which the underlying * coordinate system is based. * @param coords the coordinate system to be used. * @return a new zero vector of the correct class and length. */ REGINA_API NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation, NormalCoords coords); /** * Creates a new set of normal surface matching equations for the * given triangulation using the given coordinate system. * The returned matrix will be newly allocated and its destruction will * be the responsibility of the caller of this routine. * * Each equation will be represented as a row of the matrix. * Each column of the matrix represents a coordinate in the given * coordinate system. * * @param triangulation the triangulation upon which these matching equations * will be based. * @param coords the coordinate system to be used. * @return a newly allocated set of matching equations. */ REGINA_API NMatrixInt* makeMatchingEquations(NTriangulation* triangulation, NormalCoords coords); /** * Creates a new set of validity constraints representing the condition that * normal surfaces be embedded. The validity constraints will be expressed * relative to the given coordinate system. * * \ifacespython Not present. * * @param triangulation the triangulation upon which these validity constraints * will be based. * @param coords the coordinate system to be used. * @return a newly allocated set of constraints. */ REGINA_API NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation, NormalCoords coords); /*@}*/ // Inline functions for NNormalSurfaceList inline NNormalSurfaceList::~NNormalSurfaceList() { for_each(surfaces.begin(), surfaces.end(), FuncDelete()); } inline NormalCoords NNormalSurfaceList::getFlavour() const { return coords_; } inline NormalCoords NNormalSurfaceList::flavour() const { return coords_; } inline NormalCoords NNormalSurfaceList::coords() const { return coords_; } inline NormalList NNormalSurfaceList::which() const { return which_; } inline NormalAlg NNormalSurfaceList::algorithm() const { return algorithm_; } inline bool NNormalSurfaceList::isEmbeddedOnly() const { return which_.has(NS_EMBEDDED_ONLY); } inline unsigned long NNormalSurfaceList::getNumberOfSurfaces() const { return surfaces.size(); } inline const NNormalSurface* NNormalSurfaceList::getSurface( unsigned long index) const { return surfaces[index]; } inline bool NNormalSurfaceList::dependsOnParent() const { return true; } inline NNormalSurfaceList* NNormalSurfaceList::enumerate( NTriangulation* owner, NormalCoords coords, bool embeddedOnly, NProgressTracker* tracker) { return enumerate(owner, coords, NS_VERTEX | (embeddedOnly ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_ALG_DEFAULT, tracker); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateStandardDirect( NTriangulation* owner) { return enumerate(owner, NS_STANDARD, NS_VERTEX | NS_EMBEDDED_ONLY, NS_VERTEX_STD_DIRECT); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateStandardANDirect( NTriangulation* owner) { return enumerate(owner, NS_AN_STANDARD, NS_VERTEX | NS_EMBEDDED_ONLY, NS_VERTEX_STD_DIRECT); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateFundPrimal( NTriangulation* owner, NormalCoords coords, bool embeddedOnly, NNormalSurfaceList*, NProgressTracker* tracker) { return enumerate(owner, coords, NS_FUNDAMENTAL | (embeddedOnly ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_HILBERT_PRIMAL, tracker); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateFundDual( NTriangulation* owner, NormalCoords coords, bool embeddedOnly, NProgressTracker* tracker) { return enumerate(owner, coords, NS_FUNDAMENTAL | (embeddedOnly ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_HILBERT_DUAL, tracker); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateFundCD( NTriangulation* owner, NormalCoords coords, bool embeddedOnly) { return enumerate(owner, coords, NS_FUNDAMENTAL | (embeddedOnly ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_HILBERT_CD); } inline NNormalSurfaceList* NNormalSurfaceList::enumerateFundFullCone( NTriangulation* owner, NormalCoords coords, bool embeddedOnly) { return enumerate(owner, coords, NS_FUNDAMENTAL | (embeddedOnly ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_HILBERT_FULLCONE); } inline NMatrixInt* NNormalSurfaceList::recreateMatchingEquations() const { return makeMatchingEquations(getTriangulation(), coords_); } inline NNormalSurfaceList::VectorIterator::VectorIterator() { } inline NNormalSurfaceList::VectorIterator::VectorIterator( const NNormalSurfaceList::VectorIterator& cloneMe) : it_(cloneMe.it_) { } inline NNormalSurfaceList::VectorIterator& NNormalSurfaceList::VectorIterator:: operator =(const NNormalSurfaceList::VectorIterator& cloneMe) { it_ = cloneMe.it_; return *this; } inline bool NNormalSurfaceList::VectorIterator::operator ==( const NNormalSurfaceList::VectorIterator& other) const { return (it_ == other.it_); } inline bool NNormalSurfaceList::VectorIterator::operator !=( const NNormalSurfaceList::VectorIterator& other) const { return (it_ != other.it_); } inline const NNormalSurfaceVector* NNormalSurfaceList::VectorIterator:: operator *() const { return (*it_)->rawVector(); } inline NNormalSurfaceList::VectorIterator& NNormalSurfaceList::VectorIterator:: operator ++() { ++it_; return *this; } inline NNormalSurfaceList::VectorIterator NNormalSurfaceList::VectorIterator:: operator ++(int) { return NNormalSurfaceList::VectorIterator(it_++); } inline NNormalSurfaceList::VectorIterator& NNormalSurfaceList::VectorIterator:: operator --() { --it_; return *this; } inline NNormalSurfaceList::VectorIterator NNormalSurfaceList::VectorIterator:: operator --(int) { return NNormalSurfaceList::VectorIterator(it_--); } inline NNormalSurfaceList::VectorIterator::VectorIterator( const std::vector::const_iterator& i) : it_(i) { } inline NNormalSurfaceList::VectorIterator NNormalSurfaceList::beginVectors() const { return VectorIterator(surfaces.begin()); } inline NNormalSurfaceList::VectorIterator NNormalSurfaceList::endVectors() const { return VectorIterator(surfaces.end()); } inline NNormalSurfaceList::SurfaceInserter::SurfaceInserter( NNormalSurfaceList& newList, NTriangulation* newOwner) : list(&newList), owner(newOwner) { } inline NNormalSurfaceList::SurfaceInserter::SurfaceInserter( const SurfaceInserter& cloneMe) : list(cloneMe.list), owner(cloneMe.owner) { } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator =( const SurfaceInserter& cloneMe) { list = cloneMe.list; owner = cloneMe.owner; return *this; } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator =( NNormalSurface* surface) { list->surfaces.push_back(surface); return *this; } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator =( NNormalSurfaceVector* vector) { list->surfaces.push_back(new NNormalSurface(owner, vector)); return *this; } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator *() { return *this; } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator ++() { return *this; } inline NNormalSurfaceList::SurfaceInserter& NNormalSurfaceList::SurfaceInserter::operator ++(int) { return *this; } inline NNormalSurfaceList::NNormalSurfaceList(NormalCoords coords, NormalList which, NormalAlg algorithm) : coords_(coords), which_(which), algorithm_(algorithm) { } inline NNormalSurfaceList::Enumerator::Enumerator(NNormalSurfaceList* list, NTriangulation* triang, NProgressTracker* tracker) : list_(list), triang_(triang), tracker_(tracker) { } } // namespace regina #endif regina-4.95/engine/surfaces/normalcoords.h000644 000765 000024 00000012610 12234011536 020537 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/normalcoords.h * \brief Defines constants for normal surface coordinate systems. */ #ifndef __NORMALCOORDS_H #ifndef __DOXYGEN #define __NORMALCOORDS_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Represents different coordinate systems that can * be used for enumerating and displaying normal surfaces. * * IDs 0-9999 are reserved for future use by Regina. If you are extending * Regina to include your own coordinate system, you should choose * an ID >= 10000. */ enum NormalCoords { /** * Represents standard triangle-quadrilateral coordinates for * normal surfaces. */ NS_STANDARD = 0, /** * Represents quadrilateral coordinates for normal surfaces. * For details, see "Normal surface Q-theory", Jeffrey L. Tollefson, * Pacific J. Math. 183 (1998), no. 2, 359--374. */ NS_QUAD = 1, /** * Indicates that a list of almost normal surfaces was created * using Regina 4.5.1 or earlier, where surfaces with more than * one octagon of the same type were stripped out of the final * solution set. As of Regina 4.6 such surfaces are now * included in the solution set, since we need them if we * wish to enumerate \e all almost normal surfaces (not just * the \e vertex almost normal surfaces). * * This coordinate system is only used with legacy data files; new vectors * and lists cannot be created in this coordinate system. The underlying * coordinates are identical to those of NS_AN_STANDARD. */ NS_AN_LEGACY = 100, /** * Represents quadrilateral-octagon coordinates for octagonal * almost normal surfaces. For details, see * "Quadrilateral-octagon coordinates for almost normal surfaces", * Benjamin A. Burton, Experiment. Math. 19 (2010), 285-315. */ NS_AN_QUAD_OCT = 101, /** * Represents standard triangle-quadrilateral-octagon coordinates * for octagonal almost normal surfaces. */ NS_AN_STANDARD = 102, /** * Represents edge weight coordinates for normal surfaces. * This coordinate system is for display only; surface * vectors and lists cannot be created in this coordinate system. */ NS_EDGE_WEIGHT = 200, /** * Represents triangle arc coordinates for normal surfaces. * This coordinate system is for display only; surface * vectors and lists cannot be created in this coordinate system. */ NS_TRIANGLE_ARCS = 201, /** * A deprecated alias for NS_TRIANGLE_ARCS. * This represents triangle arc coordinates for normal surfaces. * See NS_TRIANGLE_ARCS for further details. * * \deprecated This constant will be removed in a future version * of Regina. Please use NS_TRIANGLE_ARCS instead. */ NS_FACE_ARCS = 201, /** * Represents standard triangle-quadrilateral coordinates for * transversely oriented normal surfaces. */ NS_ORIENTED = 300, /** * Represents quadrilateral coordinates for transversely oriented * normal surfaces. */ NS_ORIENTED_QUAD = 301 }; /*@}*/ } // namespace regina #endif regina-4.95/engine/surfaces/normalflags.h000644 000765 000024 00000035055 12236546232 020362 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/normalflags.h * \brief Defines constants and flags for normal surface enumeration. */ #ifndef __NORMALFLAGS_H #ifndef __DOXYGEN #define __NORMALFLAGS_H #endif #include "regina-core.h" #include "utilities/flags.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Represents different lists of normal surfaces that might be constructed * for a given triangulation. * * The NormalList enumeration refers to the \e contents of the list, * whereas the NormalAlgFlags enumeration refers to the \e algorithm * used to build it. * * \ifacespython The values in this enumeration type are present, but * they are treated by Python as NormalList objects (and they can be * combined and/or queried as such). * The underlying enumeration type is not exposed to Python. */ enum NormalListFlags { /** * An empty flag, indicating to an enumeration routine that it * should use its default behaviour. * The numeric value of this flag is zero (i.e., it has no effect * when combined with other flags using bitwise OR). */ NS_LIST_DEFAULT = 0x0000, /** * Indicates that this list is restricted to properly embedded * surfaces only. * * This flag is incompatible with NS_IMMERSED_SINGULAR. */ NS_EMBEDDED_ONLY = 0x0001, /** * Indicates that the scope of this list includes not just properly * embedded surfaces, but also immersed and/or branched surfaces. * * This is no guarantee that the list \e contains immersed and/or * branched surfaces; it merely states that such surfaces have not * been explicitly excluded (in particular, the quadrilateral * constraints have not been enforced). * * This flag is incompatible with NS_EMBEDDED_ONLY. */ NS_IMMERSED_SINGULAR = 0x0002, /** * Indicates a list of all vertex normal surfaces, with respect to * the particular normal coordinate system used by the list. * * This flag is incompatible with NS_FUNDAMENTAL. */ NS_VERTEX = 0x0004, /** * Indicates a list of all fundamental normal surfaces, with respect to * the particular normal coordinate system used by the list. * * This flag is incompatible with NS_VERTEX. */ NS_FUNDAMENTAL = 0x0008, /** * Indicates a list that was constructed using an old version of * Regina (4.93 or earlier). * * These older versions did not retain details of how each list was * constructed, beyond whether immersed and/or singular surfaces were * included. Therefore no information is available for such lists, * other than the presence or absence of the NS_EMBEDDED_ONLY and/or * NS_IMMERSED_SINGULAR flags. * * If this flag is passed to an enumeration routine, it will be ignored. */ NS_LEGACY = 0x4000, /** * Indicates some other type of list, typically hand-crafted by the * user or built by some customised algorithm. * * If this flag is passed to an enumeration routine, it will be ignored. */ NS_CUSTOM = 0x8000 }; /** * A combination of flags for types of normal surface lists. * * \ifacespython This is present, and all values in the NormalListFlags * enumeration type are treated as members of this NormalList class. */ typedef regina::Flags NormalList; /** * Returns the bitwise OR of the two given flags. * * @param lhs the first flag to combine. * @param rhs the second flag to combine. * @return the combination of both flags. */ inline NormalList operator | (NormalListFlags lhs, NormalListFlags rhs) { return NormalList(lhs) | rhs; } /** * Represents options and variants of algorithms for enumerating various * types of normal surfaces. * * These options are typically combined in a bitwise fashion using the * NormalAlgs type, and then passed to enumeration routines such as * NNormalSurfaceList::vertex(). * * \ifacespython The values in this enumeration type are present, but * they are treated by Python as NormalList objects (and they can be * combined and/or queried as such). * The underlying enumeration type is not exposed to Python. */ enum NormalAlgFlags { /** * An empty flag, indicating to an enumeration routine that it * should use its default behaviour. * The numeric value of this flag is zero (i.e., it has no effect * when combined with other flags using bitwise OR). */ NS_ALG_DEFAULT = 0x0000, /** * When enumerating in standard normal or almost normal coordinates, * this flag indicates that the algorithm should first enumerate in * quadrilateral or quadrilateral-octagon coordinates, and then * expand this "reduced" solution set to the (typically larger) * "standard" solution set. * * This is typically much faster than a direct enumeration in * standard normal or almost normal coordinates, and enumeration * routines will use this option where possible unless explicitly * requested not to (via the flag NS_VERTEX_STD_DIRECT). * * For an explanation of this procedure, see B. A. Burton, * "Converting between quadrilateral and standard solution sets in * normal surface theory", Algebr. Geom. Topol. 9 (2009), 2121-2174. * * This flag is incompatible with NS_VERTEX_STD_DIRECT. */ NS_VERTEX_VIA_REDUCED = 0x0001, /** * When enumerating in standard normal or almost normal coordinates, * this flag indicates that the algorithm should work directly in * that coordinate system, and should not go via the "reduced" * (quadrilateral or quadrilateral-octagon) coordinate system. * * This is typically \e much slower than going via the reduced * system, and users should only request this if they have a * specialised need. See NS_VERTEX_VIA_REDUCED for further information. * * This flag is incompatible with NS_VERTEX_VIA_REDUCED. */ NS_VERTEX_STD_DIRECT = 0x0002, /** * When enumerating vertex normal surfaces, * this flag indicates that the tree traversal algorithm should be * used. * * This algorithm is based on linear and integer programming * techniques, and has many desirable properties including a * relatively low overhead. Enumeration algorithms will use it if * possible unless a different method is explicitly requested. * * For details on the tree traversal algorithm, see B. A. Burton and * M. Ozlen, "A tree traversal algorithm for decision problems in * knot theory and 3-manifold topology", Algorithmica 65 (2013), * pp. 772-801. * * This flag is incompatible with NS_VERTEX_DD. */ NS_VERTEX_TREE = 0x0010, /** * When enumerating vertex normal surfaces, * this flag indicates that a modified double description method * should be used. * * This algorithm can suffer from a combinatorial explosion with * larger problems, leading to extremely large time and memory * footprints. Users should only request this if they have some * specialised need. * * For details on the modified double description method, see * B. A. Burton, "Optimizing the double description method for * normal surface enumeration", Mathematics of Computation * 79 (2010), pp. 453-484. * * This flag is incompatible with NS_VERTEX_TREE. */ NS_VERTEX_DD = 0x0020, /** * When enumerating fundamental normal surfaces, * this flag indicates that the primal method should be used for * enumerating a Hilbert basis. * * The primal method is recommended, and enumeration algorithms will * use it if possible unless a different method is explicitly requested. * This method uses code from Normaliz for parts of its processing. * * For details and comparisons of the various options for enumerating * fundamental normal surfaces, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * This flag is incompatible with NS_HILBERT_DUAL, * NS_HILBERT_CD and NS_HILBERT_FULLCONE. */ NS_HILBERT_PRIMAL = 0x0100, /** * When enumerating fundamental normal surfaces, * this flag indicates that the dual method should be used for * enumerating a Hilbert basis. * * The dual method is fast (like the primal method), but its * performance is highly variable; for this reason the primal method * is recommended instead. * This method does not make use of Normaliz, and is the recommended * method for situations in which Normaliz is not available for some * reason. * * For details and comparisons of the various options for enumerating * fundamental normal surfaces, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * This flag is incompatible with NS_HILBERT_PRIMAL, * NS_HILBERT_CD and NS_HILBERT_FULLCONE. */ NS_HILBERT_DUAL = 0x0200, /** * When enumerating fundamental normal surfaces, * this flag indicates that a modified Contejean-Devie procedure should * be used for enumerating a Hilbert basis. * * The Contejean-Devie procedure is typically \e much slower * than either the primal or dual method, and users should only * request it if they have some specialised need. * * For details and comparisons of the various options for enumerating * fundamental normal surfaces, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * This flag is incompatible with NS_HILBERT_PRIMAL, * NS_HILBERT_DUAL and NS_HILBERT_FULLCONE. */ NS_HILBERT_CD = 0x0400, /** * When enumerating fundamental normal surfaces, * this flag indicates that a Hilbert basis for the full solution * cone should be constructed, and additional combinatorial * constraints (such as the quadrilateral constraints) should only * be enforced as the final step. * * If you are only enumerating properly embedded surfaces * then this procedure \e extremely slow, and users should only * request it if they have some specialised need. * * For details and comparisons of the various options for enumerating * fundamental normal surfaces, see B. A. Burton, "Enumerating * fundamental normal surfaces: Algorithms, experiments and invariants", * to appear in ALENEX 2014: Proceedings of the Meeting on * Algorithm Engineering & Experiments, arXiv:1111.7055. * * This flag is incompatible with NS_HILBERT_PRIMAL, * NS_HILBERT_DUAL and NS_HILBERT_CD. */ NS_HILBERT_FULLCONE = 0x0800, /** * Indicates that a normal surface list was enumerated using an * older version of Regina (4.93 or earlier). * * These older versions did not retain details of the algorithm * used to build each list, and so in such cases no further * algorithmic information is available. * * If this flag is passed to an enumeration algorithm, it will be ignored. */ NS_ALG_LEGACY = 0x4000, /** * Indicates that a normal surface list was built using a customised * algorithm. In such cases, no further details on the algorithm are * available. * * If this flag is passed to an enumeration algorithm, it will be ignored. */ NS_ALG_CUSTOM = 0x8000 }; /** * A combination of flags for types of normal surface lists. * * \ifacespython This is present, and all values in the NormalAlgFlags * enumeration type are treated as members of this NormalAlg class. */ typedef regina::Flags NormalAlg; /** * Returns the bitwise OR of the two given flags. * * @param lhs the first flag to combine. * @param rhs the second flag to combine. * @return the combination of both flags. */ inline NormalAlg operator | (NormalAlgFlags lhs, NormalAlgFlags rhs) { return NormalAlg(lhs) | rhs; } /*@}*/ } // namespace regina #endif regina-4.95/engine/surfaces/normalspec-impl.h000644 000765 000024 00000017725 12234011536 021153 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "regina-core.h" #include "surfaces/nnormalsurfacelist.h" #include "surfaces/nsstandard.h" #include "surfaces/nsquad.h" #include "surfaces/nsanstandard.h" #include "surfaces/nsquadoct.h" namespace regina { /** * Constants, typedefs and operations for working with normal (as * opposed to almost normal) surfaces. See the declaration in * nnormalsurfacelist.h for further details. * * For both normal and almost normal surfaces, we refer to different * coordinate systems by the generic names \e standard form and * \e reduced form. Standard form is where we store coordinates for all * disc types (e.g., the standard tri-quad coordinates for normal * surfaces), and reduced form is where we only store coordinates for * non-triangular disc types (e.g., the quadrilateral coordinates of * Tollefson). */ struct REGINA_API NNormalSurfaceList::NormalSpec { /** * The underlying class for vectors in standard form. */ typedef NNormalSurfaceVectorStandard StandardVector; /** * The underlying class for vectors in reduced form. */ typedef NNormalSurfaceVectorQuad ReducedVector; /** * Returns the coordinate system constant corresponding to standard form. * * This has been made a routine (not a constant) to ensure the * correct return type. Perhaps this can be changed to an enum some day... */ inline static NormalCoords standardCoords() { return NS_STANDARD; } /** * Returns the coordinate system constant corresponding to reduced form. * * This has been made a routine (not a constant) to ensure the * correct return type. Perhaps this can be changed to an enum some day... */ inline static NormalCoords reducedCoords() { return NS_QUAD; } /** * Returns the number of coordinates per tetrahedron in standard form. */ static const unsigned totalPerTet = 7; /** * Returns the number of coordinates per tetrahedron in reduced form. */ static const unsigned reducedPerTet = 3; /** * Returns the total length of a vector in standard form for the * given number of tetrahedra. */ inline static size_t stdLen(unsigned long nTet) { return 7 * nTet; } /** * Returns the total length of a vector in reduced form for the * given number of tetrahedra. */ inline static size_t redLen(unsigned long nTet) { return 3 * nTet; } /** * Returns the coordinate number in standard form that corresponds * to the given disc type within the given tetrahedron. */ inline static size_t stdPos(unsigned long tet, unsigned discType) { return 7 * tet + discType; } /** * Returns the coordinate number in reduced form that corresponds * to the given disc type within the given tetrahedron. */ inline static size_t redPos(unsigned long tet, unsigned discType) { return 3 * tet + discType; } }; /** * Constants, typedefs and operations for working with almost normal (as * opposed to almost normal) surfaces. See the declaration in * nnormalsurfacelist.h for further details. * * For both normal and almost normal surfaces, we refer to different * coordinate systems by the generic names \e standard form and * \e reduced form. Standard form is where we store coordinates for all * disc types (e.g., the standard tri-quad-oct coordinates for almost normal * surfaces), and reduced form is where we only store coordinates for * non-triangular disc types (e.g., quad-oct coordinates for almost * normal surfaces). */ struct REGINA_API NNormalSurfaceList::AlmostNormalSpec { /** * The underlying class for vectors in standard form. */ typedef NNormalSurfaceVectorANStandard StandardVector; /** * The underlying class for vectors in reduced form. */ typedef NNormalSurfaceVectorQuadOct ReducedVector; /** * Returns the coordinate system constant corresponding to standard form. * * This has been made a routine (not a constant) to ensure the * correct return type. Perhaps this can be changed to an enum some day... */ inline static NormalCoords standardCoords() { return NS_AN_STANDARD; } /** * Returns the coordinate system constant corresponding to reduced form. * * This has been made a routine (not a constant) to ensure the * correct return type. Perhaps this can be changed to an enum some day... */ inline static NormalCoords reducedCoords() { return NS_AN_QUAD_OCT; } /** * Returns the number of coordinates per tetrahedron in standard form. */ static const unsigned totalPerTet = 10; /** * Returns the number of coordinates per tetrahedron in reduced form. */ static const unsigned reducedPerTet = 6; /** * Returns the total length of a vector in standard form for the * given number of tetrahedra. */ inline static size_t stdLen(unsigned long nTet) { return 10 * nTet; } /** * Returns the total length of a vector in reduced form for the * given number of tetrahedra. */ inline static size_t redLen(unsigned long nTet) { return 6 * nTet; } /** * Returns the coordinate number in standard form that corresponds * to the given disc type within the given tetrahedron. */ inline static size_t stdPos(unsigned long tet, unsigned discType) { return 10 * tet + discType; } /** * Returns the coordinate number in reduced form that corresponds * to the given disc type within the given tetrahedron. */ inline static size_t redPos(unsigned long tet, unsigned discType) { return 6 * tet + discType; } }; } // namespace regina regina-4.95/engine/surfaces/normalspec.tcc000644 000765 000024 00000004662 12234011536 020532 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/normalspec.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use normalspec-impl.h instead. #include "surfaces/normalspec-impl.h" regina-4.95/engine/surfaces/nprism.cpp000644 000765 000024 00000006327 12234011536 017710 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nnormalsurface.h" #include "surfaces/nprism.h" #include "triangulation/ntriangulation.h" namespace regina { std::ostream& operator << (std::ostream& out, const NPrismSpec& spec) { out << '(' << spec.tetIndex << ", " << spec.edge << ')'; return out; } NPrismSetSurface::NPrismSetSurface(const NNormalSurface& surface) { NTriangulation* tri = surface.getTriangulation(); unsigned long nTet = tri->getNumberOfTetrahedra(); if (nTet == 0) { quadType = 0; return; } // Work out which tetrahedra contain which quad types. quadType = new signed char[nTet]; for (unsigned long tet = 0; tet < nTet; tet++) if (surface.getQuadCoord(tet, 0) != 0) quadType[tet] = 0; else if (surface.getQuadCoord(tet, 1) != 0) quadType[tet] = 1; else if (surface.getQuadCoord(tet, 2) != 0) quadType[tet] = 2; else quadType[tet] = -1; } } // namespace regina regina-4.95/engine/surfaces/nprism.h000644 000765 000024 00000020000 12234011536 017335 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nprism.h * \brief Deals with triangular prisms defined by slicing along normal * quads in a tetrahedron. */ #ifndef __NPRISM_H #ifndef __DOXYGEN #define __NPRISM_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup surfaces * @{ */ class NNormalSurface; /** * Specifies a single triangular prism in a tetrahedron. * * If a tetrahedron contains normal quads, slicing along these quads * splits the tetrahedron into two triangular prisms (and possibly some * additional product regions). Each triangular prism contains two of * the vertices and one of the edges of the original tetrahedron. * * \pre This class should only be used with \a embedded * normal surfaces. */ struct REGINA_API NPrismSpec { unsigned long tetIndex; /**< The index in the triangulation of the tetrahedron containing the prism. */ int edge; /**< The edge of the tetrahedron that is contained in this prism. */ /** * Creates a new uninitialised prism specifier. */ NPrismSpec(); /** * Creates a new prism specifier containing the given values. * * @param newTetIndex the index in the triangulation of the tetrahedron * containing the prism. * @param newEdge the edge of the tetrahedron that is contained in * this prism; this must be between 0 and 5 inclusive. */ NPrismSpec(unsigned long newTetIndex, int newEdge); /** * Creates a new prism specifier that is a clone of the given specifier. * * @param cloneMe the prism specifier to clone. */ NPrismSpec(const NPrismSpec& cloneMe); /** * Copies the values from the given prism specifier into this specifier. * * @param cloneMe the prism specifier whose values should be copied. * @return a reference to this prism specifier. */ NPrismSpec& operator = (const NPrismSpec& cloneMe); /** * Determines if this and the given prism specifier contain identical * information. * * @param other the prism specifier to compare with this. * @return \c true if and only if this and the given prism specifier * contain identical information. */ bool operator == (const NPrismSpec& other) const; friend std::ostream& operator << (std::ostream& out, const NPrismSpec& spec); }; /** * Writes the given prism specifier to the given output stream. * The prism specifier will be written as a pair (tetIndex, edge). * * @param out the output stream to which to write. * @param spec the prism specifier to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NPrismSpec& spec); /** * Represents the set of prisms defined by slicing along all the quads * in a particular normal surface. * * Note that each tetrahedron in the underlying triangulation will supply * either zero or two prisms (depending upon whether or not it contains * any normal quads). * * \pre This class should only be used with \e embedded normal surfaces * containing no octagonal discs. * * \warning This class doesn't really do much as yet. */ class REGINA_API NPrismSetSurface { private: signed char* quadType; /**< A list of which types of normal quad are contained in which tetrahedra. Tetrahedra containing no quads at all will have quad type -1 in this array. */ public: /** * Creates a new prism set corresponding to the prisms defined * by the given normal surface. * * \pre The given normal surface is embedded and contains no * octagonal discs. * * @param surface the normal surface that defines the prisms in * this set. */ NPrismSetSurface(const NNormalSurface& surface); /** * Destroys this prism set. */ virtual ~NPrismSetSurface(); /** * Returns the quadrilateral type with which the underlying * normal surface meets the given tetrahedron. Note that the * surface might contain many quadrilateral discs of this type. * However, since the underlying surface is embedded, there * cannot be more than one such quadrilateral type. * * @param tetIndex the index in the triangulation of the * tetrahedron in which we are interested; this should be * between 0 and NTriangulation::getNumberOfTetrahedra()-1 * inclusive. * @return the quadrilateral type found within this tetrahedron. * This is 0, 1 or 2 and represents the same type parameter as is * used by NNormalSurface::getQuadCoord(). If the underlying surface * does not meet the given tetrahedron in any quadrilateral discs, * this routine returns -1. */ signed char getQuadType(unsigned long tetIndex) const; }; /*@}*/ // Inline functions for NPrismSpec inline NPrismSpec::NPrismSpec() { } inline NPrismSpec::NPrismSpec(unsigned long newTetIndex, int newEdge) : tetIndex(newTetIndex), edge(newEdge) { } inline NPrismSpec::NPrismSpec(const NPrismSpec& cloneMe) : tetIndex(cloneMe.tetIndex), edge(cloneMe.edge) { } inline NPrismSpec& NPrismSpec::operator = (const NPrismSpec& cloneMe) { tetIndex = cloneMe.tetIndex; edge = cloneMe.edge; return *this; } inline bool NPrismSpec::operator == (const NPrismSpec& other) const { return (tetIndex == other.tetIndex && edge == other.edge); } // Inline functions for NPrismSetSurface inline NPrismSetSurface::~NPrismSetSurface() { if (quadType) delete[] quadType; } inline signed char NPrismSetSurface::getQuadType(unsigned long tetIndex) const { return quadType[tetIndex]; } } // namespace regina #endif regina-4.95/engine/surfaces/nsanstandard.cpp000644 000765 000024 00000017012 12234011536 021051 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/nenumconstraint.h" #include "surfaces/nsanstandard.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NLargeInteger NNormalSurfaceVectorANStandard::getEdgeWeight( unsigned long edgeIndex, NTriangulation* triang) const { // Find a tetrahedron next to the edge in question. const NEdgeEmbedding& emb = triang->getEdges()[edgeIndex]-> getEmbeddings().front(); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int start = emb.getVertices()[0]; int end = emb.getVertices()[1]; // Add up the triangles, quads and octagons meeting that edge. // Triangles: NLargeInteger ans((*this)[10 * tetIndex + start]); ans += (*this)[10 * tetIndex + end]; // Quads: ans += (*this)[10 * tetIndex + 4 + vertexSplitMeeting[start][end][0]]; ans += (*this)[10 * tetIndex + 4 + vertexSplitMeeting[start][end][1]]; // Octagons: ans += (*this)[10 * tetIndex + 7]; ans += (*this)[10 * tetIndex + 8]; ans += (*this)[10 * tetIndex + 9]; ans += (*this)[10 * tetIndex + 7 + vertexSplit[start][end]]; return ans; } NLargeInteger NNormalSurfaceVectorANStandard::getTriangleArcs( unsigned long triIndex, int triVertex, NTriangulation* triang) const { // Find a tetrahedron next to the triangle in question. const NTriangleEmbedding& emb = triang->getTriangles()[triIndex]-> getEmbedding(0); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int vertex = emb.getVertices()[triVertex]; int backOfFace = emb.getVertices()[3]; // Add up the discs meeting that triangle in that required arc. // Triangles: NLargeInteger ans((*this)[10 * tetIndex + vertex]); // Quads: ans += (*this)[10 * tetIndex + 4 + vertexSplit[vertex][backOfFace]]; // Octagons: ans += (*this)[10 * tetIndex + 7 + vertexSplitMeeting[vertex][backOfFace][0]]; ans += (*this)[10 * tetIndex + 7 + vertexSplitMeeting[vertex][backOfFace][1]]; return ans; } NNormalSurfaceVector* NNormalSurfaceVectorANStandard::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorANStandard( 10 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorANStandard::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 10 * triangulation->getNumberOfTetrahedra(); // Three equations per non-boundary triangle. // F_boundary + 2 F_internal = 4 T long nEquations = 3 * (4 * long(triangulation->getNumberOfTetrahedra()) - long(triangulation->getNumberOfTriangles())); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); // Run through each internal triangle and add the corresponding three // equations. unsigned row = 0; int i; unsigned long tet0, tet1; NPerm4 perm0, perm1; for (NTriangulation::TriangleIterator fit = triangulation->getTriangles().begin(); fit != triangulation->getTriangles().end(); fit++) { if (! (*fit)->isBoundary()) { tet0 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(0).getTetrahedron()); tet1 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(1).getTetrahedron()); perm0 = (*fit)->getEmbedding(0).getVertices(); perm1 = (*fit)->getEmbedding(1).getVertices(); for (i=0; i<3; i++) { // Triangles: ans->entry(row, 10*tet0 + perm0[i]) += 1; ans->entry(row, 10*tet1 + perm1[i]) -= 1; // Quads: ans->entry(row, 10*tet0 + 4 + vertexSplit[perm0[i]][perm0[3]]) += 1; ans->entry(row, 10*tet1 + 4 + vertexSplit[perm1[i]][perm1[3]]) -= 1; // Octagons: ans->entry(row, 10*tet0 + 7 + vertexSplitMeeting[perm0[i]][perm0[3]][0]) += 1; ans->entry(row, 10*tet1 + 7 + vertexSplitMeeting[perm1[i]][perm1[3]][0]) -= 1; ans->entry(row, 10*tet0 + 7 + vertexSplitMeeting[perm0[i]][perm0[3]][1]) += 1; ans->entry(row, 10*tet1 + 7 + vertexSplitMeeting[perm1[i]][perm1[3]][1]) -= 1; row++; } } } return ans; } NEnumConstraintList* NNormalSurfaceVectorANStandard::makeEmbeddedConstraints( NTriangulation* triangulation) { // At most one quad/oct per tetrahedron. // Also at most one oct type overall. NEnumConstraintList* ans = new NEnumConstraintList( triangulation->getNumberOfTetrahedra() + 1); unsigned base = 0; for (unsigned c = 1; c < ans->size(); ++c) { (*ans)[c].insert((*ans)[c].end(), base + 4); (*ans)[c].insert((*ans)[c].end(), base + 5); (*ans)[c].insert((*ans)[c].end(), base + 6); (*ans)[c].insert((*ans)[c].end(), base + 7); (*ans)[c].insert((*ans)[c].end(), base + 8); (*ans)[c].insert((*ans)[c].end(), base + 9); (*ans)[0].insert((*ans)[0].end(), base + 7); (*ans)[0].insert((*ans)[0].end(), base + 8); (*ans)[0].insert((*ans)[0].end(), base + 9); base += 10; } return ans; } } // namespace regina regina-4.95/engine/surfaces/nsanstandard.h000644 000765 000024 00000014513 12234011536 020521 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsanstandard.h * \brief Implements almost normal surface vectors using standard * triangle-quad-oct coordinates. */ #ifndef __NSANSTANDARD_H #ifndef __DOXYGEN #define __NSANSTANDARD_H #endif #include "regina-core.h" #include "surfaces/nnormalsurface.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorANStandard; /** * \weakgroup surfaces * @{ */ /** * Stores information about standard almost normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorANStandard Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Standard almost normal (tri-quad-oct)"; } enum { almostNormal = 1, spun = 0, oriented = 0 }; }; /** * An almost normal surface vector using standard triangle-quad-oct * coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 10t coordinates. * The first ten coordinates will be for the first tetrahedron, the * next ten for the second tetrahedron and so on. For each * tetrahedron, the first four represent the number of * triangular discs about vertex 0, 1, 2 and 3, the next * three represent the number of quadrilateral discs of type 0, * 1 and 2 (see NNormalSurface::getQuadCoord()) and the final three * represent the number of octagonal discs of type 0, 1 and 2 (see * NNormalSurface::getOctCoord()). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorANStandard : public NNormalSurfaceVector { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorANStandard, NS_AN_STANDARD) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorANStandard(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorANStandard(const NVector& cloneMe); virtual NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang) const; virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; virtual NLargeInteger getEdgeWeight(unsigned long edgeIndex, NTriangulation* triang) const; virtual NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorANStandard inline NNormalSurfaceVectorANStandard::NNormalSurfaceVectorANStandard( size_t length) : NNormalSurfaceVector(length) { } inline NNormalSurfaceVectorANStandard::NNormalSurfaceVectorANStandard( const NVector& cloneMe) : NNormalSurfaceVector(cloneMe) { } inline NLargeInteger NNormalSurfaceVectorANStandard::getTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation*) const { return (*this)[10 * tetIndex + vertex]; } inline NLargeInteger NNormalSurfaceVectorANStandard::getQuadCoord( unsigned long tetIndex, int quadType, NTriangulation*) const { return (*this)[10 * tetIndex + 4 + quadType]; } inline NLargeInteger NNormalSurfaceVectorANStandard::getOctCoord( unsigned long tetIndex, int octType, NTriangulation*) const { return (*this)[10 * tetIndex + 7 + octType]; } } // namespace regina #endif regina-4.95/engine/surfaces/nsmirrored.h000644 000765 000024 00000023246 12234011536 020230 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsmirrored.h * \brief Provides a normal surface vector that is mirrored in another * coordinate system to avoid frequent lengthy calculations. */ #ifndef __NSMIRRORED_H #ifndef __DOXYGEN #define __NSMIRRORED_H #endif #include "regina-core.h" #include "surfaces/nnormalsurface.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * A normal surface vector that is mirrored in another coordinate system * to avoid frequent lengthy calculations. When it is difficult to * convert from the native coordinate system to standard tri-quad-oct * coordinates, use this as a base class. The conversion of the entire * vector will be done once only, and future coordinate lookups will be * performed through the pre-converted mirror vector. * * Once the first coordinate lookup has taken place (via * getTriangleCoord() or the like), this vector may * not change! The mirror will be created at this point and will * not change, so if the native coordinates change further then any * requests passed to the mirror will return incorrect results. * * Subclasses need not implement any of the coordinate lookup routines. * The default implementation will be to pass the lookup to the mirror. * If any particular lookup can be done more efficiently in the native * coordinate system, the corresponding routine should be overridden. * * Subclasses must however implement the routine makeMirror() which creates * the alternate mirror vector from this vector. * * Note that cloning a vector of this class will \e not clone the * mirror. Thus a clone may be safely modified before its first * coordinate lookup routine is called. * * \todo \prob Allow modification of the vector by overwriting * setValue(); this will require documentation changes in both this * class and in NNormalSurfaceVector. * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorMirrored : public NNormalSurfaceVector { private: NNormalSurfaceVector* mirror; /**< The mirror vector. */ public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorMirrored(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorMirrored(const NVector& cloneMe); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorMirrored(const NNormalSurfaceVectorMirrored& cloneMe); /** * Destroys this vector and its mirror if appropriate. */ virtual ~NNormalSurfaceVectorMirrored(); /** * Creates a new mirror vector corresponding to this vector. * The mirror vector should represent the same normal surface as * this vector, and should have fast coordinate lookup routines * (getTriangleCoord(), getQuadCord() and so on). It is * recommended that the mirror vector be an * NNormalSurfaceVectorStandard or an NNormalSurfaceVectorANStandard. * * @param triang the triangulation in which this normal surface * lives. * @return a newly created mirror vector. */ virtual NNormalSurfaceVector* makeMirror(NTriangulation* triang) const = 0; virtual NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang) const; virtual NLargeInteger getOrientedTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang, bool orientation) const; virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const; virtual NLargeInteger getOrientedQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang, bool orientation) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; virtual NLargeInteger getEdgeWeight(unsigned long edgeIndex, NTriangulation* triang) const; virtual NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const; }; /*@}*/ // Inline functions for NNormalSurfaceVectorMirrored inline NNormalSurfaceVectorMirrored::NNormalSurfaceVectorMirrored( size_t length) : NNormalSurfaceVector(length), mirror(0) { } inline NNormalSurfaceVectorMirrored::NNormalSurfaceVectorMirrored( const NVector& cloneMe) : NNormalSurfaceVector(cloneMe), mirror(0) { } inline NNormalSurfaceVectorMirrored::NNormalSurfaceVectorMirrored( const NNormalSurfaceVectorMirrored& cloneMe) : NNormalSurfaceVector(cloneMe), mirror(0) { } inline NNormalSurfaceVectorMirrored::~NNormalSurfaceVectorMirrored() { if (mirror) delete mirror; } inline NLargeInteger NNormalSurfaceVectorMirrored::getTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation* triang) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getTriangleCoord(tetIndex, vertex, triang); } inline NLargeInteger NNormalSurfaceVectorMirrored::getOrientedTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation* triang, bool orientation) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getOrientedTriangleCoord(tetIndex, vertex, triang, orientation); } inline NLargeInteger NNormalSurfaceVectorMirrored::getQuadCoord( unsigned long tetIndex, int quadType, NTriangulation* triang) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getQuadCoord(tetIndex, quadType, triang); } inline NLargeInteger NNormalSurfaceVectorMirrored::getOrientedQuadCoord( unsigned long tetIndex, int quadType, NTriangulation* triang, bool orientation) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getOrientedQuadCoord(tetIndex, quadType, triang, orientation); } inline NLargeInteger NNormalSurfaceVectorMirrored::getOctCoord( unsigned long tetIndex, int octType, NTriangulation* triang) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getOctCoord(tetIndex, octType, triang); } inline NLargeInteger NNormalSurfaceVectorMirrored::getEdgeWeight( unsigned long edgeIndex, NTriangulation* triang) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getEdgeWeight(edgeIndex, triang); } inline NLargeInteger NNormalSurfaceVectorMirrored::getTriangleArcs( unsigned long triIndex, int triVertex, NTriangulation* triang) const { if (! mirror) const_cast(this)->mirror = makeMirror(triang); return mirror->getTriangleArcs(triIndex, triVertex, triang); } } // namespace regina #endif regina-4.95/engine/surfaces/nsoriented.cpp000644 000765 000024 00000016617 12234011536 020555 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/nenumconstraint.h" #include "surfaces/nsoriented.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NLargeInteger NNormalSurfaceVectorOriented::getEdgeWeight( unsigned long edgeIndex, NTriangulation* triang) const { // Find a tetrahedron next to the edge in question. const NEdgeEmbedding& emb = triang->getEdges()[edgeIndex]-> getEmbeddings().front(); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int start = emb.getVertices()[0]; int end = emb.getVertices()[1]; // Add up the triangles and quads meeting that edge. // Triangles: NLargeInteger ans(getTriangleCoord(tetIndex,start,triang)); ans += getTriangleCoord(tetIndex,end,triang); // Quads: ans += getQuadCoord(tetIndex,vertexSplitMeeting[start][end][0],triang); ans += getQuadCoord(tetIndex,vertexSplitMeeting[start][end][1],triang); return ans; } NLargeInteger NNormalSurfaceVectorOriented::getTriangleArcs( unsigned long triIndex, int triVertex, NTriangulation* triang) const { // Find a tetrahedron next to the triangle in question. const NTriangleEmbedding& emb = triang->getTriangles()[triIndex]-> getEmbedding(0); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int vertex = emb.getVertices()[triVertex]; int backOfFace = emb.getVertices()[3]; // Add up the triangles and quads meeting that triangle in the required arc. // Triangles: NLargeInteger ans(getTriangleCoord(tetIndex,vertex,triang)); // Quads: ans += getQuadCoord(tetIndex,vertexSplit[vertex][backOfFace],triang); return ans; } NNormalSurfaceVector* NNormalSurfaceVectorOriented::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorOriented( 14 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorOriented::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 14 * triangulation->getNumberOfTetrahedra(); // Six equations per non-boundary triangle. // F_boundary + 2 F_internal = 4 T long nEquations = 6 * (4 * long(triangulation->getNumberOfTetrahedra()) - long(triangulation->getNumberOfTriangles())); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); // Run through each internal triangle and add the corresponding three // equations. unsigned row = 0; int i; unsigned long tet0, tet1; NPerm4 perm0, perm1; bool natural; for (NTriangulation::TriangleIterator fit = triangulation->getTriangles().begin(); fit != triangulation->getTriangles().end(); fit++) { if (! (*fit)->isBoundary()) { tet0 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(0).getTetrahedron()); tet1 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(1).getTetrahedron()); perm0 = (*fit)->getEmbedding(0).getVertices(); perm1 = (*fit)->getEmbedding(1).getVertices(); for (i=0; i<3; i++) { // row: oriented towards the vertex of the face // row+1: oriented towards the opposite face // Triangles: ans->entry(row, 14*tet0 + 2*perm0[i]) += 1; ans->entry(row+1, 14*tet0 + 2*perm0[i] + 1) += 1; ans->entry(row, 14*tet1 + 2*perm1[i]) -= 1; ans->entry(row+1, 14*tet1 + 2*perm1[i] + 1) -= 1; // Quads: natural = (perm0[i] == 0 || perm0[3] == 0); ans->entry(row, 14*tet0 + 8 + 2*vertexSplit[perm0[i]][perm0[3]] + (natural ? 0 : 1)) += 1; ans->entry(row+1, 14*tet0 + 8 + 2*vertexSplit[perm0[i]][perm0[3]] + (natural ? 1 : 0)) += 1; natural = (perm1[i] == 0 || perm1[3] == 0); ans->entry(row, 14*tet1 + 8 + 2*vertexSplit[perm1[i]][perm1[3]] + (natural ? 0 : 1)) -= 1; ans->entry(row+1, 14*tet1 + 8 + 2*vertexSplit[perm1[i]][perm1[3]] + (natural ? 1 : 0)) -= 1; row+=2; } } } return ans; } NEnumConstraintList* NNormalSurfaceVectorOriented::makeEmbeddedConstraints( NTriangulation* triangulation) { // TODO: Must be a neater way of doing this, but might mean re-working // bitmasks. NEnumConstraintList* ans = new NEnumConstraintList( 8 * triangulation->getNumberOfTetrahedra()); unsigned base = 0; unsigned c = 0; while (c < ans->size()) { for (unsigned d = 0; d < 2; d++) { for (unsigned e = 0; e < 2; e++) { for (unsigned f = 0; f < 2; f++) { (*ans)[c].insert((*ans)[c].end(), base + 8 + d); (*ans)[c].insert((*ans)[c].end(), base + 10 + e); (*ans)[c].insert((*ans)[c].end(), base + 12 + f); ++c; } } } base += 14; } return ans; } } // namespace regina regina-4.95/engine/surfaces/nsoriented.h000644 000765 000024 00000016275 12234011536 020222 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsoriented.h * \brief Implements normal surface vectors using transversly oriented * normal surface coordinates.*/ #ifndef __NSORIENTED_H #ifndef __DOXYGEN #define __NSORIENTED_H #endif #include "regina-core.h" #include "surfaces/nnormalsurface.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorOriented; /** * \weakgroup surfaces * @{ */ /** * Stores information about transversely oriented standard normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorOriented Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Transversely oriented standard normal"; } enum { almostNormal = 0, spun = 0, oriented = 1 }; }; /** * A normal surface vector using transversely oriented standard * (triangle-quad) coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 14t coordinates. * For each \a i, coordinates 2i and 2i+1 represent * the \c true and \c false orientations for coordinate \a i in the * 7t-dimensional standard coordinate system. See * NNormalSurfaceVectorStandard for further details. * * \warning Support for transversely oriented normal surfaces is still * experimental, and some features \b will break (e.g., testing * connectedness, disjointness or embeddedness). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorOriented : public NNormalSurfaceVector { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorOriented, NS_ORIENTED) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorOriented(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorOriented(const NVector& cloneMe); virtual NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang) const; virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const; virtual NLargeInteger getOrientedTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang, bool orientation) const; virtual NLargeInteger getOrientedQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang, bool orientation) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; virtual NLargeInteger getEdgeWeight(unsigned long edgeIndex, NTriangulation* triang) const; virtual NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorOriented inline NNormalSurfaceVectorOriented::NNormalSurfaceVectorOriented( size_t length) : NNormalSurfaceVector(length) { } inline NNormalSurfaceVectorOriented::NNormalSurfaceVectorOriented( const NVector& cloneMe) : NNormalSurfaceVector(cloneMe) { } inline NLargeInteger NNormalSurfaceVectorOriented::getTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation* tri) const { return getOrientedTriangleCoord(tetIndex,vertex,tri, true) + getOrientedTriangleCoord(tetIndex,vertex,tri, false); } inline NLargeInteger NNormalSurfaceVectorOriented::getQuadCoord( unsigned long tetIndex, int quadType, NTriangulation* tri) const { return getOrientedQuadCoord(tetIndex,quadType,tri, true) + getOrientedQuadCoord(tetIndex,quadType,tri, false); } inline NLargeInteger NNormalSurfaceVectorOriented::getOrientedTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation*, bool orientation) const { return (*this)[14 * tetIndex + 2 * vertex + (orientation ? 0 : 1)]; } inline NLargeInteger NNormalSurfaceVectorOriented::getOrientedQuadCoord( unsigned long tetIndex, int quadType, NTriangulation*, bool orientation) const { return (*this)[14 * tetIndex + 8 + 2 * quadType + (orientation ? 0 : 1)]; } inline NLargeInteger NNormalSurfaceVectorOriented::getOctCoord( unsigned long, int, NTriangulation*) const { return zero; } } // namespace regina #endif regina-4.95/engine/surfaces/nsorientedquad.cpp000644 000765 000024 00000036716 12234011536 021432 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "enumerate/nenumconstraint.h" #include "surfaces/nsorientedquad.h" #include "surfaces/nsoriented.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NNormalSurfaceVector* NNormalSurfaceVectorOrientedQuad::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorOrientedQuad( 6 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorOrientedQuad::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 6 * triangulation->getNumberOfTetrahedra(); // Two equation per non-boundary edge. long nEquations = 2*long(triangulation->getNumberOfEdges()); for (NTriangulation::BoundaryComponentIterator bit = triangulation-> getBoundaryComponents().begin(); bit != triangulation->getBoundaryComponents().end(); bit++) nEquations -= 2*(*bit)->getNumberOfEdges(); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); unsigned long row = 0; // Run through each internal edge and add the corresponding // equation. std::deque::const_iterator embit; NPerm4 perm; unsigned long tetIndex; bool flip; for (NTriangulation::EdgeIterator eit = triangulation->getEdges().begin(); eit != triangulation->getEdges().end(); eit++) { if (! (*eit)->isBoundary()) { for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { tetIndex = triangulation->tetrahedronIndex( (*embit).getTetrahedron()); perm = (*embit).getVertices(); flip = (perm[0] == 0 || perm[2] == 0); ans->entry(row, 6 * tetIndex + 2*vertexSplit[perm[0]][perm[2]] + (flip ? 0 : 1)) += 1; ans->entry(row+1, 6 * tetIndex + 2*vertexSplit[perm[0]][perm[2]] + (flip ? 1 : 0)) += 1; flip = (perm[0] == 0 || perm[3] == 0); ans->entry(row, 6 * tetIndex + 2*vertexSplit[perm[0]][perm[3]] + (flip ? 0 : 1)) -= 1; ans->entry(row+1, 6 * tetIndex + 2*vertexSplit[perm[0]][perm[3]] + (flip ? 1 : 0)) -= 1; } row+=2; } } return ans; } NEnumConstraintList* NNormalSurfaceVectorOrientedQuad::makeEmbeddedConstraints( NTriangulation* triangulation) { NEnumConstraintList* ans = new NEnumConstraintList( 8 * triangulation->getNumberOfTetrahedra()); unsigned base = 0; unsigned c = 0; while (c < ans->size()) { for (int a = 0 ; a < 2; a++) { for (int b = 0 ; b < 2; b++) { for (int d = 0 ; d < 2; d++) { (*ans)[c].insert((*ans)[c].end(), base + a); (*ans)[c].insert((*ans)[c].end(), base + b + 2); (*ans)[c].insert((*ans)[c].end(), base + d + 4); ++c; } } } base += 6; } return ans; } namespace { /** * A structure representing a particular end of an edge. */ struct EdgeEnd { NEdge* edge; /**< The edge under consideration. */ int end; /**< The end of the edge under consideration; this is 0 or 1. */ EdgeEnd() {} EdgeEnd(NEdge* newEdge, int newEnd) : edge(newEdge), end(newEnd) {} EdgeEnd(const EdgeEnd& cloneMe) : edge(cloneMe.edge), end(cloneMe.end) {} void operator = (const EdgeEnd& cloneMe) { edge = cloneMe.edge; end = cloneMe.end; } }; } NNormalSurfaceVector* NNormalSurfaceVectorOrientedQuad::makeMirror( NTriangulation* triang) const { // We're going to do this by wrapping around each edge and seeing // what comes. unsigned long nRows = 14 * triang->getNumberOfTetrahedra(); NNormalSurfaceVectorOriented* ans = new NNormalSurfaceVectorOriented(nRows); // Set every triangular coordinate in the answer to infinity. // For coordinates about vertices not enjoying infinitely many discs, // infinity will mean "unknown". unsigned long row; int i; for (row = 0; row < nRows; row+=14) for (i = 0; i < 8; i++) ans->setElement(row + i, NLargeInteger::infinity); for (row = 0; 14 * row < nRows; row++) for (i = 0; i < 6; i++) ans->setElement(14 * row + 8 + i, (*this)[6 * row + i]); for (int orient = 0; orient < 2; ++orient) { // Run through the vertices and work out the triangular coordinates // about each vertex in turn. // // If orient = 0 or 1, we look at triangular discs oriented towards // or away from the vertex respetively. std::set usedEdges[2]; // usedEdges[i] contains the edges for which we have already // examined end i. NLargeInteger min; // The minimum coordinate that has been assigned about this // vertex. std::deque examine; bool broken; // Are the matching equations broken about this edge end? int end; NEdge* edge; EdgeEnd current; std::vector::const_iterator vembit; std::deque::const_iterator eembit, backupit, endit, beginit; NTetrahedron* tet; NPerm4 tetPerm, adjPerm; unsigned long tetIndex, adjIndex; NLargeInteger expect; for (NTriangulation::VertexIterator vit = triang->getVertices().begin(); vit != triang->getVertices().end(); vit++) { usedEdges[0].clear(); usedEdges[1].clear(); examine.clear(); broken = false; // Pick some triangular disc and set it to zero. const NVertexEmbedding& vemb = (*vit)->getEmbedding(0); row = 14 * triang->tetrahedronIndex(vemb.getTetrahedron()) + 2 * vemb.getVertex() + orient; ans->setElement(row, NLargeInteger::zero); min = NLargeInteger::zero; // Mark the three surrounding edge ends for examination. for (i=0; i<4; i++) { if (i == vemb.getVertex()) continue; edge = vemb.getTetrahedron()->getEdge( NEdge::edgeNumber[vemb.getVertex()][i]); end = vemb.getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[vemb.getVertex()][i])[0] == i ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } // Cycle through edge ends until we are finished or until the // matching equations are broken. Each time we pick a value for // a coordinate, add the corresponding nearby edge ends to the // list of edge ends to examine. while ((! broken) && (! examine.empty())) { current = examine.front(); examine.pop_front(); // Run around this edge end. // We know there is a pre-chosen coordinate somewhere; run // forwards and find this. beginit = current.edge->getEmbeddings().begin(); endit = current.edge->getEmbeddings().end(); for (eembit = beginit; eembit != endit; eembit++) if (! (*ans)[14 * triang->tetrahedronIndex( (*eembit).getTetrahedron()) + 2 * (*eembit).getVertices()[current.end] + orient].isInfinite()) break; // We are now at the first pre-chosen coordinate about this // vertex. Run backwards from here and fill in all the // holes. backupit = eembit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); while (eembit != beginit) { eembit--; // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[14 * adjIndex + 2 * adjPerm[current.end] + orient] + (*ans)[14 * adjIndex + 8 + 2 * vertexSplit[adjPerm[3]][adjPerm[current.end]] + ((adjPerm[current.end] == 0 || adjPerm[3] == 0) ? orient : (1 - orient))] - (*ans)[14 * tetIndex + 8 + 2 * vertexSplit[tetPerm[2]][tetPerm[current.end]] + ((tetPerm[current.end] == 0 || tetPerm[2] == 0) ? orient : (1 - orient))]; ans->setElement(14 * tetIndex + 2 * tetPerm[current.end] + orient, expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]])[0] == tetPerm[2] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); adjPerm = tetPerm; adjIndex = tetIndex; } // Now move forwards from the original first pre-chosen // coordinate and fill in the holes from here onwards, // always checking to ensure the // matching equations have not been broken. eembit = backupit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); for (eembit++; eembit != endit; eembit++) { // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[14 * adjIndex + 2 * adjPerm[current.end] + orient] + (*ans)[14 * adjIndex + 8 + 2 * vertexSplit[adjPerm[2]][adjPerm[current.end]] + ((adjPerm[current.end] == 0 || adjPerm[2] == 0) ? orient : (1 - orient))] - (*ans)[14 * tetIndex + 8 + 2 * vertexSplit[tetPerm[3]][tetPerm[current.end]] + ((tetPerm[current.end] == 0 || tetPerm[3] == 0) ? orient : (1 - orient))]; row = 14 * tetIndex + 2 * tetPerm[current.end] + orient; if ((*ans)[row].isInfinite()) { ans->setElement(row, expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]])[0] == tetPerm[3] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } else { // This coordinate has already been set. // Make sure it's the same value! if ((*ans)[row] != expect) { broken = true; break; } } adjPerm = tetPerm; adjIndex = tetIndex; } } // If the matching equations were broken, set every coordinate // to infinity. Otherwise subtract min from every coordinate to // make the values as small as possible. for (vembit = (*vit)->getEmbeddings().begin(); vembit != (*vit)->getEmbeddings().end(); vembit++) { row = 14 * triang->tetrahedronIndex((*vembit).getTetrahedron()) + 2 * (*vembit).getVertex() + orient; if (broken) ans->setElement(row, NLargeInteger::infinity); else ans->setElement(row, (*ans)[row] - min); } } } // Note that there should be no need to remove common factors since // the quad coordinates have not changed and in theory they already // had gcd=1. return ans; } } // namespace regina regina-4.95/engine/surfaces/nsorientedquad.h000644 000765 000024 00000015102 12234011536 021061 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsorientedquad.h * \brief Implements normal surface vectors using * transversely oriented quad coordinates. */ #ifndef __NSORIENTEDQUAD_H #ifndef __DOXYGEN #define __NSORIENTEDQUAD_H #endif #include "regina-core.h" #include "surfaces/nsmirrored.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorOrientedQuad; /** * \weakgroup surfaces * @{ */ /** * Stores information about transversely oriented quad normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorOrientedQuad Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Transversely oriented quad normal"; } enum { almostNormal = 0, spun = 1, oriented = 1 }; }; /** * A normal surface vector using transversely oriented quadrilateral * coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 6t coordinates. * For each \a i, coordinates 2i and 2i+1 represent * the \c true and \c false orientations for coordinate \a i in the * 3t-dimensional quadrilateral coordinate system. See * NNormalSurfaceVectorQuad for further details. * * \warning Support for transversely oriented normal surfaces is still * experimental, and some features \b will break (e.g., testing * connectedness, disjointness or embeddedness). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorOrientedQuad : public NNormalSurfaceVectorMirrored { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorOrientedQuad, NS_ORIENTED_QUAD) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorOrientedQuad(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorOrientedQuad(const NVector& cloneMe); virtual NNormalSurfaceVector* makeMirror(NTriangulation* triang) const; virtual const NVertex* isVertexLink(NTriangulation* triang) const; virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const; virtual NLargeInteger getOrientedQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang, bool orientation) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorOrientedQuad inline NNormalSurfaceVectorOrientedQuad::NNormalSurfaceVectorOrientedQuad( size_t length) : NNormalSurfaceVectorMirrored(length) { } inline NNormalSurfaceVectorOrientedQuad::NNormalSurfaceVectorOrientedQuad( const NVector& cloneMe) : NNormalSurfaceVectorMirrored(cloneMe) { } inline NLargeInteger NNormalSurfaceVectorOrientedQuad::getQuadCoord( unsigned long tetIndex, int quadType, NTriangulation* tri) const { return getOrientedQuadCoord(tetIndex, quadType, tri, true) + getOrientedQuadCoord(tetIndex, quadType, tri, false); } inline NLargeInteger NNormalSurfaceVectorOrientedQuad::getOrientedQuadCoord( unsigned long tetIndex, int quadType, NTriangulation*, bool orientation) const { return (*this)[6 * tetIndex + 2 * quadType + (orientation ? 0 : 1)]; } inline const NVertex* NNormalSurfaceVectorOrientedQuad::isVertexLink( NTriangulation*) const { // Quad space does not contain vertex links at all. return 0; } inline NLargeInteger NNormalSurfaceVectorOrientedQuad::getOctCoord( unsigned long, int, NTriangulation*) const { return zero; } } // namespace regina #endif regina-4.95/engine/surfaces/nsquad.cpp000644 000765 000024 00000032364 12234011536 017673 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "enumerate/nenumconstraint.h" #include "surfaces/nsquad.h" #include "surfaces/nsstandard.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NNormalSurfaceVector* NNormalSurfaceVectorQuad::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorQuad( 3 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorQuad::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 3 * triangulation->getNumberOfTetrahedra(); // One equation per non-boundary edge. long nEquations = long(triangulation->getNumberOfEdges()); for (NTriangulation::BoundaryComponentIterator bit = triangulation-> getBoundaryComponents().begin(); bit != triangulation->getBoundaryComponents().end(); bit++) nEquations -= (*bit)->getNumberOfEdges(); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); unsigned long row = 0; // Run through each internal edge and add the corresponding // equation. std::deque::const_iterator embit; NPerm4 perm; unsigned long tetIndex; for (NTriangulation::EdgeIterator eit = triangulation->getEdges().begin(); eit != triangulation->getEdges().end(); eit++) { if (! (*eit)->isBoundary()) { for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { tetIndex = triangulation->tetrahedronIndex( (*embit).getTetrahedron()); perm = (*embit).getVertices(); ans->entry(row, 3 * tetIndex + vertexSplit[perm[0]][perm[2]]) += 1; ans->entry(row, 3 * tetIndex + vertexSplit[perm[0]][perm[3]]) -= 1; } row++; } } return ans; } NEnumConstraintList* NNormalSurfaceVectorQuad::makeEmbeddedConstraints( NTriangulation* triangulation) { NEnumConstraintList* ans = new NEnumConstraintList( triangulation->getNumberOfTetrahedra()); unsigned long base = 0; for (unsigned c = 0; c < ans->size(); ++c) { (*ans)[c].insert((*ans)[c].end(), base); (*ans)[c].insert((*ans)[c].end(), base + 1); (*ans)[c].insert((*ans)[c].end(), base + 2); base += 3; } return ans; } namespace { /** * A structure representing a particular end of an edge. */ struct EdgeEnd { NEdge* edge; /**< The edge under consideration. */ int end; /**< The end of the edge under consideration; this is 0 or 1. */ EdgeEnd() {} EdgeEnd(NEdge* newEdge, int newEnd) : edge(newEdge), end(newEnd) {} EdgeEnd(const EdgeEnd& cloneMe) : edge(cloneMe.edge), end(cloneMe.end) {} void operator = (const EdgeEnd& cloneMe) { edge = cloneMe.edge; end = cloneMe.end; } }; } NNormalSurfaceVector* NNormalSurfaceVectorQuad::makeMirror( NTriangulation* triang) const { // We're going to do this by wrapping around each edge and seeing // what comes. unsigned long nRows = 7 * triang->getNumberOfTetrahedra(); NNormalSurfaceVectorStandard* ans = new NNormalSurfaceVectorStandard(nRows); // Set every triangular coordinate in the answer to infinity. // For coordinates about vertices not enjoying infinitely many discs, // infinity will mean "unknown". unsigned long row; int i; for (row = 0; row < nRows; row+=7) for (i = 0; i < 4; i++) ans->setElement(row + i, NLargeInteger::infinity); for (row = 0; 7 * row < nRows; row++) for (i = 0; i < 3; i++) ans->setElement(7 * row + 4 + i, (*this)[3 * row + i]); // Run through the vertices and work out the triangular coordinates // about each vertex in turn. std::set usedEdges[2]; // usedEdges[i] contains the edges for which we have already // examined end i. NLargeInteger min; // The minimum coordinate that has been assigned about this // vertex. std::deque examine; bool broken; // Are the matching equations broken about this edge end? int end; NEdge* edge; EdgeEnd current; std::vector::const_iterator vembit; std::deque::const_iterator eembit, backupit, endit, beginit; NTetrahedron* tet; NPerm4 tetPerm, adjPerm; unsigned long tetIndex, adjIndex; NLargeInteger expect; for (NTriangulation::VertexIterator vit = triang->getVertices().begin(); vit != triang->getVertices().end(); vit++) { usedEdges[0].clear(); usedEdges[1].clear(); examine.clear(); broken = false; // Pick some triangular disc and set it to zero. const NVertexEmbedding& vemb = (*vit)->getEmbedding(0); row = 7 * triang->tetrahedronIndex(vemb.getTetrahedron()) + vemb.getVertex(); ans->setElement(row, NLargeInteger::zero); min = NLargeInteger::zero; // Mark the three surrounding edge ends for examination. for (i=0; i<4; i++) { if (i == vemb.getVertex()) continue; edge = vemb.getTetrahedron()->getEdge( NEdge::edgeNumber[vemb.getVertex()][i]); end = vemb.getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[vemb.getVertex()][i])[0] == i ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } // Cycle through edge ends until we are finished or until the // matching equations are broken. Each time we pick a value for // a coordinate, add the corresponding nearby edge ends to the // list of edge ends to examine. while ((! broken) && (! examine.empty())) { current = examine.front(); examine.pop_front(); // Run around this edge end. // We know there is a pre-chosen coordinate somewhere; run // forwards and find this. beginit = current.edge->getEmbeddings().begin(); endit = current.edge->getEmbeddings().end(); for (eembit = beginit; eembit != endit; eembit++) if (! (*ans)[7 * triang->tetrahedronIndex( (*eembit).getTetrahedron()) + (*eembit).getVertices()[current.end]].isInfinite()) break; // We are now at the first pre-chosen coordinate about this // vertex. Run backwards from here and fill in all the // holes. backupit = eembit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); while (eembit != beginit) { eembit--; // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[7 * adjIndex + adjPerm[current.end]] + (*ans)[7 * adjIndex + 4 + vertexSplit[adjPerm[3]][adjPerm[current.end]]] - (*ans)[7 * tetIndex + 4 + vertexSplit[tetPerm[2]][tetPerm[current.end]]]; ans->setElement(7 * tetIndex + tetPerm[current.end], expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]])[0] == tetPerm[2] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); adjPerm = tetPerm; adjIndex = tetIndex; } // Now move forwards from the original first pre-chosen // coordinate and fill in the holes from here onwards, // always checking to ensure the // matching equations have not been broken. eembit = backupit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); for (eembit++; eembit != endit; eembit++) { // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[7 * adjIndex + adjPerm[current.end]] + (*ans)[7 * adjIndex + 4 + vertexSplit[adjPerm[2]][adjPerm[current.end]]] - (*ans)[7 * tetIndex + 4 + vertexSplit[tetPerm[3]][tetPerm[current.end]]]; row = 7 * tetIndex + tetPerm[current.end]; if ((*ans)[row].isInfinite()) { ans->setElement(row, expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]])[0] == tetPerm[3] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } else { // This coordinate has already been set. // Make sure it's the same value! if ((*ans)[row] != expect) { broken = true; break; } } adjPerm = tetPerm; adjIndex = tetIndex; } } // If the matching equations were broken, set every coordinate // to infinity. Otherwise subtract min from every coordinate to // make the values as small as possible. for (vembit = (*vit)->getEmbeddings().begin(); vembit != (*vit)->getEmbeddings().end(); vembit++) { row = 7 * triang->tetrahedronIndex((*vembit).getTetrahedron()) + (*vembit).getVertex(); if (broken) ans->setElement(row, NLargeInteger::infinity); else ans->setElement(row, (*ans)[row] - min); } } // Note that there should be no need to remove common factors since // the quad coordinates have not changed and in theory they already // had gcd=1. return ans; } } // namespace regina regina-4.95/engine/surfaces/nsquad.h000644 000765 000024 00000012605 12234011536 017334 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsquad.h * \brief Implements normal surface vectors using quad coordinates. */ #ifndef __NSQUAD_H #ifndef __DOXYGEN #define __NSQUAD_H #endif #include "regina-core.h" #include "surfaces/nsmirrored.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorQuad; /** * \weakgroup surfaces * @{ */ /** * Stores information about quad normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorQuad Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Quad normal"; } enum { almostNormal = 0, spun = 1, oriented = 0 }; }; /** * A normal surface vector using quad coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 3t coordinates. * The first three coordinates will be for the first tetrahedron, the * next three for the second tetrahedron and so on. For each * tetrahedron, the three individual coordinates represent the * number of quadrilateral discs of type 0, 1 and 2 * (see NNormalSurface::getQuadCoord()). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorQuad : public NNormalSurfaceVectorMirrored { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorQuad, NS_QUAD) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorQuad(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorQuad(const NVector& cloneMe); virtual NNormalSurfaceVector* makeMirror(NTriangulation* triang) const; virtual const NVertex* isVertexLink(NTriangulation* triang) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorQuad inline NNormalSurfaceVectorQuad::NNormalSurfaceVectorQuad( size_t length) : NNormalSurfaceVectorMirrored(length) { } inline NNormalSurfaceVectorQuad::NNormalSurfaceVectorQuad( const NVector& cloneMe) : NNormalSurfaceVectorMirrored(cloneMe) { } inline const NVertex* NNormalSurfaceVectorQuad::isVertexLink( NTriangulation*) const { // Quad space does not contain vertex links at all. return 0; } inline NLargeInteger NNormalSurfaceVectorQuad::getOctCoord( unsigned long, int, NTriangulation*) const { return zero; } } // namespace regina #endif regina-4.95/engine/surfaces/nsquadoct.cpp000644 000765 000024 00000035576 12234011536 020411 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "enumerate/nenumconstraint.h" #include "surfaces/nsquadoct.h" #include "surfaces/nsanstandard.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NNormalSurfaceVector* NNormalSurfaceVectorQuadOct::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorQuadOct( 6 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorQuadOct::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 6 * triangulation->getNumberOfTetrahedra(); // One equation per non-boundary edge. long nEquations = long(triangulation->getNumberOfEdges()); for (NTriangulation::BoundaryComponentIterator bit = triangulation-> getBoundaryComponents().begin(); bit != triangulation->getBoundaryComponents().end(); bit++) nEquations -= (*bit)->getNumberOfEdges(); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); unsigned long row = 0; // Run through each internal edge and add the corresponding // equation. std::deque::const_iterator embit; NPerm4 perm; unsigned long tetIndex; for (NTriangulation::EdgeIterator eit = triangulation->getEdges().begin(); eit != triangulation->getEdges().end(); eit++) { if (! (*eit)->isBoundary()) { for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { tetIndex = triangulation->tetrahedronIndex( (*embit).getTetrahedron()); perm = (*embit).getVertices(); ans->entry(row, 6 * tetIndex + vertexSplit[perm[0]][perm[2]]) += 1; ans->entry(row, 6 * tetIndex + vertexSplit[perm[0]][perm[3]]) -= 1; ans->entry(row, 6 * tetIndex + 3 + vertexSplit[perm[0]][perm[2]]) -= 1; ans->entry(row, 6 * tetIndex + 3 + vertexSplit[perm[0]][perm[3]]) += 1; } row++; } } return ans; } NEnumConstraintList* NNormalSurfaceVectorQuadOct::makeEmbeddedConstraints( NTriangulation* triangulation) { // At most one quad/oct per tetrahedron. // At most one oct type overall. NEnumConstraintList* ans = new NEnumConstraintList( triangulation->getNumberOfTetrahedra() + 1); unsigned base = 0; for (unsigned c = 1; c < ans->size(); ++c) { (*ans)[c].insert((*ans)[c].end(), base); (*ans)[c].insert((*ans)[c].end(), base + 1); (*ans)[c].insert((*ans)[c].end(), base + 2); (*ans)[c].insert((*ans)[c].end(), base + 3); (*ans)[c].insert((*ans)[c].end(), base + 4); (*ans)[c].insert((*ans)[c].end(), base + 5); (*ans)[0].insert((*ans)[0].end(), base + 3); (*ans)[0].insert((*ans)[0].end(), base + 4); (*ans)[0].insert((*ans)[0].end(), base + 5); base += 6; } return ans; } namespace { /** * A structure representing a particular end of an edge. */ struct EdgeEnd { NEdge* edge; /**< The edge under consideration. */ int end; /**< The end of the edge under consideration; this is 0 or 1. */ EdgeEnd() {} EdgeEnd(NEdge* newEdge, int newEnd) : edge(newEdge), end(newEnd) {} EdgeEnd(const EdgeEnd& cloneMe) : edge(cloneMe.edge), end(cloneMe.end) {} void operator = (const EdgeEnd& cloneMe) { edge = cloneMe.edge; end = cloneMe.end; } }; } NNormalSurfaceVector* NNormalSurfaceVectorQuadOct::makeMirror( NTriangulation* triang) const { // We're going to do this by wrapping around each edge and seeing // what comes. unsigned long nRows = 10 * triang->getNumberOfTetrahedra(); NNormalSurfaceVectorANStandard* ans = new NNormalSurfaceVectorANStandard(nRows); // Set every triangular coordinate in the answer to infinity. // For coordinates about vertices not enjoying infinitely many discs, // infinity will mean "unknown". unsigned long row; int i; for (row = 0; row < nRows; row += 10) for (i = 0; i < 4; i++) ans->setElement(row + i, NLargeInteger::infinity); for (row = 0; 10 * row < nRows; ++row) for (i = 0; i < 6; i++) ans->setElement(10 * row + 4 + i, (*this)[6 * row + i]); // Run through the vertices and work out the triangular coordinates // about each vertex in turn. std::set usedEdges[2]; // usedEdges[i] contains the edges for which we have already // examined end i. NLargeInteger min; // The minimum coordinate that has been assigned about this // vertex. std::deque examine; bool broken; // Are the matching equations broken about this edge end? int end; NEdge* edge; EdgeEnd current; std::vector::const_iterator vembit; std::deque::const_iterator eembit, backupit, endit, beginit; NTetrahedron* tet; NPerm4 tetPerm, adjPerm; unsigned long tetIndex, adjIndex; NLargeInteger expect; for (NTriangulation::VertexIterator vit = triang->getVertices().begin(); vit != triang->getVertices().end(); vit++) { usedEdges[0].clear(); usedEdges[1].clear(); examine.clear(); broken = false; // Pick some triangular disc and set it to zero. const NVertexEmbedding& vemb = (*vit)->getEmbedding(0); row = 10 * triang->tetrahedronIndex(vemb.getTetrahedron()) + vemb.getVertex(); ans->setElement(row, NLargeInteger::zero); min = NLargeInteger::zero; // Mark the three surrounding edge ends for examination. for (i=0; i<4; i++) { if (i == vemb.getVertex()) continue; edge = vemb.getTetrahedron()->getEdge( NEdge::edgeNumber[vemb.getVertex()][i]); end = vemb.getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[vemb.getVertex()][i])[0] == i ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } // Cycle through edge ends until we are finished or until the // matching equations are broken. Each time we pick a value for // a coordinate, add the corresponding nearby edge ends to the // list of edge ends to examine. while ((! broken) && (! examine.empty())) { current = examine.front(); examine.pop_front(); // Run around this edge end. // We know there is a pre-chosen coordinate somewhere; run // forwards and find this. beginit = current.edge->getEmbeddings().begin(); endit = current.edge->getEmbeddings().end(); for (eembit = beginit; eembit != endit; eembit++) if (! (*ans)[10 * triang->tetrahedronIndex( (*eembit).getTetrahedron()) + (*eembit).getVertices()[current.end]].isInfinite()) break; // We are now at the first pre-chosen coordinate about this // vertex. Run backwards from here and fill in all the // holes. backupit = eembit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); while (eembit != beginit) { eembit--; // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[10 * adjIndex + adjPerm[current.end]] + (*ans)[10 * adjIndex + 4 + vertexSplit [adjPerm[3]][adjPerm[current.end]]] + (*ans)[10 * adjIndex + 7 + vertexSplitMeeting [adjPerm[3]][adjPerm[current.end]][0]] + (*ans)[10 * adjIndex + 7 + vertexSplitMeeting [adjPerm[3]][adjPerm[current.end]][1]] - (*ans)[10 * tetIndex + 4 + vertexSplit [tetPerm[2]][tetPerm[current.end]]] - (*ans)[10 * tetIndex + 7 + vertexSplitMeeting [tetPerm[2]][tetPerm[current.end]][0]] - (*ans)[10 * tetIndex + 7 + vertexSplitMeeting [tetPerm[2]][tetPerm[current.end]][1]]; ans->setElement(10 * tetIndex + tetPerm[current.end], expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[2]][tetPerm[current.end]])[0] == tetPerm[2] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); adjPerm = tetPerm; adjIndex = tetIndex; } // Now move forwards from the original first pre-chosen // coordinate and fill in the holes from here onwards, // always checking to ensure the // matching equations have not been broken. eembit = backupit; adjPerm = (*eembit).getVertices(); adjIndex = triang->tetrahedronIndex((*eembit).getTetrahedron()); for (eembit++; eembit != endit; eembit++) { // Work out the coordinate for the disc type at eembit. tet = (*eembit).getTetrahedron(); tetPerm = (*eembit).getVertices(); tetIndex = triang->tetrahedronIndex(tet); expect = (*ans)[10 * adjIndex + adjPerm[current.end]] + (*ans)[10 * adjIndex + 4 + vertexSplit [adjPerm[2]][adjPerm[current.end]]] + (*ans)[10 * adjIndex + 7 + vertexSplitMeeting [adjPerm[2]][adjPerm[current.end]][0]] + (*ans)[10 * adjIndex + 7 + vertexSplitMeeting [adjPerm[2]][adjPerm[current.end]][1]] - (*ans)[10 * tetIndex + 4 + vertexSplit [tetPerm[3]][tetPerm[current.end]]] - (*ans)[10 * tetIndex + 7 + vertexSplitMeeting [tetPerm[3]][tetPerm[current.end]][0]] - (*ans)[10 * tetIndex + 7 + vertexSplitMeeting [tetPerm[3]][tetPerm[current.end]][1]]; row = 10 * tetIndex + tetPerm[current.end]; if ((*ans)[row].isInfinite()) { ans->setElement(row, expect); if (expect < min) min = expect; // Remember to examine the new edge end if appropriate. edge = tet->getEdge( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]]); end = tet->getEdgeMapping( NEdge::edgeNumber[tetPerm[3]][tetPerm[current.end]])[0] == tetPerm[3] ? 1 : 0; if (usedEdges[end].insert(edge).second) examine.push_back(EdgeEnd(edge, end)); } else { // This coordinate has already been set. // Make sure it's the same value! if ((*ans)[row] != expect) { broken = true; break; } } adjPerm = tetPerm; adjIndex = tetIndex; } } // If the matching equations were broken, set every coordinate // to infinity. Otherwise subtract min from every coordinate to // make the values as small as possible. for (vembit = (*vit)->getEmbeddings().begin(); vembit != (*vit)->getEmbeddings().end(); vembit++) { row = 10 * triang->tetrahedronIndex((*vembit).getTetrahedron()) + (*vembit).getVertex(); if (broken) ans->setElement(row, NLargeInteger::infinity); else ans->setElement(row, (*ans)[row] - min); } } // Note that there should be no need to remove common factors since // the quad/oct coordinates have not changed and in theory they already // had gcd=1. return ans; } } // namespace regina regina-4.95/engine/surfaces/nsquadoct.h000644 000765 000024 00000012551 12234011536 020042 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsquadoct.h * \brief Implements almost normal surface vectors using quad-oct coordinates. */ #ifndef __NSQUADOCT_H #ifndef __DOXYGEN #define __NSQUADOCT_H #endif #include "regina-core.h" #include "surfaces/nsmirrored.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorQuadOct; /** * \weakgroup surfaces * @{ */ /** * Stores information about quad-oct almost normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorQuadOct Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Quad-oct almost normal"; } enum { almostNormal = 1, spun = 1, oriented = 0 }; }; /** * An almost normal surface vector using quad-oct coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 6t coordinates. * The first six coordinates will be for the first tetrahedron, the * next six for the second tetrahedron and so on. For each * tetrahedron, the first three coordinates represent the number of * quadrilateral discs of type 0, 1 and 2 (see NNormalSurface::getQuadCoord()), * and the final three represent the number of octagonal discs of type * 0, 1 and 2 (see NNormalSurface::getOctCoord()). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorQuadOct : public NNormalSurfaceVectorMirrored { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorQuadOct, NS_AN_QUAD_OCT) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorQuadOct(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorQuadOct(const NVector& cloneMe); virtual NNormalSurfaceVector* makeMirror(NTriangulation* triang) const; virtual const NVertex* isVertexLink(NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorQuadOct inline NNormalSurfaceVectorQuadOct::NNormalSurfaceVectorQuadOct( size_t length) : NNormalSurfaceVectorMirrored(length) { } inline NNormalSurfaceVectorQuadOct::NNormalSurfaceVectorQuadOct( const NVector& cloneMe) : NNormalSurfaceVectorMirrored(cloneMe) { } inline const NVertex* NNormalSurfaceVectorQuadOct::isVertexLink( NTriangulation*) const { // Quad-oct space does not contain vertex links at all. return 0; } } // namespace regina #endif regina-4.95/engine/surfaces/nsstandard.cpp000644 000765 000024 00000014340 12234011536 020533 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/nenumconstraint.h" #include "surfaces/nsstandard.h" #include "maths/nmatrixint.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" namespace regina { NLargeInteger NNormalSurfaceVectorStandard::getEdgeWeight( unsigned long edgeIndex, NTriangulation* triang) const { // Find a tetrahedron next to the edge in question. const NEdgeEmbedding& emb = triang->getEdges()[edgeIndex]-> getEmbeddings().front(); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int start = emb.getVertices()[0]; int end = emb.getVertices()[1]; // Add up the triangles and quads meeting that edge. // Triangles: NLargeInteger ans((*this)[7 * tetIndex + start]); ans += (*this)[7 * tetIndex + end]; // Quads: ans += (*this)[7 * tetIndex + 4 + vertexSplitMeeting[start][end][0]]; ans += (*this)[7 * tetIndex + 4 + vertexSplitMeeting[start][end][1]]; return ans; } NLargeInteger NNormalSurfaceVectorStandard::getTriangleArcs( unsigned long triIndex, int triVertex, NTriangulation* triang) const { // Find a tetrahedron next to the triangle in question. const NTriangleEmbedding& emb = triang->getTriangles()[triIndex]-> getEmbedding(0); long tetIndex = triang->tetrahedronIndex(emb.getTetrahedron()); int vertex = emb.getVertices()[triVertex]; int backOfFace = emb.getVertices()[3]; // Add up the triangles and quads meeting that triangle in the required arc. // Triangles: NLargeInteger ans((*this)[7 * tetIndex + vertex]); // Quads: ans += (*this)[7 * tetIndex + 4 + vertexSplit[vertex][backOfFace]]; return ans; } NNormalSurfaceVector* NNormalSurfaceVectorStandard::makeZeroVector( const NTriangulation* triangulation) { return new NNormalSurfaceVectorStandard( 7 * triangulation->getNumberOfTetrahedra()); } NMatrixInt* NNormalSurfaceVectorStandard::makeMatchingEquations( NTriangulation* triangulation) { unsigned long nCoords = 7 * triangulation->getNumberOfTetrahedra(); // Three equations per non-boundary triangle. // F_boundary + 2 F_internal = 4 T long nEquations = 3 * (4 * long(triangulation->getNumberOfTetrahedra()) - long(triangulation->getNumberOfTriangles())); NMatrixInt* ans = new NMatrixInt(nEquations, nCoords); // Run through each internal triangle and add the corresponding three // equations. unsigned row = 0; int i; unsigned long tet0, tet1; NPerm4 perm0, perm1; for (NTriangulation::TriangleIterator fit = triangulation->getTriangles().begin(); fit != triangulation->getTriangles().end(); fit++) { if (! (*fit)->isBoundary()) { tet0 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(0).getTetrahedron()); tet1 = triangulation->tetrahedronIndex( (*fit)->getEmbedding(1).getTetrahedron()); perm0 = (*fit)->getEmbedding(0).getVertices(); perm1 = (*fit)->getEmbedding(1).getVertices(); for (i=0; i<3; i++) { // Triangles: ans->entry(row, 7*tet0 + perm0[i]) += 1; ans->entry(row, 7*tet1 + perm1[i]) -= 1; // Quads: ans->entry(row, 7*tet0 + 4 + vertexSplit[perm0[i]][perm0[3]]) += 1; ans->entry(row, 7*tet1 + 4 + vertexSplit[perm1[i]][perm1[3]]) -= 1; row++; } } } return ans; } NEnumConstraintList* NNormalSurfaceVectorStandard::makeEmbeddedConstraints( NTriangulation* triangulation) { NEnumConstraintList* ans = new NEnumConstraintList( triangulation->getNumberOfTetrahedra()); unsigned base = 0; for (unsigned c = 0; c < ans->size(); ++c) { (*ans)[c].insert((*ans)[c].end(), base + 4); (*ans)[c].insert((*ans)[c].end(), base + 5); (*ans)[c].insert((*ans)[c].end(), base + 6); base += 7; } return ans; } } // namespace regina regina-4.95/engine/surfaces/nsstandard.h000644 000765 000024 00000014110 12234011536 020173 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsstandard.h * \brief Implements normal surface vectors using standard * triangle-quad coordinates. */ #ifndef __NSSTANDARD_H #ifndef __DOXYGEN #define __NSSTANDARD_H #endif #include "regina-core.h" #include "surfaces/nnormalsurface.h" namespace regina { class NMatrixInt; class NNormalSurfaceVectorStandard; /** * \weakgroup surfaces * @{ */ /** * Stores information about standard normal coordinates. * See the general NormalInfo template notes for further details. * * \ifacespython Not present. */ template <> struct NormalInfo { typedef NNormalSurfaceVectorStandard Class; typedef NormalInfo Standard; typedef NormalInfo Reduced; inline static const char* name() { return "Standard normal (tri-quad)"; } enum { almostNormal = 0, spun = 0, oriented = 0 }; }; /** * A normal surface vector using standard triangle-quad coordinates. * * If there are \a t tetrahedra in the underlying * triangulation, there must be precisely 7t coordinates. * The first seven coordinates will be for the first tetrahedron, the * next seven for the second tetrahedron and so on. For each * tetrahedron, the first four represent the number of * triangular discs about vertex 0, 1, 2 and 3, and the next * three represent the number of quadrilateral discs of type 0, * 1 and 2 (see NNormalSurface::getQuadCoord()). * * \ifacespython Not present. */ class REGINA_API NNormalSurfaceVectorStandard : public NNormalSurfaceVector { REGINA_NORMAL_SURFACE_FLAVOUR(NNormalSurfaceVectorStandard, NS_STANDARD) public: /** * Creates a new vector all of whose entries are initialised to * zero. * * @param length the number of elements in the new vector. */ NNormalSurfaceVectorStandard(size_t length); /** * Creates a new vector that is a clone of the given vector. * * @param cloneMe the vector to clone. */ NNormalSurfaceVectorStandard(const NVector& cloneMe); virtual NLargeInteger getTriangleCoord(unsigned long tetIndex, int vertex, NTriangulation* triang) const; virtual NLargeInteger getQuadCoord(unsigned long tetIndex, int quadType, NTriangulation* triang) const; virtual NLargeInteger getOctCoord(unsigned long tetIndex, int octType, NTriangulation* triang) const; virtual NLargeInteger getEdgeWeight(unsigned long edgeIndex, NTriangulation* triang) const; virtual NLargeInteger getTriangleArcs(unsigned long triIndex, int triVertex, NTriangulation* triang) const; static NNormalSurfaceVector* makeZeroVector( const NTriangulation* triangulation); static NMatrixInt* makeMatchingEquations(NTriangulation* triangulation); static NEnumConstraintList* makeEmbeddedConstraints( NTriangulation* triangulation); }; /*@}*/ // Inline functions for NNormalSurfaceVectorStandard inline NNormalSurfaceVectorStandard::NNormalSurfaceVectorStandard( size_t length) : NNormalSurfaceVector(length) { } inline NNormalSurfaceVectorStandard::NNormalSurfaceVectorStandard( const NVector& cloneMe) : NNormalSurfaceVector(cloneMe) { } inline NLargeInteger NNormalSurfaceVectorStandard::getTriangleCoord( unsigned long tetIndex, int vertex, NTriangulation*) const { return (*this)[7 * tetIndex + vertex]; } inline NLargeInteger NNormalSurfaceVectorStandard::getQuadCoord( unsigned long tetIndex, int quadType, NTriangulation*) const { return (*this)[7 * tetIndex + 4 + quadType]; } inline NLargeInteger NNormalSurfaceVectorStandard::getOctCoord( unsigned long, int, NTriangulation*) const { return zero; } } // namespace regina #endif regina-4.95/engine/surfaces/nsurfacefilter.cpp000644 000765 000024 00000005366 12234011536 021416 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nsurfacefilter.h" #include "surfaces/nnormalsurfacelist.h" #include "surfaces/filterregistry.h" #include "utilities/xmlutils.h" namespace regina { void NSurfaceFilter::writeXMLPacketData(std::ostream& out) const { SurfaceFilterType id = getFilterType(); out << " \n"; writeXMLFilterData(out); out << " \n"; } } // namespace regina regina-4.95/engine/surfaces/nsurfacefilter.h000644 000765 000024 00000030145 12236524106 021060 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsurfacefilter.h * \brief Contains a packet that filters through normal surfaces. */ #ifndef __NSURFACEFILTER_H #ifndef __DOXYGEN #define __NSURFACEFILTER_H #endif #include "regina-core.h" #include "packet/npacket.h" #include "surfaces/surfacefiltertype.h" namespace regina { class NNormalSurface; class NNormalSurfaceList; class NSurfaceFilter; class NXMLPacketReader; class NXMLFilterReader; /** * \weakgroup surfaces * @{ */ /** * A template that stores information about a particular type of normal * surface filter. Much of this information is given in the * form of compile-time constants and types. * * To iterate through cases for a given value of SurfaceFilterInfo that is not * known until runtime, see the various forFilter() routines defined in * filterregistry.h. * * At a bare minimum, each specialisation of this template must provide: * * - a typedef \a Class that represents the corresponding * NSurfaceFilter descendant class; * - a static function name() that returns a C-style string giving the * human-readable name of the filter type. * * \ifacespython Not present. */ template struct SurfaceFilterInfo; /** * Defines various constants, types and virtual functions for a * descendant class of NSurfaceFilter. * * Every descendant class of NSurfaceFilter \a must include * REGINA_SURFACE_FILTER at the beginning of the class definition. * * This macro provides the class with: * * - a compile-time enum constant \a filterType, which is equal to the * corresponding SurfaceFilterType constant; * - a deprecated compile-time enum constant \a filterID, which is * identical to \a filterType; * - declarations and implementations of the virtual functions * NSurfaceFilter::getFilterType() and NSurfaceFilter::getFilterTypeName(); * - declarations and implementations of the deprecated virtual functions * NSurfaceFilter::getFilterID() and NSurfaceFilter::getFilterName(). * * @param class_ the name of this descendant class of NSurfaceFilter. * @param id the corresponding SurfaceFilterType constant. */ #define REGINA_SURFACE_FILTER(class_, id) \ public: \ enum { filterType = id, filterID = id }; \ inline virtual SurfaceFilterType getFilterType() const { \ return id; \ } \ inline virtual SurfaceFilterType getFilterID() const { \ return id; \ } \ inline virtual std::string getFilterTypeName() const { \ return SurfaceFilterInfo::name(); \ } \ inline virtual std::string getFilterName() const { \ return SurfaceFilterInfo::name(); \ } /** * Stores information about the normal surface filter packet type. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NSurfaceFilter Class; inline static const char* name() { return "Surface Filter"; } }; /** * Stores information about the default accept-all surface filter. * See the general SurfaceFilterInfo template notes for further details. * * \ifacespython Not present. */ template <> struct SurfaceFilterInfo { typedef NSurfaceFilter Class; inline static const char* name() { return "Default filter"; } }; /** * A packet that accepts or rejects normal surfaces. * Different subclasses of NSurfaceFilter represent different filtering * methods. * * When deriving classes from NSurfaceFilter: *
    *
  • A new value must be added to the SurfaceFilterType enum in * surfacefiltertype.h to represent the new filter type.
  • *
  • The file filterregistry-impl.h must be updated to reflect the new * filter type (the file itself contains instructions on how to do this).
  • *
  • A corresponding specialisation of SurfaceFilterInfo<> must be * defined, typically in the same header as the new filter class.
  • *
  • The macro REGINA_SURFACE_FILTER must be added to the beginning * of the new filter class. This will declare and define various * constants, typedefs and virtual functions (see the REGINA_SURFACE_FILTER * macro documentation for details).
  • *
  • A copy constructor class(const class& cloneMe) must * be declared and implemented. You may assume that parameter * \a cloneMe is of the same class as that whose constructor you are * writing.
  • *
  • Virtual functions accept(), internalClonePacket(), writeTextLong() and * writeXMLFilterData() must be overridden.
  • *
  • Static function getXMLFilterReader() must be declared and * implemented as described in the documentation below.
  • *
* * \todo \feature Implement property \a lastAppliedTo. */ class REGINA_API NSurfaceFilter : public NPacket { REGINA_PACKET(NSurfaceFilter, PACKET_SURFACEFILTER) REGINA_SURFACE_FILTER(NSurfaceFilter, NS_FILTER_DEFAULT) public: /** * Creates a new default surface filter. This will simply accept * all normal surfaces. */ NSurfaceFilter(); /** * Creates a new default surface filter. This will simply accept * all normal surfaces. Note that the given parameter is * ignored. * * @param cloneMe this parameter is ignored. */ NSurfaceFilter(const NSurfaceFilter& cloneMe); /** * Destroys this surface filter. */ virtual ~NSurfaceFilter(); /** * Decides whether or not the given normal surface is accepted by this * filter. * * The default implementation simply returns \c true. * * @param surface the normal surface under investigation. * @return \c true if and only if the given surface is accepted * by this filter. */ virtual bool accept(const NNormalSurface& surface) const; #ifdef __DOXYGEN /** * Returns the unique integer ID corresponding to the filtering * method that is this particular subclass of NSurfaceFilter. * * @return the unique integer filtering method ID. */ virtual SurfaceFilterType getFilterType() const; /** * A deprecated alias for getFilterType(). * This returns the unique integer ID corresponding to the filtering * method that is this particular subclass of NSurfaceFilter. * * @return the unique integer filtering method ID. */ virtual SurfaceFilterType getFilterID() const; /** * Returns a string description of the filtering method that is * this particular subclass of NSurfaceFilter. * * @return a string description of this filtering method. */ virtual std::string getFilterTypeName() const; /** * A deprecated alias for getFilterTypeName(). * This returns a string description of the filtering method that is * this particular subclass of NSurfaceFilter. * * @return a string description of this filtering method. */ virtual std::string getFilterName() const; #endif /** * Returns a newly created XML filter reader that will read the * details of a particular type of surface filter. You may * assume that the filter to be read is of the same type as the * class in which you are implementing this routine. * * The XML filter reader should read exactly what * writeXMLFilterData() writes, and vice versa. * * \a parent represents the packet which will become the new * filter's parent in the tree structure. This information is * for reference only, and need not be used. * See the description of parameter \a parent in * NPacket::getXMLReader() for further details. * * \ifacespython Not present. * * @param parent the packet which will become the new filter's * parent in the tree structure, or 0 if the new filter is to be * tree matriarch. * @return the newly created XML filter reader. */ static NXMLFilterReader* getXMLFilterReader(NPacket* parent); virtual void writeTextShort(std::ostream& out) const; static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); virtual bool dependsOnParent() const; protected: /** * Writes a chunk of XML containing the details of this filter. * * You may assume that the filter opening tag (including the * filter type) has already been written, and that the filter * closing tag will be written immediately after this routine is * called. This routine need only write the additional details * corresponding to this particular subclass of NSurfaceFilter. * * @param out the output stream to which the XML should be written. */ virtual void writeXMLFilterData(std::ostream& out) const; virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; }; /*@}*/ // Inline functions for NSurfaceFilter inline NSurfaceFilter::NSurfaceFilter() { } inline NSurfaceFilter::NSurfaceFilter(const NSurfaceFilter&) : NPacket() { } inline NSurfaceFilter::~NSurfaceFilter() { } inline bool NSurfaceFilter::accept(const NNormalSurface&) const { return true; } inline void NSurfaceFilter::writeXMLFilterData(std::ostream&) const { } inline void NSurfaceFilter::writeTextShort(std::ostream& o) const { o << getFilterTypeName(); } inline bool NSurfaceFilter::dependsOnParent() const { return false; } inline NPacket* NSurfaceFilter::internalClonePacket(NPacket*) const { return new NSurfaceFilter(); } } // namespace regina #endif regina-4.95/engine/surfaces/nsurfacesubset.cpp000644 000765 000024 00000005766 12234011536 021442 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nsurfacesubset.h" #include "surfaces/nsurfacefilter.h" namespace regina { NSurfaceSubset::NSurfaceSubset(const NNormalSurfaceList& set, const NSurfaceFilter& filter) : source(set) { unsigned long n = set.getNumberOfSurfaces(); NNormalSurface* s; for (unsigned long i = 0; i < n; i++) { s = const_cast(set.getSurface(i)); if (filter.accept(*s)) surfaces.push_back(s); } } void NSurfaceSubset::writeAllSurfaces(std::ostream& out) const { unsigned long n = getNumberOfSurfaces(); out << "Number of surfaces is " << n << '\n'; for (unsigned long i = 0; i < n; i++) { getSurface(i)->writeTextShort(out); out << '\n'; } } } // namespace regina regina-4.95/engine/surfaces/nsurfacesubset.h000644 000765 000024 00000021127 12234011536 021074 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nsurfacesubset.h * \brief Provides subsets of normal surface sets. */ #ifndef __NSURFACESUBSET_H #ifndef __DOXYGEN #define __NSURFACESUBSET_H #endif #include #include "regina-core.h" #include "shareableobject.h" #include "surfaces/nnormalsurfacelist.h" namespace regina { class NSurfaceFilter; /** * \weakgroup surfaces * @{ */ /** * Represents a subset of a normal surface list. * This subset merely contains pointers to some of the normal surfaces * stored in the original list. * * If the surfaces in the original list * should change, the surfaces in this subset will thus change also. If * the original list is deleted, this subset will become invalid. * * \pre As long as this subset is in use, the surface list upon which this * subset is based must never be deleted. */ class REGINA_API NSurfaceSubset : public ShareableObject { private: std::vector surfaces; /**< Contains the surfaces contained in this subset. These will all be pointers to surfaces stored in \a source. */ const NNormalSurfaceList& source; /**< The surface list for which this is a subset. */ public: /** * Creates a new normal surface subset. * The surfaces included in the subset will be those from the * given set that are accepted by the given filter. * * @param list the surface list for which this will be a subset. * @param filter the filter that defines which surfaces in \a list * will be included in this subset. */ NSurfaceSubset(const NNormalSurfaceList& list, const NSurfaceFilter& filter); /** * Destroys this normal surface subset. */ virtual ~NSurfaceSubset(); /** * Returns the coordinate system being used by the * surfaces stored in this set. * * \deprecated Users should switch to the identical routine * coords() instead. * * @return the coordinate system used. */ NormalCoords getFlavour() const; /** * Returns the coordinate system being used by the * surfaces stored in this set. * * @return the coordinate system used. */ NormalCoords coords() const; /** * Determines if the coordinate system being used * allows for almost normal surfaces, that is, allows for * octagonal discs. * * @return \c true if and only if almost normal surfaces are * allowed. */ bool allowsAlmostNormal() const; /** * Determines if the coordinate system being used * allows for spun normal surfaces. * * @return \c true if and only if spun normal surface are * supported. */ bool allowsSpun() const; /** * Determines if the coordinate system being used * allows for transversely oriented normal surfaces. * * @return \c true if and only if transverse orientations are * supported. */ bool allowsOriented() const; /** * Returns whether this set is known to contain only embedded normal * surfaces. * * If it is possible that there are non-embedded surfaces in this * set but it is not known whether any are actually present or * not, this routine should return \c false. * * @return \c true if it is known that only embedded normal surfaces * exist in this list, or \c false if immersed and/or singular normal * surfaces might be present. */ bool isEmbeddedOnly() const; /** * Returns the triangulation in which these normal surfaces live. * * @return the triangulation in which these surfaces live. */ NTriangulation* getTriangulation() const; /** * Returns the number of surfaces stored in this set. * * @return the number of surfaces. */ unsigned long getNumberOfSurfaces() const; /** * Returns the surface at the requested index in this set. * * @param index the index of the requested surface in this set; * this must be between 0 and getNumberOfSurfaces()-1 inclusive. * * @return the normal surface at the requested index in this set. */ const NNormalSurface* getSurface(unsigned long index) const; /** * Writes the number of surfaces in this set followed by the * details of each surface to the given output stream. Output * will be over many lines. * * \ifacespython Parameter \a out is not present and is assumed * to be standard output. * * @param out the output stream to which to write. */ void writeAllSurfaces(std::ostream& out) const; virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; }; /*@}*/ // Inline functions for NSurfaceSubset inline NSurfaceSubset::~NSurfaceSubset() { } inline NormalCoords NSurfaceSubset::getFlavour() const { return source.coords(); } inline NormalCoords NSurfaceSubset::coords() const { return source.coords(); } inline bool NSurfaceSubset::allowsAlmostNormal() const { return source.allowsAlmostNormal(); } inline bool NSurfaceSubset::allowsSpun() const { return source.allowsSpun(); } inline bool NSurfaceSubset::allowsOriented() const { return source.allowsOriented(); } inline bool NSurfaceSubset::isEmbeddedOnly() const { return source.isEmbeddedOnly(); } inline NTriangulation* NSurfaceSubset::getTriangulation() const { return source.getTriangulation(); } inline unsigned long NSurfaceSubset::getNumberOfSurfaces() const { return surfaces.size(); } inline const NNormalSurface* NSurfaceSubset::getSurface(unsigned long index) const { return surfaces[index]; } inline void NSurfaceSubset::writeTextShort(std::ostream& out) const { out << "Subset containing " << surfaces.size() << " normal surface"; if (surfaces.size() != 1) out << 's'; } inline void NSurfaceSubset::writeTextLong(std::ostream& out) const { writeAllSurfaces(out); } } // namespace regina #endif regina-4.95/engine/surfaces/nxmlfilterreader.cpp000644 000765 000024 00000007442 12236524106 021752 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nxmlfilterreader.h" #include "surfaces/filterregistry.h" #include "utilities/stringutils.h" namespace regina { namespace { struct XMLReaderFunction : public Returns { NPacket* parent_; XMLReaderFunction(NPacket* parent) : parent_(parent) {} template inline NXMLElementReader* operator() (Filter) { return Filter::Class::getXMLFilterReader(parent_); } }; } NXMLElementReader* NXMLFilterPacketReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (! filter) if (subTagName == "filter") { int type; if (valueOf(props.lookup("typeid"), type)) { NXMLElementReader* ans = forFilter( static_cast(type), XMLReaderFunction(parent), 0); if (ans) return ans; else return new NXMLFilterReader(); } } return new NXMLElementReader(); } void NXMLFilterPacketReader::endContentSubElement( const std::string& subTagName, NXMLElementReader* subReader) { if (! filter) if (subTagName == "filter") filter = dynamic_cast(subReader)->getFilter(); } NXMLPacketReader* NSurfaceFilter::getXMLReader(NPacket* parent, NXMLTreeResolver& resolver) { return new NXMLFilterPacketReader(parent, resolver); } } // namespace regina regina-4.95/engine/surfaces/nxmlfilterreader.h000644 000765 000024 00000013643 12236524106 021417 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nxmlfilterreader.h * \brief Deals with parsing XML data for normal surface filters. */ #ifndef __NXMLFILTERREADER_H #ifndef __DOXYGEN #define __NXMLFILTERREADER_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "surfaces/nsurfacefilter.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * An XML element reader that reads the specific details of a normal * surface filter. These details are generally contained within a * \ ... \ pair. * * Generally a subclass of NXMLFilterReader will be used to receive and * store filters that you care about. However, if you simply wish to * ignore a particular filter (and all of its descendants), you can use * class NXMLFilterReader itself for the filter(s) you wish to ignore. * * Routine getFilter() is used to return the filter that was read; see * its documentation for further notes on how the filter should be * constructed. * * \ifacespython Not present. */ class REGINA_API NXMLFilterReader : public NXMLElementReader { public: /** * Creates a new filter element reader. */ NXMLFilterReader(); /** * Returns the newly allocated filter that has been read by * this element reader. * * Deallocation of this new filter is not the responsibility of * this class. Once this routine gives a non-zero return value, * it should continue to give the same non-zero return value * from this point onwards. * * The default implementation returns 0. * * @return the filter that has been read, or 0 if filter reading * is incomplete, the filter should be ignored or an error * occurred. */ virtual NSurfaceFilter* getFilter(); }; /** * An XML packet reader that reads a single surface filter. * The filter type will be determined by this class and an appropriate * NXMLFilterReader will be used to process the type-specific details. * * \pre The parent XML element reader is in fact an NXMLPacketReader. * * \ifacespython Not present. */ class REGINA_API NXMLFilterPacketReader : public NXMLPacketReader { private: NSurfaceFilter* filter; /**< The surface filter currently being read. */ NPacket* parent; /**< The parent packet of the filter currently being read. */ public: /** * Creates a new surface filter packet reader. * * @param newParent the parent packet of the filter to be read, * or 0 if this filter is to be tree matriarch. * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLFilterPacketReader(NPacket* newParent, NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLFilterReader inline NXMLFilterReader::NXMLFilterReader() { } inline NSurfaceFilter* NXMLFilterReader::getFilter() { return 0; } // Inline functions for NXMLFilterPacketReader inline NXMLFilterPacketReader::NXMLFilterPacketReader(NPacket* newParent, NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), filter(0), parent(newParent) { } inline NPacket* NXMLFilterPacketReader::getPacket() { return filter; } } // namespace regina #endif regina-4.95/engine/surfaces/nxmlfilterreaders.cpp000644 000765 000024 00000014400 12234011536 022121 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "surfaces/nxmlfilterreader.h" #include "surfaces/sfcombination.h" #include "surfaces/sfproperties.h" #include "utilities/stringutils.h" namespace regina { /** * A unique namespace containing XML readers for specific filter types. */ namespace { /** * Reads a plain (non-subclassed) NSurfaceFilter. */ class NPlainFilterReader : public NXMLFilterReader { private: NSurfaceFilter* filter; public: NPlainFilterReader() : filter(new NSurfaceFilter()) { } virtual NSurfaceFilter* getFilter() { return filter; } }; /** * Reads an NSurfaceFilterCombination filter. */ class NCombinationReader : public NXMLFilterReader { private: NSurfaceFilterCombination* filter; public: NCombinationReader() : filter(0) { } virtual NSurfaceFilter* getFilter() { return filter; } NXMLElementReader* startSubElement(const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (! filter) if (subTagName == "op") { std::string type = props.lookup("type"); if (type == "and") { filter = new NSurfaceFilterCombination(); filter->setUsesAnd(true); } else if (type == "or") { filter = new NSurfaceFilterCombination(); filter->setUsesAnd(false); } } return new NXMLElementReader(); } }; /** * Reads an NSurfaceFilterProperties filter. */ class NPropertiesReader : public NXMLFilterReader { private: NSurfaceFilterProperties* filter; public: NPropertiesReader() : filter(new NSurfaceFilterProperties()) { } virtual NSurfaceFilter* getFilter() { return filter; } NXMLElementReader* startSubElement(const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (subTagName == "euler") { return new NXMLCharsReader(); } else if (subTagName == "orbl") { NBoolSet b; if (valueOf(props.lookup("value"), b)) filter->setOrientability(b); } else if (subTagName == "compact") { NBoolSet b; if (valueOf(props.lookup("value"), b)) filter->setCompactness(b); } else if (subTagName == "realbdry") { NBoolSet b; if (valueOf(props.lookup("value"), b)) filter->setRealBoundary(b); } return new NXMLElementReader(); } void endSubElement( const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "euler") { std::list tokens; basicTokenise(back_inserter(tokens), dynamic_cast(subReader)->getChars()); NLargeInteger val; for (std::list::const_iterator it = tokens.begin(); it != tokens.end(); it++) if (valueOf(*it, val)) filter->addEC(val); } } }; } NXMLFilterReader* NSurfaceFilter::getXMLFilterReader(NPacket*) { return new NPlainFilterReader(); } NXMLFilterReader* NSurfaceFilterCombination::getXMLFilterReader(NPacket*) { return new NCombinationReader(); } NXMLFilterReader* NSurfaceFilterProperties::getXMLFilterReader(NPacket*) { return new NPropertiesReader(); } } // namespace regina regina-4.95/engine/surfaces/nxmlsurfacereader.cpp000644 000765 000024 00000016010 12236524106 022104 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "surfaces/coordregistry.h" #include "surfaces/nxmlsurfacereader.h" #include "triangulation/ntriangulation.h" #include "utilities/stringutils.h" namespace regina { void NXMLNormalSurfaceReader::startElement(const std::string&, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { if (! valueOf(props.lookup("len"), vecLen)) vecLen = -1; name = props.lookup("name"); } void NXMLNormalSurfaceReader::initialChars(const std::string& chars) { if (vecLen < 0 || tri == 0) return; std::vector tokens; if (basicTokenise(back_inserter(tokens), chars) % 2 != 0) return; // Create a new vector and read all non-zero entries. // Bring in cases from the coordinate system registry... NNormalSurfaceVector* vec; if (coords == NS_AN_LEGACY) vec = new NNormalSurfaceVectorANStandard(vecLen); else vec = forCoords(coords, NewFunction1(vecLen), 0); if (! vec) return; long pos; NLargeInteger value; for (unsigned long i = 0; i < tokens.size(); i += 2) { if (valueOf(tokens[i], pos)) if (valueOf(tokens[i + 1], value)) if (pos >= 0 && pos < vecLen) { // All looks valid. vec->setElement(pos, value); continue; } // Found something invalid. delete vec; return; } surface = new NNormalSurface(tri, vec); if (! name.empty()) surface->setName(name); } NXMLElementReader* NXMLNormalSurfaceReader::startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (! surface) return new NXMLElementReader(); if (subTagName == "euler") { NLargeInteger val; if (valueOf(props.lookup("value"), val)) surface->eulerChar = val; } else if (subTagName == "orbl") { bool val; if (valueOf(props.lookup("value"), val)) surface->orientable = val; } else if (subTagName == "twosided") { bool val; if (valueOf(props.lookup("value"), val)) surface->twoSided = val; } else if (subTagName == "connected") { bool val; if (valueOf(props.lookup("value"), val)) surface->connected = val; } else if (subTagName == "realbdry") { bool val; if (valueOf(props.lookup("value"), val)) surface->realBoundary = val; } else if (subTagName == "compact") { bool val; if (valueOf(props.lookup("value"), val)) surface->compact = val; } return new NXMLElementReader(); } NXMLElementReader* NXMLNormalSurfaceListReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { if (list) { // The surface list has already been created. if (subTagName == "surface") return new NXMLNormalSurfaceReader(tri, list->coords_); } else { // The surface list has not yet been created. if (subTagName == "params") { long coords; int listType, algorithm; bool embedded; if (valueOf(props.lookup("flavourid"), coords)) { if (valueOf(props.lookup("type"), listType) && valueOf(props.lookup("algorithm"), algorithm)) { // Parameters look sane; create the empty list. list = new NNormalSurfaceList( static_cast(coords), NormalList::fromInt(listType), NormalAlg::fromInt(algorithm)); } else if (valueOf(props.lookup("embedded"), embedded)) { // Parameters look sane but use the old format. list = new NNormalSurfaceList( static_cast(coords), NS_LEGACY | (embedded ? NS_EMBEDDED_ONLY : NS_IMMERSED_SINGULAR), NS_ALG_LEGACY); } } } } return new NXMLElementReader(); } void NXMLNormalSurfaceListReader::endContentSubElement( const std::string& subTagName, NXMLElementReader* subReader) { if (list) if (subTagName == "surface") if (NNormalSurface* s = dynamic_cast(subReader)-> getSurface()) list->surfaces.push_back(s); } NXMLPacketReader* NNormalSurfaceList::getXMLReader(NPacket* parent, NXMLTreeResolver& resolver) { return new NXMLNormalSurfaceListReader( dynamic_cast(parent), resolver); } } // namespace regina regina-4.95/engine/surfaces/nxmlsurfacereader.h000644 000765 000024 00000014057 12236524106 021562 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/nxmlsurfacereader.h * \brief Deals with parsing XML data for normal surface lists. */ #ifndef __NXMLSURFACEREADER_H #ifndef __DOXYGEN #define __NXMLSURFACEREADER_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "surfaces/nnormalsurfacelist.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * An XML element reader that reads a single normal surface. * * \ifacespython Not present. */ class REGINA_API NXMLNormalSurfaceReader : public NXMLElementReader { private: NNormalSurface* surface; /**< The normal surface currently being read. */ NTriangulation* tri; /**< The triangulation in which this surface lives. */ NormalCoords coords; /**< The coordinate system used by this surface. */ long vecLen; /**< The length of corresponding normal surface vector. */ std::string name; /**< The optional name associated with this normal surface. */ public: /** * Creates a new normal surface reader. * * @param newTri the triangulation in which this normal surface lives. * @param newCoords the coordinate system used by this normal surface. */ NXMLNormalSurfaceReader(NTriangulation* newTri, NormalCoords newCoords); /** * Returns the normal surface that has been read. * * @return the newly allocated normal surface, or 0 if an error * occurred. */ NNormalSurface* getSurface(); virtual void startElement(const std::string& tagName, const regina::xml::XMLPropertyDict& tagProps, NXMLElementReader* parentReader); virtual void initialChars(const std::string& chars); virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); }; /** * An XML packet reader that reads a single normal surface list. * * \pre The parent XML element reader is in fact an * NXMLTriangulationReader. * * \ifacespython Not present. */ class REGINA_API NXMLNormalSurfaceListReader : public NXMLPacketReader { private: NNormalSurfaceList* list; /**< The normal surface list currently being read. */ NTriangulation* tri; /**< The triangulation in which these normal surfaces live. */ public: /** * Creates a new normal surface list reader. * * @param newTri the triangulation in which these normal surfaces live. * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLNormalSurfaceListReader(NTriangulation* newTri, NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLNormalSurfaceReader inline NXMLNormalSurfaceReader::NXMLNormalSurfaceReader( NTriangulation* newTri, NormalCoords newCoords) : surface(0), tri(newTri), coords(newCoords), vecLen(-1) { } inline NNormalSurface* NXMLNormalSurfaceReader::getSurface() { return surface; } // Inline functions for NXMLNormalSurfaceListReader inline NXMLNormalSurfaceListReader::NXMLNormalSurfaceListReader( NTriangulation* newTri, NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), list(0), tri(newTri) { } inline NPacket* NXMLNormalSurfaceListReader::getPacket() { return list; } } // namespace regina #endif regina-4.95/engine/surfaces/orientable.cpp000644 000765 000024 00000021757 12234011536 020530 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "surfaces/ndisc.h" namespace regina { namespace { /** * Stores orientation and sides A/B for a normal disc. */ struct OrientData { int orient; /**< Specifies the orientation of the disc. 1 represents with the natural boundary orientation. -1 represents against the natural boundary orientation. 0 means orientation is not yet determined. */ int sides; /**< Specifies which sides of the disc are sides A/B. If sides is 1, discs are numbered from side A to B. If sides is -1, discs are numbered from side B to A. A value of 0 means sides are not yet determined. */ /** * Create a new structure with all values initialised to 0. */ OrientData() : orient(0), sides(0) { } }; } void NNormalSurface::calculateOrientable() const { // This is going to be ghastly. // We will create an orientation and side selection for every disc. // First check that the precondition (compactness) holds, since if // it doesn't we'll have a rather nasty crash (thanks Nathan). if (! isCompact()) { orientable.clear(); twoSided.clear(); connected.clear(); return; } // All right. Off we go. orientable.clear(); twoSided.clear(); connected.clear(); NDiscSetSurfaceData orients(*this); // Stores the orientation of each disc. std::queue discQueue; // A queue of discs whose orientations must be propagated. NDiscSpecIterator it(orients); // Runs through the discs whose orientations might not have yet // been determined. NDiscSpec use; // The disc that currently holds our interest. int nGluingArcs; // The number of arcs on the current disc to // which an adjacent disc might may be glued. NDiscSpec* adjDisc; // The disc to which the current disc is glued. NPerm4 arc[8]; // Holds each gluing arc for the current disc. NPerm4 adjArc; // Represents the corresponding gluing arc on the // adjacent disc. bool myOrient, yourOrient, sameOrient; bool mySides, yourSides, sameSides; int i; bool noComponents = true; while (true) { // If there's no discs to propagate from, choose the next // unoriented one. while (discQueue.empty() && (! it.done())) { if (orients.data(*it).orient == 0) { orients.data(*it).orient = 1; orients.data(*it).sides = 1; discQueue.push(*it); if (noComponents) noComponents = false; else connected = false; } it++; } if (discQueue.empty()) break; // At the head of the queue is the next already-oriented disc // whose orientation must be propagated. use = discQueue.front(); discQueue.pop(); // Determine along which arcs we may glue other discs. if (use.type < 4) { // Current disc is a triangle. nGluingArcs = 3; for (i = 0; i < 3; i++) arc[i] = triDiscArcs(use.type, i); } else if (use.type < 7) { // Current disc is a quad. nGluingArcs = 4; for (i = 0; i < 4; i++) arc[i] = quadDiscArcs(use.type - 4, i); } else { // Current disc is an octagon. nGluingArcs = 8; for (i = 0; i < 8; i++) arc[i] = octDiscArcs(use.type - 7, i); } // Process any discs that might be adjacent to each of these // gluing arcs. for (i = 0; i < nGluingArcs; i++) { // Establish which is the adjacent disc. adjDisc = orients.adjacentDisc(use, arc[i], adjArc); if (adjDisc == 0) continue; // There is actually a disc glued along this arc. // Determine the desired properties of the adjacent disc. if (! orientable.known()) { myOrient = discOrientationFollowsEdge(use.type, arc[i][0], arc[i][1], arc[i][2]); yourOrient = discOrientationFollowsEdge(adjDisc->type, adjArc[0], adjArc[2], adjArc[1]); sameOrient = (myOrient && yourOrient) || ((! myOrient) && (! yourOrient)); } else sameOrient = true; if (! twoSided.known()) { mySides = numberDiscsAwayFromVertex(use.type, arc[i][0]); yourSides = numberDiscsAwayFromVertex( adjDisc->type, adjArc[0]); sameSides = (mySides && yourSides) || ((! mySides) && (! yourSides)); } else sameSides = true; // Propagate these properties. if (orients.data(*adjDisc).orient == 0) { orients.data(*adjDisc).orient = (sameOrient ? orients.data(use).orient : -orients.data(use).orient); orients.data(*adjDisc).sides = (sameSides ? orients.data(use).sides : -orients.data(use).sides); discQueue.push(*adjDisc); } else { if (! orientable.known()) { if (sameOrient) { if (orients.data(*adjDisc).orient != orients.data(use).orient) orientable = false; } else { if (orients.data(*adjDisc).orient == orients.data(use).orient) orientable = false; } } if (! twoSided.known()) { if (sameSides) { if (orients.data(*adjDisc).sides != orients.data(use).sides) twoSided = false; } else { if (orients.data(*adjDisc).sides == orients.data(use).sides) twoSided = false; } } } // Tidy up. delete adjDisc; if (orientable.known() && twoSided.known() && connected.known()) return; } } // We made it through! Any properties that weren't proven false // must be true. if (! orientable.known()) orientable = true; if (! twoSided.known()) twoSided = true; if (! connected.known()) connected = true; } } // namespace regina regina-4.95/engine/surfaces/quadtostd.cpp000644 000765 000024 00000062566 12234011536 020417 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "regina-config.h" #include "enumerate/ndoubledescription.h" #include "maths/nmatrixint.h" #include "maths/nray.h" #include "surfaces/nnormalsurface.h" #include "surfaces/nnormalsurfacelist.h" #include "surfaces/normalspec-impl.h" #include "triangulation/ntriangulation.h" #include "triangulation/nvertex.h" #include "utilities/nbitmask.h" #include #include namespace regina { // Although the conversion routines are template routines, we implement // them here in this C++ file to avoid dragging them into the headers. // // The following definitions and declarations should ensure that the // templates are fully instantiated where they need to be. NNormalSurfaceList* NNormalSurfaceList::quadToStandard() const { return internalReducedToStandard(); } NNormalSurfaceList* NNormalSurfaceList::quadOctToStandardAN() const { return internalReducedToStandard(); } template void NNormalSurfaceList::buildStandardFromReduced< NNormalSurfaceList::NormalSpec>(NTriangulation*, const std::vector&, NProgressTracker*); template void NNormalSurfaceList::buildStandardFromReduced< NNormalSurfaceList::AlmostNormalSpec>(NTriangulation*, const std::vector&, NProgressTracker*); /** * Put helper classes and constants into an anonymous namespace. */ namespace { /** * A back insertion iterator that defines \a value_type, which is * required by NDoubleDescription::enumerate(). * * The standard back_insert_iterator does not provide this typedef, * so we subclass and provide the typedef ourselves. */ class VectorInserter : public std::back_insert_iterator< std::vector > { public: typedef NNormalSurfaceVector* value_type; VectorInserter(std::vector& v) : std::back_insert_iterator< std::vector >(v) { } }; /** * A helper class for converting between reduced and standard * solution sets, describing a single ray (which is typically a * vertex in some partial solution space). * * This class derives from NRay, which stores the coordinates of * the ray itself in standard coordinates. This RaySpec class also * stores a bitmask indicating which of these coordinates are set to zero. * * The \a BitmaskType template argument describes how the bitmask of * zero coordinates will be stored. The ith coordinate position * corresponds to the ith bit in the bitmask, and each bit is set * to \c true if and only if the corresponding coordinate is zero. * * Since this class is used heavily, faster bitmask types such as * NBitmask1 and NBitmask2 are preferred; however, if the number * of coordinates is too large then the slower general-use NBitmask * class will need to be used instead. * * \pre The template argument \a BitmaskType is one of Regina's * bitmask types, such as NBitmask, NBitmask1 or NBitmask2. */ template class RaySpec : private NRay { private: BitmaskType facets_; /**< A bitmask listing which coordinates of this ray are currently set to zero. */ public: /** * Creates a new ray whose coordinates are a clone of the * given vector. * * @param v the vector to clone. */ RaySpec(const NNormalSurfaceVector* v) : NRay(v->size()), facets_(v->size()) { // Note that the vector is initialised to zero since // this is what NLargeInteger's default constructor does. for (size_t i = 0; i < v->size(); ++i) if ((elements[i] = (*v)[i]) == zero) facets_.set(i, true); } /** * Creates a new ray that represents the \e negative of * the link of the given vertex. * * @param tri the underlying triangulation. * @param whichLink the index of the vertex whose link * we should negate; this must be strictly less than * tri->getNumberOfVertices(). * @param coordsPerTet the number of standard coordinate * positions for each tetrahedron (that is, 7 if we are * working with normal surfaces, or 10 if we are working * with almost normal surfaces). */ RaySpec(const NTriangulation* tri, unsigned long whichLink, unsigned coordsPerTet) : NRay(coordsPerTet * tri->getNumberOfTetrahedra()), facets_(coordsPerTet * tri->getNumberOfTetrahedra()) { // Note that the vector is initialised to zero since // this is what NLargeInteger's default constructor does. for (size_t i = 0; i < size(); ++i) if (i % coordsPerTet > 3) { // Not a triangular coordinate. facets_.set(i, true); } else if (tri->getTetrahedron(i / coordsPerTet)-> getVertex(i % coordsPerTet)->markedIndex() == whichLink) { // A triangular coordinate in our vertex link. elements[i] = -1; } else { // A triangular coordinate not in our vertex link. facets_.set(i, true); } } /** * Creates a new ray, describing where the plane between the * two given rays meets the given axis hyperplane. Here * "the given axis hyperplane" means the hyperplane along which * the coordth coordinate is zero. * * \pre The coordth coordinates of \a pos and \a neg * are strictly positive and negative respectively. * * @param pos the first of the given rays, in which the given * coordinate is positive. * @param neg the second of the given rays, in which the given * coordinate is negative. * @param coord the index of the coordinate that we must set * to zero to form the intersecting hyperplane. */ RaySpec(const RaySpec& pos, const RaySpec& neg, size_t coord) : NRay(pos.size()), facets_(pos.facets_) { facets_ &= neg.facets_; // Note that we may need to re-enable some bits in \a facets_, // since we may end up setting some triangle coordinates // to zero that were not zero in either \a pos or \a neg. NLargeInteger posDiff = pos[coord]; NLargeInteger negDiff = neg[coord]; for (size_t i = 0; i < size(); ++i) if ((elements[i] = neg[i] * posDiff - pos[i] * negDiff) == zero) facets_.set(i, true); scaleDown(); } /** * Returns the bitmask listing which coordinates of this ray * are currently set to zero. See the class notes for details. * * The length of this bitmask is the same as the length of the * underlying vector for this ray. * * @return the bitmask of zero coordinates. */ inline const BitmaskType& facets() const { return facets_; } /** * Determines whether this ray has zero coordinates in every * position where \e both of the given rays simultaneously * have zero coordinates. * * The bitmask \a ignoreFacets represents a list of coordinate * positions that should be ignored for the purposes of this * routine. * * @param x the first of the two given rays to examine. * @param y the second of the two given rays to examine. * @param ignoreFacets a bitmask of coordinate positions to * ignore. * @return \c false if there is some coordinate position * where (i) both \a x and \a y are zero, (ii) this vector * is not zero, and (iii) the corresponding bit in \a ignoreFacets * is not set (i.e., is \c false). Returns \c true otherwise. */ inline bool onAllCommonFacets(const RaySpec& x, const RaySpec& y, BitmaskType ignoreFacets) const { ignoreFacets |= facets_; return ignoreFacets.containsIntn(x.facets_, y.facets_); } /** * Reduces the underlying vector by subtracting as many copies * of the given vertex link as possible, without allowing any of * the corresponding coordinates in this ray to become negative. * * \pre None of the coordinates in this ray that correspond * to discs in the given vertex link are already negative. * * @param link the vertex link to subtract copies of. */ void reduce(const RaySpec& link) { if (! (facets_ <= link.facets_)) return; NLargeInteger max = NLargeInteger::infinity; size_t i; for (i = 0; i < size(); ++i) if (! link.facets_.get(i)) if (max > elements[i]) max = elements[i]; for (i = 0; i < size(); ++i) if (! link.facets_.get(i)) if ((elements[i] -= max) == zero) facets_.set(i, true); } /** * Returns a new normal (or almost normal) surface whose * coordinates are described by this vector. The template * argument dictates the class of the underlying normal surface * vector (i.e., the underlying coordinate system). * * @param tri the underlying triangulation. * @return a newly created normal surface based on this vector. */ template NNormalSurface* recover(NTriangulation* tri) const { VectorClass* v = new VectorClass(size()); for (size_t i = 0; i < size(); ++i) v->setElement(i, elements[i]); return new NNormalSurface(tri, v); } /** * Returns the sign of the given element of this vector. * * @return 1, 0 or -1 according to whether the indexth * element of this vector is positive, zero or negative * respectively. */ inline int sign(size_t index) const { if (facets_.get(index)) return 0; return (elements[index] > zero ? 1 : -1); } using NRay::scaleDown; }; } // anonymous namespace template NNormalSurfaceList* NNormalSurfaceList::internalReducedToStandard() const { NTriangulation* owner = getTriangulation(); // Basic sanity checks: if (coords_ != Variant::reducedCoords()) return 0; if (which_ != (NS_EMBEDDED_ONLY | NS_VERTEX)) return 0; if (owner->isIdeal() || ! owner->isValid()) return 0; // Prepare a final surface list. NNormalSurfaceList* ans = new NNormalSurfaceList( Variant::standardCoords(), NS_EMBEDDED_ONLY | NS_VERTEX, algorithm_ | NS_VERTEX_VIA_REDUCED); if (owner->getNumberOfTetrahedra() > 0) { // Run our internal conversion routine. ans->buildStandardFromReduced(owner, surfaces); } // All done! owner->insertChildLast(ans); return ans; } template void NNormalSurfaceList::buildStandardFromReduced(NTriangulation* owner, const std::vector& reducedList, NProgressTracker* tracker) { size_t nFacets = Variant::stdLen(owner->getNumberOfTetrahedra()); // Choose a bitmask type for representing the set of facets that a // ray belongs to; in particular, use a (much faster) optimised // bitmask type if we can. // Then farm the work out to the real conversion routine that is // templated on the bitmask type. if (nFacets <= 8 * sizeof(unsigned)) buildStandardFromReducedUsing >(owner, reducedList, tracker); else if (nFacets <= 8 * sizeof(unsigned long)) buildStandardFromReducedUsing >(owner, reducedList, tracker); #ifdef LONG_LONG_FOUND else if (nFacets <= 8 * sizeof(unsigned long long)) buildStandardFromReducedUsing >(owner, reducedList, tracker); else if (nFacets <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned)) buildStandardFromReducedUsing >(owner, reducedList, tracker); else if (nFacets <= 8 * sizeof(unsigned long long) + 8 * sizeof(unsigned long)) buildStandardFromReducedUsing >(owner, reducedList, tracker); else if (nFacets <= 16 * sizeof(unsigned long long)) buildStandardFromReducedUsing >(owner, reducedList, tracker); #else else if (nFacets <= 8 * sizeof(unsigned long) + 8 * sizeof(unsigned)) buildStandardFromReducedUsing >(owner, reducedList, tracker); else if (nFacets <= 16 * sizeof(unsigned long)) buildStandardFromReducedUsing >(owner, reducedList, tracker); #endif else buildStandardFromReducedUsing(owner, reducedList, tracker); } template void NNormalSurfaceList::buildStandardFromReducedUsing(NTriangulation* owner, const std::vector& reducedList, NProgressTracker* tracker) { // Prepare for the reduced-to-standard double description run. unsigned long n = owner->getNumberOfTetrahedra(); size_t slen = Variant::stdLen(n); // # standard coordinates unsigned long llen = owner->getNumberOfVertices(); // # vertex links unsigned i; // Recreate the quadrilateral constraints (or the corresponding // constraints for almost normal surfaces) as bitmasks. // Since we have a non-empty triangulation, we know the list of // constraints is non-empty. NEnumConstraintList* constraints = Variant::StandardVector::makeEmbeddedConstraints(owner); BitmaskType* constraintsBegin = new BitmaskType[constraints->size()]; BitmaskType* constraintsEnd = constraintsBegin; NEnumConstraintList::const_iterator cit; for (NEnumConstraintList::const_iterator cit = constraints->begin(); cit != constraints->end(); ++cit, ++constraintsEnd) { constraintsEnd->reset(slen); constraintsEnd->set(cit->begin(), cit->end(), true); } delete constraints; // Create all vertex links. typename Variant::StandardVector** link = new typename Variant::StandardVector*[llen]; for (i = 0; i < llen; ++i) { link[i] = new typename Variant::StandardVector(slen); const std::vector& emb = owner->getVertex(i)-> getEmbeddings(); std::vector::const_iterator embit; for (embit = emb.begin(); embit != emb.end(); ++embit) link[i]->setElement(Variant::stdPos( embit->getTetrahedron()->markedIndex(), embit->getVertex()), 1); } // Create the initial set of rays: typedef std::vector*> RaySpecList; RaySpecList list[2]; NNormalSurfaceVector* v; std::vector::const_iterator qit; for (qit = reducedList.begin(); qit != reducedList.end(); ++qit) { v = static_cast( (*qit)->rawVector())->makeMirror(owner); list[0].push_back(new RaySpec( static_cast(v))); delete v; } // Each additional inequality is of the form tri_coord >= 0. // We will therefore just create them on the fly as we need them. // And run! BitmaskType ignoreFacets(slen); for (i = 0; i < slen; ++i) if (i % Variant::totalPerTet < 4) ignoreFacets.set(i, true); int workingList = 0; unsigned vtx; size_t tcoord; RaySpec* linkSpec; RaySpecList pos, neg; typename RaySpecList::iterator it, posit, negit; int sign; BitmaskType* constraintMask; bool broken; unsigned long slices = 0; unsigned iterations; for (vtx = 0; vtx < llen; ++vtx) { linkSpec = new RaySpec(link[vtx]); delete link[vtx]; list[workingList].push_back(new RaySpec(owner, vtx, Variant::totalPerTet)); const std::vector& emb = owner->getVertex(vtx)-> getEmbeddings(); std::vector::const_iterator embit; for (embit = emb.begin(); embit != emb.end(); ++embit) { // Update the state of progress and test for cancellation. if (tracker && ! tracker->setPercent(25.0 * slices++ / n)) { for (it = list[workingList].begin(); it != list[workingList].end(); ++it) delete *it; return; } tcoord = Variant::stdPos(embit->getTetrahedron()->markedIndex(), embit->getVertex()); // Add the inequality v[tcoord] >= 0. for (it = list[workingList].begin(); it != list[workingList].end(); ++it) { sign = (*it)->sign(tcoord); if (sign == 0) list[1 - workingList].push_back(*it); else if (sign > 0) { list[1 - workingList].push_back(*it); pos.push_back(*it); } else neg.push_back(*it); } iterations = 0; for (posit = pos.begin(); posit != pos.end(); ++posit) for (negit = neg.begin(); negit != neg.end(); ++negit) { // Test for cancellation, but not every time (since // this involves expensive mutex locking). if (tracker && ++iterations == 100) { iterations = 0; if (tracker->isCancelled()) { for (it = list[1 - workingList].begin(); it != list[1 - workingList].end(); ++it) delete *it; for (it = neg.begin(); it != neg.end(); ++it) delete *it; return; } } // Find the facets that both rays have in common. BitmaskType join((*posit)->facets()); join &= ((*negit)->facets()); // Fukuda and Prodon's dimensional filtering. // Initial experimentation suggests that this // is not helpful (perhaps because of the extremely // nice structure of this particular enumeration problem // and the consequential way in which one solution set // expands to the next). Comment it out for now. /* BitmaskType tmpMask(ignoreFacets); tmpMask.flip(); tmpMask &= join; if (tmpMask.bits() < 2 * n + vtx - 1) continue; */ // Are these vectors compatible? // Invert join so that it has a true bit for each // non-zero coordinate. join.flip(); broken = false; for (constraintMask = constraintsBegin; constraintMask != constraintsEnd; ++constraintMask) { BitmaskType mask(join); mask &= *constraintMask; if (! mask.atMostOneBit()) { broken = true; break; } } if (broken) continue; // Are these vectors adjacent? broken = false; for (it = list[workingList].begin(); it != list[workingList].end(); ++it) { if (*it != *posit && *it != *negit && (*it)->onAllCommonFacets(**posit, **negit, ignoreFacets)) { broken = true; break; } } if (broken) continue; // All good! Join them and put the intersection in the // new solution set. list[1 - workingList].push_back(new RaySpec( **posit, **negit, tcoord)); } // Clean up and prepare for the next iteration. for (negit = neg.begin(); negit != neg.end(); ++negit) delete *negit; pos.clear(); neg.clear(); list[workingList].clear(); ignoreFacets.set(tcoord, false); workingList = 1 - workingList; } // We're done cancelling this vertex link. // Now add the vertex link itself, and cancel any future vertex // links that we might have created. // Note that cancelling future vertex links might introduce // new common factors that can be divided out. list[workingList].push_back(linkSpec); for (it = list[workingList].begin(); it != list[workingList].end(); ++it) { for (i = vtx + 1; i < llen; ++i) (*it)->reduce(link[i]); (*it)->scaleDown(); } } // All done! Put the solutions into the normal surface list and clean up. for (typename RaySpecList::iterator it = list[workingList].begin(); it != list[workingList].end(); ++it) { surfaces.push_back((*it)-> template recover(owner)); delete *it; } delete[] link; delete[] constraintsBegin; if (tracker) tracker->setPercent(100); } } // namespace regina regina-4.95/engine/surfaces/sfcombination.cpp000644 000765 000024 00000006520 12234011536 021226 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/sfcombination.h" #define TYPE_AND 1 #define TYPE_OR 2 namespace regina { bool NSurfaceFilterCombination::accept(const NNormalSurface& surface) const { if (usesAnd) { // Combine all child filters using AND. for (NPacket* child = getFirstTreeChild(); child; child = child->getNextTreeSibling()) if (child->getPacketType() == NSurfaceFilter::packetType) if (! (dynamic_cast(child)->accept(surface))) return false; return true; } else { // Combine all child filters using OR. for (NPacket* child = getFirstTreeChild(); child; child = child->getNextTreeSibling()) if (child->getPacketType() == NSurfaceFilter::packetType) if (dynamic_cast(child)->accept(surface)) return true; return false; } } void NSurfaceFilterCombination::writeXMLFilterData(std::ostream& out) const { out << " \n"; } } // namespace regina regina-4.95/engine/surfaces/sfcombination.h000644 000765 000024 00000013547 12234011536 020702 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/sfcombination.h * \brief Contains a normal surface filter that simply combines other * filters. */ #ifndef __SFCOMBINATION_H #ifndef __DOXYGEN #define __SFCOMBINATION_H #endif #include "regina-core.h" #include "surfaces/nsurfacefilter.h" namespace regina { class NSurfaceFilterCombination; /** * \weakgroup surfaces * @{ */ /** * Stores information about the combination surface filter. * See the general SurfaceFilterInfo template notes for further details. * * \ifacespython Not present. */ template <> struct SurfaceFilterInfo { typedef NSurfaceFilterCombination Class; inline static const char* name() { return "Combination filter"; } }; /** * A normal surface filter that simply combines other filters. * This filter will combine, using boolean \a and or \a or, all of the * filters that are immediate children of this packet. This packet may * have children that are not normal surface filters; such children will * simply be ignored. * * If there are no immediate child filters, a normal surface will be * accepted if this is an \a and filter and rejected if this is an \a or * filter. */ class REGINA_API NSurfaceFilterCombination : public NSurfaceFilter { REGINA_SURFACE_FILTER(NSurfaceFilterCombination, NS_FILTER_COMBINATION) private: bool usesAnd; /**< \c true if children are combined using boolean \a and, or \c false if children are combined using boolean \a or. */ public: /** * Creates a new surface filter that accepts all normal surfaces. * This will be an \a and filter. */ NSurfaceFilterCombination(); /** * Creates a new surface filter that is a clone of the given * surface filter. * * @param cloneMe the surface filter to clone. */ NSurfaceFilterCombination(const NSurfaceFilterCombination& cloneMe); /** * Determines whether this is an \a and or an \a or combination. * * @return \c true if this is an \a and combination, or \c false * if this is an \a or combination. */ bool getUsesAnd() const; /** * Sets whether this is an \a and or an \a or combination. * * @param value \c true if this is to be an \a and combination, * or \c false if this is to be an \a or combination. */ void setUsesAnd(bool value); virtual bool accept(const NNormalSurface& surface) const; virtual void writeTextLong(std::ostream& out) const; static NXMLFilterReader* getXMLFilterReader(NPacket* parent); protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLFilterData(std::ostream& out) const; }; /*@}*/ // Inline functions for NSurfaceFilterCombination inline NSurfaceFilterCombination::NSurfaceFilterCombination() : usesAnd(true) { } inline NSurfaceFilterCombination::NSurfaceFilterCombination( const NSurfaceFilterCombination& cloneMe) : NSurfaceFilter(), usesAnd(cloneMe.usesAnd) { } inline bool NSurfaceFilterCombination::getUsesAnd() const { return usesAnd; } inline void NSurfaceFilterCombination::setUsesAnd(bool value) { ChangeEventSpan span(this); usesAnd = value; } inline void NSurfaceFilterCombination::writeTextLong(std::ostream& o) const { o << (usesAnd ? "AND" : "OR") << " combination normal surface filter\n"; } inline NPacket* NSurfaceFilterCombination::internalClonePacket(NPacket*) const { return new NSurfaceFilterCombination(*this); } } // namespace regina #endif regina-4.95/engine/surfaces/sfproperties.cpp000644 000765 000024 00000011406 12234011536 021117 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/sfproperties.h" #include "surfaces/nnormalsurface.h" #include "utilities/xmlutils.h" #define PROPSF_EULER 1001 #define PROPSF_ORIENT 1002 #define PROPSF_COMPACT 1003 #define PROPSF_REALBDRY 1004 namespace regina { NLargeInteger NSurfaceFilterProperties::getEC(unsigned long index) const { std::set::const_iterator it = eulerCharacteristic.begin(); advance(it, index); return *it; } bool NSurfaceFilterProperties::accept(const NNormalSurface& surface) const { if (! realBoundary.contains(surface.hasRealBoundary())) return false; if (! compactness.contains(surface.isCompact())) return false; // Some properties may only be calculated for compact surfaces. if (surface.isCompact()) { if (! orientability.contains(surface.isOrientable())) return false; if (eulerCharacteristic.size() > 0) if (! eulerCharacteristic.count(surface.getEulerCharacteristic())) return false; } // All tests passed. return true; } void NSurfaceFilterProperties::writeTextLong(std::ostream& o) const { o << "Filter normal surfaces with restrictions:\n"; if (eulerCharacteristic.size() > 0) { o << " Euler characteristic:"; std::set::const_reverse_iterator it; for (it = eulerCharacteristic.rbegin(); it != eulerCharacteristic.rend(); it++) o << ' ' << *it; o << '\n'; } if (orientability != NBoolSet::sBoth) o << " Orientability: " << orientability << '\n'; if (compactness != NBoolSet::sBoth) o << " Compactness: " << compactness << '\n'; if (realBoundary != NBoolSet::sBoth) o << " Has real boundary: " << realBoundary << '\n'; } void NSurfaceFilterProperties::writeXMLFilterData(std::ostream& out) const { using regina::xml::xmlValueTag; if (eulerCharacteristic.size() > 0) { out << " "; for (std::set::const_iterator it = eulerCharacteristic.begin(); it != eulerCharacteristic.end(); it++) out << (*it) << ' '; out << "\n"; } if (orientability != NBoolSet::sBoth) out << " " << xmlValueTag("orbl", orientability) << '\n'; if (compactness != NBoolSet::sBoth) out << " " << xmlValueTag("compact", compactness) << '\n'; if (realBoundary != NBoolSet::sBoth) out << " " << xmlValueTag("realbdry", realBoundary) << '\n'; } } // namespace regina regina-4.95/engine/surfaces/sfproperties.h000644 000765 000024 00000026357 12234011536 020577 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/sfproperties.h * \brief Contains a normal surface filter that filters by basic * properties. */ #ifndef __SFPROPERTIES_H #ifndef __DOXYGEN #define __SFPROPERTIES_H #endif #include #include "regina-core.h" #include "maths/ninteger.h" #include "surfaces/nsurfacefilter.h" #include "utilities/nbooleans.h" namespace regina { class NSurfaceFilterProperties; /** * \weakgroup surfaces * @{ */ /** * Stores information about the property-based surface filter. * See the general SurfaceFilterInfo template notes for further details. * * \ifacespython Not present. */ template <> struct SurfaceFilterInfo { typedef NSurfaceFilterProperties Class; inline static const char* name() { return "Filter by basic properties"; } }; /** * A normal surface filter that filters by basic properties of the normal * surface. * * If a property of the surface (such as Euler characteristic or * orientability) cannot be determined, the surface will pass any test * based on that particular property. For instance, say a surface is * required to be both orientable and compact, and say that orientability * cannot be determined. Then the surface will be accepted solely on the * basis of whether or not it is compact. */ class REGINA_API NSurfaceFilterProperties : public NSurfaceFilter { REGINA_SURFACE_FILTER(NSurfaceFilterProperties, NS_FILTER_PROPERTIES) private: std::set eulerCharacteristic; /**< The set of allowable Euler characteristics. An empty set signifies that any Euler characteristic is allowed. */ NBoolSet orientability; /**< The set of allowable orientability properties. */ NBoolSet compactness; /**< The set of allowable compactness properties. */ NBoolSet realBoundary; /**< The set of allowable has-real-boundary properties. */ public: /** * Creates a new surface filter that accepts all normal surfaces. */ NSurfaceFilterProperties(); /** * Creates a new surface filter that is a clone of the given * surface filter. * * @param cloneMe the surface filter to clone. */ NSurfaceFilterProperties(const NSurfaceFilterProperties& cloneMe); /** * Returns the set of allowable Euler characteristics. Any * surface whose Euler characteristic is not in this set will not * be accepted by this filter. The set will be given in * ascending order with no element repeated. * * If this set is empty, all Euler characteristics will be * accepted. * * \ifacespython This routine returns a python sequence. * * @return the set of allowable Euler characteristics. */ const std::set& getECs() const; /** * Returns the number of allowable Euler characteristics. * See getECs() for further details. * * @return the number of allowable Euler characteristics. */ unsigned long getNumberOfECs() const; /** * Returns the allowable Euler characteristic at the given index * in the set. See getECs() for further details. * * @param index the index in the set of allowable Euler * characteristics; this must be between 0 and getNumberOfECs()-1 * inclusive. * @return the requested allowable Euler characteristic. */ NLargeInteger getEC(unsigned long index) const; /** * Returns the set of allowable orientabilities. Note that this * is a subset of { true, false }. * Any surface whose orientability is not in this set will not be * accepted by this filter. * * @return the set of allowable orientabilities. */ NBoolSet getOrientability() const; /** * Returns the set of allowable compactness properties. * Note that this is a subset of { true, false }. * Any surface whose compactness property is not in this set will * not be accepted by this filter. * * @return the set of allowable compactness properties. */ NBoolSet getCompactness() const; /** * Returns the set of allowable has-real-boundary properties. * Note that this is a subset of { true, false }. * Any surface whose has-real-boundary property is not in this set * will not be accepted by this filter. * * @return the set of allowable has-real-boundary properties. */ NBoolSet getRealBoundary() const; /** * Adds the given Euler characteristic to the set of allowable * Euler characteristics. See getECs() for further details. * * @param ec the new allowable Euler characteristic. */ void addEC(const NLargeInteger& ec); /** * Removes the given Euler characteristic from the set of allowable * Euler characteristics. See getECs() for further details. * * Note that if the allowable set is completely emptied, this * filter will allow any Euler characteristic to pass. * * \pre The given Euler characteristic is currently in the * allowable set. * * @param ec the allowable Euler characteristic to remove. */ void removeEC(const NLargeInteger& ec); /** * Empties the set of allowable Euler characteristics. See * getECs() for further details. * * Note that this will mean that this filter will allow * any Euler characteristic to pass. */ void removeAllECs(); /** * Sets the set of allowable orientabilities. * See getOrientability() for further details. * * @param value the new set of allowable orientabilities. */ void setOrientability(const NBoolSet& value); /** * Sets the set of allowable compactness properties. * See getCompactness() for further details. * * @param value the new set of allowable compactness properties. */ void setCompactness(const NBoolSet& value); /** * Sets the set of allowable has-real-boundary properties. * See getRealBoundary() for further details. * * @param value the new set of allowable has-real-boundary * properties. */ void setRealBoundary(const NBoolSet& value); virtual bool accept(const NNormalSurface& surface) const; virtual void writeTextLong(std::ostream& out) const; static NXMLFilterReader* getXMLFilterReader(NPacket* parent); protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLFilterData(std::ostream& out) const; }; /*@}*/ // Inline functions for NSurfaceFilterProperties inline NSurfaceFilterProperties::NSurfaceFilterProperties() : orientability(NBoolSet::sBoth), compactness(NBoolSet::sBoth), realBoundary(NBoolSet::sBoth) { } inline NSurfaceFilterProperties::NSurfaceFilterProperties( const NSurfaceFilterProperties& cloneMe) : NSurfaceFilter(), eulerCharacteristic(cloneMe.eulerCharacteristic), orientability(cloneMe.orientability), compactness(cloneMe.compactness), realBoundary(cloneMe.realBoundary) { } inline const std::set& NSurfaceFilterProperties::getECs() const { return eulerCharacteristic; } inline unsigned long NSurfaceFilterProperties::getNumberOfECs() const { return eulerCharacteristic.size(); } inline NBoolSet NSurfaceFilterProperties::getOrientability() const { return orientability; } inline NBoolSet NSurfaceFilterProperties::getCompactness() const { return compactness; } inline NBoolSet NSurfaceFilterProperties::getRealBoundary() const { return realBoundary; } inline void NSurfaceFilterProperties::addEC(const NLargeInteger& ec) { ChangeEventSpan span(this); eulerCharacteristic.insert(ec); } inline void NSurfaceFilterProperties::removeEC(const NLargeInteger& ec) { ChangeEventSpan span(this); eulerCharacteristic.erase(ec); } inline void NSurfaceFilterProperties::removeAllECs() { ChangeEventSpan span(this); eulerCharacteristic.clear(); } inline void NSurfaceFilterProperties::setOrientability(const NBoolSet& value) { ChangeEventSpan span(this); orientability = value; } inline void NSurfaceFilterProperties::setCompactness(const NBoolSet& value) { ChangeEventSpan span(this); compactness = value; } inline void NSurfaceFilterProperties::setRealBoundary(const NBoolSet& value) { ChangeEventSpan span(this); realBoundary = value; } inline NPacket* NSurfaceFilterProperties::internalClonePacket(NPacket*) const { return new NSurfaceFilterProperties(*this); } } // namespace regina #endif regina-4.95/engine/surfaces/spheres.cpp000644 000765 000024 00000015644 12234011536 020053 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/nnormalsurface.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/ntriangulation.h" namespace regina { NNormalSurface* NNormalSurface::findNonTrivialSphere(NTriangulation* tri) { if (! tri->hasBoundaryTriangles()) { // Switch to the optimised (and non-deprecated) verison of this routine. // Here we use the fact that, with no boundary triangles, there // cannot be any normal discs. return tri->hasNonTrivialSphereOrDisc(); } // The older, deprecated code follows. // If the triangulation is already known to be 0-efficient, there // are no non-trivial normal 2-spheres. if (tri->knowsZeroEfficient() && tri->isZeroEfficient()) return 0; // Construct the vertex normal surfaces and look for any spheres // or 1-sided projective planes. NNormalSurfaceList* surfaces = NNormalSurfaceList::enumerate(tri, NS_STANDARD); unsigned long nSurfaces = surfaces->getNumberOfSurfaces(); const NNormalSurface* s; NLargeInteger chi; for (unsigned long i = 0; i < nSurfaces; i++) { s = surfaces->getSurface(i); // No need to test for connectedness since these are vertex surfaces. if (s->isCompact() && (! s->hasRealBoundary()) && (! s->isVertexLinking())) { chi = s->getEulerCharacteristic(); if (chi == 2 || (chi == 1 && ! s->isTwoSided())) { // It's a non-trivial 2-sphere! // Clone the surface for our return value. NNormalSurface* ans = (chi == 1 ? s->doubleSurface() : s->clone()); surfaces->makeOrphan(); delete surfaces; return ans; } } } // Nothing was found. // Therefore there cannot be any non-trivial 2-spheres at all. surfaces->makeOrphan(); delete surfaces; return 0; } NNormalSurface* NNormalSurface::findVtxOctAlmostNormalSphere( NTriangulation* tri, bool quadOct) { // This routine is deprecated. // However, stick with this implementation instead of running the // faster NTriangulation::hasOctagonalAlmostNormalSphere(), since // the docs for this routine insist on a vertex surface in a // particular coordinate system. NNormalSurfaceList* surfaces = NNormalSurfaceList::enumerate(tri, quadOct ? NS_AN_QUAD_OCT : NS_AN_STANDARD); unsigned long nSurfaces = surfaces->getNumberOfSurfaces(); unsigned long nTets = tri->getNumberOfTetrahedra(); // Note that our surfaces are guaranteed to be in smallest possible // integer coordinates. // We are also guaranteed at most one non-zero octagonal coordinate. // Note that in this search a 1-sided projective plane is no good, // since when doubled it gives too many octagonal discs. const NNormalSurface* s; unsigned long tet; int oct; NLargeInteger octCoord; for (unsigned long i = 0; i < nSurfaces; i++) { s = surfaces->getSurface(i); // No need to test for connectedness since these are vertex surfaces. // No need to test for vertex links since we're about to test // for octagons. if (s->isCompact() && (! s->hasRealBoundary())) { if (s->getEulerCharacteristic() == 2) { // Test for the existence of precisely one octagon. for (tet = 0; tet < nTets; tet++) for (oct = 0; oct < 3; oct++) if ((octCoord = s->getOctCoord(tet, oct)) > 0) { // We found our one and only non-zero // octagonal coordinate. if (octCoord > 1) { // Too many octagons. Move along. // Bail out of all our loops. tet = nTets; // To escape tet loop break; // To escape oct loop } else { // This is our almost normal 2-sphere! // Clone the surface for our return value. NNormalSurface* ans = s->clone(); surfaces->makeOrphan(); delete surfaces; return ans; } } // Either too many octagons or none at all. // On to the next surface. } } } // Nothing was found. // Therefore there cannot be any non-trivial 2-spheres at all. surfaces->makeOrphan(); delete surfaces; return 0; } } // namespace regina regina-4.95/engine/surfaces/stdtoquad.cpp000644 000765 000024 00000015534 12234011536 020410 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "surfaces/normalspec-impl.h" #include "surfaces/nnormalsurface.h" #include "surfaces/nnormalsurfacelist.h" #include "surfaces/nsquad.h" #include "surfaces/nsquadoct.h" #include "triangulation/ntriangulation.h" namespace regina { // Although internalStandardToReduced() is a template routine, we implement // it here in this C++ file to avoid dragging it into the headers. // // The following definitions should ensure that the template is fully // instantiated where it needs to be. NNormalSurfaceList* NNormalSurfaceList::standardToQuad() const { return internalStandardToReduced(); } NNormalSurfaceList* NNormalSurfaceList::standardANToQuadOct() const { return internalStandardToReduced(); } template NNormalSurfaceList* NNormalSurfaceList::internalStandardToReduced() const { // And off we go! NTriangulation* owner = getTriangulation(); // Basic sanity checks: if (coords_ != Variant::standardCoords()) return 0; if (which_ != (NS_EMBEDDED_ONLY | NS_VERTEX)) return 0; if (owner->isIdeal() || ! owner->isValid()) return 0; // Prepare a final surface list. NNormalSurfaceList* ans = new NNormalSurfaceList( Variant::reducedCoords(), NS_EMBEDDED_ONLY | NS_VERTEX, NS_ALG_CUSTOM); // Get the empty triangulation out of the way now. unsigned long n = owner->getNumberOfTetrahedra(); if (n == 0) { owner->insertChildLast(ans); return ans; } // We need to get rid of vertex links entirely before we start. typedef const NNormalSurfaceVector* VectorPtr; VectorPtr* use = new VectorPtr[surfaces.size()]; unsigned long nUse = 0; std::vector::const_iterator it; for (it = surfaces.begin(); it != surfaces.end(); ++it) if (! (*it)->isVertexLinking()) use[nUse++] = (*it)->rawVector(); // We want to take all surfaces with maximal zero sets in quad space. // That is, we want surface S if and only if there is no other surface T // where, for every quadrilateral coordinate where S is zero, T is // zero also. // For almost normal surfaces, simply replace "quadrilateral" with // "quadrilateral or octagonal". bool dominates, strict; unsigned tet, quad, pos; typename Variant::ReducedVector* v; unsigned long i, j; for (i = 0; i < nUse; ++i) { if (use[i] == 0) continue; dominates = strict = false; for (j = 0; j < nUse; ++j) { if (j == i || use[j] == 0) continue; dominates = true; strict = false; for (tet = 0; tet < n && dominates; ++tet) for (quad = 0; quad < Variant::reducedPerTet; ++quad) if ((*use[i])[Variant::stdPos(tet, 4 + quad)] == NLargeInteger::zero && (*use[j])[Variant::stdPos(tet, 4 + quad)] != NLargeInteger::zero) { dominates = false; break; } else if ((*use[i])[Variant::stdPos(tet, 4 + quad)] != NLargeInteger::zero && (*use[j])[Variant::stdPos(tet, 4 + quad)] == NLargeInteger::zero) { // If this *does* turn out to be a domination of // zero sets, we know it's strict. strict = true; } if (dominates) break; } if (! dominates) { // We want this surface. v = new typename Variant::ReducedVector(Variant::redLen(n)); pos = 0; for (tet = 0; tet < n; ++tet) for (quad = 0; quad < Variant::reducedPerTet; ++quad) v->setElement(pos++, (*use[i])[Variant::stdPos(tet, 4 + quad)]); ans->surfaces.push_back(new NNormalSurface(owner, v)); } else if (strict) { // We can drop this surface entirely from our list. // We don't want it for our final solution set, and if // use[i] is going to rule out some *other* surface then // use[j] will rule out that same other surface also. // // The domination need to be strict because otherwise we // might want use[i] to rule out use[j] (i.e., they both // rule out each other). use[i] = 0; } } delete[] use; // All done! owner->insertChildLast(ans); return ans; } } // namespace regina regina-4.95/engine/surfaces/surfacefiltertype.h000644 000765 000024 00000006550 12234011536 021603 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file surfaces/surfacefiltertype.h * \brief Defines constants for normal surface filter types. */ #ifndef __SURFACEFILTERTYPE_H #ifndef __DOXYGEN #define __SURFACEFILTERTYPE_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup surfaces * @{ */ /** * Represents different types of filter classes that can be used to filter * lists of normal surfaces in 3-manifold triangulations. * * IDs 0-9999 are reserved for future use by Regina. If you are extending * Regina to include your own filter class, you should choose an ID >= 10000. */ enum SurfaceFilterType { /** * Represents the NSurfaceFilter class: a do-nothing filter that * accepts any normal surface. */ NS_FILTER_DEFAULT = 0, /** * Represents the NSurfaceFilterProperties subclass: a filter that * examines simple properties of a normal surface. */ NS_FILTER_PROPERTIES = 1, /** * Represents the NSurfaceFilterCombination subclass: a filter that * combines other filters using boolean AND or OR. */ NS_FILTER_COMBINATION = 2 }; /*@}*/ } // namespace regina #endif regina-4.95/engine/triangulation/CMakeLists.txt000644 000765 000024 00000002166 12234011536 021476 0ustar00babstaff000000 000000 # triangulation # Files to compile SET ( FILES cover crushtri decompose homology homotopy hydrate insertlayered isomorphic nboundarycomponent ncomponent nedge nexampletriangulation nfacepair nhomologicaldata nisomorphism ntetrahedron ntriangle ntriangulation nvertex nxmltrireader reorder simplify simplifyglobal skeleton subdivide surfaces turaevviro ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} triangulation/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES dimtraits.h nboundarycomponent.h ncomponent.h nedge.h nexampletriangulation.h nface.h nfacepair.h nfacetspec.h ngenericisomorphism.h ngenericisomorphism.tcc nhomologicaldata.h nisomorphism.h nperm.h npermit.h ntetface.h ntetrahedron.h ntriangle.h ntriangulation.h nvertex.h nxmltrireader.h DESTINATION ${INCLUDEDIR}/triangulation COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/triangulation/cover.cpp000644 000765 000024 00000013622 12234011536 020557 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "triangulation/ntriangulation.h" namespace regina { void NTriangulation::makeDoubleCover() { unsigned long sheetSize = tetrahedra.size(); if (sheetSize == 0) return; ChangeEventSpan span(this); // Create a second sheet of tetrahedra. NTetrahedron** upper = new NTetrahedron*[sheetSize]; unsigned long i; for (i = 0; i < sheetSize; i++) upper[i] = newTetrahedron(tetrahedra[i]->getDescription()); // Reset each tetrahedron orientation. TetrahedronIterator tit = tetrahedra.begin(); for (i = 0; i < sheetSize; i++) { (*tit++)->tetOrientation = 0; upper[i]->tetOrientation = 0; } // Run through the upper sheet and recreate the gluings as we // propagate tetrahedron orientations through components. std::queue tetQueue; /**< Tetrahedra whose orientation must be propagated. */ int face; unsigned long upperTet; NTetrahedron* lowerTet; unsigned long upperAdj; NTetrahedron* lowerAdj; int lowerAdjOrientation; NPerm4 gluing; for (i = 0; i < sheetSize; i++) if (upper[i]->tetOrientation == 0) { // We've found a new component. // Completely recreate the gluings for this component. upper[i]->tetOrientation = 1; tetrahedra[i]->tetOrientation = -1; tetQueue.push(i); while (! tetQueue.empty()) { upperTet = tetQueue.front(); tetQueue.pop(); lowerTet = tetrahedra[upperTet]; for (face = 0; face < 4; face++) { lowerAdj = lowerTet->adjacentTetrahedron(face); // See if this tetrahedron is glued to something in the // lower sheet. if (! lowerAdj) continue; // Make sure we haven't already fixed this gluing in // the upper sheet. if (upper[upperTet]->adjacentTetrahedron(face)) continue; // Determine the expected orientation of the // adjacent tetrahedron in the lower sheet. gluing = lowerTet->adjacentGluing(face); lowerAdjOrientation = (gluing.sign() == 1 ? -lowerTet->tetOrientation : lowerTet->tetOrientation); upperAdj = tetrahedronIndex(lowerAdj); if (lowerAdj->tetOrientation == 0) { // We haven't seen the adjacent tetrahedron yet. lowerAdj->tetOrientation = lowerAdjOrientation; upper[upperAdj]->tetOrientation = -lowerAdjOrientation; upper[upperTet]->joinTo(face, upper[upperAdj], gluing); tetQueue.push(upperAdj); } else if (lowerAdj->tetOrientation == lowerAdjOrientation) { // The adjacent tetrahedron already has the // correct orientation. upper[upperTet]->joinTo(face, upper[upperAdj], gluing); } else { // The adjacent tetrahedron already has the // incorrect orientation. Make a cross between // the two sheets. lowerTet->unjoin(face); lowerTet->joinTo(face, upper[upperAdj], gluing); upper[upperTet]->joinTo(face, lowerAdj, gluing); } } } } // Tidy up. delete[] upper; } } // namespace regina regina-4.95/engine/triangulation/crushtri.cpp000644 000765 000024 00000015333 12234011536 021305 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/ntriangulation.h" namespace regina { void NTriangulation::maximalForestInBoundary(std::set& edgeSet, std::set& vertexSet) const { if (! calculatedSkeleton) calculateSkeleton(); vertexSet.clear(); edgeSet.clear(); for (BoundaryComponentIterator bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) stretchBoundaryForestFromVertex((*bit)->getVertex(0), edgeSet, vertexSet); } void NTriangulation::stretchBoundaryForestFromVertex(NVertex* from, std::set& edgeSet, std::set& vertexSet) const { vertexSet.insert(from); std::vector::const_iterator it = from->getEmbeddings().begin(); NTetrahedron* tet; NVertex* otherVertex; NEdge* edge; int vertex, yourVertex; while (it != from->getEmbeddings().end()) { const NVertexEmbedding& emb = *it; tet = emb.getTetrahedron(); vertex = emb.getVertex(); for (yourVertex = 0; yourVertex < 4; yourVertex++) { if (vertex == yourVertex) continue; edge = tet->getEdge(NEdge::edgeNumber[vertex][yourVertex]); if (! (edge->isBoundary())) continue; otherVertex = tet->getVertex(yourVertex); if (! vertexSet.count(otherVertex)) { edgeSet.insert(edge); stretchBoundaryForestFromVertex(otherVertex, edgeSet, vertexSet); } } it++; } } void NTriangulation::maximalForestInSkeleton(std::set& edgeSet, bool canJoinBoundaries) const { if (! calculatedSkeleton) calculateSkeleton(); std::set vertexSet; std::set thisBranch; if (canJoinBoundaries) edgeSet.clear(); else maximalForestInBoundary(edgeSet, vertexSet); for (VertexIterator vit = vertices.begin(); vit != vertices.end(); vit++) if (! (vertexSet.count(*vit))) { stretchForestFromVertex(*vit, edgeSet, vertexSet, thisBranch); thisBranch.clear(); } } bool NTriangulation::stretchForestFromVertex(NVertex* from, std::set& edgeSet, std::set& vertexSet, std::set& thisStretch) const { // Moves out from the vertex until we hit a vertex that has already // been visited; then stops. // Returns true if we make such a link. // PRE: Such a link has not already been made. vertexSet.insert(from); thisStretch.insert(from); std::vector::const_iterator it = from->getEmbeddings().begin(); NTetrahedron* tet; NVertex* otherVertex; int vertex, yourVertex; bool madeLink = false; while (it != from->getEmbeddings().end()) { const NVertexEmbedding& emb = *it; tet = emb.getTetrahedron(); vertex = emb.getVertex(); for (yourVertex = 0; yourVertex < 4; yourVertex++) { if (vertex == yourVertex) continue; otherVertex = tet->getVertex(yourVertex); if (thisStretch.count(otherVertex)) continue; madeLink = vertexSet.count(otherVertex); edgeSet.insert(tet->getEdge(NEdge::edgeNumber[vertex][yourVertex])); if (! madeLink) madeLink = stretchForestFromVertex(otherVertex, edgeSet, vertexSet, thisStretch); if (madeLink) return true; } it++; } return false; } void NTriangulation::maximalForestInDualSkeleton(std::set& triSet) const { if (! calculatedSkeleton) calculateSkeleton(); triSet.clear(); std::set visited; for (TetrahedronIterator it = tetrahedra.begin(); it != tetrahedra.end(); it++) if (! (visited.count(*it))) stretchDualForestFromTet(*it, triSet, visited); } void NTriangulation::stretchDualForestFromTet(NTetrahedron* tet, std::set& triSet, std::set& visited) const { visited.insert(tet); NTetrahedron* adjTet; for (int face = 0; face < 4; face++) { adjTet = tet->adjacentTetrahedron(face); if (adjTet) if (! (visited.count(adjTet))) { triSet.insert(tet->getTriangle(face)); stretchDualForestFromTet(adjTet, triSet, visited); } } } } // namespace regina regina-4.95/engine/triangulation/decompose.cpp000644 000765 000024 00000125216 12236524106 021426 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "enumerate/ntreetraversal.h" #include "packet/ncontainer.h" #include "subcomplex/nsnappedball.h" #include "surfaces/nnormalsurface.h" #include "surfaces/nnormalsurfacelist.h" #include "triangulation/nboundarycomponent.h" #include "triangulation/nisomorphism.h" #include "triangulation/ntriangulation.h" namespace regina { unsigned long NTriangulation::splitIntoComponents(NPacket* componentParent, bool setLabels) { // Knock off the empty triangulation first. if (tetrahedra.empty()) return 0; if (! componentParent) componentParent = this; // Create the new component triangulations. // Note that the following line forces a skeletal recalculation. unsigned long nComp = getNumberOfComponents(); // Initialise the component triangulations. NTriangulation** newTris = new NTriangulation*[nComp]; unsigned long whichComp; for (whichComp = 0; whichComp < nComp; ++whichComp) newTris[whichComp] = new NTriangulation(); // Clone the tetrahedra, sorting them into the new components. unsigned long nTets = tetrahedra.size(); NTetrahedron** newTets = new NTetrahedron*[nTets]; NTetrahedron *tet, *adjTet; unsigned long tetPos, adjPos; NPerm4 adjPerm; int face; for (tetPos = 0; tetPos < nTets; tetPos++) newTets[tetPos] = newTris[componentIndex(tetrahedra[tetPos]->getComponent())]-> newTetrahedron(tetrahedra[tetPos]->getDescription()); // Clone the tetrahedron gluings also. for (tetPos = 0; tetPos < nTets; tetPos++) { tet = tetrahedra[tetPos]; for (face = 0; face < 4; face++) { adjTet = tet->adjacentTetrahedron(face); if (adjTet) { adjPos = tetrahedronIndex(adjTet); adjPerm = tet->adjacentGluing(face); if (adjPos > tetPos || (adjPos == tetPos && adjPerm[face] > face)) newTets[tetPos]->joinTo(face, newTets[adjPos], adjPerm); } } } // Insert the component triangulations into the packet tree and clean up. for (whichComp = 0; whichComp < nComp; ++whichComp) { componentParent->insertChildLast(newTris[whichComp]); if (setLabels) { std::ostringstream label; if (getPacketLabel().empty()) label << "Cmpt #" << (whichComp + 1); else label << getPacketLabel() << " - Cmpt #" << (whichComp + 1); newTris[whichComp]->setPacketLabel(label.str()); } } delete[] newTets; delete[] newTris; return whichComp; } unsigned long NTriangulation::connectedSumDecomposition(NPacket* primeParent, bool setLabels) { // Precondition checks. if (! (isValid() && isClosed() && isOrientable() && isConnected())) return 0; if (! primeParent) primeParent = this; // Make a working copy, simplify and record the initial homology. NTriangulation* working = new NTriangulation(*this); working->intelligentSimplify(); unsigned long initZ, initZ2, initZ3; { const NAbelianGroup& homology = working->getHomologyH1(); initZ = homology.getRank(); initZ2 = homology.getTorsionRank(2); initZ3 = homology.getTorsionRank(3); } // Start crushing normal spheres. NContainer toProcess; toProcess.insertChildLast(working); std::list primeComponents; unsigned long whichComp = 0; NTriangulation* processing; NTriangulation* crushed; NNormalSurface* sphere; while ((processing = static_cast( toProcess.getFirstTreeChild()))) { // INV: Our triangulation is the connected sum of all the // children of toProcess, all the elements of primeComponents // and possibly some copies of S2xS1, RP3 and/or L(3,1). // Work with the last child. processing->makeOrphan(); // Find a normal 2-sphere to crush. sphere = processing->hasNonTrivialSphereOrDisc(); if (sphere) { crushed = sphere->crush(); delete sphere; delete processing; crushed->intelligentSimplify(); // Insert each component of the crushed triangulation back // into the list to process. if (crushed->getNumberOfComponents() == 0) delete crushed; else if (crushed->getNumberOfComponents() == 1) toProcess.insertChildLast(crushed); else { crushed->splitIntoComponents(&toProcess, false); delete crushed; } } else { // We have no non-trivial normal 2-spheres! // The triangulation is 0-efficient (and prime). // Is it a 3-sphere? if (processing->getNumberOfVertices() > 1) { // Proposition 5.1 of Jaco & Rubinstein's 0-efficiency // paper: If a closed orientable triangulation T is // 0-efficient then either T has one vertex or T is a // 3-sphere with precisely two vertices. // // It follows then that this is a 3-sphere. // Toss it away. delete sphere; delete processing; } else { // Now we have a closed orientable one-vertex 0-efficient // triangulation. // We have to look for an almost normal sphere. // // From the proof of Proposition 5.12 in Jaco & Rubinstein's // 0-efficiency paper, we see that we can restrict our // search to octagonal almost normal surfaces. // Furthermore, from the result in the quadrilateral-octagon // coordinates paper, we can restrict this search further // to vertex octagonal almost normal surfaces in // quadrilateral-octagonal space. sphere = processing->hasOctagonalAlmostNormalSphere(); if (sphere) { // It's a 3-sphere. Toss this component away. delete sphere; delete processing; } else { // It's a non-trivial prime component! primeComponents.push_back(processing); } } } } // Run a final homology check and put back our missing S2xS1, RP3 // and L(3,1) terms. unsigned long finalZ = 0, finalZ2 = 0, finalZ3 = 0; for (std::list::iterator it = primeComponents.begin(); it != primeComponents.end(); it++) { const NAbelianGroup& homology = (*it)->getHomologyH1(); finalZ += homology.getRank(); finalZ2 += homology.getTorsionRank(2); finalZ3 += homology.getTorsionRank(3); } while (finalZ++ < initZ) { working = new NTriangulation(); working->insertLayeredLensSpace(0, 1); primeComponents.push_back(working); irreducible = false; // Implied by the S2xS1 summand. zeroEfficient = false; // Implied by the S2xS1 summand. } while (finalZ2++ < initZ2) { working = new NTriangulation(); working->insertLayeredLensSpace(2, 1); primeComponents.push_back(working); } while (finalZ3++ < initZ3) { working = new NTriangulation(); working->insertLayeredLensSpace(3, 1); primeComponents.push_back(working); } // All done! for (std::list::iterator it = primeComponents.begin(); it != primeComponents.end(); it++) { primeParent->insertChildLast(*it); if (setLabels) { std::ostringstream label; if (getPacketLabel().empty()) label << "Summand #" << (whichComp + 1); else label << getPacketLabel() << " - Summand #" << (whichComp + 1); (*it)->setPacketLabel(label.str()); } whichComp++; } // Set irreducibility while we're at it. if (whichComp > 1) { threeSphere = false; irreducible = false; zeroEfficient = false; } else if (whichComp == 1) { threeSphere = false; if (! irreducible.known()) { // If our manifold is S2xS1 then it is *not* irreducible; // however, in this case we will have already set irreducible // to false when putting back the S2xS1 summands above (and // therefore irreducible.known() will be true). irreducible = true; } } else if (whichComp == 0) { threeSphere = true; irreducible = true; haken = false; } return whichComp; } bool NTriangulation::isThreeSphere() const { if (threeSphere.known()) return threeSphere.value(); // Basic property checks. if (! (isValid() && isClosed() && isOrientable() && isConnected() && getNumberOfTetrahedra() > 0)) { threeSphere = false; return false; } // Check homology and fundamental group. // Better simplify first, which means we need a clone. NTriangulation* working = new NTriangulation(*this); working->intelligentSimplify(); // The Poincare conjecture! if (working->getFundamentalGroup().getNumberOfGenerators() == 0) { threeSphere = true; // Some other things that come for free: irreducible = true; haken = false; return true; } // We could still have a trivial group but not know it. // At least we can at least check homology precisely. if (! working->getHomologyH1().isTrivial()) { threeSphere = false; delete working; return false; } // Time for some more heavy machinery. On to normal surfaces. NContainer toProcess; toProcess.insertChildLast(working); NTriangulation* processing; NTriangulation* crushed; NNormalSurface* sphere; while ((processing = static_cast( toProcess.getLastTreeChild()))) { // INV: Our triangulation is the connected sum of all the // children of toProcess. Each of these children has trivial // homology (and therefore we have no S2xS1 / RP3 / L(3,1) // summands to worry about). // Work with the last child. processing->makeOrphan(); // Find a normal 2-sphere to crush. sphere = processing->hasNonTrivialSphereOrDisc(); if (sphere) { crushed = sphere->crush(); delete sphere; delete processing; crushed->intelligentSimplify(); // Insert each component of the crushed triangulation in the // list to process. if (crushed->getNumberOfComponents() == 0) delete crushed; else if (crushed->getNumberOfComponents() == 1) toProcess.insertChildLast(crushed); else { crushed->splitIntoComponents(&toProcess, false); delete crushed; } } else { // We have no non-trivial normal 2-spheres! // The triangulation is 0-efficient. // We can now test directly whether we have a 3-sphere. if (processing->getNumberOfVertices() > 1) { // Proposition 5.1 of Jaco & Rubinstein's 0-efficiency // paper: If a closed orientable triangulation T is // 0-efficient then either T has one vertex or T is a // 3-sphere with precisely two vertices. // // It follows then that this is a 3-sphere. // Toss it away. delete sphere; delete processing; } else { // Now we have a closed orientable one-vertex 0-efficient // triangulation. // We have to look for an almost normal sphere. // // From the proof of Proposition 5.12 in Jaco & Rubinstein's // 0-efficiency paper, we see that we can restrict our // search to octagonal almost normal surfaces. // Furthermore, from the result in the quadrilateral-octagon // coordinates paper, we can restrict this search further // to vertex octagonal almost normal surfaces in // quadrilateral-octagonal space. sphere = processing->hasOctagonalAlmostNormalSphere(); if (sphere) { // It's a 3-sphere. Toss this component away. delete sphere; delete processing; } else { // It's not a 3-sphere. We're done! threeSphere = false; delete processing; return false; } } } } // Our triangulation is the connected sum of 0 components! threeSphere = true; // Some other things that we get for free: irreducible = true; haken = false; return true; } bool NTriangulation::knowsThreeSphere() const { if (threeSphere.known()) return true; // Run some very fast prelimiary tests before we give up and say no. if (! (isValid() && isClosed() && isOrientable() && isConnected())) { threeSphere = false; return true; } // More work is required. return false; } bool NTriangulation::isBall() const { if (threeBall.known()) return threeBall.value(); // Basic property checks. if (! (isValid() && hasBoundaryTriangles() && isOrientable() && isConnected() && boundaryComponents.size() == 1 && boundaryComponents.front()->getEulerCharacteristic() == 2)) { threeBall = false; return false; } // Pass straight to isThreeSphere (which in turn will check faster things // like homology before pulling out the big guns). // // Cone the boundary to a point (i.e., fill it with a ball), then // call isThreeSphere() on the resulting closed triangulation. NTriangulation working(*this); working.intelligentSimplify(); working.finiteToIdeal(); // Simplify again in case our coning was inefficient. working.intelligentSimplify(); threeBall = working.isThreeSphere(); return threeBall.value(); } bool NTriangulation::knowsBall() const { if (threeBall.known()) return true; // Run some very fast prelimiary tests before we give up and say no. if (! (isValid() && hasBoundaryTriangles() && isOrientable() && isConnected() && boundaryComponents.size() == 1 && boundaryComponents.front()->getEulerCharacteristic() == 2)) { threeBall = false; return true; } // More work is required. return false; } bool NTriangulation::isSolidTorus() const { if (solidTorus.known()) return solidTorus.value(); // Basic property checks. if (! (isValid() && isOrientable() && isConnected() && boundaryComponents.size() == 1 && boundaryComponents.front()->getEulerCharacteristic() == 0 && boundaryComponents.front()->isOrientable())) return (solidTorus = false); // If it's ideal, make it a triangulation with real boundary. // If it's not ideal, clone it anyway so we can modify it. NTriangulation* working = new NTriangulation(*this); working->intelligentSimplify(); if (working->isIdeal()) { working->idealToFinite(); working->intelligentSimplify(); } // Check homology. if (! (working->getHomologyH1().isZ())) { delete working; return (solidTorus = false); } // So: // We are valid, orientable, compact and connected, with H1 = Z. // There is exactly one boundary component, and this is a torus. // Note that the homology results imply that this is not a connected // sum of something with S2xS1 (otherwise we would have two Z terms // in the homology: one from the torus boundary and one from the S2xS1). // This observation simplifies the crushing cases later on. // Pull out the big guns: normal surface time. NNormalSurface* s; NTriangulation* crushed; NPacket* p; NTriangulation* comp; while (true) { // INVARIANT: working is homeomorphic to our original manifold. if (working->getNumberOfVertices() > 1) { // Try *really* hard to get to a 1-vertex triangulation, // since this will make hasNonTrivialSphereOrDisc() much // faster (it will be able to use linear programming). working->intelligentSimplify(); if (working->getNumberOfVertices() > 1) { working->barycentricSubdivision(); working->intelligentSimplify(); working->intelligentSimplify(); } } // Find a non-trivial normal disc or sphere. s = working->hasNonTrivialSphereOrDisc(); if (! s) { // No non-trivial normal disc. This cannot be a solid torus. delete working; return (solidTorus = false); } // Crush it and see what happens. // Given what we know about the manifold so far, the only things // that can happen during crushing are: // - undo connected sum decompositions; // - cut along properly embedded discs; // - gain and/or lose 3-balls and/or 3-spheres. crushed = s->crush(); delete s; delete working; working = 0; crushed->intelligentSimplify(); crushed->splitIntoComponents(0, false); for (p = crushed->getFirstTreeChild(); p; p = p->getNextTreeSibling()) { // Examine each connected component after crushing. comp = static_cast(p); if (comp->isClosed()) { // A closed piece. // Must be a 3-sphere, or else we didn't have a solid torus. if (! comp->isThreeSphere()) { delete crushed; return (solidTorus = false); } } else if (comp->getNumberOfBoundaryComponents() > 1) { // Multiple boundaries on the same component. // This should never happen, since it implies there was // an S2xS1 summand. std::cerr << "ERROR: S2xS1 summand detected in " "isSolidTorus() that should not exist." << std::endl; // At any rate, it means we did not have a solid torus. delete crushed; return (solidTorus = false); } else if (comp->getBoundaryComponent(0)-> getEulerCharacteristic() == 2) { // A component with sphere boundary. // Must be a 3-ball, or else we didn't have a solid torus. if (! comp->isBall()) { delete crushed; return (solidTorus = false); } } else { // The only other possibility is a component with torus // boundary. We should only see at most one of these. // // Unless some other non-trivial component was split off // (i.e., a non-ball and/or non-sphere that will be // detected separately in the tests above), this // component must be identical to our original manifold. if (working) { std::cerr << "ERROR: Multiple torus boundary " "components detected in isSolidTorus(), which " "should not be possible." << std::endl; } working = comp; } } if (! working) { // We have reduced everything down to balls and spheres. // The only way this can happen is if we had a solid torus // (and we crushed and/or cut along a compressing disc // during the crushing operation). delete crushed; return (solidTorus = true); } // We have the original manifold in working, but this time with // fewer tetrahedra. Around we go again. working->makeOrphan(); delete crushed; } } bool NTriangulation::knowsSolidTorus() const { if (solidTorus.known()) return true; // Run some very fast prelimiary tests before we give up and say no. if (! (isValid() && isOrientable() && isConnected())) { solidTorus = false; return true; } if (boundaryComponents.size() != 1) { solidTorus = false; return true; } if (boundaryComponents.front()->getEulerCharacteristic() != 0 || (! boundaryComponents.front()->isOrientable())) { solidTorus = false; return true; } // More work is required. return false; } NPacket* NTriangulation::makeZeroEfficient() { // Extract a connected sum decomposition. NContainer* connSum = new NContainer(); if (getPacketLabel().empty()) connSum->setPacketLabel("Decomposition"); else connSum->setPacketLabel(getPacketLabel() + " - Decomposition"); unsigned long ans = connectedSumDecomposition(connSum, true); if (ans > 1) { // Composite! return connSum; } else if (ans == 1) { // Prime. NTriangulation* newTri = dynamic_cast( connSum->getLastTreeChild()); if (! isIsomorphicTo(*newTri).get()) { removeAllTetrahedra(); insertTriangulation(*newTri); } delete connSum; return 0; } else { // 3-sphere. if (getNumberOfTetrahedra() > 1) { removeAllTetrahedra(); insertLayeredLensSpace(1,0); } delete connSum; return 0; } } bool NTriangulation::isIrreducible() const { if (irreducible.known()) return irreducible.value(); // Precondition checks. if (! (isValid() && isClosed() && isOrientable() && isConnected())) return 0; // We will essentially carry out a connected sum decomposition, but // instead of keeping prime summands we will just count them and // throw them away. unsigned long summands = 0; // Make a working copy, simplify and record the initial homology. NTriangulation* working = new NTriangulation(*this); working->intelligentSimplify(); unsigned long Z, Z2, Z3; { const NAbelianGroup& homology = working->getHomologyH1(); Z = homology.getRank(); Z2 = homology.getTorsionRank(2); Z3 = homology.getTorsionRank(3); } // Start crushing normal spheres. NContainer toProcess; toProcess.insertChildLast(working); unsigned long whichComp = 0; NTriangulation* processing; NTriangulation* crushed; NNormalSurface* sphere; while ((processing = static_cast( toProcess.getFirstTreeChild()))) { // INV: Our triangulation is the connected sum of all the // children of toProcess, all the prime components that we threw away, // and possibly some copies of S2xS1, RP3 and/or L(3,1). // Work with the last child. processing->makeOrphan(); // Find a normal 2-sphere to crush. sphere = processing->hasNonTrivialSphereOrDisc(); if (sphere) { crushed = sphere->crush(); delete sphere; delete processing; crushed->intelligentSimplify(); // Insert each component of the crushed triangulation back // into the list to process. if (crushed->getNumberOfComponents() == 0) delete crushed; else if (crushed->getNumberOfComponents() == 1) toProcess.insertChildLast(crushed); else { crushed->splitIntoComponents(&toProcess, false); delete crushed; } } else { // We have no non-trivial normal 2-spheres! // The triangulation is 0-efficient (and prime). // Is it a 3-sphere? if (processing->getNumberOfVertices() > 1) { // Proposition 5.1 of Jaco & Rubinstein's 0-efficiency // paper: If a closed orientable triangulation T is // 0-efficient then either T has one vertex or T is a // 3-sphere with precisely two vertices. // // It follows then that this is a 3-sphere. // Toss it away. delete sphere; delete processing; } else { // Now we have a closed orientable one-vertex 0-efficient // triangulation. // We have to look for an almost normal sphere. // // From the proof of Proposition 5.12 in Jaco & Rubinstein's // 0-efficiency paper, we see that we can restrict our // search to octagonal almost normal surfaces. // Furthermore, from the result in the quadrilateral-octagon // coordinates paper, we can restrict this search further // to vertex octagonal almost normal surfaces in // quadrilateral-octagonal space. sphere = processing->hasOctagonalAlmostNormalSphere(); if (sphere) { // It's a 3-sphere. Toss this component away. delete sphere; delete processing; } else { // It's a non-trivial prime component! // Note that this will never be an S2xS1 summand; // those get crushed away entirely (we account for // them later). if (summands > 0) { // We have found more than one prime component. threeSphere = false; // Implied by reducibility. zeroEfficient = false; // Implied by reducibility. delete processing; return (irreducible = false); } ++summands; // Note which parts of our initial homology we have // now accounted for. const NAbelianGroup& h1 = processing->getHomologyH1(); Z -= h1.getRank(); Z2 -= h1.getTorsionRank(2); Z3 -= h1.getTorsionRank(3); // Toss away our prime summand and keep going. delete processing; } } } } // Run a final homology check: were there any additional S2xS1, RP3 // or L(3,1) terms? if (Z > 0) { // There were S2xS1 summands that were crushed away. // The manifold must be reducible. threeSphere = false; // Implied by reducibility. zeroEfficient = false; // Implied by reducibility. return (irreducible = false); } if (summands + Z2 + Z3 > 1) { // At least two summands were found and/or crushed away: the // manifold must be composite. threeSphere = false; // Implied by reducibility. zeroEfficient = false; // Implied by reducibility. return (irreducible = false); } // There are no S2xS1 summands, and the manifold is prime. return (irreducible = true); } bool NTriangulation::knowsIrreducible() const { return irreducible.known(); } bool NTriangulation::hasCompressingDisc() const { if (compressingDisc.known()) return compressingDisc.value(); // Some sanity checks; also enforce preconditions. if (! hasBoundaryTriangles()) return (compressingDisc = false); if ((! isValid()) || isIdeal()) return (compressingDisc = false); long minBdryEuler = 2; for (BoundaryComponentIterator it = boundaryComponents.begin(); it != boundaryComponents.end(); ++it) { if ((*it)->getEulerCharacteristic() < minBdryEuler) minBdryEuler = (*it)->getEulerCharacteristic(); } if (minBdryEuler == 2) return (compressingDisc = false); // Off we go. // Work with a simplified triangulation. NTriangulation* use = new NTriangulation(*this); use->intelligentSimplify(); // Try for a fast answer first. if (use->hasSimpleCompressingDisc()) { delete use; return (compressingDisc = true); } // Nope. Decide whether we can use the fast linear programming // machinery or whether we need to do a full vertex surface enumeration. if (use->isOrientable() && use->getNumberOfBoundaryComponents() == 1) { NNormalSurface* ans; NTriangulation* crush; NTriangulation* comp; unsigned nComp; while (true) { use->intelligentSimplify(); if (use->getNumberOfVertices() > 1) { // Try harder. use->barycentricSubdivision(); use->intelligentSimplify(); if (use->getNumberOfVertices() > 1) { // Fall back to a full vertex enumeration. // This mirrors the code for non-orientable // triangulations; see that later block for details. NNormalSurfaceList* q = NNormalSurfaceList::enumerate( use, NS_STANDARD); unsigned long nSurfaces = q->getNumberOfSurfaces(); for (unsigned long i = 0; i < nSurfaces; ++i) { if (q->getSurface(i)->isCompressingDisc(true)) { delete use; return (compressingDisc = true); } } // No compressing discs! delete use; return (compressingDisc = false); } } NTreeSingleSoln search(use, NS_STANDARD); if (! search.find()) { // No compressing discs! delete use; return (compressingDisc = false); } ans = search.buildSurface(); crush = ans->crush(); delete ans; delete use; nComp = crush->splitIntoComponents(); comp = static_cast(crush->getFirstTreeChild()); while (comp) { if (comp->getNumberOfBoundaryComponents() == 1 && comp->getBoundaryComponent(0)->getEulerCharacteristic() == minBdryEuler) { // This must be our original manifold. comp->makeOrphan(); break; } comp = static_cast(comp->getNextTreeSibling()); } delete crush; if (! comp) { // We must have compressed. return (compressingDisc = true); } // Around we go again, but this time with a smaller triangulation. use = comp; } } else { // Sigh. Enumerate all vertex normal surfaces. // // Hum, are we allowed to do this in quad space? Jaco and Tollefson // use standard coordinates. Jaco, Letscher and Rubinstein mention // quad space, but don't give details (which I'd prefer to see). // Leave it in standard coordinates for now. NNormalSurfaceList* q = NNormalSurfaceList::enumerate(use, NS_STANDARD); // Run through all vertex surfaces looking for a compressing disc. unsigned long nSurfaces = q->getNumberOfSurfaces(); for (unsigned long i = 0; i < nSurfaces; ++i) { // Use the fact that all vertex normal surfaces are connected. if (q->getSurface(i)->isCompressingDisc(true)) { delete use; return (compressingDisc = true); } } // No compressing discs! delete use; return (compressingDisc = false); } } bool NTriangulation::knowsCompressingDisc() const { if (compressingDisc.known()) return true; // Quickly check for non-spherical boundary components before we give up. for (BoundaryComponentIterator it = boundaryComponents.begin(); it != boundaryComponents.end(); ++it) { if ((*it)->getEulerCharacteristic() < 2) return false; } // All boundary components are 2-spheres. compressingDisc = false; return true; } bool NTriangulation::hasSimpleCompressingDisc() const { // Some sanity checks; also enforce preconditions. if (! hasBoundaryTriangles()) return false; if ((! isValid()) || isIdeal()) return false; // Off we go. // Work with a simplified triangulation. NTriangulation use(*this); use.intelligentSimplify(); // Check to see whether any component is a one-tetrahedron solid torus. for (ComponentIterator cit = use.getComponents().begin(); cit != use.getComponents().end(); ++cit) if ((*cit)->getNumberOfTetrahedra() == 1 && (*cit)->getNumberOfTriangles() == 3 && (*cit)->getNumberOfVertices() == 1) { // Because we know the triangulation is valid, this rules out // all one-tetrahedron triangulations except for LST(1,2,3). return (compressingDisc = true); } // Open up as many boundary triangles as possible (to make it easier to // find simple compressing discs). TriangleIterator fit; bool opened = true; while (opened) { opened = false; for (fit = use.getTriangles().begin(); fit != use.getTriangles().end(); ++fit) if (use.openBook(*fit, true, true)) { opened = true; break; } } // How many boundary spheres do we currently have? // This is important because we test whether a disc is a compressing // disc by cutting along it and looking for any *new* boundary // spheres that might result. unsigned long origSphereCount = 0; BoundaryComponentIterator bit; for (bit = use.getBoundaryComponents().begin(); bit != use.getBoundaryComponents().end(); ++bit) if ((*bit)->getEulerCharacteristic() == 2) ++origSphereCount; // Look for a single internal triangle surrounded by three boundary edges. // It doesn't matter whether the edges and/or vertices are distinct. NEdge *e0, *e1, *e2; unsigned long newSphereCount; for (fit = use.getTriangles().begin(); fit != use.getTriangles().end(); ++fit) { if ((*fit)->isBoundary()) continue; e0 = (*fit)->getEdge(0); e1 = (*fit)->getEdge(1); e2 = (*fit)->getEdge(2); if (! (e0->isBoundary() && e1->isBoundary() && e2->isBoundary())) continue; // This could be a compressing disc. // Cut along the triangle to be sure. const NTriangleEmbedding& emb = (*fit)->getEmbedding(0); NTriangulation cut(use); cut.getTetrahedron(emb.getTetrahedron()->markedIndex())->unjoin( emb.getTriangle()); // If we don't see a new boundary component, the disc boundary is // non-separating in the manifold boundary and is therefore a // non-trivial curve. if (cut.getNumberOfBoundaryComponents() == use.getNumberOfBoundaryComponents()) return (compressingDisc = true); newSphereCount = 0; for (bit = cut.getBoundaryComponents().begin(); bit != cut.getBoundaryComponents().end(); ++bit) if ((*bit)->getEulerCharacteristic() == 2) ++newSphereCount; // Was the boundary of the disc non-trivial? if (newSphereCount == origSphereCount) return (compressingDisc = true); } // Look for a tetrahedron with two faces folded together, giving a // degree-one edge on the inside and a boundary edge on the outside. // The boundary edge on the outside will surround a disc that cuts // right through the tetrahedron. TetrahedronIterator tit; NSnappedBall* ball; for (tit = use.tetrahedra.begin(); tit != use.tetrahedra.end(); ++tit) { ball = NSnappedBall::formsSnappedBall(*tit); if (! ball) continue; int equator = ball->getEquatorEdge(); if (! (*tit)->getEdge(equator)->isBoundary()) { delete ball; continue; } // This could be a compressing disc. // Cut through the tetrahedron to be sure. // We do this by removing the tetrahedron, and then plugging // both holes on either side of the disc with new copies of the // tetrahedron. int upper = ball->getBoundaryFace(0); delete ball; NTetrahedron* adj = (*tit)->adjacentTetrahedron(upper); if (! adj) { // The disc is trivial. continue; } NTriangulation cut(use); cut.getTetrahedron((*tit)->markedIndex())->unjoin(upper); NTetrahedron* tet = cut.newTetrahedron(); tet->joinTo(NEdge::edgeVertex[equator][0], tet, NPerm4( NEdge::edgeVertex[equator][0], NEdge::edgeVertex[equator][1])); tet->joinTo(upper, cut.getTetrahedron(adj->markedIndex()), (*tit)->adjacentGluing(upper)); // If we don't see a new boundary component, the disc boundary is // non-separating in the manifold boundary and is therefore a // non-trivial curve. if (cut.getNumberOfBoundaryComponents() == use.getNumberOfBoundaryComponents()) return (compressingDisc = true); newSphereCount = 0; for (bit = cut.getBoundaryComponents().begin(); bit != cut.getBoundaryComponents().end(); ++bit) if ((*bit)->getEulerCharacteristic() == 2) ++newSphereCount; // Was the boundary of the disc non-trivial? if (newSphereCount == origSphereCount) return (compressingDisc = true); } // Nothing found. return false; } namespace { /** * Used to sort candidate incompressible surfaces by Euler characteristic. * Surfaces with smaller genus (i.e., larger Euler characteristic) * are to be processed first. */ struct SurfaceID { long index; /**< Which surface in the list are we referring to? */ long euler; /**< What is its Euler characteristic? */ inline bool operator < (const SurfaceID& rhs) const { return (euler > rhs.euler || (euler == rhs.euler && index < rhs.index)); } }; } bool NTriangulation::isHaken() const { if (haken.known()) return haken.value(); // Check basic preconditions. if (! (isValid() && isOrientable() && isClosed() && isConnected())) return false; // Irreducibility is not a precondition, but we promise to return // false immediately if the triangulation is not irreducible. // Do not set the property in this situation. if (! isIrreducible()) return false; // Okay: we are closed, connected, orientable and irreducible. // Move to a copy of this triangulation, which we can mess with. NTriangulation t(*this); // First check for an easy answer via homology: if (t.getHomologyH1().getRank() > 0) { threeSphere = false; // Implied by Hakenness. return (haken = true); } // Enumerate vertex normal surfaces in quad coordinates. // std::cout << "Enumerating surfaces..." << std::endl; NNormalSurfaceList* list = NNormalSurfaceList::enumerate(&t, NS_QUAD); // Run through each surface, one at a time. // Sort them first however, so we process the (easier) smaller genus // surfaces first. SurfaceID* id = new SurfaceID[list->getNumberOfSurfaces()]; unsigned i; for (i = 0; i < list->getNumberOfSurfaces(); ++i) { id[i].index = i; id[i].euler = list->getSurface(i)->getEulerCharacteristic().longValue(); } std::sort(id, id + list->getNumberOfSurfaces()); const NNormalSurface* f; unsigned long discs; for (unsigned i = 0; i < list->getNumberOfSurfaces(); ++i) { // std::cout << "Testing surface " << i << "..." << std::endl; if (list->getSurface(id[i].index)->isIncompressible()) { delete[] id; threeSphere = false; // Implied by Hakenness. return (haken = true); } } delete[] id; return (haken = false); } bool NTriangulation::knowsHaken() const { return haken.known(); } } // namespace regina regina-4.95/engine/triangulation/dimtraits.h000644 000765 000024 00000004657 12234011536 021116 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/dimtraits.h * \brief Deprecated header. */ #warning This header is deprecated; please use generic/dimtraits.h instead. #include "generic/dimtraits.h" regina-4.95/engine/triangulation/homology.cpp000644 000765 000024 00000025366 12234011536 021306 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ // ntriangulationhomology.cpp // Implements homology as a property of a triangulation. #include "triangulation/ntriangulation.h" #include "maths/nmatrixint.h" namespace regina { const NAbelianGroup& NTriangulation::getHomologyH1() const { if (H1.known()) return *H1.value(); if (getNumberOfTetrahedra() == 0) return *(H1 = new NAbelianGroup()); // Calculate the first homology. // Find a maximal forest in the dual 1-skeleton. // Note that this will ensure the skeleton has been calculated. std::set forest; maximalForestInDualSkeleton(forest); // Build a presentation matrix. // Each non-boundary not-in-forest triangle is a generator. // Each non-boundary edge is a relation. unsigned long nBdryEdges = 0; unsigned long nBdryTri = 0; for (BoundaryComponentIterator bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) { nBdryEdges += (*bit)->getNumberOfEdges(); nBdryTri += (*bit)->getNumberOfTriangles(); } long nGens = getNumberOfTriangles() - nBdryTri - forest.size(); long nRels = getNumberOfEdges() - nBdryEdges; NMatrixInt pres(nRels, nGens); // Find out which triangle corresponds to which generator. long* genIndex = new long[getNumberOfTriangles()]; long i = 0; for (TriangleIterator fit = triangles.begin(); fit != triangles.end(); fit++) { if ((*fit)->isBoundary()) genIndex[fit - triangles.begin()] = -1; else if (forest.count(*fit)) genIndex[fit - triangles.begin()] = -1; else { genIndex[fit - triangles.begin()] = i; i++; } } // Run through each edge and put the relations in the matrix. std::deque::const_iterator embit; NTetrahedron* currTet; NTriangle* triangle; int currTetFace; long triGenIndex; i = 0; for (EdgeIterator eit = edges.begin(); eit != edges.end(); eit++) { if (! (*eit)->isBoundary()) { // Put in the relation corresponding to this edge. for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { currTet = (*embit).getTetrahedron(); currTetFace = (*embit).getVertices()[2]; triangle = currTet->getTriangle(currTetFace); triGenIndex = genIndex[triangleIndex(triangle)]; if (triGenIndex >= 0) { if ((triangle->getEmbedding(0).getTetrahedron() == currTet) && (triangle->getEmbedding(0).getTriangle() == currTetFace)) pres.entry(i, triGenIndex) += 1; else pres.entry(i, triGenIndex) -= 1; } } i++; } } delete[] genIndex; // Build the group from the presentation matrix and tidy up. NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(pres); return *(H1 = ans); } const NAbelianGroup& NTriangulation::getHomologyH1Rel() const { if (H1Rel.known()) return *H1Rel.value(); if (getNumberOfBoundaryComponents() == 0) return *(H1Rel = new NAbelianGroup(getHomologyH1())); // Calculate the relative first homology wrt the boundary. // Find a maximal forest in the 1-skeleton. // Note that this will ensure the skeleton has been calculated. std::set forest; maximalForestInSkeleton(forest, false); // Build a presentation matrix. // Each non-boundary not-in-forest edge is a generator. // Each non-boundary triangle is a relation. unsigned long nBdryVertices = 0; unsigned long nBdryEdges = 0; unsigned long nBdryTri = 0; unsigned long nClosedComponents = 0; for (BoundaryComponentIterator bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) { nBdryVertices += (*bit)->getNumberOfVertices(); nBdryEdges += (*bit)->getNumberOfEdges(); nBdryTri += (*bit)->getNumberOfTriangles(); } for (ComponentIterator cit = components.begin(); cit != components.end(); cit++) if ((*cit)->isClosed()) nClosedComponents++; long nGens = getNumberOfEdges() - nBdryEdges - getNumberOfVertices() + nBdryVertices + nClosedComponents; long nRels = getNumberOfTriangles() - nBdryTri; NMatrixInt pres(nRels, nGens); // Find out which edge corresponds to which generator. long* genIndex = new long[getNumberOfEdges()]; long i = 0; for (EdgeIterator eit = edges.begin(); eit != edges.end(); eit++) { if ((*eit)->isBoundary()) genIndex[eit - edges.begin()] = -1; else if (forest.count(*eit)) genIndex[eit - edges.begin()] = -1; else { genIndex[eit - edges.begin()] = i; i++; } } // Run through each triangle and put the relations in the matrix. NTetrahedron* currTet; NPerm4 currTetVertices; long edgeGenIndex; i = 0; int triEdge, currEdgeStart, currEdgeEnd, currEdge; for (TriangleIterator fit = triangles.begin(); fit != triangles.end(); fit++) { if (! (*fit)->isBoundary()) { // Put in the relation corresponding to this triangle. currTet = (*fit)->getEmbedding(0).getTetrahedron(); currTetVertices = (*fit)->getEmbedding(0).getVertices(); for (triEdge = 0; triEdge < 3; triEdge++) { currEdgeStart = currTetVertices[triEdge]; currEdgeEnd = currTetVertices[(triEdge + 1) % 3]; // Examine the edge from vertex edgeStart to edgeEnd // in tetrahedron currTet. currEdge = NEdge::edgeNumber[currEdgeStart][currEdgeEnd]; edgeGenIndex = genIndex[edgeIndex( currTet->getEdge(currEdge))]; if (edgeGenIndex >= 0) { if (currTet->getEdgeMapping(currEdge)[0] == currEdgeStart) pres.entry(i, edgeGenIndex) += 1; else pres.entry(i, edgeGenIndex) -= 1; } } i++; } } delete[] genIndex; // Build the group from the presentation matrix and tidy up. NAbelianGroup* ans = new NAbelianGroup(); ans->addGroup(pres); return *(H1Rel = ans); } const NAbelianGroup& NTriangulation::getHomologyH1Bdry() const { if (H1Bdry.known()) return *H1Bdry.value(); // Run through the individual boundary components and add the // appropriate pieces to the homology group. unsigned long rank = 0; unsigned long z2rank = 0; // Ensure that the skeleton has been calculated. if (! calculatedSkeleton) calculateSkeleton(); for (BoundaryComponentIterator bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) { if ((*bit)->isOrientable()) { rank += (2 - (*bit)->getEulerCharacteristic()); } else { rank += (1 - (*bit)->getEulerCharacteristic()); z2rank++; } } // Build the group and tidy up. NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(rank); ans->addTorsionElement(2, z2rank); return *(H1Bdry = ans); } const NAbelianGroup& NTriangulation::getHomologyH2() const { if (H2.known()) return *H2.value(); if (getNumberOfTetrahedra() == 0) return *(H2 = new NAbelianGroup()); // Calculations are different for orientable vs non-orientable // components. // We know the only components will be Z and Z_2. long rank, z2rank; if (isOrientable()) { // Same as H1Rel without the torsion elements. rank = getHomologyH1Rel().getRank(); z2rank = 0; } else { // Non-orientable! // z2rank = # closed cmpts - # closed orientable cmpts z2rank = 0; for (ComponentIterator cit = components.begin(); cit != components.end(); cit++) if ((*cit)->isClosed()) if (! ((*cit)->isOrientable())) z2rank++; // Find rank(Z_2) + rank(Z) and take off z2rank. rank = getHomologyH1Rel().getRank() + getHomologyH1Rel().getTorsionRank(2) - getHomologyH1().getTorsionRank(2) - z2rank; } // Build the new group and tidy up. NAbelianGroup* ans = new NAbelianGroup(); ans->addRank(rank); if (z2rank) ans->addTorsionElement(2, z2rank); return *(H2 = ans); } } // namespace regina regina-4.95/engine/triangulation/homotopy.cpp000644 000765 000024 00000011712 12234011536 021315 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/ntriangulation.h" namespace regina { const NGroupPresentation& NTriangulation::getFundamentalGroup() const { if (fundamentalGroup.known()) return *fundamentalGroup.value(); NGroupPresentation* ans = new NGroupPresentation(); if (getNumberOfTetrahedra() == 0) return *(fundamentalGroup = ans); // Find a maximal forest in the dual 1-skeleton. // Note that this will ensure the skeleton has been calculated. std::set forest; maximalForestInDualSkeleton(forest); // Each non-boundary not-in-forest triangle is a generator. // Each non-boundary edge is a relation. unsigned long nBdryTri = 0; for (BoundaryComponentIterator bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) nBdryTri += (*bit)->getNumberOfTriangles(); long nGens = getNumberOfTriangles() - nBdryTri - forest.size(); // Insert the generators. ans->addGenerator(nGens); // Find out which triangle corresponds to which generator. long *genIndex = new long[getNumberOfTriangles()]; long i = 0; for (TriangleIterator fit = triangles.begin(); fit != triangles.end(); fit++) if ((*fit)->isBoundary() || forest.count(*fit)) genIndex[fit - triangles.begin()] = -1; else genIndex[fit - triangles.begin()] = i++; // Run through each edge and put the relations in the matrix. std::deque::const_iterator embit; NTetrahedron* currTet; NTriangle* triangle; int currTetFace; long triGenIndex; NGroupExpression* rel; for (EdgeIterator eit = edges.begin(); eit != edges.end(); eit++) if (! (*eit)->isBoundary()) { // Put in the relation corresponding to this edge. rel = new NGroupExpression(); for (embit = (*eit)->getEmbeddings().begin(); embit != (*eit)->getEmbeddings().end(); embit++) { currTet = (*embit).getTetrahedron(); currTetFace = (*embit).getVertices()[2]; triangle = currTet->getTriangle(currTetFace); triGenIndex = genIndex[triangleIndex(triangle)]; if (triGenIndex >= 0) { if ((triangle->getEmbedding(0).getTetrahedron() == currTet) && (triangle->getEmbedding(0).getTriangle() == currTetFace)) rel->addTermLast(triGenIndex, 1); else rel->addTermLast(triGenIndex, -1); } } ans->addRelation(rel); } // Tidy up. delete[] genIndex; ans->intelligentSimplify(); return *(fundamentalGroup = ans); } } // namespace regina regina-4.95/engine/triangulation/hydrate.cpp000644 000765 000024 00000032107 12234011536 021100 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "triangulation/ntriangulation.h" /** * Determine the integer value represented by the given letter * in a dehydration string. */ #define VAL(x) ((x) - 'a') /** * Determine the letter that represents the given integer value * in a dehydration string. */ #define LETTER(x) (char((x) + 'a')) namespace regina { NTriangulation* NTriangulation::rehydrate(const std::string& dehydration) { NTriangulation* ans = new NTriangulation; if (ans->insertRehydration(dehydration)) return ans; delete ans; return 0; } bool NTriangulation::insertRehydration(const std::string& dehydration) { // Ensure the string is non-empty. if (dehydration.empty()) return false; // Rewrite the string in lower case and verify that it contains only // letters. std::string proper(dehydration); for (std::string::iterator it = proper.begin(); it != proper.end(); it++) { if (*it >= 'A' && *it <= 'Z') *it = *it + ('a' - 'A'); else if (*it < 'a' || *it > 'z') return false; } // Determine the number of tetrahedra. unsigned nTet = VAL(proper[0]); // Determine the expected length of each piece of the dehydrated string. unsigned lenNewTet = 2 * ((nTet + 3) / 4); unsigned lenGluings = nTet + 1; // Ensure the string has the expected length. if (dehydration.length() != 1 + lenNewTet + lenGluings + lenGluings) return false; // Determine which face gluings should involve new tetrahedra. bool* newTetGluings = new bool[2 * nTet]; unsigned val; int i, j; for (i = 0; i < lenNewTet; i++) { val = VAL(proper[i + 1]); if (val > 15) { delete[] newTetGluings; return false; } if (i % 2 == 0) { // This letter stores values 4i+4 -> 4i+7. for (j = 0; (j < 4) && (4*i + 4 + j < 2 * nTet); j++) newTetGluings[4*i + 4 + j] = ((val & (1 << j)) != 0); } else { // This letter stores values 4i-4 -> 4i-1. for (j = 0; (j < 4) && (4*i - 4 + j < 2 * nTet); j++) newTetGluings[4*i - 4 + j] = ((val & (1 << j)) != 0); } } // Create the tetrahedra and start gluing. ChangeEventSpan span(this); NTetrahedron** tet = new NTetrahedron*[nTet]; for (i = 0; i < nTet; i++) tet[i] = newTetrahedron(); unsigned currTet = 0; // Tetrahedron of the next face to glue. int currFace = 0; // Face number of the next face to glue. unsigned gluingsMade = 0; // How many face pairs have we already glued? unsigned specsUsed = 0; // How many gluing specs have we already used? unsigned tetsUsed = 0; // How many tetrahedra have we already used? bool broken = false; // Have we come across an inconsistency? unsigned adjTet; // The tetrahedron to glue this to. unsigned permIndex; // The index of the gluing permutation to use. NPerm4 adjPerm; // The gluing permutation to use. NPerm4 identity; // The identity permutation. NPerm4 reverse(3,2,1,0); // The reverse permutation. while (currTet < nTet) { // Is this face already glued? if (tet[currTet]->adjacentTetrahedron(currFace)) { if (currFace < 3) currFace++; else { currFace = 0; currTet++; } continue; } // If this is a new tetrahedron, be aware of this fact. if (tetsUsed <= currTet) tetsUsed = currTet + 1; // Do we simply glue to a new tetrahedron? if (newTetGluings[gluingsMade]) { // Glue to tetrahedron tetsUsed. if (tetsUsed < nTet) { tet[currTet]->joinTo(currFace, tet[tetsUsed], identity); tetsUsed++; } else { broken = true; break; } } else { // Glue according to the next gluing spec. if (specsUsed >= lenGluings) { broken = true; break; } adjTet = VAL(proper[1 + lenNewTet + specsUsed]); permIndex = VAL(proper[1 + lenNewTet + lenGluings + specsUsed]); if (adjTet >= nTet || permIndex >= 24) { broken = true; break; } adjPerm = NPerm4::orderedS4[permIndex] * reverse; if (tet[adjTet]->adjacentTetrahedron(adjPerm[currFace]) || (adjTet == currTet && adjPerm[currFace] == currFace)) { broken = true; break; } tet[currTet]->joinTo(currFace, tet[adjTet], adjPerm); specsUsed++; } // Increment everything for the next gluing. gluingsMade++; if (currFace < 3) currFace++; else { currFace = 0; currTet++; } } // And we're done! if (broken) { // Delete tetrahedra in the reverse order so tetrahedra are // always removed from the end of the internal array, and the // combined operation is therefore O(nTet) not O(nTet^2). for (i = nTet - 1; i >= 0; --i) removeTetrahedron(tet[i]); } delete[] newTetGluings; delete[] tet; return (! broken); } std::string NTriangulation::dehydrate() const { // Can we even dehydrate at all? if (tetrahedra.size() > 25 || hasBoundaryTriangles() || ! isConnected()) return ""; // Get the empty case out of the way, since it requires an // additional two redundant letters (two blocks of N+1 letters to // specify "non-obvious gluings"). if (tetrahedra.empty()) return "aaa"; // Find an isomorphism that will put the triangulation in a form // sufficiently "canonical" to be described by a dehydration string. // When walking through tetrahedron faces from start to finish, this affects // only gluings to previously unseen tetrahedra: // (i) such gluings must be to the smallest numbered unused tetrahedron; // (ii) the gluing permutation must be the identity permutation. // // The array image[] maps tetrahedron numbers from this // triangulation to the canonical triangulation; preImage[] is the // inverse map. The array vertexMap[] describes the corresponding // rearrangement of tetrahedron vertices and faces; specifically, // vertex i of tetrahedron t of this triangulation maps to vertex // vertexMap[t][i] of tetrahedron image[t]. // // Each element of newTet[] is an 8-bit integer. These bits // describe whether the gluings for some corresponding 8 tetrahedron faces // point to previously-seen or previously-unseen tetrahedra. // See the Callahan, Hildebrand and Weeks paper for details. unsigned nTets = tetrahedra.size(); int* image = new int[nTets]; int* preImage = new int[nTets]; NPerm4* vertexMap = new NPerm4[nTets]; unsigned char* newTet = new unsigned char[(nTets / 4) + 2]; unsigned newTetPos = 0; unsigned newTetBit = 0; char* destChars = new char[nTets + 2]; char* permChars = new char[nTets + 2]; unsigned currGluingPos = 0; unsigned nextUnused = 1; unsigned tetIndex, tet, dest, faceIndex, face; NPerm4 map; unsigned mapIndex; for (tet = 0; tet < nTets; tet++) image[tet] = preImage[tet] = -1; image[0] = preImage[0] = 0; vertexMap[0] = NPerm4(); newTet[0] = 0; for (tetIndex = 0; tetIndex < nTets; tetIndex++) { // We must run through the tetrahedra in image order, not // preimage order. tet = preImage[tetIndex]; for (faceIndex = 0; faceIndex < 4; faceIndex++) { // Likewise for faces. face = vertexMap[tet].preImageOf(faceIndex); // INVARIANTS (held while tet < nTets): // - nextUnused > tetIndex // - image[tet], preImage[image[tet]] and vertexMap[tet] are // all filled in. // These invariants are preserved because the triangulation is // connected. They break when tet == nTets. dest = tetrahedronIndex( tetrahedra[tet]->adjacentTetrahedron(face)); // Is it a gluing we've already seen from the other side? if (image[dest] >= 0) if (image[dest] < image[tet] || (image[dest] == image[tet] && vertexMap[tet][tetrahedra[tet]->adjacentFace(face)] < vertexMap[tet][face])) continue; // Is it a completely new tetrahedron? if (image[dest] < 0) { // Previously unseen. image[dest] = nextUnused; preImage[nextUnused] = dest; vertexMap[dest] = vertexMap[tet] * tetrahedra[tet]->adjacentGluing(face). inverse(); nextUnused++; newTet[newTetPos] |= (1 << newTetBit); } else { // It's a tetrahedron we've seen before. Record the gluing. // Don't forget that our permutation abcd becomes dcba // in dehydration language. destChars[currGluingPos] = LETTER(image[dest]); map = vertexMap[dest] * tetrahedra[tet]->adjacentGluing(face) * vertexMap[tet].inverse() * NPerm4(3, 2, 1, 0); // Just loop to find the index of the corresponding // gluing permutation. There's only 24 permutations and // at most 25 tetrahedra; we'll live with it. for (mapIndex = 0; mapIndex < 24; mapIndex++) if (map == NPerm4::orderedS4[mapIndex]) break; permChars[currGluingPos] = LETTER(mapIndex); currGluingPos++; } newTetBit++; if (newTetBit == 8) { newTetPos++; newTetBit = 0; newTet[newTetPos] = 0; } } } // We have all we need. Tidy up the strings and put them all // together. if (newTetBit > 0) { // We're partway through a bitset; assume it's finished and // point to the next unused slot in the array. newTetPos++; } // At this stage we should have currGluingPos == nTets + 1. destChars[currGluingPos] = 0; permChars[currGluingPos] = 0; std::string ans; ans += LETTER(nTets); for (unsigned i = 0; i < newTetPos; i++) { ans += LETTER(newTet[i] >> 4); ans += LETTER(newTet[i] & 15); } ans += destChars; ans += permChars; // Done! delete[] newTet; delete[] permChars; delete[] destChars; delete[] vertexMap; delete[] preImage; delete[] image; return ans; } } // namespace regina regina-4.95/engine/triangulation/insertlayered.cpp000644 000765 000024 00000027153 12234011536 022317 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "manifold/nlensspace.h" #include "manifold/nsfs.h" #include "triangulation/ntriangulation.h" namespace regina { NTetrahedron* NTriangulation::layerOn(NEdge* edge) { // Locate the two boundary triangles. // Note that our preconditions ensure they exist and are distinct; // we won't test this again here. const std::deque& embs(edge->getEmbeddings()); NTetrahedron* tet1 = embs.front().getTetrahedron(); NTetrahedron* tet2 = embs.back().getTetrahedron(); NPerm4 roles1 = embs.front().getVertices(); NPerm4 roles2 = embs.back().getVertices(); // At this stage, roles1 maps (0,1,2) to the tet1 tetrahedron vertices // for the first boundary triangle, and roles2 maps (0,1,3) to the tet2 // tetrahedron vertices for the second boundary triangle. In each case, // (0,1) maps to the endpoints of the given edge. // // The simplest thing to do is let (0,1,2,3) in the preimages for // roles1 and roles2 match up with vertices (0,1,2,3) of the new // tetrahedron. ChangeEventSpan span(this); NTetrahedron* newTet = newTetrahedron(); newTet->joinTo(3, tet1, roles1); newTet->joinTo(2, tet2, roles2); return newTet; } NTetrahedron* NTriangulation::insertLayeredSolidTorus( unsigned long cuts0, unsigned long cuts1) { ChangeEventSpan span(this); unsigned long cuts2 = cuts0 + cuts1; NTetrahedron* newTet = newTetrahedron(); // Take care of the case that can be done with a single // tetrahedron. if (cuts2 == 3) { // Must be a 1-2-3 arrangement that can be done with a single // tetrahedron. newTet->joinTo(0, newTet, NPerm4(1,2,3,0)); return newTet; } // Take care of the special small cases. if (cuts2 == 2) { // Make a 1-2-1 arrangement. NTetrahedron* base = insertLayeredSolidTorus(1, 2); base->joinTo(2, newTet, NPerm4(2,3,0,1)); base->joinTo(3, newTet, NPerm4(2,3,0,1)); return newTet; } if (cuts2 == 1) { // Make a 1-1-0 arrangement. NTetrahedron* base = insertLayeredSolidTorus(1, 1); base->joinTo(2, newTet, NPerm4(0,2,1,3)); base->joinTo(3, newTet, NPerm4(3,1,2,0)); return newTet; } // At this point we know cuts2 > 3. Recursively build the layered // triangulation. if (cuts1 - cuts0 > cuts0) { NTetrahedron* base = insertLayeredSolidTorus(cuts0, cuts1 - cuts0); base->joinTo(2, newTet, NPerm4(0,2,1,3)); base->joinTo(3, newTet, NPerm4(3,1,2,0)); } else { NTetrahedron* base = insertLayeredSolidTorus(cuts1 - cuts0, cuts0); base->joinTo(2, newTet, NPerm4(3,1,0,2)); base->joinTo(3, newTet, NPerm4(0,2,3,1)); } return newTet; } void NTriangulation::insertLayeredLensSpace(unsigned long p, unsigned long q) { ChangeEventSpan span(this); NTetrahedron* chain; if (p == 0) { chain = insertLayeredSolidTorus(1, 1); chain->joinTo(3, chain, NPerm4(3, 0, 1, 2)); } else if (p == 1) { chain = insertLayeredSolidTorus(1, 2); chain->joinTo(3, chain, NPerm4(0, 1, 3, 2)); } else if (p == 2) { chain = insertLayeredSolidTorus(1, 3); chain->joinTo(3, chain, NPerm4(0, 1, 3, 2)); } else if (p == 3) { chain = insertLayeredSolidTorus(1, 1); // Either of the following gluings will work. chain->joinTo(3, chain, NPerm4(1, 3, 0, 2)); // chain->joinTo(3, chain, NPerm4(0, 1, 3, 2)); } else { if (2 * q > p) q = p - q; if (3 * q > p) { chain = insertLayeredSolidTorus(p - 2 * q, q); chain->joinTo(3, chain, NPerm4(1, 3, 0, 2)); } else { chain = insertLayeredSolidTorus(q, p - 2 * q); chain->joinTo(3, chain, NPerm4(3, 0, 1, 2)); } } } void NTriangulation::insertLayeredLoop(unsigned long length, bool twisted) { if (length == 0) return; ChangeEventSpan span(this); // Insert a layered chain of the given length. // We should probably split this out into a separate routine. NTetrahedron* base; NTetrahedron* curr; NTetrahedron* next; base = newTetrahedron(); curr = base; for (unsigned long i = 1; i < length; i++) { next = newTetrahedron(); curr->joinTo(0, next, NPerm4(1, 0, 2, 3)); curr->joinTo(3, next, NPerm4(0, 1, 3, 2)); curr = next; } // Join the two ends of the layered chain. if (twisted) { curr->joinTo(0, base, NPerm4(2, 3, 1, 0)); curr->joinTo(3, base, NPerm4(3, 2, 0, 1)); } else { curr->joinTo(0, base, NPerm4(1, 0, 2, 3)); curr->joinTo(3, base, NPerm4(0, 1, 3, 2)); } } void NTriangulation::insertAugTriSolidTorus(long a1, long b1, long a2, long b2, long a3, long b3) { ChangeEventSpan span(this); int i; // Construct the core triangular solid torus. NTetrahedron* core[3]; for (i = 0; i < 3; i++) core[i] = newTetrahedron(); for (i = 0; i < 3; i++) core[i]->joinTo(0, core[(i + 1) % 3], NPerm4(3, 0, 1, 2)); // Attach the external layered solid tori. long axis, major, minor; unsigned long absAxis, absMajor, absMinor; NTetrahedron* lstTop; for (i = 0; i < 3; i++) { if (i == 0) axis = a1; else if (i == 1) axis = a2; else axis = a3; if (i == 0) major = b1; else if (i == 1) major = b2; else major = b3; minor = -(axis + major); absAxis = (axis < 0 ? -axis : axis); absMajor = (major < 0 ? -major : major); absMinor = (minor < 0 ? -minor : minor); // Are we simply attaching a mobius band? if (absAxis <= 2 && absMajor <= 2 && absMinor <= 2) { // We have either (2,1,1) or (1,1,0). if (absAxis == 2) { core[i]->joinTo(2, core[(i + 1) % 3], NPerm4(0, 2, 1, 3)); continue; } else if (absMajor == 2) { core[i]->joinTo(2, core[(i + 1) % 3], NPerm4(2, 3, 1, 0)); continue; } else if (absMinor == 2) { core[i]->joinTo(2, core[(i + 1) % 3], NPerm4(3, 0, 1, 2)); continue; } // It's (1,1,0). But this needs to be handled specially anyway. lstTop = insertLayeredSolidTorus(0, 1); if (absAxis == 0) { core[i]->joinTo(2, lstTop, NPerm4(0, 2, 3, 1)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(0, 2, 3, 1)); } else if (absMajor == 0) { core[i]->joinTo(2, lstTop, NPerm4(1, 0, 3, 2)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(3, 2, 1, 0)); } else { core[i]->joinTo(2, lstTop, NPerm4(3, 0, 2, 1)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(0, 3, 1, 2)); } continue; } if (absAxis >= absMajor && absAxis >= absMinor) { // Most cuts on the axis edges. if (absMinor <= absMajor) { // (minor, major, axis) lstTop = insertLayeredSolidTorus(absMinor, absMajor); core[i]->joinTo(2, lstTop, NPerm4(0, 2, 3, 1)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(0, 2, 3, 1)); } else { // (major, minor, axis) lstTop = insertLayeredSolidTorus(absMajor, absMinor); core[i]->joinTo(2, lstTop, NPerm4(1, 2, 3, 0)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(1, 2, 3, 0)); } } else if (absMajor >= absMinor) { // Most cuts on the major edges. if (absMinor <= absAxis) { // (minor, axis, major) lstTop = insertLayeredSolidTorus(absMinor, absAxis); core[i]->joinTo(2, lstTop, NPerm4(0, 1, 3, 2)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(3, 2, 0, 1)); } else { // (axis, minor, major) lstTop = insertLayeredSolidTorus(absAxis, absMinor); core[i]->joinTo(2, lstTop, NPerm4(1, 0, 3, 2)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(3, 2, 1, 0)); } } else { // Most cuts on the minor edges. if (absAxis <= absMajor) { // (axis, major, minor) lstTop = insertLayeredSolidTorus(absAxis, absMajor); core[i]->joinTo(2, lstTop, NPerm4(3, 1, 2, 0)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(1, 3, 0, 2)); } else { // (major, axis, minor) lstTop = insertLayeredSolidTorus(absMajor, absAxis); core[i]->joinTo(2, lstTop, NPerm4(3, 0, 2, 1)); core[(i + 1) % 3]->joinTo(1, lstTop, NPerm4(0, 3, 1, 2)); } } } } void NTriangulation::insertSFSOverSphere(long a1, long b1, long a2, long b2, long a3, long b3) { // Construct the SFS that we seek. NSFSpace sfs; if (a1 < 0) sfs.insertFibre(-a1, -b1); else sfs.insertFibre(a1, b1); if (a2 < 0) sfs.insertFibre(-a2, -b2); else sfs.insertFibre(a2, b2); if (a3 < 0) sfs.insertFibre(-a3, -b3); else sfs.insertFibre(a3, b3); sfs.reduce(); // Use the SFS construction routine, which can handle this type of SFS. NTriangulation* ans = sfs.construct(); insertTriangulation(*ans); delete ans; } } // namespace regina regina-4.95/engine/triangulation/isomorphic.cpp000644 000765 000024 00000043334 12234011536 021620 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "triangulation/nisomorphism.h" #include "triangulation/ntriangulation.h" namespace regina { std::auto_ptr NTriangulation::isIsomorphicTo( const NTriangulation& other) const { std::list results; if (findIsomorphisms(other, results, true, true)) return std::auto_ptr(results.front()); else return std::auto_ptr(0); } std::auto_ptr NTriangulation::isContainedIn( const NTriangulation& other) const { std::list results; if (findIsomorphisms(other, results, false, true)) return std::auto_ptr(results.front()); else return std::auto_ptr(0); } unsigned long NTriangulation::findAllSubcomplexesIn( const NTriangulation& other, std::list& results) const { return findIsomorphisms(other, results, false, false); } unsigned long NTriangulation::findIsomorphisms( const NTriangulation& other, std::list& results, bool completeIsomorphism, bool firstOnly) const { if (! calculatedSkeleton) calculateSkeleton(); if (! other.calculatedSkeleton) other.calculateSkeleton(); // Deal with the empty triangulation first. if (tetrahedra.empty()) { if (completeIsomorphism && ! other.tetrahedra.empty()) return 0; results.push_back(new NIsomorphism(0)); return 1; } // Basic property checks. Unfortunately, if we allow boundary // incomplete isomorphisms then we can't test that many properties. if (completeIsomorphism) { // Must be boundary complete, 1-to-1 and onto. // That is, combinatorially the two triangulations must be // identical. if (tetrahedra.size() != other.tetrahedra.size()) return 0; if (triangles.size() != other.triangles.size()) return 0; if (edges.size() != other.edges.size()) return 0; if (vertices.size() != other.vertices.size()) return 0; if (components.size() != other.components.size()) return 0; if (boundaryComponents.size() != other.boundaryComponents.size()) return 0; if (orientable ^ other.orientable) return 0; // Test degree sequences and the like. std::map map1; std::map map2; std::map::iterator mapIt; { EdgeIterator it; for (it = edges.begin(); it != edges.end(); it++) { // Find this degree, or insert it with frequency 0 if it's // not already present. mapIt = map1.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } for (it = other.edges.begin(); it != other.edges.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } { VertexIterator it; for (it = vertices.begin(); it != vertices.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } for (it = other.vertices.begin(); it != other.vertices.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfEmbeddings(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } { ComponentIterator it; for (it = components.begin(); it != components.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfTetrahedra(), 0)).first; (*mapIt).second++; } for (it = other.components.begin(); it != other.components.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfTetrahedra(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } { BoundaryComponentIterator it; for (it = boundaryComponents.begin(); it != boundaryComponents.end(); it++) { mapIt = map1.insert( std::make_pair((*it)->getNumberOfTriangles(), 0)).first; (*mapIt).second++; } for (it = other.boundaryComponents.begin(); it != other.boundaryComponents.end(); it++) { mapIt = map2.insert( std::make_pair((*it)->getNumberOfTriangles(), 0)).first; (*mapIt).second++; } if (! (map1 == map2)) return 0; map1.clear(); map2.clear(); } } else { // May be boundary incomplete, and need not be onto. // Not much we can test for unfortunately. if (tetrahedra.size() > other.tetrahedra.size()) return 0; if ((! orientable) && other.orientable) return 0; } // Start searching for the isomorphism. // From the tests above, we are guaranteed that both triangulations // have at least one tetrahedron. unsigned long nResults = 0; unsigned long nTetrahedra = tetrahedra.size(); unsigned long nDestTetrahedra = other.tetrahedra.size(); unsigned long nComponents = components.size(); unsigned i; NIsomorphism iso(nTetrahedra); for (i = 0; i < nTetrahedra; i++) iso.tetImage(i) = -1; // Which source component does each destination tetrahedron correspond to? long* whichComp = new long[nDestTetrahedra]; std::fill(whichComp, whichComp + nDestTetrahedra, -1); // The image of the first source tetrahedron of each component. The // remaining images can be derived by following gluings. unsigned long* startTet = new unsigned long[nComponents]; std::fill(startTet, startTet + nComponents, 0); int* startPerm = new int[nComponents]; std::fill(startPerm, startPerm + nComponents, 0); // The tetrahedra whose neighbours must be processed when filling // out the current component. std::queue toProcess; // Temporary variables. unsigned long compSize; NTetrahedron* tet; NTetrahedron* adj; NTetrahedron* destTet; NTetrahedron* destAdj; unsigned long tetIndex, adjIndex; unsigned long destTetIndex, destAdjIndex; NPerm4 tetPerm, adjPerm; int face; bool broken; long comp = 0; while (comp >= 0) { // Continue trying to find a mapping for the current component. // The next mapping to try is the one that starts with // startTet[comp] and startPerm[comp]. if (comp == static_cast(nComponents)) { // We have an isomorphism!!! results.push_back(new NIsomorphism(iso)); if (firstOnly) { delete[] whichComp; delete[] startTet; delete[] startPerm; return 1; } else nResults++; // Back down to the previous component, and clear the // mapping for that previous component so we can make way // for a new one. // Since nComponents > 0, we are guaranteed that comp > 0 also. comp--; for (i = 0; i < nTetrahedra; i++) if (iso.tetImage(i) >= 0 && whichComp[iso.tetImage(i)] == comp) { whichComp[iso.tetImage(i)] = -1; iso.tetImage(i) = -1; } startPerm[comp]++; continue; } // Sort out the results of any previous startPerm++. if (startPerm[comp] == 24) { // Move on to the next destination tetrahedron. startTet[comp]++; startPerm[comp] = 0; } // Be sure we're looking at a tetrahedron we can use. compSize = components[comp]->getNumberOfTetrahedra(); if (completeIsomorphism) { // Conditions: // 1) The destination tetrahedron is unused. // 2) The component sizes match precisely. while (startTet[comp] < nDestTetrahedra && (whichComp[startTet[comp]] >= 0 || other.tetrahedra[startTet[comp]]->getComponent()-> getNumberOfTetrahedra() != compSize)) startTet[comp]++; } else { // Conditions: // 1) The destination tetrahedron is unused. // 2) The destination component is at least as large as // the source component. while (startTet[comp] < nDestTetrahedra && (whichComp[startTet[comp]] >= 0 || other.tetrahedra[startTet[comp]]->getComponent()-> getNumberOfTetrahedra() < compSize)) startTet[comp]++; } // Have we run out of possibilities? if (startTet[comp] == nDestTetrahedra) { // No more possibilities for filling this component. // Move back to the previous component, and clear the // mapping for that previous component. startTet[comp] = 0; startPerm[comp] = 0; comp--; if (comp >= 0) { for (i = 0; i < nTetrahedra; i++) if (iso.tetImage(i) >= 0 && whichComp[iso.tetImage(i)] == comp) { whichComp[iso.tetImage(i)] = -1; iso.tetImage(i) = -1; } startPerm[comp]++; } continue; } // Try to fill the image of this component based on the selected // image of its first source tetrahedron. // Note that there is only one way of doing this (as seen by // following adjacent tetrahedron gluings). It either works or // it doesn't. tetIndex = tetrahedronIndex(components[comp]->getTetrahedron(0)); whichComp[startTet[comp]] = comp; iso.tetImage(tetIndex) = startTet[comp]; iso.facePerm(tetIndex) = NPerm4::S4[startPerm[comp]]; toProcess.push(tetIndex); broken = false; while ((! broken) && (! toProcess.empty())) { tetIndex = toProcess.front(); toProcess.pop(); tet = tetrahedra[tetIndex]; tetPerm = iso.facePerm(tetIndex); destTetIndex = iso.tetImage(tetIndex); destTet = other.tetrahedra[destTetIndex]; // If we are after a complete isomorphism, we might as well // test whether the edge and vertex degrees match. if (completeIsomorphism && ! compatibleTets(tet, destTet, tetPerm)) { broken = true; break; } for (face = 0; face < 4; face++) { adj = tet->adjacentTetrahedron(face); if (adj) { // There is an adjacent source tetrahedron. // Is there an adjacent destination tetrahedron? destAdj = destTet->adjacentTetrahedron(tetPerm[face]); if (! destAdj) { broken = true; break; } // Work out what the isomorphism *should* say. adjIndex = tetrahedronIndex(adj); destAdjIndex = other.tetrahedronIndex(destAdj); adjPerm = destTet->adjacentGluing(tetPerm[face]) * tetPerm * tet->adjacentGluing(face).inverse(); if (iso.tetImage(adjIndex) >= 0) { // We've already decided upon an image for this // source tetrahedron. Does it match? if (static_cast(destAdjIndex) != iso.tetImage(adjIndex) || adjPerm != iso.facePerm(adjIndex)) { broken = true; break; } } else if (whichComp[destAdjIndex] >= 0) { // We haven't decided upon an image for this // source tetrahedron but the destination // tetrahedron has already been used. broken = true; break; } else { // We haven't seen either the source or the // destination tetrahedron. whichComp[destAdjIndex] = comp; iso.tetImage(adjIndex) = destAdjIndex; iso.facePerm(adjIndex) = adjPerm; toProcess.push(adjIndex); } } else if (completeIsomorphism) { // There is no adjacent source tetrahedron, and we // are after a boundary complete isomorphism. // There had better be no adjacent destination // tetrahedron also. if (destTet->adjacentTetrahedron(tetPerm[face])) { broken = true; break; } } } } if (! broken) { // Therefore toProcess is empty. // The image for this component was successfully filled out. // Move on to the next component. comp++; } else { // The image for this component was not successfully filled out. // Undo our partially created image, and then try another // starting image for this component. while (! toProcess.empty()) toProcess.pop(); for (i = 0; i < nTetrahedra; i++) if (iso.tetImage(i) >= 0 && whichComp[iso.tetImage(i)] == comp) { whichComp[iso.tetImage(i)] = -1; iso.tetImage(i) = -1; } startPerm[comp]++; } } // All out of options. delete[] whichComp; delete[] startTet; delete[] startPerm; return nResults; } bool NTriangulation::compatibleTets(NTetrahedron* src, NTetrahedron* dest, NPerm4 p) { for (int edge = 0; edge < 6; edge++) { if (src->getEdge(edge)->getNumberOfEmbeddings() != dest->getEdge(NEdge::edgeNumber[p[NEdge::edgeVertex[edge][0]]] [p[NEdge::edgeVertex[edge][1]]]) ->getNumberOfEmbeddings()) return false; } for (int vertex = 0; vertex < 4; vertex++) { if (src->getVertex(vertex)->getNumberOfEmbeddings() != dest->getVertex(p[vertex])->getNumberOfEmbeddings()) return false; if (src->getVertex(vertex)->getLink() != dest->getVertex(p[vertex])->getLink()) return false; } return true; } } // namespace regina regina-4.95/engine/triangulation/nboundarycomponent.cpp000644 000765 000024 00000006426 12234011536 023371 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/nboundarycomponent.h" namespace regina { void NBoundaryComponent::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; if (isIdeal()) { NVertex* v = vertices.front(); out << "Vertex: " << v->markedIndex() << std::endl; out << "Appears as:" << std::endl; std::vector::const_iterator it; for (it = v->getEmbeddings().begin(); it != v->getEmbeddings().end(); ++it) out << " " << it->getTetrahedron()->markedIndex() << " (" << it->getVertex() << ')' << std::endl; } else { out << "Triangles:" << std::endl; std::vector::const_iterator it; for (it = triangles.begin(); it != triangles.end(); ++it) { const NTriangleEmbedding& emb((*it)->getEmbedding(0)); out << " " << emb.getTetrahedron()->markedIndex() << " (" << emb.getVertices().trunc3() << ')' << std::endl; } } } } // namespace regina regina-4.95/engine/triangulation/nboundarycomponent.h000644 000765 000024 00000027243 12234011536 023036 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NBOUNDARYCOMPONENT_H #ifndef __DOXYGEN #define __NBOUNDARYCOMPONENT_H #endif /*! \file triangulation/nboundarycomponent.h * \brief Deals with components of the boundary of a triangulation. */ #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class NEdge; class NTriangle; class NTetrahedron; class NVertex; /** * \weakgroup triangulation * @{ */ /** * Represents a component of the boundary of a triangulation. * Note that an ideal vertex constitutes a boundary component of its * own. * * We can run into some interesting cases with invalid triangulations. * Suppose some vertex link is a multiply punctured surface (which makes * the vertex and hence the entire triangulation invalid). This means * that different parts of the 3-manifold boundary are effectively * "pinched" together. If this happens, the different parts of the * boundary that are pinched might or might not be listed as part of the * same boundary component; if not then the offending vertex will be * included in all of these boundary components. Nevertheless, only one * of these can be considered the "official" boundary component of the * vertex as returned by NVertex::getBoundaryComponent(). This is all a * bit of a mess, but then again the entire triangulation is invalid and * so you almost certainly have bigger problems to deal with. * * Boundary components are highly temporary; once a triangulation * changes, all its boundary component objects will be deleted and new * ones will be created. */ class REGINA_API NBoundaryComponent : public ShareableObject, public NMarkedElement { private: std::vector triangles; /**< List of triangles in the component. */ std::vector edges; /**< List of edges in the component. */ std::vector vertices; /**< List of vertices in the component. */ bool orientable; /**< Is this boundary component orientable? */ public: /** * Default destructor. */ virtual ~NBoundaryComponent(); /** * Returns the number of triangles in this boundary component. * * @return the number of triangles. */ unsigned long getNumberOfTriangles() const; /** * A deprecated alias for getNumberOfTriangles(). * * This routine returns the number of triangular faces in this * boundary component. See getNumberOfTriangles() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getNumberOfTriangles() instead. * * @return the number of triangles. */ unsigned long getNumberOfFaces() const; /** * Returns the number of edges in this boundary component. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of vertices in this boundary component. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the requested triangle in this boundary component. * * For an ideal boundary component (which consists of a single * vertex), there are no real triangles in the boundary component * and this routine cannot be used. * * @param index the index of the requested triangle in the boundary * component. This should be between 0 and getNumberOfTriangles()-1 * inclusive. * Note that the index of a triangle in the boundary component need * not be the index of the same triangle in the entire * triangulation. * @return the requested triangle. */ NTriangle* getTriangle(unsigned long index) const; /** * A deprecated alias for getTriangle(). * * This routine returns the requested triangular face in this * boundary component. See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @param index the index of the requested triangle in the boundary * component. This should be between 0 and getNumberOfTriangles()-1 * inclusive. * Note that the index of a triangle in the boundary component need * not be the index of the same triangle in the entire * triangulation. * @return the requested triangle. */ NTriangle* getFace(unsigned long index) const; /** * Returns the requested edge in this boundary component. * * For an ideal boundary component (which consists of a single * vertex), there are no real edges in the boundary component * and this routine cannot be used. * * @param index the index of the requested edge in the boundary * component. This should be between 0 and getNumberOfEdges()-1 * inclusive. * Note that the index of a edge in the boundary component need * not be the index of the same edge in the entire * triangulation. * @return the requested edge. */ NEdge* getEdge(unsigned long index) const; /** * Returns the requested vertex in this boundary component. * * @param index the index of the requested vertex in the boundary * component. This should be between 0 and getNumberOfVertices()-1 * inclusive. * Note that the index of a vertex in the boundary component need * not be the index of the same vertex in the entire * triangulation. * @return the requested vertex. */ NVertex* getVertex(unsigned long index) const; /** * Returns the Euler characteristic of this boundary component. * If this boundary component is ideal, the Euler characteristic * of the link of the corresponding ideal vertex is returned. * * @return the Euler characteristic. */ long getEulerCharacteristic() const; /** * Determines if this boundary component is ideal. * This is the case if and only if it consists of a single * (ideal) vertex and no triangles. * * @return \c true if and only if this boundary component is * ideal. */ bool isIdeal() const; /** * Determines if this boundary component is orientable. * If the boundary component is ideal, the orientability * of the link of the corresponding ideal vertex is returned. * * @return \c true if and only if this boundary component is * orientable. */ bool isOrientable() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Default constructor. */ NBoundaryComponent(); /** * Creates a new boundary component consisting only of the given * ideal vertex. * * \pre The given vertex is ideal as returned by NVertex::isIdeal(). * * @param idealVertex the vertex to place in the new boundary * component. */ NBoundaryComponent(NVertex* idealVertex); friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/nvertex.h" namespace regina { // Inline functions for NBoundaryComponent inline NBoundaryComponent::NBoundaryComponent() { } inline NBoundaryComponent::NBoundaryComponent(NVertex* idealVertex) { vertices.push_back(idealVertex); } inline NBoundaryComponent::~NBoundaryComponent() { } inline unsigned long NBoundaryComponent::getNumberOfTriangles() const { return triangles.size(); } inline unsigned long NBoundaryComponent::getNumberOfFaces() const { return triangles.size(); } inline unsigned long NBoundaryComponent::getNumberOfEdges() const { return edges.size(); } inline unsigned long NBoundaryComponent::getNumberOfVertices() const { return vertices.size(); } inline NTriangle* NBoundaryComponent::getTriangle(unsigned long index) const { return triangles[index]; } inline NTriangle* NBoundaryComponent::getFace(unsigned long index) const { return triangles[index]; } inline NEdge* NBoundaryComponent::getEdge(unsigned long index) const { return edges[index]; } inline NVertex* NBoundaryComponent::getVertex(unsigned long index) const { return vertices[index]; } inline long NBoundaryComponent::getEulerCharacteristic() const { return (isIdeal() ? vertices.front()->getLinkEulerCharacteristic() : long(vertices.size()) - long(edges.size()) + long(triangles.size())); } inline bool NBoundaryComponent::isIdeal() const { return triangles.empty(); } inline bool NBoundaryComponent::isOrientable() const { return orientable; } inline void NBoundaryComponent::writeTextShort(std::ostream& out) const { out << (isIdeal() ? "Ideal " : "Finite ") << "boundary component"; } } // namespace regina #endif regina-4.95/engine/triangulation/ncomponent.cpp000644 000765 000024 00000005701 12234011536 021620 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/ncomponent.h" #include "triangulation/ntetrahedron.h" namespace regina { void NComponent::writeTextShort(std::ostream& out) const { if (tetrahedra.size() == 1) out << "Component with 1 tetrahedron"; else out << "Component with " << getNumberOfTetrahedra() << " tetrahedra"; } void NComponent::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << (tetrahedra.size() == 1 ? "Tetrahedron:" : "Tetrahedra:"); std::vector::const_iterator it; for (it = tetrahedra.begin(); it != tetrahedra.end(); ++it) out << ' ' << (*it)->markedIndex(); out << std::endl; } } // namespace regina regina-4.95/engine/triangulation/ncomponent.h000644 000765 000024 00000030452 12234011536 021266 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __NCOMPONENT_H #ifndef __DOXYGEN #define __NCOMPONENT_H #endif /*! \file triangulation/ncomponent.h * \brief Deals with components of a triangulation. */ #include #include "regina-core.h" #include "shareableobject.h" #include "utilities/nmarkedvector.h" namespace regina { class NTetrahedron; class NTriangle; class NEdge; class NVertex; class NBoundaryComponent; /** * \weakgroup triangulation * @{ */ /** * Represents a component of a triangulation. * Components are highly temporary; once a triangulation changes, all * its component objects will be deleted and new ones will be created. */ class REGINA_API NComponent : public ShareableObject, public NMarkedElement { private: std::vector tetrahedra; /**< List of tetrahedra in the component. */ std::vector triangles; /**< List of triangles in the component. */ std::vector edges; /**< List of edges in the component. */ std::vector vertices; /**< List of vertices in the component. */ std::vector boundaryComponents; /**< List of boundary components in the component. */ bool ideal; /**< Is the component ideal? */ bool orientable; /**< Is the component orientable? */ public: /** * Default destructor. */ virtual ~NComponent(); /** * Returns the number of tetrahedra in this component. * * @return the number of tetrahedra. */ unsigned long getNumberOfTetrahedra() const; /** * A dimension-agnostic alias for getNumberOfTetrahedra(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getNumberOfTetrahedra() for further information. */ unsigned long getNumberOfSimplices() const; /** * Returns the number of triangles in this component. * * @return the number of triangles. */ unsigned long getNumberOfTriangles() const; /** * A deprecated alias for getNumberOfTriangles(). * * This routine returns the number of triangular faces in this * component. See getNumberOfTriangles() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getNumberOfTriangles() instead. * * @return the number of triangles. */ unsigned long getNumberOfFaces() const; /** * Returns the number of edges in this component. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of vertices in this component. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the number of boundary components in this component. * * @return the number of boundary components. */ unsigned long getNumberOfBoundaryComponents() const; /** * Returns the requested tetrahedron in this component. * * @param index the index of the requested tetrahedron in the * component. This should be between 0 and * getNumberOfTetrahedra()-1 inclusive. * Note that the index of a tetrahedron in the component need * not be the index of the same tetrahedron in the entire * triangulation. * @return the requested tetrahedron. */ NTetrahedron* getTetrahedron(unsigned long index) const; /** * A dimension-agnostic alias for getTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getTetrahedron() for further information. */ NTetrahedron* getSimplex(unsigned long index) const; /** * Returns the requested triangle in this component. * * @param index the index of the requested triangle in the * component. This should be between 0 and * getNumberOfTriangles()-1 inclusive. * Note that the index of a triangle in the component need * not be the index of the same triangle in the entire * triangulation. * @return the requested triangle. */ NTriangle* getTriangle(unsigned long index) const; /** * A deprecated alias for getTriangle(). * * This routine returns the requested triangular face in this * component. See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @param index the index of the requested triangle in the * component. This should be between 0 and * getNumberOfTriangles()-1 inclusive. * Note that the index of a triangle in the component need * not be the index of the same triangle in the entire * triangulation. * @return the requested triangle. */ NTriangle* getFace(unsigned long index) const; /** * Returns the requested edge in this component. * * @param index the index of the requested edge in the * component. This should be between 0 and * getNumberOfEdges()-1 inclusive. * Note that the index of an edge in the component need * not be the index of the same edge in the entire * triangulation. * @return the requested edge. */ NEdge* getEdge(unsigned long index) const; /** * Returns the requested vertex in this component. * * @param index the index of the requested vertex in the * component. This should be between 0 and * getNumberOfVertices()-1 inclusive. * Note that the index of a vertex in the component need * not be the index of the same vertex in the entire * triangulation. * @return the requested vertex. */ NVertex* getVertex(unsigned long index) const; /** * Returns the requested boundary component in this component. * * @param index the index of the requested boundary component in * this component. This should be between 0 and * getNumberOfBoundaryComponents()-1 inclusive. * Note that the index of a boundary component in the component * need not be the index of the same boundary component in the * entire triangulation. * @return the requested boundary component. */ NBoundaryComponent* getBoundaryComponent(unsigned long index) const; /** * Determines if this component is ideal. * This is the case if and only if it contains an ideal vertex * as described by NVertex::isIdeal(). * * @return \c true if and only if this component is ideal. */ bool isIdeal() const; /** * Determines if this component is orientable. * * @return \c true if and only if this component is orientable. */ bool isOrientable() const; /** * Determines if this component is closed. * This is the case if and only if it has no boundary. * Note that ideal components are not closed. * * @return \c true if and only if this component is closed. */ bool isClosed() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Default constructor. */ NComponent(); friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ // Inline functions for NComponent inline NComponent::NComponent() : ideal(false), orientable(true) { } inline NComponent::~NComponent() { } inline unsigned long NComponent::getNumberOfTetrahedra() const { return tetrahedra.size(); } inline unsigned long NComponent::getNumberOfSimplices() const { return tetrahedra.size(); } inline unsigned long NComponent::getNumberOfTriangles() const { return triangles.size(); } inline unsigned long NComponent::getNumberOfFaces() const { return triangles.size(); } inline unsigned long NComponent::getNumberOfEdges() const { return edges.size(); } inline unsigned long NComponent::getNumberOfVertices() const { return vertices.size(); } inline unsigned long NComponent::getNumberOfBoundaryComponents() const { return boundaryComponents.size(); } inline NTetrahedron* NComponent::getTetrahedron(unsigned long index) const { return tetrahedra[index]; } inline NTetrahedron* NComponent::getSimplex(unsigned long index) const { return tetrahedra[index]; } inline NTriangle* NComponent::getTriangle(unsigned long index) const { return triangles[index]; } inline NTriangle* NComponent::getFace(unsigned long index) const { return triangles[index]; } inline NEdge* NComponent::getEdge(unsigned long index) const { return edges[index]; } inline NVertex* NComponent::getVertex(unsigned long index) const { return vertices[index]; } inline NBoundaryComponent* NComponent::getBoundaryComponent(unsigned long index) const { return boundaryComponents[index]; } inline bool NComponent::isIdeal() const { return ideal; } inline bool NComponent::isOrientable() const { return orientable; } inline bool NComponent::isClosed() const { return (boundaryComponents.empty()); } } // namespace regina #endif regina-4.95/engine/triangulation/nedge.cpp000644 000765 000024 00000006526 12234011536 020530 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/nedge.h" namespace regina { const int edgeNumber[4][4] = { { -1, 0, 1, 2 }, { 0,-1, 3, 4 }, { 1, 3,-1, 5 }, { 2, 4, 5,-1 }}; const int edgeStart[6] = { 0, 0, 0, 1, 1, 2 }; const int edgeEnd[6] = { 1, 2, 3, 2, 3, 3 }; const int NEdge::edgeNumber[4][4] = { { -1, 0, 1, 2 }, { 0,-1, 3, 4 }, { 1, 3,-1, 5 }, { 2, 4, 5,-1 }}; const int NEdge::edgeVertex[6][2] = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 }, { 2, 3 }}; const NPerm4 NEdge::ordering[6] = { NPerm4(0, 1, 2, 3), NPerm4(0, 2, 3, 1), NPerm4(0, 3, 1, 2), NPerm4(1, 2, 0, 3), NPerm4(1, 3, 2, 0), NPerm4(2, 3, 0, 1) }; void NEdge::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << "Appears as:" << std::endl; std::deque::const_iterator it; for (it = embeddings.begin(); it != embeddings.end(); ++it) out << " " << it->getTetrahedron()->markedIndex() << " (" << it->getVertices().trunc2() << ')' << std::endl; } } // namespace regina regina-4.95/engine/triangulation/nedge.h000644 000765 000024 00000043044 12234011536 020171 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nedge.h * \brief Deals with edges in a triangulation. */ #ifndef __NEDGE_H #ifndef __DOXYGEN #define __NEDGE_H #endif #include #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class NBoundaryComponent; class NComponent; class NTetrahedron; class NTriangulation; class NVertex; /** * \weakgroup triangulation * @{ */ /** * edgeNumber[i][j] is the number of the edge linking vertices * i and j in a tetrahedron. i and j * must be between 0 and 3 inclusive and may be given in any order. * The resulting edge number will be between 0 and 5 inclusive. * * Note that edge numbers of opposite edges will always add to 5. * * \deprecated This array has been replaced with the identical array * NEdge::edgeNumber. Users are advised to switch to NEdge::edgeNumber * instead, since the old regina::edgeNumber will eventually be removed * in some future version of Regina. */ REGINA_API extern const int edgeNumber[4][4]; /** * edgeStart[k] is the vertex of a tetrahedron at which edge * k of the tetrahedron begins. k must be between 0 and 5 * inclusive. The resulting vertex number will be between 0 and 3 inclusive. * * Note that edge numbers of opposite edges will always add to 5. * You are guaranteed that edgeStart[e] will always be smaller * than edgeEnd[e]. * * \deprecated This array has been superceded by NEdge::edgeVertex * (where edgeStart[i] is now NEdge::edgeVertex[i][0]). * Users are advised to switch to NEdge::edgeVertex instead, since the old * regina::edgeStart and regina::edgeEnd will eventually be removed in some * future version of Regina. */ REGINA_API extern const int edgeStart[6]; /** * edgeEnd[k] is the vertex of a tetrahedron * at which edge k of the tetrahedron ends. * k must be between 0 and 5 inclusive. * The resulting vertex number will be between 0 and 3 inclusive. * * Note that edge numbers of opposite edges will always add to 5. * You are guaranteed that edgeStart[e] will always be smaller * than edgeEnd[e]. * * \deprecated This array has been superceded by NEdge::edgeVertex * (where edgeEnd[i] is now NEdge::edgeVertex[i][1]). * Users are advised to switch to NEdge::edgeVertex instead, since the old * regina::edgeStart and regina::edgeEnd will eventually be removed in some * future version of Regina. */ REGINA_API extern const int edgeEnd[6]; /** * Details how an edge in the skeleton forms part of an individual * tetrahedron. */ class REGINA_API NEdgeEmbedding { private: NTetrahedron* tetrahedron; /**< The tetrahedron in which this edge is contained. */ int edge; /**< The edge number of the tetrahedron that is this edge. */ public: /** * Default constructor. The embedding descriptor created is * unusable until it has some data assigned to it using * operator =. * * \ifacespython Not present. */ NEdgeEmbedding(); /** * Creates an embedding descriptor containing the given data. * * @param newTet the tetrahedron in which this edge is * contained. * @param newEdge the edge number of \a newTet that is this edge. */ NEdgeEmbedding(NTetrahedron* newTet, int newEdge); /** * Creates an embedding descriptor containing the same data as * the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ NEdgeEmbedding(const NEdgeEmbedding& cloneMe); /** * Assigns to this embedding descriptor the same data as is * contained in the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ NEdgeEmbedding& operator =(const NEdgeEmbedding& cloneMe); /** * Returns the tetrahedron in which this edge is contained. * * @return the tetrahedron. */ NTetrahedron* getTetrahedron() const; /** * Returns the edge number within getTetrahedron() that is * this edge. * * @return the edge number that is this edge. */ int getEdge() const; /** * Returns a mapping from vertices (0,1) of this edge to the * corresponding vertex numbers in getTetrahedron(). This * permutation also maps (2,3) to the two remaining tetrahedron * vertices in a manner that preserves orientation as you walk * around the edge. See NTetrahedron::getEdgeMapping() for details. * * @return a mapping from the vertices of this edge to the * vertices of getTetrahedron(). */ NPerm4 getVertices() const; }; /** * Represents an edge in the skeleton of a triangulation. * Edges are highly temporary; once a triangulation changes, all its * edge objects will be deleted and new ones will be created. */ class REGINA_API NEdge : public ShareableObject, public NMarkedElement { public: /** * A table that maps vertices of a tetrahedron to edge numbers. * * Edges in a tetrahedron are numbered 0,...,5. This table * converts vertices to edge numbers; in particular, the edge * joining vertices \a i and \a j of a tetrahedron is edge * number edgeNumber[i][j]. Here \a i and \a j must be * distinct, must be between 0 and 3 inclusive, and may be given * in any order. The resulting edge number will be between 0 and 5 * inclusive. * * Note that edge \a i is always opposite edge \a 5-i in a * tetrahedron. * * For reference, Regina assigns edge numbers in lexicographical * order. That is, edge 0 joins vertices 0 and 1, edge 1 joins * vertices 0 and 2, edge 2 joins vertices 0 and 3, and so on. * * This is identical to the old regina::edgeNumber global array. * Users are advised to use this NEdge::edgeNumber array instead, * since the global regina::edgeNumber is deprecated and will * eventually be removed in some future version of Regina. */ static const int edgeNumber[4][4]; /** * A table that maps edges of a tetrahedron to vertex numbers. * * Edges in a tetrahedron are numbered 0,...,5. This table * converts edge numbers to vertices; in particular, edge \a i * in a tetrahedron joins vertices edgeVertex[i][0] and * edgeVertex[i][1]. Here \a i must be bewteen 0 and 5 * inclusive; the resulting vertex numbers will be between 0 and 3 * inclusive. * * Note that edge \a i is always opposite edge \a 5-i in a tetrahedron. * It is guaranteed that edgeVertex[i][0] will always * be smaller than edgeVertex[i][1]. * * This is a combination of the old regina::edgeStart and * regina::edgeEnd global arrays (where * edgeVertex[i][0] == edgeStart[i] and * edgeVertex[i][1] == edgeEnd[i]). Users are advised * to use this NEdge::edgeVertex array instead, since the global * regina::edgeStart and regina::edgeEnd arrays are deprecated * and will eventually be removed in some future version of Regina. */ static const int edgeVertex[6][2]; /** * An array that maps edge numbers within a tetrahedron to the * canonical ordering of the individual tetrahedron vertices * that form each edge. * * This means that the vertices of edge \a i in a tetrahedron * are, in canonical order, ordering[i][0,1]. The * images ordering[i][2,3] are chosen to make each * permutation even. * * This table does \e not describe the mapping from specific * triangulation edges into individual tetrahedra (for that, * see NTetrahedron::getEdgeMapping() instead). This table * merely provides a neat and consistent way of listing the * vertices of any given tetrahedron edge. * * This lookup table replaces the deprecated routine * regina::edgeOrdering(). */ static const NPerm4 ordering[6]; private: std::deque embeddings; /**< A list of descriptors telling how this edge forms a part of each individual tetrahedron that it belongs to. */ NComponent* component; /**< The component that this edge is a part of. */ NBoundaryComponent* boundaryComponent; /**< The boundary component that this edge is a part of, or 0 if this edge is internal. */ bool valid; /**< Is this edge valid? */ public: /** * Default destructor. */ ~NEdge(); /** * Returns the list of descriptors detailing how this edge forms a * part of various tetrahedra in the triangulation. * Note that if this edge represents multiple edges of a * particular tetrahedron, then there will be multiple embedding * descriptors in the list regarding that tetrahedron. * * These embedding descriptors will be stored in order in the * list, so that if you run through the list and follow in turn * the edges of each tetrahedron defined by the images of (2,3) * under NEdgeEmbedding::getVertices(), then you will obtain an * ordered chain circling this edge. * * \ifacespython This routine returns a python list. * * @return the list of embedding descriptors. * @see NEdgeEmbedding */ const std::deque& getEmbeddings() const; /** * Returns the number of descriptors in the list returned by * getEmbeddings(). Note that this is identical to getDegree(). * * @return the number of embedding descriptors. */ unsigned long getNumberOfEmbeddings() const; /** * Returns the requested descriptor from the list returned by * getEmbeddings(). * * @param index the index of the requested descriptor. This * should be between 0 and getNumberOfEmbeddings()-1 inclusive. * @return the requested embedding descriptor. */ const NEdgeEmbedding& getEmbedding(unsigned long index) const; /** * Returns the triangulation to which this edge belongs. * * @return the triangulation containing this edge. */ NTriangulation* getTriangulation() const; /** * Returns the component of the triangulation to which this * edge belongs. * * @return the component containing this edge. */ NComponent* getComponent() const; /** * Returns the boundary component of the triangulation to which * this edge belongs. * * @return the boundary component containing this edge, or 0 if this * edge does not lie entirely within the boundary of the triangulation. */ NBoundaryComponent* getBoundaryComponent() const; /** * Returns the vertex of the triangulation that corresponds * to the given vertex of this edge. * * @param vertex the vertex of this edge to examine. This should * be 0 or 1. * @return the corresponding vertex of the triangulation. */ NVertex* getVertex(int vertex) const; /** * Returns the degree of this edge. Note that this is identical * to getNumberOfEmbeddings(). * * @return the degree of this edge. */ unsigned long getDegree() const; /** * Determines if this edge lies entirely on the boundary of the * triangulation. * * @return \c true if and only if this edge lies on the boundary. */ bool isBoundary() const; /** * Determines if this edge is valid. * An edge is valid if and only if it is not glued to itself * in reverse. * * @return \c true if and only if this edge is valid. */ bool isValid() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new edge and marks it as belonging to the * given triangulation component. * * @param myComponent the triangulation component to which this * edge belongs. */ NEdge(NComponent* myComponent); friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/ntetrahedron.h" namespace regina { // Inline functions for NEdge inline NEdge::NEdge(NComponent* myComponent) : component(myComponent), boundaryComponent(0), valid(true) { } inline NEdge::~NEdge() { } inline NTriangulation* NEdge::getTriangulation() const { return embeddings.front().getTetrahedron()->getTriangulation(); } inline NComponent* NEdge::getComponent() const { return component; } inline NBoundaryComponent* NEdge::getBoundaryComponent() const { return boundaryComponent; } inline NVertex* NEdge::getVertex(int vertex) const { return embeddings.front().getTetrahedron()->getVertex( embeddings.front().getVertices()[vertex]); } inline unsigned long NEdge::getDegree() const { return embeddings.size(); } inline bool NEdge::isBoundary() const { return (boundaryComponent != 0); } inline bool NEdge::isValid() const { return valid; } inline const std::deque & NEdge::getEmbeddings() const { return embeddings; } inline unsigned long NEdge::getNumberOfEmbeddings() const { return embeddings.size(); } inline const NEdgeEmbedding& NEdge::getEmbedding(unsigned long index) const { return embeddings[index]; } inline void NEdge::writeTextShort(std::ostream& out) const { out << (isBoundary() ? "Boundary " : "Internal ") << "edge of degree " << getNumberOfEmbeddings(); } inline NEdgeEmbedding::NEdgeEmbedding() : tetrahedron(0) { } inline NEdgeEmbedding::NEdgeEmbedding(const NEdgeEmbedding& cloneMe) : tetrahedron(cloneMe.tetrahedron), edge(cloneMe.edge) { } inline NEdgeEmbedding::NEdgeEmbedding(NTetrahedron* newTet, int newEdge) : tetrahedron(newTet), edge(newEdge) { } inline NEdgeEmbedding& NEdgeEmbedding::operator = (const NEdgeEmbedding& cloneMe) { tetrahedron = cloneMe.tetrahedron; edge = cloneMe.edge; return *this; } inline NTetrahedron* NEdgeEmbedding::getTetrahedron() const { return tetrahedron; } inline int NEdgeEmbedding::getEdge() const { return edge; } inline NPerm4 NEdgeEmbedding::getVertices() const { return tetrahedron->getEdgeMapping(edge); } } // namespace regina #endif regina-4.95/engine/triangulation/nexampletriangulation.cpp000644 000765 000024 00000031757 12234011536 024064 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "split/nsignature.h" #include "triangulation/nexampletriangulation.h" #include "triangulation/ntriangulation.h" namespace { static const int poincareAdj[5][4] = { { 1, 2, 3, 4}, { 0, 2, 4, 3}, { 0, 1, 3, 4}, { 0, 1, 4, 2}, { 0, 1, 2, 3} }; static const int poincareGluings[5][4][4] = { { { 0, 3, 2, 1 }, { 3, 0, 1, 2 }, { 3, 2, 0, 1 }, { 3, 1, 2, 0 } }, { { 0, 3, 2, 1 }, { 2, 1, 0, 3 }, { 2, 3, 1, 0 }, { 2, 0, 3, 1 } }, { { 1, 2, 3, 0 }, { 2, 1, 0, 3 }, { 1, 2, 3, 0 }, { 3, 0, 1, 2 } }, { { 2, 3, 1, 0 }, { 1, 3, 0, 2 }, { 1, 2, 3, 0 }, { 3, 0, 1, 2 } }, { { 3, 1, 2, 0 }, { 3, 2, 0, 1 }, { 1, 2, 3, 0 }, { 3, 0, 1, 2 } } }; static const int weeksAdj[9][4] = { { 0, 0, 1, 2}, { 0, 3, 4, 5}, { 0, 3, 4, 6}, { 1, 2, 5, 7}, { 1, 2, 7, 8}, { 1, 3, 6, 8}, { 2, 5, 8, 7}, { 3, 4, 8, 6}, { 4, 5, 6, 7} }; static const int weeksGluings[9][4][4] = { { { 1, 2, 3, 0 }, { 3, 0, 1, 2 }, { 3, 2, 0, 1 }, { 2, 3, 1, 0 } }, { { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 1, 3, 0, 2 }, { 2, 3, 1, 0 } }, { { 3, 2, 0, 1 }, { 0, 1, 3, 2 }, { 0, 2, 1, 3 }, { 1, 2, 3, 0 } }, { { 1, 0, 2, 3 }, { 0, 1, 3, 2 }, { 2, 3, 1, 0 }, { 2, 3, 1, 0 } }, { { 2, 0, 3, 1 }, { 0, 2, 1, 3 }, { 0, 2, 1, 3 }, { 2, 3, 1, 0 } }, { { 3, 2, 0, 1 }, { 3, 2, 0, 1 }, { 0, 2, 1, 3 }, { 3, 2, 0, 1 } }, { { 3, 0, 1, 2 }, { 0, 2, 1, 3 }, { 1, 0, 2, 3 }, { 2, 1, 0, 3 } }, { { 3, 2, 0, 1 }, { 0, 2, 1, 3 }, { 1, 2, 3, 0 }, { 2, 1, 0, 3 } }, { { 3, 2, 0, 1 }, { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 3, 0, 1, 2 } } }; static const int closedOrHypAdj[9][4] = { { 6, 8, 2, 8 }, { 6, 8, 3, 7 }, { 7, 0, 3, 4 }, { 1, 5, 5, 2 }, { 2, 6, 5, 7 }, { 3, 8, 3, 4 }, { 0, 4, 7, 1 }, { 1, 4, 2, 6 }, { 1, 0, 5, 0 } }; static const int closedOrHypGluings[9][4][4] = { { { 0, 1, 3, 2 }, { 3, 1, 2, 0 }, { 0, 2, 1, 3 }, { 0, 2, 1, 3 } }, { { 3, 1, 2, 0 }, { 1, 0, 2, 3 }, { 3, 2, 0, 1 }, { 2, 3, 1, 0 } }, { { 2, 0, 3, 1 }, { 0, 2, 1, 3 }, { 0, 1, 3, 2 }, { 3, 1, 2, 0 } }, { { 2, 3, 1, 0 }, { 3, 2, 0, 1 }, { 2, 1, 0, 3 }, { 0, 1, 3, 2 } }, { { 3, 1, 2, 0 }, { 0, 1, 3, 2 }, { 0, 1, 3, 2 }, { 3, 2, 0, 1 } }, { { 2, 1, 0, 3 }, { 0, 2, 1, 3 }, { 2, 3, 1, 0 }, { 0, 1, 3, 2 } }, { { 0, 1, 3, 2 }, { 0, 1, 3, 2 }, { 0, 1, 3, 2 }, { 3, 1, 2, 0 } }, { { 3, 2, 0, 1 }, { 2, 3, 1, 0 }, { 1, 3, 0, 2 }, { 0, 1, 3, 2 } }, { { 1, 0, 2, 3 }, { 3, 1, 2, 0 }, { 0, 2, 1, 3 }, { 0, 2, 1, 3 } } }; static const int closedNorHypAdj[11][4] = { { 8, 2, 8, 2 }, { 5, 3, 2, 9 }, { 1, 4, 0, 0 }, { 6, 1, 4, 6 }, { 10, 2, 10, 3 }, { 7, 7, 6, 1 }, { 8, 3, 3, 5 }, { 5, 9, 8, 5 }, { 0, 0, 6, 7 }, { 10, 10, 1, 7 }, { 9, 4, 4, 9 } }; static const int closedNorHypGluings[11][4][4] = { { { 1, 3, 2, 0 }, { 0, 3, 2, 1 }, { 2, 1, 0, 3 }, { 3, 1, 0, 2 } }, { { 3, 0, 1, 2 }, { 3, 1, 0, 2 }, { 2, 1, 0, 3 }, { 1, 0, 3, 2 } }, { { 2, 1, 0, 3 }, { 3, 1, 2, 0 }, { 2, 1, 3, 0 }, { 0, 3, 2, 1 } }, { { 2, 1, 3, 0 }, { 2, 1, 3, 0 }, { 2, 0, 3, 1 }, { 0, 3, 2, 1 } }, { { 2, 1, 0, 3 }, { 3, 1, 2, 0 }, { 3, 2, 1, 0 }, { 1, 3, 0, 2 } }, { { 3, 1, 2, 0 }, { 1, 0, 3, 2 }, { 0, 1, 3, 2 }, { 1, 2, 3, 0 } }, { { 2, 1, 0, 3 }, { 0, 3, 2, 1 }, { 3, 1, 0, 2 }, { 0, 1, 3, 2 } }, { { 1, 0, 3, 2 }, { 0, 3, 2, 1 }, { 0, 1, 3, 2 }, { 3, 1, 2, 0 } }, { { 2, 1, 0, 3 }, { 3, 0, 2, 1 }, { 2, 1, 0, 3 }, { 0, 1, 3, 2 } }, { { 3, 1, 2, 0 }, { 2, 0, 1, 3 }, { 1, 0, 3, 2 }, { 0, 3, 2, 1 } }, { { 1, 2, 0, 3 }, { 3, 2, 1, 0 }, { 2, 1, 0, 3 }, { 3, 1, 2, 0 } } }; static const int whiteheadAdj[4][4] = { { 3, 2, 1, 3}, { 3, 2, 2, 0}, { 1, 3, 0, 1}, { 2, 0, 0, 1} }; static const int whiteheadGluings[4][4][4] = { { { 2, 3, 1, 0 }, { 3, 2, 0, 1 }, { 0, 1, 3, 2 }, { 3, 2, 0, 1 } }, { { 3, 2, 0, 1 }, { 2, 3, 1, 0 }, { 3, 2, 0, 1 }, { 0, 1, 3, 2 } }, { { 2, 3, 1, 0 }, { 1, 0, 2, 3 }, { 2, 3, 1, 0 }, { 3, 2, 0, 1 } }, { { 1, 0, 2, 3 }, { 2, 3, 1, 0 }, { 3, 2, 0, 1 }, { 2, 3, 1, 0 } } }; } namespace regina { NTriangulation* NExampleTriangulation::threeSphere() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("3-sphere"); ans->insertLayeredLensSpace(1, 0); return ans; } NTriangulation* NExampleTriangulation::bingsHouse() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Bing's house with two rooms"); NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(0, r, NPerm4(0, 1)); s->joinTo(0, s, NPerm4(0, 1)); r->joinTo(2, s, NPerm4(3, 1, 2, 0)); s->joinTo(3, r, NPerm4(2, 1, 0, 3)); return ans; } NTriangulation* NExampleTriangulation::s2xs1() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("S2 x S1"); ans->insertLayeredLensSpace(0, 1); return ans; } NTriangulation* NExampleTriangulation::rp2xs1() { // Section 3.5.1 of Benjamin Burton's PhD thesis describes how to // construct RP^2 x S^1 by identifying the boundary triangles of a // solid Klein bottle. NTriangulation* ans = solidKleinBottle(); ans->setPacketLabel("RP2 x S1"); NTetrahedron* r = ans->getTetrahedron(0); NTetrahedron* t = ans->getTetrahedron(2); r->joinTo(1, t, NPerm4(2, 3, 0, 1)); r->joinTo(3, t, NPerm4(2, 3, 0, 1)); return ans; } NTriangulation* NExampleTriangulation::rp3rp3() { // This can be generated as the enclosing triangulation of a splitting // surface, as described in chapter 4 of Benjamin Burton's PhD thesis. NSignature* sig = NSignature::parse("aabccd.b.d"); NTriangulation* ans = sig->triangulate(); ans->setPacketLabel("RP3 # RP3"); delete sig; return ans; } NTriangulation* NExampleTriangulation::lens8_3() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("L(8,3)"); ans->insertLayeredLensSpace(8, 3); return ans; } NTriangulation* NExampleTriangulation::poincareHomologySphere() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Poincare homology sphere"); ans->insertConstruction(5, poincareAdj, poincareGluings); return ans; } NTriangulation* NExampleTriangulation::weeks() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Weeks manifold"); ans->insertConstruction(9, weeksAdj, weeksGluings); return ans; } NTriangulation* NExampleTriangulation::weberSeifert() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Weber-Seifert dodecahedral space"); // Bah. Dehydration strings are somewhat impenetrable, // but the alternative is 23 lines of hard-coded tetrahedron gluings. // // This triangulation was constructed by building a 60-tetrahedron // dodecahedron and identifying opposite pentagonal faces with a 3/10 twist, // and then simplifying down to one vertex and 23 tetrahedra. ans->insertRehydration( "xppphocgaeaaahimmnkontspmuuqrsvuwtvwwxwjjsvvcxxjjqattdwworrko"); ans->orient(); return ans; } NTriangulation* NExampleTriangulation::seifertWeber() { // Kept for backward compatibility. // Use the old name in the packet label. NTriangulation* ans = weberSeifert(); ans->setPacketLabel("Seifert-Weber dodecahedral space"); return ans; } NTriangulation* NExampleTriangulation::smallClosedOrblHyperbolic() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Closed orientable hyperbolic 3-manifold"); ans->insertConstruction(9, closedOrHypAdj, closedOrHypGluings); return ans; } NTriangulation* NExampleTriangulation::smallClosedNonOrblHyperbolic() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Closed non-orientable hyperbolic 3-manifold"); ans->insertConstruction(11, closedNorHypAdj, closedNorHypGluings); return ans; } NTriangulation* NExampleTriangulation::lst3_4_7() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Layered solid torus"); ans->insertLayeredSolidTorus(3, 4); return ans; } NTriangulation* NExampleTriangulation::solidKleinBottle() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Solid Klein bottle"); // A three-tetrahedron solid Klein bottle is described in section // 3.5.1 of Benjamin Burton's PhD thesis. NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); NTetrahedron* t = ans->newTetrahedron(); s->joinTo(0, r, NPerm4(0, 1, 2, 3)); s->joinTo(3, r, NPerm4(3, 0, 1, 2)); s->joinTo(1, t, NPerm4(3, 0, 1, 2)); s->joinTo(2, t, NPerm4(0, 1, 2, 3)); return ans; } NTriangulation* NExampleTriangulation::figureEightKnotComplement() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Figure eight knot complement"); // The two-tetrahedron figure eight knot complement is described at // the beginning of chapter 8 of Richard Rannard's PhD thesis. NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); r->joinTo(0, s, NPerm4(1, 3, 0, 2)); r->joinTo(1, s, NPerm4(2, 0, 3, 1)); r->joinTo(2, s, NPerm4(0, 3, 2, 1)); r->joinTo(3, s, NPerm4(2, 1, 0, 3)); return ans; } NTriangulation* NExampleTriangulation::whiteheadLinkComplement() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Whitehead link complement"); ans->insertConstruction(4, whiteheadAdj, whiteheadGluings); return ans; } NTriangulation* NExampleTriangulation::gieseking() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Gieseking manifold"); NTetrahedron* r = ans->newTetrahedron(); r->joinTo(0, r, NPerm4(1, 2, 0, 3)); r->joinTo(2, r, NPerm4(0, 2, 3, 1)); return ans; } NTriangulation* NExampleTriangulation::cuspedGenusTwoTorus() { NTriangulation* ans = new NTriangulation(); ans->setPacketLabel("Cusped genus two solid torus"); // We create this by first constructing an ordinary solid genus two // torus and then converting the real boundary to an ideal vertex. NTetrahedron* r = ans->newTetrahedron(); NTetrahedron* s = ans->newTetrahedron(); NTetrahedron* t = ans->newTetrahedron(); NTetrahedron* u = ans->newTetrahedron(); r->joinTo(0, s, NPerm4()); r->joinTo(1, t, NPerm4(1, 2, 3, 0)); r->joinTo(2, u, NPerm4(1, 0, 3, 2)); s->joinTo(3, t, NPerm4()); t->joinTo(1, u, NPerm4()); ans->finiteToIdeal(); return ans; } } // namespace regina regina-4.95/engine/triangulation/nexampletriangulation.h000644 000765 000024 00000023742 12234011536 023524 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nexampletriangulation.h * \brief Offers several example 3-manifold triangulations as starting * points for testing code or getting used to Regina. */ #ifndef __NEXAMPLETRIANGULATION_H #ifndef __DOXYGEN #define __NEXAMPLETRIANGULATION_H #endif #include "regina-core.h" namespace regina { class NTriangulation; /** * \weakgroup triangulation * @{ */ /** * This class offers routines for constructing sample 3-manifold triangulations * of various types. These triangulations may be useful for testing new * code, or for simply getting a feel for how Regina works. * * The sample triangulations offered here may prove especially useful in * Regina's scripting interface, where working with pre-existing files * is more complicated than in the GUI. * * Note that each of these routines constructs a new triangulation from * scratch. It is up to the caller of each routine to destroy the * triangulation that is returned. */ class REGINA_API NExampleTriangulation { public: /** * \name Closed Triangulations */ /*@{*/ /** * Returns a one-tetrahedron triangulation of the 3-sphere. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* threeSphere(); /** * Returns the two-tetrahedron triangulation of the 3-sphere * that is dual to Bing's house with two rooms. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* bingsHouse(); /** * Returns a two-tetrahedron triangulation of the product space * S^2 x S^1. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* s2xs1(); /** * Returns a three-tetrahedron triangulation of the non-orientable * product space RP^2 x S^1. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* rp2xs1(); /** * Returns a triangulation of the connected sum * RP^3 # RP^3. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* rp3rp3(); /** * Returns the minimal triangulation of the lens space * L(8,3). * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* lens8_3(); /** * Returns the five-tetrahedron triangulation of the * Poincare homology sphere. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* poincareHomologySphere(); /** * Returns a nine-tetrahedron minimal triangulation of the Weeks * manifold. The Weeks manifold is the smallest-volume closed * hyperbolic 3-manifold, with a volume of roughly 0.9427. * Note that there are nine minimal triangulations of the Weeks * manifold (of course this routine returns just one). * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* weeks(); /** * Returns a one-vertex triangulation of the Weber-Seifert * dodecahedral space. * * This 3-manifold is described in "Die beiden Dodekaederraume", * C. Weber and H. Seifert, Math. Z. 37 (1933), no. 1, 237-253. * The triangulation returned by this routine (with 23 tetrahedra) * is given in "The Weber-Seifert dodecahedral space is non-Haken", * Benjamin A. Burton, J. Hyam Rubinstein and Stephan Tillmann, * Trans. Amer. Math. Soc. 364:2 (2012), pp. 911-932. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* weberSeifert(); /** * Returns a one-vertex triangulation of the Weber-Seifert * dodecahedral space. * * \deprecated This routine is now called weberSeifert(), * for consistency with Weber and Seifert's original paper. * The old name seifertWeber() has been kept for backward * compatibility, but will be removed in a future version of Regina. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* seifertWeber(); /** * Returns the nine-tetrahedron closed orientable hyperbolic * 3-manifold with volume 0.94270736. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* smallClosedOrblHyperbolic(); /** * Returns the eleven-tetrahedron closed non-orientable hyperbolic * 3-manifold with volume 2.02988321. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* smallClosedNonOrblHyperbolic(); /*@}*/ /** * \name Finite Bounded Triangulations */ /*@{*/ /** * Returns the three-tetrahedron layered solid torus * LST(3,4,7). * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* lst3_4_7(); /** * Returns a triangulation of the solid Klein bottle. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* solidKleinBottle(); /*@}*/ /** * \name Ideal Triangulations */ /*@{*/ /** * Returns a two-tetrahedron ideal triangulation of the figure * eight knot complement. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* figureEightKnotComplement(); /** * Returns a four-tetrahedron ideal triangulation of the * Whitehead link complement. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* whiteheadLinkComplement(); /** * Returns the one-tetrahedron ideal triangulation of the * non-orientable Gieseking manifold. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* gieseking(); /** * Returns a triangulation of a solid genus two torus with a * cusped boundary. This triangulation has one internal finite * vertex and one genus two ideal vertex. * * @return a newly constructed triangulation, which must be * destroyed by the caller of this routine. */ static NTriangulation* cuspedGenusTwoTorus(); /*@}*/ }; /*@}*/ } // namespace regina #endif regina-4.95/engine/triangulation/nface.h000644 000765 000024 00000005772 12234011536 020171 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nface.h * \brief Deprecated header. */ #warning This header is deprecated; please use ntriangle.h instead. #include "triangulation/ntriangle.h" /** * A legacy typedef provided for backward compatibility only. * * \deprecated The old NFace class has been renamed to NTriangle. * This typedef is provided for backward compatibility, and will be removed * in some future version of Regina. */ typedef NTriangle NFace; /** * A legacy typedef provided for backward compatibility only. * * \deprecated The old NFaceEmbedding class has been renamed to * NTriangleEmbedding. This typedef is provided for backward compatibility, * and will be removed in some future version of Regina. */ typedef NTriangleEmbedding NFaceEmbedding; regina-4.95/engine/triangulation/nfacepair.cpp000644 000765 000024 00000006342 12234011536 021372 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/nfacepair.h" namespace regina { NFacePair::NFacePair(int newFirst, int newSecond) { if (newFirst < newSecond) { first = newFirst; second = newSecond; } else { first = newSecond; second = newFirst; } } NFacePair NFacePair::complement() const { if (first > 1) return NFacePair(0, 1); else if (first == 1) return (second == 2 ? NFacePair(0, 3) : NFacePair(0, 2)); else if (second == 1) return NFacePair(2, 3); else if (second == 2) return NFacePair(1, 3); else return NFacePair(1, 2); } void NFacePair::operator ++ (int) { if (second < 3) second++; else if (first < 3) { first++; if (first < 3) second = first + 1; } } void NFacePair::operator -- (int) { if (second > first + 1) second--; else if (first > 0) { first--; second = 3; } else second = 0; } } // namespace regina regina-4.95/engine/triangulation/nfacepair.h000644 000765 000024 00000022721 12234011536 021036 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nfacepair.h * \brief Deals with simple pairs of face numbers. */ #ifndef __NFACEPAIR_H #ifndef __DOXYGEN #define __NFACEPAIR_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup triangulation * @{ */ /** * Represents a pair of tetrahedron face numbers. * * Note that we are not storing triangle numbers in a triangulation * skeleton, but merely face numbers 0, 1, 2 and 3 in a single * tetrahedron. Thus there are only six possible face pairs; this class * merely makes working with such pairs more convenient. * * As well as providing the six possible face pairs, there is also a * before-the-start and a past-the-end value; this (combined with the * increment operator) allows for iteration through face pairs. * * When iterating through face pairs, the ordering will be the * lexicographical ordering (0,1), (0,2), (0,3), (1,2), (1,3), (2,3). * * The before-the-start and past-the-end values are internally * represented as (0,0) and (3,3) respectively. */ class REGINA_API NFacePair { private: unsigned first; /**< The smaller of the two faces in this pair. */ unsigned second; /**< The larger of the two faces in this pair. */ public: /** * Creates a new face pair (0,1). */ NFacePair(); /** * Creates a new face pair from the two given face numbers. * The two given numbers need not be in any particular order. * * \pre The two given face numbers must be distinct integers * between 0 and 3 inclusive. * * @param newFirst the first face number in the new pair. * @param newSecond the second face number in the new pair. */ NFacePair(int newFirst, int newSecond); /** * Creates a new face pair that is a clone of the given pair. * * @param cloneMe the face pair to clone. */ NFacePair(const NFacePair& cloneMe); /** * Returns the smaller of the two face numbers in this pair. * * \pre This face pair is neither before-the-start or * past-the-end. * * @return the lower face number. */ unsigned lower() const; /** * Returns the larger of the two face numbers in this pair. * * \pre This face pair is neither before-the-start or * past-the-end. * * @return the upper face number. */ unsigned upper() const; /** * Determines if this face pair represents a before-the-start * value. * * @return \c true if and only if this face pair is * before-the-start. */ bool isBeforeStart() const; /** * Determines if this face pair represents a past-the-end * value. * * @return \c true if and only if this face pair is * past-the-end. */ bool isPastEnd() const; /** * Returns the complement of this face pair. The complement * is the pair containing the two faces not included in this * face pair. * * \pre This face pair is neither before-the-start nor * past-the-end. * * @return the complement of this face pair. */ NFacePair complement() const; /** * Sets this face pair to be a copy of the given pair. * * @param cloneMe the face pair to clone. * @return a reference to this face pair. */ NFacePair& operator = (const NFacePair& cloneMe); /** * Determines if this and the given face pair are equal. * * @param other the pair to compare with this. * @return \c true if and only if this and the given pair are equal. */ bool operator == (const NFacePair& other) const; /** * Determines if this is less than the given face pair. * * @param other the pair to compare with this. * @return \c true if and only if this is less than \a other. */ bool operator < (const NFacePair& other) const; /** * Determines if this is greater than the given face pair. * * @param other the pair to compare with this. * @return \c true if and only if this is greater than \a other. */ bool operator > (const NFacePair& other) const; /** * Determines if this is less than or equal to the given face pair. * * @param other the pair to compare with this. * @return \c true if and only if this is less than or * equal to \a other. */ bool operator <= (const NFacePair& other) const; /** * Determines if this is greater than or equal to the given face pair. * * @param other the pair to compare with this. * @return \c true if and only if this is greater than or * equal to \a other. */ bool operator >= (const NFacePair& other) const; /** * Increments this face pair. It will be set to the following * face pair in the lexicographical ordering, or to a * past-the-end value if there are no more face pairs. * * \ifacespython This routine is called inc(), since Python does * not support the increment operator. */ void operator ++ (int); /** * Decrements this face pair. It will be set to the previous * face pair in the lexicographical ordering, or to a * before-the-start value if there are no previous face pairs. * * \ifacespython This routine is called dec(), since Python does * not support the decrement operator. */ void operator -- (int); }; /*@}*/ // Inline functions for NFacePair inline NFacePair::NFacePair() : first(0), second(1) { } inline NFacePair::NFacePair(const NFacePair& cloneMe) : first(cloneMe.first), second(cloneMe.second) { } inline unsigned NFacePair::lower() const { return first; } inline unsigned NFacePair::upper() const { return second; } inline bool NFacePair::isBeforeStart() const { return (second <= 0); } inline bool NFacePair::isPastEnd() const { return (first >= 3); } inline NFacePair& NFacePair::operator = (const NFacePair& cloneMe) { first = cloneMe.first; second = cloneMe.second; return *this; } inline bool NFacePair::operator == (const NFacePair& other) const { return (first == other.first && second == other.second); } inline bool NFacePair::operator < (const NFacePair& other) const { if (first < other.first) return true; if (first > other.first) return false; return (second < other.second); } inline bool NFacePair::operator > (const NFacePair& other) const { return (other < *this); } inline bool NFacePair::operator <= (const NFacePair& other) const { if (first < other.first) return true; if (first > other.first) return false; return (second <= other.second); } inline bool NFacePair::operator >= (const NFacePair& other) const { return (other <= *this); } } // namespace regina #endif regina-4.95/engine/triangulation/nfacetspec.h000644 000765 000024 00000004662 12234011536 021225 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nfacetspec.h * \brief Deprecated header. */ #warning This header is deprecated; please use generic/nfacetspec.h instead. #include "generic/nfacetspec.h" regina-4.95/engine/triangulation/ngenericisomorphism.h000644 000765 000024 00000004715 12234011536 023175 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ngenericisomorphism.h * \brief Deprecated header. */ #warning This header is deprecated; please use generic/ngenericisomorphism.h instead. #include "generic/ngenericisomorphism.h" regina-4.95/engine/triangulation/ngenericisomorphism.tcc000644 000765 000024 00000004731 12234011536 023515 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ngenericisomorphism.tcc * \brief Deprecated header. */ #warning This header is deprecated; please use generic/ngenericisomorphism-impl.h instead. #include "generic/ngenericisomorphism-impl.h" regina-4.95/engine/triangulation/nhomologicaldata.cpp000644 000765 000024 00000231374 12236713375 022770 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/matrixops.h" #include "maths/nprimes.h" #include "triangulation/nhomologicaldata.h" #include #include #include #include namespace regina { void NHomologicalData::writeTextShort(std::ostream& out) const { if (mHomology0.get()) { out<<"H_0(M) = "; mHomology0->writeTextShort(out); out<<" "; } if (mHomology1.get()) { out<<"H_1(M) = "; mHomology1->writeTextShort(out); out<<" "; } if (mHomology2.get()) { out<<"H_2(M) = "; mHomology2->writeTextShort(out); out<<" "; } if (mHomology3.get()) { out<<"H_3(M) = "; mHomology3->writeTextShort(out); out<<" "; } if (bHomology0.get()) { out<<"H_0(BM) = "; bHomology0->writeTextShort(out); out<<" "; } if (bHomology1.get()) { out<<"H_1(BM) = "; bHomology1->writeTextShort(out); out<<" "; } if (bHomology2.get()) { out<<"H_2(BM) = "; bHomology2->writeTextShort(out); out<<" "; } if (bmMap0.get()) { out<<"H_0(BM) --> H_0(M) = "; bmMap0->writeTextShort(out); out<<" "; } if (bmMap1.get()) { out<<"H_1(BM) --> H_1(M) = "; bmMap1->writeTextShort(out); out<<" "; } if (bmMap2.get()) { out<<"H_2(BM) --> H_2(M) = "; bmMap2->writeTextShort(out); out<<" "; } if (dmTomMap1.get()) { out<<"PD map = "; dmTomMap1->writeTextShort(out); out<<" "; } if (torsionFormComputed) { out<<"Torsion form rank vector: "<getVertices().begin(); vit != tri->getVertices().end(); vit++) { if (!((*vit)->isIdeal())) sNIV.push_back(i); i++; } // sNIV for (NTriangulation::EdgeIterator eit = tri->getEdges().begin(); eit != tri->getEdges().end(); eit++) { for (i=0;i<2;i++) { if ((*eit)->getVertex(i)->isIdeal()) sIEOE.push_back(2*j+i); } j++; } j=0; // sIEOE for (NTriangulation::TriangleIterator fit = tri->getTriangles().begin(); fit != tri->getTriangles().end(); fit++) { for (i=0;i<3;i++) { if ((*fit)->getVertex(i)->isIdeal()) sIEEOF.push_back(3*j+i); } j++; } j=0; // sIEEOF for (NTriangulation::TetrahedronIterator tit = tri->getTetrahedra().begin(); tit != tri->getTetrahedra().end(); tit++) { for (i=0;i<4;i++) { if ((*tit)->getVertex(i)->isIdeal()) { sIEFOT.push_back(4*j+i); } } j++; } j=0;// sIEFOT for (NTriangulation::VertexIterator vit = tri->getVertices().begin(); vit != tri->getVertices().end(); vit++) // dNINBV {if ((!((*vit)->isIdeal())) && (!((*vit)->isBoundary()))) dNINBV.push_back(j); j++; } j=0; for (NTriangulation::EdgeIterator eit = tri->getEdges().begin(); eit != tri->getEdges().end(); eit++) { if (!((*eit)->isBoundary())) dNBE.push_back(j); j++; } j=0; // dNBE for (NTriangulation::TriangleIterator fit = tri->getTriangles().begin(); fit != tri->getTriangles().end(); fit++) { if (!((*fit)->isBoundary())) dNBF.push_back(j); j++; } i=0; // dNBF for (NTriangulation::VertexIterator vit = tri->getVertices().begin(); vit != tri->getVertices().end(); vit++) // sBNIV {if ( (!((*vit)->isIdeal())) && ((*vit)->isBoundary())) sBNIV.push_back(i); i++; } i=0; for (NTriangulation::EdgeIterator eit = tri->getEdges().begin(); eit != tri->getEdges().end(); eit++) // sBNIE {if ((*eit)->isBoundary()) sBNIE.push_back(i); i++; } i=0; for (NTriangulation::TriangleIterator fit = tri->getTriangles().begin(); fit != tri->getTriangles().end(); fit++) // sBNIF {if ((*fit)->isBoundary()) sBNIF.push_back(i); i++; } ccIndexingComputed = true; // standard (0..3)-cells: numStandardCells[0] = sNIV.size() + sIEOE.size(); numStandardCells[1] = tri->getNumberOfEdges() + sIEEOF.size(); numStandardCells[2] = tri->getNumberOfTriangles() + sIEFOT.size(); numStandardCells[3] = tri->getNumberOfTetrahedra(); // dual (0..3)-cells: numDualCells[0] = tri->getNumberOfTetrahedra(); numDualCells[1] = dNBF.size(); numDualCells[2] = dNBE.size(); numDualCells[3] = dNINBV.size(); // boundary (0..2)-cells: numBdryCells[0] = sBNIV.size() + sIEOE.size(); numBdryCells[1] = sBNIE.size() + sIEEOF.size(); numBdryCells[2] = sBNIF.size() + sIEFOT.size(); } void NHomologicalData::computeChainComplexes() { // Only do this if we haven't already done it. if (chainComplexesComputed) return; // Off we go... if (!ccIndexingComputed) computeccIndexing(); chainComplexesComputed = true; // need to convert this so that it does not use tri B0_.reset(new NMatrixInt(1, numDualCells[0])); B1.reset(new NMatrixInt(numDualCells[0], numDualCells[1])); B2.reset(new NMatrixInt(numDualCells[1], numDualCells[2])); B3.reset(new NMatrixInt(numDualCells[2], numDualCells[3])); B4.reset(new NMatrixInt(numDualCells[3], 1)); A0.reset(new NMatrixInt(1, numStandardCells[0])); A1.reset(new NMatrixInt(numStandardCells[0], numStandardCells[1])); A2.reset(new NMatrixInt(numStandardCells[1], numStandardCells[2])); A3.reset(new NMatrixInt(numStandardCells[2], numStandardCells[3])); A4.reset(new NMatrixInt(numStandardCells[3], 1)); H1map.reset(new NMatrixInt(numStandardCells[1], numDualCells[1])); Bd0.reset(new NMatrixInt(1, numBdryCells[0])); Bd1.reset(new NMatrixInt(numBdryCells[0], numBdryCells[1])); Bd2.reset(new NMatrixInt(numBdryCells[1], numBdryCells[2])); Bd3.reset(new NMatrixInt(numBdryCells[2], 1)); B0Incl.reset(new NMatrixInt(numStandardCells[0], numBdryCells[0])); B1Incl.reset(new NMatrixInt(numStandardCells[1], numBdryCells[1])); B2Incl.reset(new NMatrixInt(numStandardCells[2], numBdryCells[2])); long int temp; unsigned long i,j; NPerm4 p1,p2; // This fills out matrix A1 for (i=0;igetNumberOfEdges();i++) { // these are the standard edges temp=sNIV.index(tri->vertexIndex(tri->getEdge(i)->getVertex(0))); (A1->entry( ((temp==(-1)) ? (sNIV.size()+sIEOE.index(2*i)) : temp ), i))-=1; temp=sNIV.index(tri->vertexIndex(tri->getEdge(i)->getVertex(1))); (A1->entry( ((temp==(-1)) ? (sNIV.size()+sIEOE.index(2*i+1)) : temp), i))+=1; } // ok for (i=0;igetTriangle(sIEEOF[i]/3)->getEdgeMapping( (sIEEOF[i] + 1) % 3); if (p1.sign()==1) { A1->entry(sNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )+1), tri->getNumberOfEdges()+i)-=1; } else { A1->entry(sNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )) , tri->getNumberOfEdges()+i)-=1; } p1=tri->getTriangle(sIEEOF[i]/3)->getEdgeMapping( (sIEEOF[i] + 2) % 3); if (p1.sign()==1) { A1->entry(sNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )) , tri->getNumberOfEdges()+i)+=1; } else { A1->entry(sNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )+1) , tri->getNumberOfEdges()+i)+=1; } } // that handles matrix A1. // start filling out A2... for (i=0;igetNumberOfTriangles();i++) { // put boundary edges into A2.. for (j=0;j<6;j++) { // run through the 6 possible boundary edges of the triangle // the first 3 are standard, the last three are the ideal // edges (if they exist) if ( (j/3) == 0) { p1=tri->getTriangle(i)->getEdgeMapping(j % 3); A2->entry( tri->edgeIndex( tri->getTriangle(i)->getEdge(j % 3)) ,i) += ( (p1.sign()==1) ? +1 : -1 ); } else { // check triangle i vertex j % 3 is ideal if (tri->getTriangle(i)->getVertex(j % 3)->isIdeal()) A2->entry( tri->getNumberOfEdges() + sIEEOF.index((3*i) + (j % 3)), i) += 1; } } } for (i=0;igetTetrahedron( sIEFOT[i]/4 )->getTriangleMapping( (sIEFOT[i] + j) % 4); A2->entry( tri->getNumberOfEdges() + sIEEOF.index( 3*tri->triangleIndex(tri->getTetrahedron( sIEFOT[i]/4 )->getTriangle( (sIEFOT[i] + j) % 4)) + p1.preImageOf(sIEFOT[i] % 4) ) , tri->getNumberOfTriangles()+i ) += ( (p1.sign()==1 ? -1 : 1 ) ); } } // end A2 // start A3 for (i=0;igetNumberOfTetrahedra();i++) { for (j=0;j<4;j++) { // first go through standard faces 0 through 3 p1=tri->getTetrahedron(i)->getTriangleMapping(j); A3->entry( tri->triangleIndex( tri->getTetrahedron(i)->getTriangle(j) ), i) += ( (p1.sign()==1) ? 1 : -1 ); // then ideal faces 0 through 3, if they exist if (tri->getTetrahedron(i)->getVertex(j)->isIdeal()==1) { // this part is in error. A3->entry( tri->getNumberOfTriangles() + sIEFOT.index((4*i) + j), i) += 1; } } } // end A3 // start B1: for each dual edge == non-boundary triangle, // find the tetrahedra that bound it for (i=0;ientry( tri->tetrahedronIndex( tri->getTriangle(dNBF[i])->getEmbedding(1).getTetrahedron() ),i)+=1; B1->entry( tri->tetrahedronIndex( tri->getTriangle(dNBF[i])->getEmbedding(0).getTetrahedron() ),i)-=1; } // end B1 // start B2: for each dual triangle == non-boundary edge, // find dual edges it bounds == link of tetrahedra that contain it for (i=0;i& edgeque( tri->getEdge(dNBE[i])->getEmbeddings()); for (j=0;jentry( dNBF.index( tri->triangleIndex( edgeque[j].getTetrahedron()->getTriangle(p1[2]) ) ) ,i)+= ( ( edgeque[j].getTetrahedron() == edgeque[j].getTetrahedron()->getTriangle( p1[2] )->getEmbedding( 0 ).getTetrahedron() && edgeque[j].getTetrahedron()->getTriangle( p1[2] )->getEmbedding( 0 ).getTriangle() == p1[2] ) ? 1 : -1); } } // end B2 std::vector tetor; long int ind1; long int ind2; int k; NEdgeEmbedding tempe; // start B3: for each dual tetrahedron==nonboundary vertex, // find the corresp edges==non-boundary boundary triangles for (i=0;i& vtetlist( tri->getVertex(dNINBV[i])->getEmbeddings()); tetor.resize(vtetlist.size(),0); std::vector > unorientedlist; // This should be the list of unoriented tetrahedra, together // with marked vertices. // Indices into the vector are 4*tetindex + vertex no. // Values are (index into vtetlist, already oriented). unorientedlist.resize(4 * tri->getNumberOfTetrahedra()); for (j=0;jtetrahedronIndex( vtetlist[j].getTetrahedron() ) + vtetlist[j].getVertex() ] = std::make_pair(j, false); } // need to set up a local orientation for the tangent // bundle at the vertex so that we can compare with the // normal orientations of the edges incident. This normal // orientation will have the form of a sign +-1 for each // NVertexEmbedding in the list vtetlist. Our orientation convention // will be chosen so that vtetlist[0] is positively oriented, // ie: tetor[0]==1 always. tetor[0]=1; unorientedlist[ 4*tri->tetrahedronIndex( vtetlist[0].getTetrahedron()) + vtetlist[0].getVertex() ].second = true; size_t stillToOrient = vtetlist.size() - 1; while (stillToOrient > 0) for (j=0;jtetrahedronIndex( vtetlist[j].getTetrahedron() ) + vtetlist[j].getVertex(); if ( unorientedlist[ ind1 ].second ) { // this tetrahedron has been oriented check to see // if any of the adjacent // tetrahedra are unoriented, and if so, orient them. for (k=0;k<4;k++) { if (k!= (ind1 % 4)) { p1=vtetlist[j].getTetrahedron() -> adjacentGluing(k); ind2=4*tri->tetrahedronIndex( vtetlist[j].getTetrahedron() -> adjacentTetrahedron(k) ) + p1[ind1 % 4]; if (! unorientedlist[ ind2 ].second ) { // we have an adjacent unoriented tetrahedron. // we orient it. tetor[ unorientedlist[ind2].first ] = (-1)*tetor[j]*p1.sign(); unorientedlist[ ind2 ].second = true; --stillToOrient; } } } } } // now a local orientation is set up and can compute the boundary. // to do this, it seems best to compile a list of incident edges // which contains their endpoint data and sign. // the list will be a std::set edge_adjacency, // data will be stored as // 4*(edge index) + 2*(endpt index) + sign stored as 0 or 1. std::set edge_adjacency; for (j=0;jgetEdgeMapping(k). preImageOf( vtetlist[j].getVertex() ); if ( ind2<2 ) { // edge k of tetrahedron j, moreover we know that // the vertex of the edge corresponds to ind2 tempe=NEdgeEmbedding( vtetlist[j].getTetrahedron(), k ); // the corresp orientation coming from our local // orientation // plus orienting the edge out of vertex k % 2... p1=tempe.getVertices(); if ( ind2 == 1 ) p1=p1*(NPerm4(0,1)); // now p1 sends 0 to point corresp to v, 1 to point // corresp to end of edge. // if p1.sign() == tetor[j] then sign = +1 otherwise -1. ind1=4*tri->edgeIndex( vtetlist[j].getTetrahedron()->getEdge(k) ) + 2*ind2 + (p1.sign() == tetor[j] ? 1 : 0); // Insertion in std::set is harmless if the key // already exists. edge_adjacency.insert(ind1); } } std::set::const_iterator it; for (it = edge_adjacency.begin(); it != edge_adjacency.end(); ++it) { B3->entry( dNBE.index((*it)/4) , i) += ( ( ((*it) % 2)==0 ) ? 1 : -1 ); } } // end B3 // proceed to fill out H1map // the algorithm will proceed in 2 steps. // step 1) fix once and for all a map from dual 0-cells to regular // 0-cells, the only condition this map needs to satisfy is that the // regular 0-cell associated to a dual 0-cell must be contained in // the same ideal simplex. std::vector zeroCellMap(tri->getNumberOfTetrahedra()); // zeroCellMap[i] describes the vertex of tetrahedra[i] that the dual // 0-cell is sent to. It will be stored as // 4*(vertex number 0,1,2,3) + 0,1,2,3 (equal to prev. number if // non-ideal for (i=0; igetTetrahedron(i)->getVertex(j)->isIdeal()) j++; if (j<4) zeroCellMap[i]=4*j+j; else zeroCellMap[i]=1; } // step 2) fill out the matrix. each dual 1-cell corresponds to a // triangular face of the ideal triangulation. the map of 0-cells has // already been chosen so for the map of 1-cells simply choose any path // from the first 0-cell to the 2nd 0-cell with the condition that // the path stays inside the two ideal simplicies and only crosses // the triangle corresponding to the dual 1-cell once. (and no other // triangles). for (j=0; jcolumns(); j++) // H1map.columns()==dNBF.size() // while H1map.rows() is edges.size()+sIEEOF.size() { // now we have to decide where dual edge j == ideal triangulation // triangle j is sent. unsigned tet0TriIndex = tri->getTriangle(dNBF[j])-> getEmbedding(0).getTriangle(); unsigned tet1TriIndex = tri->getTriangle(dNBF[j])-> getEmbedding(1).getTriangle(); unsigned vert0Num = zeroCellMap[tri->tetrahedronIndex( tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() )]/4; // vertex number of start vertex in tet0 unsigned vert1Num = zeroCellMap[tri->tetrahedronIndex( tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() )]/4; // vertex number of end vertex in tet1. unsigned vert0id = zeroCellMap[tri->tetrahedronIndex( tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() )]%4; // not equal to vert0Num if and only if vert0 is ideal. unsigned vert1id = zeroCellMap[tri->tetrahedronIndex( tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() )]%4; // not equal to vert1Num if and only if vert1 is ideal. NPerm4 P1 = tri->getTriangle(dNBF[j])->getEmbedding(0).getVertices(); NPerm4 P2 = tri->getTriangle(dNBF[j])->getEmbedding(1).getVertices(); NPerm4 P3; // the permutation from the start simplex vertices // to the end simplex. bool stage0nec = false; unsigned long stage0edgeNum = 0; bool stage0posOr = false; unsigned stage0choice = 0; // this indicates the vertex of the simplex // that our chosen edge // with its induced orientation ends... if (vert0Num == tet0TriIndex) // stage 0 { stage0nec = true; if (vert0Num == vert0id) { stage0choice = (tet0TriIndex + 1) % 4; } // not ideal else { stage0choice = vert0id; } // ideal stage0edgeNum = tri->edgeIndex(tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() -> getEdge( NEdge::edgeNumber[vert0Num][stage0choice] )); stage0posOr = ( static_cast(tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[vert0Num][stage0choice])[1]) == stage0choice) ? true : false ; } bool stage4nec = false; // stage 4 unsigned long stage4edgeNum = 0; bool stage4posOr = false; unsigned stage4choice = 0; if (vert1Num == tet1TriIndex) { stage4nec = true; if (vert1Num == vert1id) // the non-ideal case. { stage4choice = (tet1TriIndex + 1) % 4; } // duh, this is all wrong. else { stage4choice = vert1id; } stage4edgeNum = tri->edgeIndex(tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() -> getEdge( NEdge::edgeNumber[vert1Num][stage4choice] )); stage4posOr = ( static_cast(tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[vert1Num][stage4choice])[1]) == vert1Num ) ? true : false ; } // decide if stages 1 and 3 are neccessary... bool stage1nec = false; // stage 1 unsigned stage1v = 0; unsigned stage1vi = 0; unsigned long stage1edgeNum = 0; bool stage1posOr = false; unsigned stage1TriToUse = 0; if (stage0nec && tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() -> getVertex(stage0choice)->isIdeal() ) { stage1v = stage0choice; stage1vi = vert0Num; stage1nec=true; } else if ((!stage0nec) && (vert0Num != vert0id) && (vert0id == tet0TriIndex)) { stage1v = vert0Num; stage1vi = vert0id; stage1nec = true; } if (stage1nec) { // we need to decide which triangle to use... stage1TriToUse = tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[stage1v][tet0TriIndex] )[2]; P3 = tri->getTriangle(dNBF[j])->getEmbedding(0).getTetrahedron()-> getTriangleMapping(stage1TriToUse); stage1edgeNum = tri->getNumberOfEdges() + sIEEOF.index( 3*(tri->triangleIndex(tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() -> getTriangle(stage1TriToUse))) + P3.preImageOf(stage1v) ); stage1posOr = ( ( static_cast( P3[(P3.preImageOf(stage1v)+1) % 3]) != stage1vi ) ? true : false ); } bool stage3nec = false; unsigned stage3v = 0; unsigned stage3vi = 0; unsigned long stage3edgeNum = 0; bool stage3posOr = false; unsigned stage3TriToUse = 0; if (stage4nec && tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() -> getVertex(stage4choice)->isIdeal() ) { // ideal case stage3v = stage4choice; stage3vi = vert1Num; stage3nec=true; } else if ((!stage4nec) && (vert1Num != vert1id) && (vert1id == tet1TriIndex)) { // non-ideal case stage3v = vert1Num; stage3vi = vert1id; stage3nec = true; } if (stage3nec) { // we need to decide which triangle to use... stage3TriToUse = tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[stage3v][tet1TriIndex] )[2]; P3 = tri->getTriangle(dNBF[j])->getEmbedding(1).getTetrahedron()-> getTriangleMapping(stage3TriToUse); stage3edgeNum = tri->getNumberOfEdges() + sIEEOF.index( 3*(tri->triangleIndex(tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() -> getTriangle(stage3TriToUse))) + P3.preImageOf(stage3v) ); stage3posOr = ( ( static_cast( P3[(P3.preImageOf(stage3v)+1) % 3]) == stage3vi ) ? true : false ); } unsigned stage2startdata = 0; unsigned stage2enddata = 0; // 3*vertex number(0,1,2) + another vertex number (0,1,2) // these are the same indicates the vertex is non-ideal // these are different indicates the vertex is ideal and dir // of relevant point.. if (stage1nec) // set up stage2startdata { stage2startdata = 3*P1.preImageOf( stage1v ) + P1.preImageOf((tri->getTriangle(dNBF[j]) -> getEmbedding(0).getTetrahedron() -> getEdgeMapping( NEdge::edgeNumber[stage1v][stage1vi] ))[3] ); } else { // we have to deal with 2 possibilities a) stage 0 was called // and it jumped here, so it is not an ideal vertex. // b) neither stage 0 or 1 was called and this may or may // not be an ideal vertex if (stage0nec) { // this is the non-ideal situation stage2startdata = 3*P1.preImageOf( stage0choice ) + ((P1.preImageOf( stage0choice )+1) % 3); } else { // this is the starting point... back to using vert0 info... if (vert0Num != vert0id) stage2startdata = 3*P1.preImageOf( vert0Num ) + P1.preImageOf( vert0id ); else stage2startdata = 3*P1.preImageOf( vert0Num ) + ((P1.preImageOf( vert0Num ) + 1) % 3); } } if (stage3nec) // set up stage2enddata { stage2enddata = 3*P2.preImageOf( stage3v ) + P2.preImageOf((tri->getTriangle(dNBF[j]) -> getEmbedding(1).getTetrahedron() -> getEdgeMapping( NEdge::edgeNumber[stage3v][stage3vi] ))[3] ); } else { if (stage4nec) { // this is the non-ideal situation stage2enddata = 3*P2.preImageOf( stage4choice ) + ((P2.preImageOf( stage4choice ) + 1) % 3); } else { // this is the starting point... back to using vert1 info... if (vert1Num != vert1id) stage2enddata = 3*P2.preImageOf( vert1Num ) + P2.preImageOf( vert1id ); else stage2enddata = 3*P2.preImageOf( vert1Num ) + ((P2.preImageOf( vert1Num ) + 1) % 3); } } // now cycle through pairs of adjacent vertices on the triangle // and check to see if the corresponding edge is required... unsigned currV = stage2startdata; unsigned prevV = stage2startdata; if (stage2startdata != stage2enddata) while (currV != stage2enddata) { // first, increment currV -- this is a number from the // set { 1, 2, 3, 5, 6, 7 } describing an ideal vertex // of the triangle is triadic 3*vert num + direction... switch (currV) { case 1: currV = 3; break; case 2: currV = 1; break; case 3: currV=5; break; case 5: currV = 7; break; case 6: currV = 2; break; case 7: currV=6; break; } // main alg here. if (( currV/3 == prevV/3 ) && (tri->getTriangle(dNBF[j])-> getVertex(currV/3)->isIdeal()) ) // ideal edge { H1map->entry( tri->getNumberOfEdges() + sIEEOF.index(3*dNBF[j] + (currV/3)) , j ) += 1; } if ( currV/3 != prevV/3 ) // regular edge { H1map->entry(tri->edgeIndex(tri->getTriangle(dNBF[j])->getEdge(((currV/3) + 1) % 3 )), j) += ( ( static_cast( tri->getTriangle(dNBF[j])->getEdgeMapping(((currV/3) + 1) % 3)[1] ) == currV/3 ) ? +1 : -1 ); } // move prevV to be equal to currV. prevV = currV; } // now we fill out the matrix. if (stage0nec) H1map->entry( stage0edgeNum, j ) += ( stage0posOr ? 1 : -1 ); if (stage1nec) H1map->entry( stage1edgeNum, j ) += ( stage1posOr ? 1 : -1 ); if (stage3nec) H1map->entry( stage3edgeNum, j ) += ( stage3posOr ? 1 : -1 ); if (stage4nec) H1map->entry( stage4edgeNum, j ) += ( stage4posOr ? 1 : -1 ); } // This fills out matrix Bd1: rows==sBNIV.size()+sIEOE.size(), // cols==sBNIE.size()+sIEEOF.size() for (i=0;ivertexIndex(tri->getEdge(sBNIE[i])-> getVertex(0))); (Bd1->entry( ((temp==(-1)) ? (sBNIV.size()+2*i) : temp ), i))-=1; temp=sBNIV.index(tri->vertexIndex(tri->getEdge(sBNIE[i])-> getVertex(1))); (Bd1->entry( ((temp==(-1)) ? (sBNIV.size()+2*i+1) : temp), i))+=1; } // ok for (i=0;igetTriangle(sIEEOF[i]/3)->getEdgeMapping( (sIEEOF[i] + 1) % 3); if (p1.sign()==1) { Bd1->entry(sBNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )+1), sBNIE.size()+i)-=1; } else { Bd1->entry(sBNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )) , sBNIE.size()+i)-=1; } p1=tri->getTriangle(sIEEOF[i]/3)->getEdgeMapping( (sIEEOF[i] + 2) % 3); if (p1.sign()==1) { Bd1->entry(sBNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )) , sBNIE.size()+i)+=1; } else { Bd1->entry(sBNIV.size() + sIEOE.index(2*( tri->edgeIndex((tri->getTriangle(sIEEOF[i]/3))-> getEdge(p1[2])) )+1) , sBNIE.size()+i)+=1; } } // that handles matrix Bd1. // start filling out Bd2: rows==sBNIE.size()+sIEEOF.size(), // cols==sBNIF.size()+sIEFOT.size() for (i=0;igetTriangle(sBNIF[i])->getEdgeMapping(j % 3); Bd2->entry( sBNIE.index( tri->edgeIndex(tri->getTriangle( sBNIF[i])->getEdge(j % 3)) ) ,i) += ( (p1.sign()==1) ? +1 : -1 ); } else { // check triangle i vertex j % 3 is ideal if (tri->getTriangle(sBNIF[i])->getVertex(j % 3)->isIdeal()) Bd2->entry( sBNIF.size() + sIEEOF.index( (3*i) + (j % 3)), i) += 1; } } } for (i=0;igetTetrahedron( sIEFOT[i]/4 )->getTriangleMapping( (sIEFOT[i] + j) % 4); Bd2->entry( sBNIE.size() + sIEEOF.index(3*tri->triangleIndex( tri->getTetrahedron(sIEFOT[i]/4 )->getTriangle( (sIEFOT[i] + j) % 4)) + p1.preImageOf(sIEFOT[i] % 4) ) , sBNIF.size()+i ) += ( (p1.sign()==1 ? -1 : 1 ) ); } } // end Bd2 // fill out b0Incl // boundary 0-cells: for (i=0;icolumns();i++) B0Incl->entry( ( ( i < sBNIV.size()) ? sNIV.index(sBNIV[i]) : sNIV.size() + i - sBNIV.size() ) ,i)+=1; // fill out b1Incl for (i=0;icolumns();i++) // each boundary edge corresponds to a triangulation edge B1Incl->entry( ( ( i < sBNIE.size() ) ? sBNIE[i] : tri->getNumberOfEdges() + i - sBNIE.size() ) ,i)+=1; // fill out b2Incl for (i=0;icolumns();i++) B2Incl->entry( ( ( i < sBNIF.size() ) ? sBNIF[i] : tri->getNumberOfTriangles() + i - sBNIF.size() ) ,i)+=1; } const NMarkedAbelianGroup& NHomologicalData::getHomology(unsigned q) { if (q==0) { if (!mHomology0.get()) { computeChainComplexes(); mHomology0.reset(new NMarkedAbelianGroup(*A0,*A1)); } return *mHomology0; } else if (q==1) { if (!mHomology1.get()) { computeChainComplexes(); mHomology1.reset(new NMarkedAbelianGroup(*A1,*A2)); } return *mHomology1; } else if (q==2) { if (!mHomology2.get()) { computeChainComplexes(); mHomology2.reset(new NMarkedAbelianGroup(*A2,*A3)); } return *mHomology2; } else { // Assume q == 3. This will at least avoid a crash if q lies // outside the required range. if (!mHomology3.get()) { computeChainComplexes(); mHomology3.reset(new NMarkedAbelianGroup(*A3,*A4)); } return *mHomology3; } // the A's should probably be redone as an array of pointers... } const NMarkedAbelianGroup& NHomologicalData::getBdryHomology(unsigned q) { if (q==0) { if (!bHomology0.get()) { computeChainComplexes(); bHomology0.reset(new NMarkedAbelianGroup(*Bd0,*Bd1)); } return *bHomology0; } else if (q==1) { if (!bHomology1.get()) { computeChainComplexes(); bHomology1.reset(new NMarkedAbelianGroup(*Bd1,*Bd2)); } return *bHomology1; } else { // Assume q == 2. This will at least avoid a crash if q lies // outside the required range. if (!bHomology2.get()) { computeChainComplexes(); bHomology2.reset(new NMarkedAbelianGroup(*Bd2,*Bd3)); } return *bHomology2; } } const NMarkedAbelianGroup& NHomologicalData::getDualHomology(unsigned q) { if (q==0) { if (!dmHomology0.get()) { computeChainComplexes(); dmHomology0.reset(new NMarkedAbelianGroup(*B0_,*B1)); } return *dmHomology0; } else if (q==1) { if (!dmHomology1.get()) { computeChainComplexes(); dmHomology1.reset(new NMarkedAbelianGroup(*B1,*B2)); } return *dmHomology1; } else if (q==2) { if (!dmHomology2.get()) { computeChainComplexes(); dmHomology2.reset(new NMarkedAbelianGroup(*B2,*B3)); } return *dmHomology2; } else { // Assume q == 3. This will at least avoid a crash if q lies // outside the required range. if (!dmHomology3.get()) { computeChainComplexes(); dmHomology3.reset(new NMarkedAbelianGroup(*B3,*B4)); } return *dmHomology3; } } void NHomologicalData::computeHomology() { computeChainComplexes(); if (!mHomology0.get()) mHomology0.reset(new NMarkedAbelianGroup(*A0,*A1)); if (!mHomology1.get()) mHomology1.reset(new NMarkedAbelianGroup(*A1,*A2)); if (!mHomology2.get()) mHomology2.reset(new NMarkedAbelianGroup(*A2,*A3)); if (!mHomology3.get()) mHomology3.reset(new NMarkedAbelianGroup(*A3,*A4)); } void NHomologicalData::computeBHomology() { computeChainComplexes(); if (!bHomology0.get()) bHomology0.reset(new NMarkedAbelianGroup(*Bd0,*Bd1)); if (!bHomology1.get()) bHomology1.reset(new NMarkedAbelianGroup(*Bd1,*Bd2)); if (!bHomology2.get()) bHomology2.reset(new NMarkedAbelianGroup(*Bd2,*Bd3)); } void NHomologicalData::computeDHomology() { computeChainComplexes(); if (!dmHomology0.get()) dmHomology0.reset(new NMarkedAbelianGroup(*B0_,*B1)); if (!dmHomology1.get()) dmHomology1.reset(new NMarkedAbelianGroup(*B1,*B2)); if (!dmHomology2.get()) dmHomology2.reset(new NMarkedAbelianGroup(*B2,*B3)); if (!dmHomology3.get()) dmHomology3.reset(new NMarkedAbelianGroup(*B3,*B4)); } const NHomMarkedAbelianGroup& NHomologicalData::getH1CellAp() { if (!dmTomMap1.get()) { computeHomology(); computeDHomology(); dmTomMap1.reset(new NHomMarkedAbelianGroup( *dmHomology1, *mHomology1, *H1map )); } return (*dmTomMap1); } const NHomMarkedAbelianGroup& NHomologicalData::getBdryHomologyMap(unsigned q) { if (q==0) { if (!bmMap0.get()) { computeHomology(); computeBHomology(); bmMap0.reset(new NHomMarkedAbelianGroup( *bHomology0, *mHomology0, *B0Incl )); } return *bmMap0; } else if (q==1) { if (!bmMap1.get()) { computeHomology(); computeBHomology(); bmMap1.reset(new NHomMarkedAbelianGroup( *bHomology1, *mHomology1, *B1Incl )); } return *bmMap1; } else { // Assume q == 2. This will at least avoid a crash if q lies // outside the required range. if (!bmMap2.get()) { computeHomology(); computeBHomology(); bmMap2.reset(new NHomMarkedAbelianGroup( *bHomology2, *mHomology2, *B2Incl )); } return *bmMap2; } } void NHomologicalData::computeBIncl() { computeHomology(); computeBHomology(); if (!bmMap0.get()) bmMap0.reset(new NHomMarkedAbelianGroup( *bHomology0, *mHomology0, *B0Incl)); if (!bmMap1.get()) bmMap1.reset(new NHomMarkedAbelianGroup( *bHomology1, *mHomology1, *B1Incl)); if (!bmMap2.get()) bmMap2.reset(new NHomMarkedAbelianGroup( *bHomology2, *mHomology2, *B2Incl)); } void NHomologicalData::computeTorsionLinkingForm() { // Only do this if we haven't done it already. if (torsionFormComputed) return; // dual h1 --> standard h1 isomorphism: const NHomMarkedAbelianGroup& h1CellAp(getH1CellAp()); // min number of torsion gens: unsigned long niv(dmHomology1->getNumberOfInvariantFactors()); // for holding prime decompositions.: std::vector > tFac; NLargeInteger tI; // step 1: go through H1 of the manifold, take prime power decomposition // of each summand. building primePowerH1Torsion vector and // pTorsionH1Mat matrix... // also, we need to find the 2-chains bounding2c // boundary(bounding2c[i]) = orderinh1(pvList[i])*pvList[i] std::vector< NLargeInteger > tV; // temporary vector for holding dual // cc vectors. std::vector ppList; // prime power list std::vector< std::pair > pPrList; // proper prime power list. std::vector< std::vector > pvList; // list of vectors // the above two lists will have the same length. for each i, // pvList[i] will be a vector in the dual h1 homology chain complex, and // ppList[i] will be its order. unsigned long i, j, k, l; for (i=0; igetInvariantFactor(i); tFac = NPrimes::primePowerDecomp(tI); for (j=0; jgetTorsionRep(i); for (k=0; k > > // Use a list because we are continually inserting items in the middle. typedef std::vector > IndexingPowerVector; typedef std::pair IndexingPrimePair; typedef std::list IndexingList; IndexingList indexing; // indexing[i] is the i-th prime in increasing order, the first bit is // the prime, the 2nd bit is the vector list of powers, the power is an // unsigned long, and its respective index in ppList and pvList is the // 2nd bit... IndexingList::iterator it1, il1; IndexingPowerVector::iterator it2, il2; IndexingPrimePair dummyv; for (i=0; i= comparison. it1 = indexing.begin(); // now run up p until we either get to the end, or // pPrList[i].first >= it1->first il1 = indexing.end(); // the idea is that this while loop will terminate with il1 pointing // to the right insertion location. while ( it1 != indexing.end() ) { if (pPrList[i].first <= it1->first) { il1 = it1; it1 = indexing.end(); } if (it1 != indexing.end()) it1++; } // now do the same for the power... but we have to make a decision // on whether to grow the // indexing or not... we grow the indexing iff il1 == indexing.end() or // (pPrList[i].first > il1->first) if (il1 == indexing.end()) { dummyv.first = pPrList[i].first; dummyv.second.resize(1); dummyv.second[0] = std::make_pair( pPrList[i].second, i ); indexing.insert( il1, dummyv ); } else if (pPrList[i].first < il1->first) { dummyv.first = pPrList[i].first; dummyv.second.resize(1); dummyv.second[0] = std::make_pair( pPrList[i].second, i ); indexing.insert( il1, dummyv ); } else { // NOW we know this prime is already in the list, so we do // the same search for the power... it2 = il1->second.begin(); il2 = il1->second.end(); while ( it2 != il1->second.end() ) { // it2->first is the power, it2->second is the index. if (pPrList[i].second <= it2->first) { il2 = it2; it2 = il1->second.end(); } if (it2 != il1->second.end()) it2++; } il1->second.insert(il2, std::make_pair( pPrList[i].second, i )); } } // step 2: construct dual vectors // for every pvList vector, find corresponding standard vector. NMatrixInt standardBasis( numStandardCells[1], pvList.size() ); const NMatrixInt& dualtostandard(h1CellAp.getDefiningMatrix()); for (i=0; igetN()); NMatrixInt R(ON.columns(),ON.columns()); NMatrixInt Ri(ON.columns(),ON.columns()); NMatrixInt C(ON.rows(),ON.rows()); NMatrixInt Ci(ON.rows(),ON.rows()); smithNormalForm(ON, R, Ri, C, Ci); // boundingMat=R*(divide by ON diag, rescale(C*areboundariesM)) // ---- stepa ----- // ---------------- stepb --- // ----stepc---- // first I guess we need to determine rank of ON? NMatrixInt areboundariesM( standardBasis ); for (i=0; i torsionLinkingFormPresentationMat( pvList.size(), pvList.size() ); NLargeInteger tN,tD,tR; for (i=0; iorientation() is +1 or -1 depending on if the // natural orientation agrees with the manifolds one or not. // // dual orientation of triangle points into some tetrahedron // given by triangle[?]->getEmbedding(0) is an NTriangleEmbedding // triangle[]->getEmbedding(0).getTetrahedron() tet pointer // triangle[]->getEmbedding(0).getVertices() is an NPerm // // triangles[dNBF[k]] is the triangle pointer of the dual 1-cell // boundingMat is vectors in standard 2-complex so it has // the same dimension as the standard 2-cells + ideal 2-cells, // standard ones coming first. // pvList is vectors in dual 1-cells torsionLinkingFormPresentationMat.entry(i,j) += NRational( boundingMat.entry(dNBF[k],i)*pvList[j][k]* NLargeInteger( tri->getTriangle(dNBF[k])->getEmbedding(0). getTetrahedron()->orientation()* tri->getTriangle(dNBF[k])->getEmbedding(0). getVertices().sign() ), ppList[i] ); } tN=torsionLinkingFormPresentationMat.entry(i,j).getNumerator(); tD=torsionLinkingFormPresentationMat.entry(i,j).getDenominator(); tN.divisionAlg(tD,tR); tN = tR.gcd(tD); tR.divByExact(tN); tD.divByExact(tN); torsionLinkingFormPresentationMat.entry(i,j)=NRational(tR,tD); } // Compute indexing.size() just once, since for std::list this might be // a slow operation. unsigned long indexingSize = indexing.size(); h1PrimePowerDecomp.resize(indexingSize); linkingFormPD.resize(indexingSize); for (i=0, it1 = indexing.begin(); it1 != indexing.end(); i++, it1++) { h1PrimePowerDecomp[i].second.resize(it1->second.size()); h1PrimePowerDecomp[i].first = it1->first; for (j=0; jsecond.size(); j++) h1PrimePowerDecomp[i].second[j] = it1->second[j].first; linkingFormPD[i] = new NMatrixRing(it1->second.size(), it1->second.size() ); for (j=0; jsecond.size(); j++) for (k=0; ksecond.size(); k++) linkingFormPD[i]->entry(j,k) = torsionLinkingFormPresentationMat.entry( it1->second[j].second, it1->second[k].second ); } // now we should implement the classification of these forms // due to Seifert, Wall, Burger, Kawauchi, Kojima, Deloup: // this will have 3 parts, first the rank vector will be a list // n1 Z_p1^k1 + ... + nj Z_pj^kj which will be in lexicographically // increasing order: first the p?'s then the k?'s. // the 2nd part will be the 2-torsion sigma-vector: // sigma_k for k=1,2,3,... these are fractions 0/8, ..., 7/8 or infinity. // the 3rd part will be the odd p-torsion Legendre symbol data // this will be in lexicographical increasing order, first // by the prime, then by k \chi_p^k k=1,2,3,... // CLASSIFICATION // step 1: rank vectors (done) // // this will be a std::vector< std::pair< NLargeInteger, // std::vector< unsigned long > > > // rankv[i].first is the prime, and rankv[i].second is the vector which // lists the ranks // ie: if rankv[i].first==3 then rankv[i].second=(0,1,0,2,0,1) means that // there are no copies of Z_3, one copy of Z_9, no copies of Z_27 but two // copies of Z_{3^4}, etc. // std::vector< std::pair< NLargeInteger, // std::vector > > > indexing; // prime , list of (exponents, index) torRankV.resize(indexingSize); // std::vector< std::pair< NLargeInteger, // std::vector< unsigned long > > > torRankV(indexing.size()); // vector which lists the primes and the number of each power... for (i=0, it1 = indexing.begin(); it1 != indexing.end(); i++, it1++) { torRankV[i].first = it1->first; torRankV[i].second.resize( it1->second[it1->second.size()-1].first, 0); for (j=0; jsecond.size(); j++) { // indexing[i].second[j] is a pair (order, index) where the order k // indicates one copy of p^k where p==indexing[i].first. torRankV[i].second[it1->second[j].first-1]++; } } // step 2: KK 2-torsion invariant (need to implement) // *what is a smart way to implement the sigma invariant?* // I guess it should be of the form std::vector< int > // since it is only holding the reps 0,1,2,3,4,5,6,7 and inf. // inf we can represent by -1 or something? or we could use // and NLargeInteger instead. // decide on if there is 2-torsion... NLargeInteger twoPow; static const NRational pi = NRational( NLargeInteger("314159265358979323846264338327950288"), NLargeInteger("100000000000000000000000000000000000") ); std::vector< NLargeInteger > groupV; bool notatend; NRational tSum; unsigned long incind; bool incrun; long double tLD; long double xlD, ylD; std::vector< NLargeInteger > ProperPrimePower; if (h1PrimePowerDecomp.size() > 0) if (h1PrimePowerDecomp[0].first == NLargeInteger(2)) { // there is 2-torsion. now we put together the sigma vector // twoTorSigmaV // first initialize the length of twoTorSigmaV twoTorSigmaV.resize(torRankV[0].second.size()); groupV.resize(h1PrimePowerDecomp[0].second.size(), NLargeInteger("0") ); ProperPrimePower.resize( h1PrimePowerDecomp[0].second.size() ); for (i=0; i > > h1PrimePowerDecomp; // as it's easier to work with. // h1PrimePowerDecomp[0].first == 2 // so we just need to cycle through // h1PrimePowerDecomp[0].second which is an increasing list // of the powers of 2, ie: 2^i... twoPow = NLargeInteger(2); twoPow.raiseToPower(i+1); xlD=0.0; ylD=0.0; // now start the sum through the group. notatend=true; while (notatend) { // compute twoPow * pi * form(x,x), reduce mod 1 then // call doubleApprox() // first we evaluate the form(x,x) for x==groupV. // the form is linkingformPD[0] tSum=NRational::zero; for (j=0; jrows(); j++) for (k=0; kcolumns(); k++) tSum += NRational(groupV[j]*groupV[k])* linkingFormPD[0]->entry(j,k); // reduce mod 1, then turn into a long double and // evaluate cos, sin tN = tSum.getNumerator(); tD = tSum.getDenominator(); tN.divisionAlg(tD,tR); tSum = NRational(twoPow) * pi * NRational( tR, tD ); tLD = tSum.doubleApprox(); // we ignore `inrange' parameter as the number is reduced // mod 1, so either way it is // returning essentially the correct number. xlD = xlD + cos(tLD); ylD = ylD + sin(tLD); // increment the groupV incind=0; incrun=true; // tells while loop to increment at incind while (incrun) { groupV[incind] = (groupV[incind] + NLargeInteger::one) % ProperPrimePower[incind]; if (groupV[incind] == NLargeInteger::zero) { incind++; } else { incrun=false; } if ( (incind == groupV.size()) && (incrun) ) { incrun=false; notatend=false; } } } // this sum is either zero or a multiple of e^{2pi i sigma /8} // and we now we need to determine if (xlD,ylD) is 0 or // nonzero with some sigma*2pi/8 angle... if ( (xlD*xlD)+(ylD*ylD)<0.0000001 ) // this we accept as zero. { twoTorSigmaV[i] = NLargeInteger::infinity; } else { // now we need to determine the sigma angle... // since it's all integer multiples of 2pi/8, we just // need to check for // xld==0, yld<>0, yld==0, xld<>0 and xld/yld=pm1 if ( fabs(xlD) < 0.001*fabs(ylD) ) { if (ylD > 0.0) twoTorSigmaV[i]=2; else twoTorSigmaV[i]=6; } else if ( fabs(ylD) < 0.001*fabs(xlD) ) { if (xlD > 0.0) twoTorSigmaV[i]=0L; else twoTorSigmaV[i]=4; } else if (xlD/ylD > 0.0 ) { if (xlD > 0.0) twoTorSigmaV[i]=1; else twoTorSigmaV[i]=5; } else { if (xlD > 0.0) twoTorSigmaV[i]=7; else twoTorSigmaV[i]=3; } } } } // step 3: Seifert odd p-torsion legendre symbol invariant (done) // to do this I need to add a determinant to NMatrixRing class // this invariant will be expressed as a // std::vector< std::pair< NLargeInteger, std::vector< int > > > // storing the odd prime, list of Legendre symbols -1, 0, 1. // one for each quotient up to p^k where k is the largest order of // p in the torsion subgroup. unsigned long starti=0; if (torRankV.size() > 0) if (torRankV[0].first == NLargeInteger(2)) starti=1; // this ensures we skip the 2-torsion std::vector tempa; unsigned long curri; for (i=starti; i > > torRankV(indexing.size()); // starting at curri ending at torRankV[i].second[j] for (j=0; j entry(k+curri,l+curri)).getNumerator(); tempa.push_back( tempM.det().legendre(torRankV[i].first) ); // legendre symbol, compute and append to tempa // compute determinant. // increment curri curri = curri + torRankV[i].second[j]; // crashes here. } oddTorLegSymV.push_back( make_pair( torRankV[i].first , tempa) ); } // step 4: kk test for: split, hyperbolic, and the embeddability // 2^k-torsion condition. torsionLinkingFormIsSplit=true; torsionLinkingFormIsHyperbolic=true; starti=0; if (torRankV.size() > 0) if (torRankV[0].first == NLargeInteger(2)) starti=1; for (i=0; i > > h1PrimePowerDecomp; // stored as list { (2, (1, 1, 2)), (3, (1, 2, 2, 3)), (5, (1, 1, 2)) } //std::vector< NMatrixRing* > linkingFormPD; for (i=0; ientry(i,i); tN = tRat.getNumerator(); tD = tRat.getDenominator(); tN.divisionAlg(tD,tR); if (tR != NLargeInteger::zero) torsionLinkingFormSatisfiesKKtwoTorCondition=false; } } torsionRankString.assign(""); if (torRankV.size()==0) torsionRankString.append("no torsion"); else for (i=0; iisOrientable()) { torsionSigmaString.assign(""); if (twoTorSigmaV.size()==0) torsionSigmaString.append("no 2-torsion"); else for (i=0; iisOrientable()) { torsionLegendreString.assign(""); if (oddTorLegSymV.size()==0) torsionLegendreString.append("no odd p-torsion"); else for (i=0; igetNumberOfTetrahedra() == 0) { // special-case the empty triangulation embeddabilityString = "Manifold is empty."; } else if (tri->isOrientable()) { // orientable -- we need the torsion linking form computeTorsionLinkingForm(); if (getBdryHomology(0).isTrivial()) { // no boundary : orientable if (torRankV.size()==0) { // no torsion : no boundary, orientable if (tri->knowsThreeSphere() && tri->isThreeSphere()) embeddabilityString = "This manifold is S^3."; else if (getDualHomology(1).isTrivial()) embeddabilityString = "Manifold is a homology 3-sphere."; else embeddabilityString = "No information."; } // no torsion : no boundary, orientable else {// torsion : no boundary, orientable if (!torsionLinkingFormSatisfiesKKtwoTorCondition) embeddabilityString = "This manifold, once-punctured, " "does not embed in a homology 4-sphere."; else if (!torsionLinkingFormIsHyperbolic) embeddabilityString = "Does not embed in homology 4-sphere."; else embeddabilityString = "The torsion linking form is " "of hyperbolic type."; if (getDualHomology(1).getRank()==0) embeddabilityString += " Manifold is a rational " "homology sphere."; } // torsion : no boundary, orientable } // no boundary : orientable else { // boundary : orientable if (torRankV.size()==0) { // orientable with boundary, no torsion. We have no tests // so far for checking if it embeds in a homology 4-sphere // unless we implement the Kojima alexander polynomials. // H1 map check... boundary map has full rank iff embeds in // rational homology 3-sph // boundary map epic iff embeds in homology 3-sphere if (getBdryHomologyMap(1).isEpic()) { embeddabilityString = "Embeds in a homology 3-sphere as a "; if (getBdryHomology(1).getRank() == 2*getBdryHomology(0).getRank()) { if (getBdryHomology(0).getRank()==1) embeddabilityString += "knot complement."; else embeddabilityString += "link complement."; } else { if (getBdryHomology(1).getRank() == 0) embeddabilityString += "ball complement."; else embeddabilityString += "graph complement."; } } else if (getBdryHomologyMap(1).getCokernel().getRank()==0) { embeddabilityString = "Embeds in a rational homology 3-sphere as a "; if (getBdryHomology(1).getRank() == 2*getBdryHomology(0).getRank() ) { if (getBdryHomology(0).getRank()==1) embeddabilityString += "knot complement."; else embeddabilityString += "link complement."; } else { if (getBdryHomology(1).getRank() == 0) embeddabilityString += "ball complement."; else embeddabilityString += "graph complement."; } } else embeddabilityString = "Does not embed in a rational homology 3-sphere."; } // no torsion : boundary, orientable else { // torsion : boundary, orientable if (!torsionLinkingFormSatisfiesKKtwoTorCondition) { // two tor condition not satisfied if (getBdryHomologyMap(1).isEpic()) embeddabilityString = "Embeds in homology 3-sphere " "but not homology 4-sphere."; else if (getBdryHomologyMap(1).getCokernel().getRank()==0) embeddabilityString = "Embeds in rational homology 3-sphere but not " "homology 4-sphere."; else embeddabilityString = "Does not embed in homology 3-sphere, " "nor homology 4-sphere."; } else { // KK twotor condition satisfied... if (getBdryHomologyMap(1).isEpic()) embeddabilityString = "Embeds in homology 3-sphere. " "KK 2-tor condition satisfied."; else if (getBdryHomologyMap(1).getCokernel().getRank()==0) embeddabilityString = "Embeds in rational homology 3-sphere. " "KK 2-tor condition satisfied."; else embeddabilityString = "Does not embed in homology 3-sphere. " "KK 2-tor condition satisfied."; } } // torsion : boundary, orientable } // boundary : orientable } // end orientable else { // triangulation is NOT orientable, therefore can not embed // in any rational homology 3-sphere. So we look at the // orientation cover... NTriangulation orTri(*tri); orTri.makeDoubleCover(); NHomologicalData covHomol(orTri); // break up into two cases, boundary and no boundary... if (covHomol.getBdryHomology(0).isTrivial()) { // no boundary if (covHomol.formIsHyperbolic()) embeddabilityString = "Orientation cover has hyperbolic" " torsion linking form."; else embeddabilityString = "Does not embed in homology 4-sphere."; } else {// boundary if (covHomol.formSatKK()) embeddabilityString = "Orientation cover satisfies" " KK 2-torsion condition."; else embeddabilityString = "Does not embed in homology 4-sphere."; } } } // end computeEmbeddabilityString() bool NHomologicalData::formIsHyperbolic() { if (torsionFormComputed) return torsionLinkingFormIsHyperbolic; unsigned long nif=tri->getHomologyH1().getNumberOfInvariantFactors(); if (nif == 0) return true; if ((nif % 2) != 0) return false; // check invariant factors agree in pairs, if so call // computeTorsionLinkingForm for (unsigned long i=0;i<(nif/2);i++) { if (tri->getHomologyH1().getInvariantFactor(2*i) < tri->getHomologyH1().getInvariantFactor((2*i)+1)) return false; } computeTorsionLinkingForm(); return torsionLinkingFormIsHyperbolic; } } // namespace regina regina-4.95/engine/triangulation/nhomologicaldata.h000644 000765 000024 00000106702 12236713375 022431 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nhomologicaldata.h * \brief Deals with all the details of the cellular homology of a manifold. */ #ifndef __NHOMOLOGICALDATA_H #ifndef __DOXYGEN #define __NHOMOLOGICALDATA_H #endif #include "regina-core.h" #include "algebra/nmarkedabeliangroup.h" #include "maths/nrational.h" #include "triangulation/ntriangulation.h" #include "utilities/ptrutils.h" #include #include #include #include namespace regina { class NTriangulation; /** * \weakgroup algebra * @{ */ /** * Data type that deals with all the detailed homological information in a * manifold. This information includes: * * - the manifold's homology; * - the boundary's homology; * - the map from boundary -> manifold; * - the dual cellular homology; * - the isomorphism on H1 from the dual cellular homology to the regular * cellular homology; * - the H1 torsion form; * - the Kawauchi-Kojima invariants of torsion linking forms. * * This class takes a "least effort" approach to all computations. It * only computes what is neccessary for your requests. It also keeps a * record of all previous computations you've made. If a computation can * be sped up by not recomputing some data, it takes that short-cut. * * All these algorithms use two transverse CW decompositions of the manifold. * They correspond to the (possibly ideal) triangulation native to Regina, * and the dual polyhedral (CW) decomposition which appears in Seifert and * Threlfall's textbook. * * In the following lists we describe the canonical ordering of both the * cells and the dual cells of the given triangulation. * * First we list the cell orderings for the standard CW decomposition, * which most closely resembles the ideal triangulation. * * - \b 0-cells: The non-ideal vertices given in the order vertices.begin() * to vertices.end(), followed by the ideal endpoints of the * edges edges.begin() to edges.end() with endpoints * for each edge taken in the order 0,1. * * - \b 1-cells: edges.begin() to edges.end(), followed by the ideal edges of * faces.begin() to faces.end() in order 0,1,2. * * - \b 2-cells: faces.begin() to faces.end(), followed by the ideal faces of * tetrahedra.begin() through tetrahedra.end() in order 0,1,2,3. * * - \b 3-cells: tetrahedra.begin() through tetrahedra.end(). * * Next we list the cell orderings for the dual CW decomposition: * if the standard CW decomposition came from a morse function \a f, this * would be the one for -\a f. * * - \b 0-cells: tetrahedra.begin() through tetrahedra.end(). * * - \b 1-cells: the non-boundary faces.begin() through faces.end(). * * - \b 2-cells: the non-boundary edges.begin() through edges.end(). * * - \b 3-cells: the non-boundary, non-ideal vertices.begin() through * vertices.end(). * * \deprecated This class will be removed in Regina 5.0. A new and more * flexible class called NCellularData will take its place. * * \testpart * * @author Ryan Budney */ class REGINA_API NHomologicalData : public ShareableObject { private: /** * A fairly primitive class that implements sorted arrays of * unsigned integers, with logarithmic-time lookup. The interface is * extremely basic. * * This class is a placeholder, and is \emph not for long-term use. * Eventually it will (probably) be replaced with something richer, * slicker and/or more appropriate. * * \warning A precondition of using this class is that elements are * inserted in increasing order only. */ class SortedArray { private: std::vector data_; /**< The underlying array of integers. */ public: /** * Construct an empty array. */ inline SortedArray() { } /** * Return the number of elements in this array. * * @return the number of elements. */ inline size_t size() const { return data_.size(); } /** * Return the integer at the given index in this array. * * @param index the requested array index; this must be * between 0 and size()-1 inclusive. * @return the corresponding element of this array. */ inline unsigned long operator [] (size_t index) const { return data_[index]; } /** * Finds the index of the given integer in this array. * * This routine runs in logarithmic time (it uses a * binary search). * * @param value the integer to search for. * @return the array index that holds the given integer, * or -1 if the given integer is not stored in this array. */ inline ptrdiff_t index(unsigned long value) const { std::vector::const_iterator it = std::lower_bound(data_.begin(), data_.end(), value); if (it != data_.end() && *it == value) return (it - data_.begin()); else return -1; } /** * Pushes the given integer onto the end of this array. * * \pre The given integer is at least as large as every * integer currently stored in the array. * * @param value the integer to insert into this array. */ inline void push_back(unsigned long value) { data_.push_back(value); } }; /** * Stored pointer to a valid triangulation. All routines use this * triangulation as reference. * This is the triangulation that it is initialized by. */ std::auto_ptr tri; /** * Pointer to the 0-th homology group in standard cellular coordinates, * or 0 if it has not yet been computed. */ std::auto_ptr mHomology0; /** * Pointer to the 1st homology group in standard cellular coordinates, * or 0 if it has not yet been computed. */ std::auto_ptr mHomology1; /** * Pointer to the 2nd homology group in standard cellular coordinates, * or 0 if it has not yet been computed. */ std::auto_ptr mHomology2; /** * Pointer to the 3rd homology group in standard cellular coordinates, * or 0 if it has not yet been computed. */ std::auto_ptr mHomology3; /** * Pointer to the 0-th boundary homology group in standard cellular * coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bHomology0; /** * Pointer to the 1st boundary homology group in standard cellular * coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bHomology1; /** * Pointer to the 2nd boundary homology group in standard cellular * coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bHomology2; /** * Pointer to the boundary inclusion on 0-th homology, standard * cellular coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bmMap0; /** * Pointer to the boundary inclusion on 1st homology, standard * cellular coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bmMap1; /** * Pointer to the boundary inclusion on 2nd homology, standard * cellular coordinates, or 0 if it has not yet been computed. */ std::auto_ptr bmMap2; /** * Pointer to the 0-th homology group in dual cellular coordinates, or * 0 if it has not yet been computed. */ std::auto_ptr dmHomology0; /** * Pointer to the 1st homology group in dual cellular coordinates, or * 0 if it has not yet been computed. */ std::auto_ptr dmHomology1; /** * Pointer to the 2nd homology group in dual cellular coordinates, or * 0 if it has not yet been computed. */ std::auto_ptr dmHomology2; /** * Pointer to the 3rd homology group in dual cellular coordinates, or * 0 if it has not yet been computed. */ std::auto_ptr dmHomology3; /** * Pointer to the cellular approx of the identity H1(M) --> H1(M) * from dual to standard cellular coordinates, or 0 if it has not yet * been computed. */ std::auto_ptr dmTomMap1; // below here and the public declaration go the internal bits of // data that are not publicly accessible... // the chain complexes for the regular cellular homology /** true if the indexing of the chain complexes is complete */ bool ccIndexingComputed; /** number of standard cells in dimension 0, 1, 2, 3. */ unsigned long numStandardCells[4]; /** number of dual cells in dimension 0, 1, 2, 3. */ unsigned long numDualCells[4]; /** number of (standard) boundary cells in dimension 0, 1, 2. */ unsigned long numBdryCells[3]; /** non-ideal vertices */ SortedArray sNIV; /** vertices which are ideal endpoints of edges */ SortedArray sIEOE; /** edges which are ideal end edges of faces */ SortedArray sIEEOF; /** faces which are ideal end faces of tetrahedra */ SortedArray sIEFOT; /** vertices which are not ideal, and nonboundary */ SortedArray dNINBV; /** interior edges ie: non-boundary edges */ SortedArray dNBE; /** non-boundary faces */ SortedArray dNBF; /** boundary, non-ideal vertices */ SortedArray sBNIV; /** boundary non-ideal edges */ SortedArray sBNIE; /** boundary non-ideal faces */ SortedArray sBNIF; /** True if the chain complexes A0,A1,A2,A3,A4, B0,B1,B2,B3,B4, ** Bd0,Bd1,Bd2,Bd3, B0Incl,B1Incl,B2Incl are computed */ bool chainComplexesComputed; /** 0th term in chain complex for cellular homology, using standard CW-complex struc */ std::auto_ptr A0; /** 1st term in chain complex for cellular homology, using standard CW-complex struc */ std::auto_ptr A1; /** 2nd term in chain complex for cellular homology, using standard CW-complex struc */ std::auto_ptr A2; /** 3rd term in chain complex for cellular homology, using standard CW-complex struc */ std::auto_ptr A3; /** 4th term in chain complex for cellular homology, using standard CW-complex struc */ std::auto_ptr A4; /** 0-th term in chain complex for dual cellular homology */ std::auto_ptr B0_; // B0 is #defined in some system headers :/ /** 1st term in chain complex for dual cellular homology */ std::auto_ptr B1; /** 2nd term in chain complex for dual cellular homology */ std::auto_ptr B2; /** 3rd term in chain complex for dual cellular homology */ std::auto_ptr B3; /** 4th term in chain complex for dual cellular homology */ std::auto_ptr B4; /** 0th term in chain complex for boundary cellular homology */ std::auto_ptr Bd0; /** 1st term in chain complex for boundary cellular homology */ std::auto_ptr Bd1; /** 2nd term in chain complex for boundary cellular homology */ std::auto_ptr Bd2; /** 3rd term in chain complex for boundary cellular homology */ std::auto_ptr Bd3; /** Chain map from C_0 boundary to C_0 manifold, standard coords */ std::auto_ptr B0Incl; /** Chain map from C_1 boundary to C_1 manifold, standard coords */ std::auto_ptr B1Incl; /** Chain map from C_2 boundary to C_2 manifold, standard coords */ std::auto_ptr B2Incl; /** Isomorphism from C_1 dual to C_1 standard */ std::auto_ptr H1map; /** Call this routine to demand the indexing of the chain complexes. */ void computeccIndexing(); /** This routine computes all the chain complexes. */ void computeChainComplexes(); /** Computes all the homology groups of the manifold using standard cells. */ void computeHomology(); /** Computes all the homology groups of the boundary using its standard cells. */ void computeBHomology(); /** Computes all the homology groups of the manifold using dual cells. This ** routine is the faster than computeHomology() but it's likely a bit ** slower than NTriangulation's homology routines. */ void computeDHomology(); /** The induced map on homology corresponding to inclusion of the boundary. */ void computeBIncl(); /** true when the torsionlinking form has been computed. */ bool torsionFormComputed; /** * This routine computes the H1 torsion linking form. It is only * well-defined for orientable 3-manifolds, so don't bother calling * this routine unless you know the manifold is orientable. * * \pre The triangulation is of a connected orientable 3-manifold. */ void computeTorsionLinkingForm(); /** * Unlike computeTorsionLinkingForm(), this routine \e can be called * for non-orientable manifolds (in which case we look at the * orientable double cover). * * \pre The triangulation is of a connected 3-manifold. */ void computeEmbeddabilityString(); /** the prime power decomposition of the torsion subgroup of H1 ** So if the invariant factors were 2,2,4,3,9,9,27,5,5, this would ** be the list: (2, (1, 1, 2)), (3, (1, 2, 2, 3)), (5, (1, 1)) */ std::vector< std::pair< NLargeInteger, std::vector > > h1PrimePowerDecomp; /** p-primary decomposition of the torsion linking form as needed to ** construct the Kawauchi-Kojima invariants. */ std::vector< NMatrixRing* > linkingFormPD; /** True if torsion linking form is `hyperbolic'. */ bool torsionLinkingFormIsHyperbolic; /** True if torsion linking form is `split' */ bool torsionLinkingFormIsSplit; /** True if torsion linking form satisfies the Kawauchi-Kojima 2-torsion ** condition */ bool torsionLinkingFormSatisfiesKKtwoTorCondition; /** 1 of 3 Kawauchi-Kojima invariants: this describes the rank of the ** torsion subgroup of H1 */ std::vector< std::pair< NLargeInteger, std::vector< unsigned long > > > torRankV; /** 2 of 3 Kawauchi-Kojima invariants: this is the sigma-invariant ** of 2-torsion. */ std::vector< NLargeInteger > twoTorSigmaV; /** 3 of 3 Kawauchi-Kojima invariants: this is the Legendre symbol ** invariant of odd torsion. */ std::vector< std::pair< NLargeInteger, std::vector< int > > > oddTorLegSymV; /** string representing torRankV */ std::string torsionRankString; /** string representing twoTorSigmaV */ std::string torsionSigmaString; /** string representing oddTorLegSymV */ std::string torsionLegendreString; /** comment on what kind of homology spheres the manifold may or may ** not embed in. */ std::string embeddabilityString; public: /** * Takes as input a triangulation. * * This class takes its own copy of the input triangulation. This * means that the input triangulation can change or even be * destroyed, and this homological data will happily continue to work * with the original triangulation as it was first passed to the * constructor. * * @param input the triangulation to use. */ NHomologicalData(const NTriangulation& input); /** * Copy constructor. * * @param h the homological data to clone. */ NHomologicalData(const NHomologicalData& h); /** * Destructor. */ virtual ~NHomologicalData(); /** * Short text representation as required by SharableObject. * * Note this only writes pre-computed data. Thus if you have * not yet asked NHomologicalData to compute anything about this * triangulation, writeTextShort may be empty. * * @param out the stream to write to. */ virtual void writeTextShort(std::ostream& out) const; /** * This routine gives access to the manifold's homology computed * with the regular CW-decomposition. * * This routine is typically slower than getDualHomology(), since * getDualHomology() uses the dual CW-decomposition which typically * has an order of magnitude fewer cells. * * Note that the groups returned by getHomology() and getDualHomology() * are isomorphic, though they are generally described by different * presentations. * * @param q the dimension of the homology group: can be 0, 1, 2 or 3. * @return the q-th homology group, computed in the standard * CW-decomposition. */ const NMarkedAbelianGroup& getHomology(unsigned q); /** * This routine gives access to the homology of the boundary * of the manifold, computed with the regular CW-decomposition. * * @param q the dimension of the homology group: can be 0, 1 or 2. * @return the q-th boundary homology group, in standard cellular * homology coordinates */ const NMarkedAbelianGroup& getBdryHomology(unsigned q); /** * This routine gives access to the homomorphism from the * homology of the boundary to the homology of the manifold. * * @param q the dimension of the map: can be 0, 1 or 2. * @return the map from H_q of the boundary to H_q of the manifold, * computed in standard coordinates. */ const NHomMarkedAbelianGroup& getBdryHomologyMap(unsigned q); /** * This routine gives access to the manifold's homology computed * with the dual CW-decomposition. * * This routine is typically faster than getHomology() since the * dual CW-decomposition generally has far fewer cells. * * Note that the groups returned by getHomology() and getDualHomology() * are isomorphic, though they are generally described by different * presentations. * * @param q the dimension of the homology group: can be 0, 1, 2 or 3. * @return the q-th homology group, computed in the dual CW-decomposition. */ const NMarkedAbelianGroup& getDualHomology(unsigned q); /** * Returns the isomorphism from getDualHomology(1) to getHomology(1) * given by a cellular approximation to the identity map on the manifold. * * @return The isomorphism from getDualHomology(1) to getHomology(1) * computed via a cellular approximation of the identity map from * the first 1-skeleton to the second. */ const NHomMarkedAbelianGroup& getH1CellAp(); /** * Returns the number of cells of the given dimension * in the standard genuine CW-decomposition of the manifold. * * In the case that the triangulation is a proper * triangulation of a manifold (or delta-complex decomposition) it * simply returns the same information as in the NTriangulation * vertex, edge, face and tetrahedron lists. * * In the case that this is an ideal triangulation, this algorithm * returns the details of the corresponding compact manifold with * boundary a union of closed surfaces. * * @param dimension the dimension of the cells in question; this must * be 0, 1, 2 or 3. * @return the number of cells of the given dimension in the standard * CW-decomposition of the closed manifold. */ unsigned long getNumStandardCells(unsigned dimension); /** * Returns the number of cells of the given dimension * in the dual CW-decomposition of the manifold. This is typically * much smaller than getNumStandardCells(). * * @param dimension the dimension of the cells in question; this must * be 0, 1, 2 or 3. * @return the number of cells of the given dimension in the dual * CW-decomposition to the triangulation. */ unsigned long getNumDualCells(unsigned dimension); /** * Returns the number of cells of the given dimension in the * standard CW-decomposition of the boundary of the manifold. * This is a subcomplex of the complex used in getNumStandardCells(). * * @param dimension the dimension of the cells in question; this must * be 0, 1 or 2. * @return the number of cells of the given dimension in the standard * CW-decomposition of the boundary. */ unsigned long getNumBdryCells(unsigned dimension); /** * The proper Euler characteristic of the manifold, computed from * the dual CW-decomposition. * * This routine calculates the Euler characteristic of the * corresponding compact triangulated 3-manifold, with each ideal * vertex treated as a surface boundary component. * * This routine returns the same value as * NTriangulation::getEulerCharManifold(), though it computes it * in a different way. * * On the other hand, this routine differs from * NTriangulation::getEulerCharTri(), which handles ideal triangulations * in a non-standard way (treating each ideal vertex as just a single * vertex). * * @return the Euler characteristic of the corresponding compact * triangulated 3-manifold. */ long getEulerChar(); /** * Returns the torsion form rank vector. This is the first of * the three Kawauchi-Kojima complete invariants of the torsion * linking form. * * This vector describes the rank of the torsion subgroup of H1, * given in prime power form. It is a vector of pairs (\a p, \a x), * where \a p is a prime and \a x is its exponent. * * For details, see "Algebraic classification of linking pairings on * 3-manifolds", Akio Kawauchi and Sadayoshi Kojima, * Math. Ann. 253 (1980), 29--42. * * \pre The triangulation is of a connected orientable 3-manifold. * * \ifacespython Not available, though the string routine * getTorsionRankVectorString() can still be used. * * @return the torsion form rank vector. */ const std::vector< std::pair< NLargeInteger, std::vector< unsigned long > > >& getTorsionRankVector(); /** * Same as getTorsionRankVector() but returns as a human-readable string. * * \pre The triangulation is of a connected orientable 3-manifold. * * @return human-readable prime power factorization of the order of * the torsion subgroup of H1. */ const std::string& getTorsionRankVectorString(); /** * Returns the 2-torsion sigma vector. This is the second of the three * Kawauchi-Kojima invariants. It is orientation-sensitive. * * For details, see "Algebraic classification of linking pairings on * 3-manifolds", Akio Kawauchi and Sadayoshi Kojima, * Math. Ann. 253 (1980), 29--42. * * \pre The triangulation is of a connected orientable 3-manifold. * * \ifacespython Not available, though the string routine * getTorsionSigmaVectorString() can still be used. * * @return the Kawauchi-Kojima sigma-vector. */ const std::vector& getTorsionSigmaVector(); /** * Same as getTorsionSigmaVector() but returns as a human-readable string. * This is an orientation-sensitive invariant. * * \pre The triangulation is of a connected orientable 3-manifold. * * @return the Kawauchi-Kojima sigma-vector in human readable form. */ const std::string& getTorsionSigmaVectorString(); /** * Returns the odd p-torsion Legendre symbol vector. This is the * last of the three Kawauchi-Kojima invariants. * * For details, see "Algebraic classification of linking pairings on * 3-manifolds", Akio Kawauchi and Sadayoshi Kojima, * Math. Ann. 253 (1980), 29--42. * * \pre The triangulation is of a connected orientable 3-manifold. * * \ifacespython Not available, though the string routine * getTorsionLegendreSymbolVectorString() can still be used. * * @return the Legendre symbol vector associated to the torsion * linking form. */ const std::vector< std::pair< NLargeInteger, std::vector< int > > >& getTorsionLegendreSymbolVector(); /** * Same as getTorsionLegendreSymbolVector() but returns as a * human-readable string. * * \pre The triangulation is of a connected orientable 3-manifold. * * @return the Legendre symbol vector in human-readable form. */ const std::string& getTorsionLegendreSymbolVectorString(); /** * Returns true iff torsion linking form is `hyperbolic' in * the linking-form sense of the word. * * To be a little more precise, Poincare-duality in a * compact orientable boundaryless manifold * gives an isomorphism between the torsion subgroup of H_1(M) * denoted tH_1(M) and Hom(tH_1(M),Q/Z), where Q is the rationals and Z the * integers. The associated bilinear form (with values in Q/Z) is said * to be `hyperbolic' if tH_1(M) splits as a direct sum A+B such * that Poincare duality sends A to Hom(B,Q/Z) and B to Hom(A,Q/Z). * * \pre The triangulation is of a connected orientable 3-manifold. * * @return \c true iff the torsion linking form is hyperbolic. */ bool formIsHyperbolic(); /** * Returns true iff the torsion linking form is split. * * \pre The triangulation is of a connected orientable 3-manifold. * * @return \c true iff the linking form is split. */ bool formIsSplit(); /** * Returns true iff the torsion linking form satisfies the * Kawauchi-Kojima 2-torsion condition. This condition * states that on all elements \a x of order 2^k, * 2^{k-1}form(x,x) = 0. * * This is a neccessary condition for an orientable 3-manifold * perhaps with boundary to embed in a homology 4-sphere. * * \pre The triangulation is of a connected orientable 3-manifold. * * @return \c true iff the form satisfies the 2-torsion * condition of Kawauchi-Kojima. */ bool formSatKK(); /** * Returns a comment on whether the manifold might embed in * a homology 3-sphere or 4-sphere. Basically, this routine runs * through all the Kawauchi-Kojima conditions, plus a * few other `elementary' conditions. * * Each comment will be formatted as one or more English sentences * (i.e., with capitalisation and punctuation). The comments * themselves are subject to change between releases of Regina, * since later releases may have more detailed tests at their disposal. * * This routine is available for both orientable and non-orientable * triangulations. In the non-orientable case it may return * additional information regarding the orientable double cover. * * \pre The triangulation is of a connected 3-manifold. * * @return a string giving a one-line description of what * is known about where this manifold embeds, based solely * on the manifold's homological data. */ const std::string& getEmbeddabilityComment(); }; /*@}*/ // Inline functions for NHomologicalData // constructor inline NHomologicalData::NHomologicalData(const NTriangulation& input): ShareableObject(), tri(new NTriangulation(input)), ccIndexingComputed(false), chainComplexesComputed(false), torsionFormComputed(false), h1PrimePowerDecomp(0), linkingFormPD(0) { std::fill(numStandardCells, numStandardCells + 4, 0); std::fill(numDualCells, numDualCells + 4, 0); std::fill(numBdryCells, numBdryCells + 3, 0); } // copy constructor inline NHomologicalData::NHomologicalData(const NHomologicalData& g) : ShareableObject(), tri(clonePtr(g.tri)), mHomology0(clonePtr(g.mHomology0)), mHomology1(clonePtr(g.mHomology1)), mHomology2(clonePtr(g.mHomology2)), mHomology3(clonePtr(g.mHomology3)), bHomology0(clonePtr(g.bHomology0)), bHomology1(clonePtr(g.bHomology1)), bHomology2(clonePtr(g.bHomology2)), bmMap0(clonePtr(g.bmMap0)), bmMap1(clonePtr(g.bmMap1)), bmMap2(clonePtr(g.bmMap2)), dmHomology0(clonePtr(g.dmHomology0)), dmHomology1(clonePtr(g.dmHomology1)), dmHomology2(clonePtr(g.dmHomology2)), dmHomology3(clonePtr(g.dmHomology3)), dmTomMap1(clonePtr(g.dmTomMap1)), ccIndexingComputed(g.ccIndexingComputed), chainComplexesComputed(g.chainComplexesComputed), A0(clonePtr(g.A0)), A1(clonePtr(g.A1)), A2(clonePtr(g.A2)), A3(clonePtr(g.A3)), A4(clonePtr(g.A4)), B0_(clonePtr(g.B0_)), B1(clonePtr(g.B1)), B2(clonePtr(g.B2)), B3(clonePtr(g.B3)), B4(clonePtr(g.B4)), Bd0(clonePtr(g.Bd0)), Bd1(clonePtr(g.Bd1)), Bd2(clonePtr(g.Bd2)), Bd3(clonePtr(g.Bd3)), B0Incl(clonePtr(g.B0Incl)), B1Incl(clonePtr(g.B1Incl)), B2Incl(clonePtr(g.B2Incl)), H1map(clonePtr(g.H1map)), torsionFormComputed(g.torsionFormComputed), embeddabilityString(g.embeddabilityString) { // More complex initialisation: if (ccIndexingComputed) { // Numbers of cells, dual cells and standard boundary cells std::copy(g.numStandardCells, g.numStandardCells + 4, numStandardCells); std::copy(g.numDualCells, g.numDualCells + 4, numDualCells); std::copy(g.numBdryCells, g.numBdryCells + 3, numBdryCells); sNIV = g.sNIV; // non-ideal vertices sIEOE = g.sIEOE; // ideal endpoints of edges sIEEOF = g.sIEEOF; // ideal end edges of faces sIEFOT = g.sIEFOT; // ideal end faces of tetrahedra dNINBV = g.dNINBV; // nonideal nonboundary vertices dNBE = g.dNBE; // non-boundary edges dNBF = g.dNBF; // non-boundary faces sBNIV = g.sBNIV; // boundary non-ideal vertices sBNIE = g.sBNIE; // boundary non-ideal edges sBNIF = g.sBNIF; // boundary non-ideal faces } if (torsionFormComputed) { h1PrimePowerDecomp = g.h1PrimePowerDecomp; linkingFormPD.resize( g.linkingFormPD.size(), 0 ); for (unsigned long i=0; i (*g.linkingFormPD[i]); torsionLinkingFormIsHyperbolic = g.torsionLinkingFormIsHyperbolic; torsionLinkingFormIsSplit = g.torsionLinkingFormIsSplit; torsionLinkingFormSatisfiesKKtwoTorCondition = g.torsionLinkingFormSatisfiesKKtwoTorCondition; torRankV = g.torRankV; twoTorSigmaV = g.twoTorSigmaV; oddTorLegSymV = g.oddTorLegSymV; torsionRankString = g.torsionRankString; torsionSigmaString = g.torsionSigmaString; torsionLegendreString = g.torsionLegendreString; } } // destructor inline NHomologicalData::~NHomologicalData() { if (torsionFormComputed) { for (unsigned long i=0; i > >& NHomologicalData::getTorsionRankVector() { computeTorsionLinkingForm(); return torRankV; } inline const std::vector& NHomologicalData::getTorsionSigmaVector() { computeTorsionLinkingForm(); return twoTorSigmaV; } inline const std::vector< std::pair< NLargeInteger, std::vector< int > > >& NHomologicalData::getTorsionLegendreSymbolVector() { computeTorsionLinkingForm(); return oddTorLegSymV; } inline bool NHomologicalData::formIsSplit() { computeTorsionLinkingForm(); return torsionLinkingFormIsSplit; } inline bool NHomologicalData::formSatKK() { computeTorsionLinkingForm(); return torsionLinkingFormSatisfiesKKtwoTorCondition; } inline const std::string& NHomologicalData::getTorsionRankVectorString() { computeTorsionLinkingForm(); return torsionRankString; } inline const std::string& NHomologicalData::getTorsionSigmaVectorString() { computeTorsionLinkingForm(); return torsionSigmaString; } inline const std::string& NHomologicalData::getTorsionLegendreSymbolVectorString() { computeTorsionLinkingForm(); return torsionLegendreString; } inline const std::string& NHomologicalData::getEmbeddabilityComment() { computeEmbeddabilityString(); return embeddabilityString; } } // namespace regina #endif regina-4.95/engine/triangulation/nisomorphism.cpp000644 000765 000024 00000005751 12234011536 022174 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "generic/ngenericisomorphism-impl.h" #include "triangulation/ntriangulation.h" #include "triangulation/nisomorphism.h" namespace regina { // Instatiate all templates from the -impl.h file. template void NGenericIsomorphism<3>::writeTextShort(std::ostream&) const; template void NGenericIsomorphism<3>::writeTextLong(std::ostream&) const; template bool NGenericIsomorphism<3>::isIdentity() const; template NGenericIsomorphism<3>::NGenericIsomorphism( const NGenericIsomorphism<3>&); template NIsomorphism* NGenericIsomorphism<3>::random(unsigned); template NTriangulation* NGenericIsomorphism<3>::apply(const NTriangulation*) const; template void NGenericIsomorphism<3>::applyInPlace(NTriangulation*) const; } // namespace regina regina-4.95/engine/triangulation/nisomorphism.h000644 000765 000024 00000025747 12234011536 021650 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nisomorphism.h * \brief Deals with combinatorial isomorphisms of triangulations. */ #ifndef __NISOMORPHISM_H #ifndef __DOXYGEN #define __NISOMORPHISM_H #endif #include "regina-core.h" #include "shareableobject.h" #include "generic/ngenericisomorphism.h" #include "maths/nperm4.h" #include "triangulation/ntetface.h" namespace regina { class NTriangulation; /** * \weakgroup triangulation * @{ */ /** * Represents a combinatorial isomorphism from one triangulation into * another. * * In essence, a combinatorial isomorphism from triangulation T to * triangulation U is a one-to-one map from the tetrahedra of T to the * tetrahedra of U that allows relabelling of both the tetrahedra and * their faces (or equivalently, their vertices), and that preserves * gluings across adjacent tetrahedra. * * More precisely: An isomorphism consists of (i) a one-to-one map f * from the tetrahedra of T to the tetrahedra of U, and (ii) for each * tetrahedron S of T, a permutation f_S of the faces (0,1,2,3) of S, * for which the following condition holds: * * - If face k of tetrahedron S and face k' of tetrahedron S' * are identified in T, then face f_S(k) of f(S) and face f_S'(k') * of f(S') are identified in U. Moreover, their gluing is consistent * with the face/vertex permutations; that is, there is a commutative * square involving the gluing maps in T and U and the permutations * f_S and f_S'. * * Isomorphisms can be boundary complete or * boundary incomplete. A boundary complete isomorphism * satisfies the additional condition: * * - If face x is a boundary triangle of T then face f(x) is a boundary * triangle of U. * * A boundary complete isomorphism thus indicates that a copy of * triangulation T is present as an entire component (or components) of U, * whereas a boundary incomplete isomorphism represents an embedding of a * copy of triangulation T as a subcomplex of some possibly larger component * (or components) of U. * * Note that in all cases triangulation U may contain more tetrahedra * than triangulation T. * * \testpart * * \todo \feature Composition of isomorphisms. */ class REGINA_API NIsomorphism : public NGenericIsomorphism<3> { public: /** * Creates a new isomorphism with no initialisation. * * \ifacespython Not present. * * @param sourceTetrahedra the number of tetrahedra in the source * triangulation associated with this isomorphism; this may be zero. */ NIsomorphism(unsigned sourceTetrahedra); /** * Creates a new isomorphism identical to the given isomorphism. * * @param cloneMe the isomorphism upon which to base the new * isomorphism. */ NIsomorphism(const NIsomorphism& cloneMe); /** * Returns the number of tetrahedra in the source triangulation * associated with this isomorphism. Note that this is always * less than or equal to the number of tetrahedra in the * destination triangulation. * * This is a convenience routine specific to three dimensions, and is * identical to the dimension-agnostic routine getSourceSimplices(). * * @return the number of tetrahedra in the source triangulation. */ unsigned getSourceTetrahedra() const; /** * Determines the image of the given source tetrahedron under * this isomorphism. * * This is a convenience routine specific to three dimensions, and is * identical to the dimension-agnostic routine simpImage(). * * \ifacespython Not present, though the read-only version of * this routine is. * * @param sourceTet the index of the source tetrahedron; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return a reference to the index of the destination tetrahedron * that the source tetrahedron maps to. */ int& tetImage(unsigned sourceTet); /** * Determines the image of the given source tetrahedron under * this isomorphism. * * This is a convenience routine specific to three dimensions, and is * identical to the dimension-agnostic routine simpImage(). * * @param sourceTet the index of the source tetrahedron; this must * be between 0 and getSourceSimplices()-1 inclusive. * @return the index of the destination tetrahedron * that the source tetrahedron maps to. */ int tetImage(unsigned sourceTet) const; /** * Returns a read-write reference to the permutation that is * applied to the four faces of the given source tetrahedron * under this isomorphism. * Face \a i of source tetrahedron \a sourceTet will be mapped to * face facePerm(sourceTet)[i] of tetrahedron * tetImage(sourceTet). * * This is a convenience routine specific to three dimensions, and is * identical to the dimension-agnostic routine facetPerm(). * * \ifacespython Not present, though the read-only version of this * routine is. * * @param sourceTet the index of the source tetrahedron containing * the original four faces; this must be between 0 and * getSourceSimplices()-1 inclusive. * @return a read-write reference to the permutation applied to the * four faces of the source tetrahedron. */ NPerm4& facePerm(unsigned sourceTet); /** * Determines the permutation that is applied to the four faces * of the given source tetrahedron under this isomorphism. * Face \a i of source tetrahedron \a sourceTet will be mapped to * face facePerm(sourceTet)[i] of tetrahedron * tetImage(sourceTet). * * This is a convenience routine specific to three dimensions, and is * identical to the dimension-agnostic routine facetPerm(). * * @param sourceTet the index of the source tetrahedron containing * the original four faces; this must be between 0 and * getSourceSimplices()-1 inclusive. * @return the permutation applied to the four faces of the * source tetrahedron. */ NPerm4 facePerm(unsigned sourceTet) const; }; /** * A deprecated synonym for NIsomorphism, provided for backward * compatibility only. See NIsomorphism for further details (and please * use the NIsomorphism class instead). * * \deprecated All of the functionality that NIsomorphismDirect used to * provide in old versions of Regina has now been moved into the parent class * NIsomorphism. The NIsomorphismDirect class is now an empty subclass of * NIsomorphism, provided for backward compatibility only, and should not * be used in new applications. This class will be removed from Regina * in the near future. * * \ifacespython Not present. */ class REGINA_API NIsomorphismDirect : public NIsomorphism { public: /** * Creates a new isomorphism with no initialisation. * * @param sourceTetrahedra the number of tetrahedra in the source * triangulation associated with this isomorphism; this may be zero. */ NIsomorphismDirect(unsigned sourceTetrahedra); /** * Creates a new isomorphism identical to the given isomorphism. * * @param cloneMe the isomorphism upon which to base the new * isomorphism. */ NIsomorphismDirect(const NIsomorphism& cloneMe); }; /*@}*/ // Inline functions for NIsomorphism inline NIsomorphism::NIsomorphism(unsigned sourceTetrahedra) : NGenericIsomorphism<3>(sourceTetrahedra) { } inline NIsomorphism::NIsomorphism(const NIsomorphism& cloneMe) : NGenericIsomorphism<3>(cloneMe) { } inline unsigned NIsomorphism::getSourceTetrahedra() const { return nSimplices_; } inline int& NIsomorphism::tetImage(unsigned sourceTet) { return simpImage_[sourceTet]; } inline int NIsomorphism::tetImage(unsigned sourceTet) const { return simpImage_[sourceTet]; } inline NPerm4& NIsomorphism::facePerm(unsigned sourceTet) { return facetPerm_[sourceTet]; } inline NPerm4 NIsomorphism::facePerm(unsigned sourceTet) const { return facetPerm_[sourceTet]; } // Inline functions for NIsomorphismDirect inline NIsomorphismDirect::NIsomorphismDirect(unsigned sourceTetrahedra) : NIsomorphism(sourceTetrahedra) { } inline NIsomorphismDirect::NIsomorphismDirect(const NIsomorphism& cloneMe) : NIsomorphism(cloneMe) { } } // namespace regina #endif regina-4.95/engine/triangulation/nperm.h000644 000765 000024 00000005700 12234011536 020225 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nperm.h * \brief A deprecated header for dealing with permutations of {0,1,2,3}. */ #ifndef __NPERM_H #ifndef __DOXYGEN #define __NPERM_H #endif #warning This header is deprecated; please use maths/nperm4.h instead. #include "maths/nperm4.h" namespace regina { /** * \weakgroup triangulation * @{ */ /** * A legacy typedef provided for backward compatibility only. * * \deprecated As of Regina 4.6.1, the class NPerm has been renamed as * NPerm4 (which has identical functionality). This NPerm typedef is * provided for backward compatibility, and will be removed in some future * version of Regina. */ typedef NPerm4 NPerm; /*@}*/ } // namespace regina #endif regina-4.95/engine/triangulation/npermit.h000644 000765 000024 00000010360 12234011536 020560 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/npermit.h * \brief Provides utilities for iterating through permutations. */ #ifndef __NPERMIT_H #ifndef __DOXYGEN #define __NPERMIT_H #endif #include "regina-core.h" #include "maths/nperm4.h" namespace regina { /** * \weakgroup triangulation * @{ */ /** * An iterator class that runs through all 24 permutations of four * elements. * * \deprecated This class will removed in a future release of Regina, since * it is completely unnecessary. Just loop directly through the 24 elements * of NPerm4::S4. * * \ifacespython Not present. */ class REGINA_API NPermItS4 { private: int permIndex; public: /** * Creates a new iterator pointing at the first permutation. */ NPermItS4(); /** * Points this iterator at the first permutation. */ void init(); /** * Points this iterator at the next permutation after the one it * is currently pointing to. * * \pre This iterator is not past-the-end. */ void operator ++ (int); /** * Returns the permutation at which this iterator is pointing. * * \pre This iterator is not past-the-end. * * @return the permutation at which this iterator is pointing. */ const NPerm4& operator * () const; /** * Determines if this iterator is past-the-end (has run through * all possible permutations). * * @return \c true if and only if this iterator is past-the-end. */ bool done() const; }; /*@}*/ // Inline functions for NPermItS4 inline NPermItS4::NPermItS4() : permIndex(0) { } inline void NPermItS4::init() { permIndex = 0; } inline void NPermItS4::operator ++ (int) { permIndex++; } inline const NPerm4& NPermItS4::operator * () const { return NPerm4::S4[permIndex]; } inline bool NPermItS4::done() const { return (permIndex >= 24); } } // namespace regina #endif regina-4.95/engine/triangulation/ntetface.h000644 000765 000024 00000004717 12234011536 020704 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ntetface.h * \brief Deprecated header; please use nfacetspec.h instead. */ #ifndef __NTETFACE_H #ifndef __DOXYGEN #define __NTETFACE_H #endif #include "generic/nfacetspec.h" #endif regina-4.95/engine/triangulation/ntetrahedron.cpp000644 000765 000024 00000011742 12234011536 022137 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangulation.h" namespace regina { NTetrahedron::NTetrahedron() : tri(0) { for (int i=0; i<4; i++) tetrahedra[i] = 0; } NTetrahedron::NTetrahedron(const std::string& desc) : description(desc), tri(0) { for (int i=0; i<4; i++) tetrahedra[i] = 0; } bool NTetrahedron::hasBoundary() const { for (int i=0; i<4; i++) if (tetrahedra[i] == 0) return true; return false; } void NTetrahedron::isolate() { for (int i=0; i<4; i++) if (tetrahedra[i]) unjoin(i); } NTetrahedron* NTetrahedron::unjoin(int myFace) { // TODO: Make this a stack variable once we know that tri != 0 always. std::auto_ptr span(tri ? new NPacket::ChangeEventSpan(tri) : 0); NTetrahedron* you = tetrahedra[myFace]; int yourFace = tetrahedronPerm[myFace][myFace]; assert(you); assert(you->tetrahedra[yourFace]); you->tetrahedra[yourFace] = 0; tetrahedra[myFace] = 0; if (tri) tri->clearAllProperties(); return you; } void NTetrahedron::joinTo(int myFace, NTetrahedron* you, NPerm4 gluing) { // TODO: Make this a stack variable once we know that tri != 0 always. std::auto_ptr span(tri ? new NPacket::ChangeEventSpan(tri) : you->tri ? new NPacket::ChangeEventSpan(you->tri) : 0); assert((! tetrahedra[myFace]) || (tetrahedra[myFace] == you && tetrahedronPerm[myFace] == gluing)); // TODO: Temporary measure while we transition from old-style to // new-style tetrahedron management. if (tri && ! you->tri) tri->addTetrahedron(you); else if (you->tri && ! tri) you->tri->addTetrahedron(this); assert(tri == you->tri); tetrahedra[myFace] = you; tetrahedronPerm[myFace] = gluing; int yourFace = gluing[myFace]; assert((! you->tetrahedra[yourFace]) || (you->tetrahedra[yourFace] == this && you->tetrahedronPerm[yourFace] == gluing.inverse())); assert(! (you == this && yourFace == myFace)); you->tetrahedra[yourFace] = this; you->tetrahedronPerm[yourFace] = gluing.inverse(); if (tri) tri->clearAllProperties(); } void NTetrahedron::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; for (int i = 3; i >= 0; --i) { out << NTriangle::ordering[i].trunc3() << " -> "; if (! tetrahedra[i]) out << "boundary"; else out << tetrahedra[i]->markedIndex() << " (" << (tetrahedronPerm[i] * NTriangle::ordering[i]).trunc3() << ')'; out << std::endl; } } } // namespace regina regina-4.95/engine/triangulation/ntetrahedron.h000644 000765 000024 00000107416 12234011536 021610 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ntetrahedron.h * \brief Deals with tetrahedra in a triangulation. */ #ifndef __NTETRAHEDRON_H #ifndef __DOXYGEN #define __NTETRAHEDRON_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class NTriangle; class NEdge; class NVertex; class NComponent; class NTriangulation; /** * \weakgroup triangulation * @{ */ /** * Represents a tetrahedron in a triangulation. * * With each tetrahedron is stored various pieces of information * regarding the overall skeletal structure and component structure of * the triangulation. This skeletal information will be allocated, calculated * and deallocated by the NTriangulation object containing the * corresponding tetrahedra. * * The management of tetrahedra has changed significantly as of Regina 4.90: * * - Users no longer need to call NTriangulation::gluingsHaveChanged() * when gluing or ungluing tetrahedra. This notification is now handled * automatically, and NTriangulation::gluingsHaveChanged() now does nothing. * * - You should now create tetrahedra by calling * NTriangulation::newTetrahedron() or * NTriangulation::newTetrahedron(const std::string&), which will * automatically add the tetrahedron to the triangulation. You should * not need to call NTriangulation::addTetrahedron() at all. * * - When you remove a tetrahedron using NTriangulation::removeTetrahedron() * or NTriangulation::removeTetrahedronAt(), the tetrahedron will now be * automatically destroyed. * * - The old way of adding tetrahedra (creating an NTetrahedron and then * calling NTriangulation::addTetrahedron()) is deprecated, and will * be removed completely in the near future. In the meantime, you may * find that tetrahedra are now added to a triangulation automatically (both * NTriangulation::addTetrahedron() and NTetrahedron::joinTo() will * aggressively try to add nearby tetrahedra that do not already belong to * the current triangulation). This is to avoid tetrahedra and * triangulations being in an inconsistent state. Any redundant calls * to NTriangulation::addTetrahedron() (i.e., when the tetrahedron * already belongs to the triangulation) are harmless, and will have no * effect. * * These changes are designed to ensure that triangulations and * tetrahedra are always in a consistent state, and to make it more * difficult for users to inadvertently crash the program. */ class REGINA_API NTetrahedron : public ShareableObject, public NMarkedElement { private: NTetrahedron* tetrahedra[4]; /**< Stores the tetrahedra glued to each face of this tetrahedron. Specifically, tetrahedra[f] represents the tetrahedron joined to triangular face \c f of this tetrahedron, or is 0 if face \c f lies on the triangulation boundary. Faces are numbered from 0 to 3 inclusive, where face \c i is opposite vertex \c i. */ NPerm4 tetrahedronPerm[4]; /**< Stores the corresponence between vertices of this tetrahedron and adjacent tetrahedra. If face \c f is joined to another tetrahedron, tetrahedronPerm[f] represents the permutation \c p whereby vertex \c v of this tetrahedron is identified with vertex p[v] of the adjacent tetrahedron along face \c f. */ std::string description; /**< A text description of this tetrahedron. Descriptions are not mandatory and need not be unique. */ NVertex* vertices[4]; /**< Vertices in the triangulation skeleton that are vertices of this tetrahedron. */ NEdge* edges[6]; /**< Edges in the triangulation skeleton that are edges of this tetrahedron. */ NTriangle* triangles[4]; /**< Triangles in the triangulation skeleton that are faces of this tetrahedron. */ int tmpOrientation[4]; /**< Temporary array used to represent orientations of triangles and vertex link triangles when calculating orientability of boundary components and vertex links. Each orientation will be +/-1. The array should only be used within these orientability routines, and its contents afterwards are unpredictable. */ NPerm4 vertexMapping[4]; /**< Maps 0 to each vertex of this tetrahedron in turn whilst mapping (1,2,3) in a suitably "orientation-preserving" way, as described in getVertexMapping(). */ NPerm4 edgeMapping[6]; /**< Maps (0,1) to the vertices of this tetrahedron that form each edge whilst mapping (2,3) in a suitably "orientation- preserving" way, as described in getEdgeMapping(). */ NPerm4 triMapping[4]; /**< Maps (0,1,2) to the vertices of this tetrahedron that form each triangular face, as described in getTriangleMapping(). */ int tetOrientation; /**< The orientation of this tetrahedron in the triangulation. This will either be 1 or -1. */ NTriangulation* tri; /**< The triangulation to which this tetrahedron belongs. */ NComponent* component; /**< The component to which this tetrahedron belongs in the triangulation. */ public: /** * Creates a new tetrahedron with empty description and no * faces joined to anything. * The new tetrahedron will not belong to any triangulation. * * \deprecated Users should now create new tetrahedra by calling * NTriangulation::newTetrahedron(). For details, see the changes in * tetrahedron management outlined in the NTetrahedron class notes. */ NTetrahedron(); /** * Creates a new tetrahedron with the given description and * no faces joined to anything. * The new tetrahedron will not belong to any triangulation. * * \deprecated Users should now create new tetrahedra by calling * NTriangulation::newTetrahedron(const std::string&). For details, * see the changes in tetrahedron management outlined in the * NTetrahedron class notes. * * @param desc the description to give the new tetrahedron. */ NTetrahedron(const std::string& desc); /** * Destroys this tetrahedron. */ virtual ~NTetrahedron(); /** * Returns the text description associated with this * tetrahedron. * * @return the description of this tetrahedron. */ const std::string& getDescription() const; /** * Sets the text description associated with this tetrahedron. * Note that descriptions need not be unique, and may be empty. * * @param desc the new description to assign to this * tetrahedron. */ void setDescription(const std::string& desc); /** * Returns the adjacent tetrahedron glued to the given face of this * tetrahedron, or 0 if the given face is on the triangulation * boundary. * * @param face the face of this tetrahedron to examine. This * should be between 0 and 3 inclusive, where face \c i is * opposite vertex \c i of the tetrahedron. * @return the adjacent tetrahedron glued to the given face, or 0 * if the given face lies on the boundary. */ NTetrahedron* adjacentTetrahedron(int face) const; /** * A dimension-agnostic alias for adjacentTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See adjacentTetrahedron() for further information. */ NTetrahedron* adjacentSimplex(int face) const; /** * Deprecated in favour of adjacentTetrahedron(). The old routine * getAdjacentTetrahedron() has been renamed to adjacentTetrahedron() * as part of an effort to make programming and scripting with * Regina a little less work on the fingers. * * \deprecated This routine will eventually be removed in some future * version of Regina. Users are advised to use adjacentTetrahedron() * instead, which is an identical routine with a shorter name. * * @param face the face of this tetrahedron to examine. This * should be between 0 and 3 inclusive, where face \c i is * opposite vertex \c i of the tetrahedron. * @return the adjacent tetrahedron glued to the given face, or 0 * if the given face lies on the boundary. */ NTetrahedron* getAdjacentTetrahedron(int face) const; /** * Returns a permutation describing the correspondence between * vertices of this tetrahedron and vertices of the adjacent * tetrahedron glued to the given face of this tetrahedron. * * If we call this permutation \c p, then for each vertex \c v of this * tetrahedron, p[v] will be the vertex of the adjacent * tetrahedron that is identified with \c v according to the gluing * along the given face of this tetrahedron. * * \pre The given face of this tetrahedron has some tetrahedron * (possibly this one) glued to it. * * @param face the face of this tetrahedron whose gluing we * will examine. This should be between 0 and 3 inclusive, where * face \c i is opposite vertex \c i of the tetrahedron. * @return a permutation mapping the vertices of this * tetrahedron to the vertices of the tetrahedron adjacent along * the given face. */ NPerm4 adjacentGluing(int face) const; /** * Deprecated in favour of adjacentGluing(). The old routine * getAdjacentTetrahedronGluing() has been renamed to adjacentGluing() * as part of an effort to make programming and scripting with * Regina a little less work on the fingers. * * \deprecated This routine will eventually be removed in some future * version of Regina. Users are advised to use adjacentGluing() * instead, which is an identical routine with a shorter name. * * @param face the face of this tetrahedron whose gluing we * will examine. This should be between 0 and 3 inclusive, where * face \c i is opposite vertex \c i of the tetrahedron. * @return a permutation mapping the vertices of this * tetrahedron to the vertices of the tetrahedron adjacent along * the given face. */ NPerm4 getAdjacentTetrahedronGluing(int face) const; /** * Examines the tetrahedron glued to the given face of this * tetrahedron, and returns the corresponding face of that * tetrahedron. That is, the returned face of the adjacent * tetrahedron is glued to the given face of this tetrahedron. * * \pre The given face of this tetrahedron has some tetrahedron * (possibly this one) glued to it. * * @param face the face of this tetrahedron whose gluing we * will examine. This * should be between 0 and 3 inclusive, where face \c i is * opposite vertex \c i of the tetrahedron. * @return the face of the tetrahedron adjacent along the given * face that is in fact glued to the given face of this * tetrahedron. */ int adjacentFace(int face) const; /** * A dimension-agnostic alias for adjacentFace(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "facet" refers to a facet of a top-dimensional simplex * (which for 3-manifold triangulations means a face of a tetrahedron). * * See adjacentFace() for further information. */ int adjacentFacet(int facet) const; /** * Deprecated in favour of adjacentFace(). The old routine * getAdjacentFace() has been renamed to adjacentFace() * as part of an effort to make programming and scripting with * Regina a little less work on the fingers. * * \deprecated This routine will eventually be removed in some future * version of Regina. Users are advised to use adjacentFace() * instead, which is an identical routine with a shorter name. * * @param face the face of this tetrahedron whose gluing we * will examine. This should be between 0 and 3 inclusive, where * face \c i is opposite vertex \c i of the tetrahedron. * @return the face of the tetrahedron adjacent along the given * face that is in fact glued to the given face of this tetrahedron. */ int getAdjacentFace(int face) const; /** * Determines if this tetrahedron has any faces that are * boundary triangles. * * @return \c true if and only if this tetrahedron has any * boundary triangles. */ bool hasBoundary() const; /** * Joins the given face of this tetrahedron to another * tetrahedron. The other tetrahedron involved will be * automatically updated. * * Neither tetrahedron needs to belong to a triangulation (i.e., * you can join tetrahedra together before or after calling * NTriangulation::addTetrahedron()). However, if both * tetrahedra do belong to a triangulation then it must be the * \e same triangulation. * * \pre This and the given tetrahedron do not belong to * different triangulations. * \pre The given face of this tetrahedron is not currently glued to * anything. * \pre The face of the other tetrahedron that will be glued to the * given face of this tetrahedron is not currently glued to anything. * \pre If the other tetrahedron involved is this tetrahedron, we are * not attempting to glue a face to itself. * * \warning If one tetrahedron belongs to a triangulation but * the other does not, the missing tetrahedron (along with anything * that it is joined to, directly or indirectly) will be automatically * added to the triangulation. This is new behaviour as of * Regina 4.90; see the NTetrahedron class notes for details. * * @param myFace the face of this tetrahedron that will be glued * to the given other tetrahedron. This * should be between 0 and 3 inclusive, where face \c i is * opposite vertex \c i of the tetrahedron. * @param you the tetrahedron (possibly this one) that will be * glued to the given face of this tetrahedron. * @param gluing a permutation describing the mapping of * vertices by which the two tetrahedra will be joined. Each * vertex \c v of this tetrahedron that lies on the given face will * be identified with vertex gluing[v] of tetrahedron * you. In addition, the face of you that * will be glued to the given face of this tetrahedron will be * face number gluing[myFace]. */ void joinTo(int myFace, NTetrahedron* you, NPerm4 gluing); /** * Unglues the given face of this tetrahedron from whatever is * joined to it. The other tetrahedron involved (possibly this * one) will be automatically updated. * * \pre The given face of this tetrahedron has some tetrahedron * (possibly this one) glued to it. * * @param myFace the face of this tetrahedron whose gluing we * will undo. This should be between 0 and 3 inclusive, where * face \c i is opposite vertex \c i of the tetrahedron. * @return the ex-adjacent tetrahedron that was originally glued * to the given face of this tetrahedron. */ NTetrahedron* unjoin(int myFace); /** * Undoes any face gluings involving this tetrahedron. * Any other tetrahedra involved will be automatically updated. */ void isolate(); /** * Returns the triangulation to which this tetrahedron belongs. * * @return the triangulation containing this tetrahedron. */ NTriangulation* getTriangulation() const; /** * Returns the triangulation component to which this tetrahedron * belongs. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @return the component containing this tetrahedron. */ NComponent* getComponent() const; /** * Returns the vertex in the triangulation skeleton * corresponding to the given vertex of this tetrahedron. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param vertex the vertex of this tetrahedron to examine. * This should be between 0 and 3 inclusive. * @return the vertex of the skeleton corresponding to the * requested tetrahedron vertex. */ NVertex* getVertex(int vertex) const; /** * Returns the edge in the triangulation skeleton * corresponding to the given edge of this tetrahedron. * * See NEdge::edgeNumber and NEdge::edgeVertex for * the conventions of how edges are numbered within a tetrahedron. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param edge the edge of this tetrahedron to examine. * This should be between 0 and 5 inclusive. * @return the edge of the skeleton corresponding to the * requested tetrahedron edge. */ NEdge* getEdge(int edge) const; /** * Returns the triangle in the triangulation skeleton * corresponding to the given face of this tetrahedron. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param face the face of this tetrahedron to examine. * This should be between 0 and 3 inclusive, where face \c i * lies opposite vertex \c i. * @return the triangle of the skeleton corresponding to the * requested tetrahedron face. */ NTriangle* getTriangle(int face) const; /** * A deprecated alias for getTriangle(). * * This routine returns the triangle in the triangulation * skeleton corresponding to the given face of this tetrahedron. * See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @param face the face of this tetrahedron to examine. * This should be between 0 and 3 inclusive, where face \c i * lies opposite vertex \c i. * @return the triangle of the skeleton corresponding to the * requested tetrahedron face. */ NTriangle* getFace(int face) const; /** * Returns a permutation that maps 0 to the given vertex of this * tetrahedron, and that maps (1,2,3) to the three remaining vertices * in the following "orientation-preserving" fashion. * * The images of (1,2,3) under this permutation imply an * orientation for the tetrahedron face opposite the given vertex. * These orientations will be consistent for all tetrahedra * containing the given vertex, if this is possible (i.e., if * the vertex link is orientable). * * Note that there are still arbitrary decisions to be made for * the images of (1,2,3), since there will always be three possible * mappings that yield the correct orientation. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param vertex the vertex of this tetrahedron to examine. * This should be between 0 and 3 inclusive. * @return a permutation that maps 0 to the given vertex of this * tetrahedron, with the properties outlined above. */ NPerm4 getVertexMapping(int vertex) const; /** * Examines the given edge of this tetrahedron, and returns a * permutation that maps the "canonical" vertices (0,1) of the * corresponding edge of the triangulation to the matching vertices * of this tetrahedron. This permutation also maps (2,3) to the * remaining tetrahedron vertices in an "orientation-preserving" * way, as described below. * * In detail: Suppose several edges of several tetrahedra are * identified within the overall triangulation. We call this a * single "edge of the triangulation", and arbitrarily * label its vertices (0,1). This routine then maps the vertices * (0,1) of this edge of the triangulation to the individual * vertices of this tetrahedron that make up the given edge. * * Because we are passing the argument \a edge, we already know * \e which vertices of this tetrahedron are involved. What this * routine tells us is the \a order in which they appear to form the * overall edge of the triangulation. * * As a consequence: Consider some collection of tetrahedron edges * that are identified together as a single edge of the triangulation, * and choose some \a i from the set {0,1}. Then the vertices * getEdgeMapping(...)[i] of the individual tetrahedra * are all identified together, since they all become the same * vertex of the same edge of the triangulation (assuming of * course that we pass the correct edge number in each case to * getEdgeMapping()). * * The images of 2 and 3 under the permutations that are returned * have the following properties. In each tetrahedron, the images * of 2 and 3 under this map form a directed edge of the tetrahedron * (running from the image of vertex 2 to the image of vertex 3). * For any given edge of the triangulation, these corresponding * directed edges together form an ordered path within the * triangulation that circles the common edge of the triangulation * (like an edge link, except that it is not near to the edge and so * might intersect itself). Furthermore, if we consider the individual * tetrahedra in the order in which they appear in the list * NEdge::getEmbeddings(), these corresponding directed edges * appear in order from the start of this path to the finish * (for internal edges this path is actually a cycle, and the * starting point is arbitrary). * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param edge the edge of this tetrahedron to examine. * This should be between 0 and 5 inclusive. * @return a mapping from vertices (0,1) of the requested * triangulation edge to the vertices of this tetrahedron. */ NPerm4 getEdgeMapping(int edge) const; /** * Examines the given face of this tetrahedron, and returns a * mapping from the "canonical" vertices of the corresponding * triangle of the triangulation to the matching vertices of this * tetrahedron. * * In detail: Suppose two faces of two tetrahedra are identified * within the overall triangulation. We call this a single * "triangle of the triangulation", and arbitrarily label its * vertices (0,1,2). This routine then maps the vertices * (0,1,2) of this triangle of the triangulation to the individual * vertices of this tetrahedron that make up the given face. * * Because we are passing the argument \a face, we already know * \e which vertices of this tetrahedron are involved. What this * routine tells us is the \a order in which they appear to form the * overall face of the triangulation. * * As a consequence: Consider some pair of tetrahedron faces that are * identified together as a single triangle of the triangulation, * and choose some \a i from the set {0,1,2}. Then the vertices * getTriangleMapping(...)[i] of the individual tetrahedra * are identified together, since they both become the same * vertex of the same triangle of the triangulation (assuming of * course that we pass the correct face number in each case to * getTriangleMapping()). * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @param face the face of this tetrahedron to examine. * This should be between 0 and 3 inclusive. * @return a mapping from vertices (0,1,2) of the corresponding * triangle to the vertices of this tetrahedron. */ NPerm4 getTriangleMapping(int face) const; /** * A deprecated alias for getTriangleMapping(). * * This routine examines the given face of this tetrahedron, and * returns a mapping from the "canonical" vertices of the corresponding * triangle of the triangulation to the matching vertices of this * tetrahedron. See getTriangleMapping() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangleMapping() instead. * * @param face the face of this tetrahedron to examine. * This should be between 0 and 3 inclusive. * @return a mapping from vertices (0,1,2) of the corresponding * triangle to the vertices of this tetrahedron. */ NPerm4 getFaceMapping(int face) const; /** * Returns the orientation of this tetrahedron in the * triangulation. * * The orientation of each tetrahedron is always +1 or -1. * In an orientable component of a triangulation, * adjacent tetrahedra have the same orientations if one could be * transposed onto the other without reflection, and they have * opposite orientations if a reflection would be required. * In a non-orientable component, orientations are still +1 and * -1 but no further guarantees can be made. * * As of Regina 4.90, if the skeletal information for the * triangulation has not been computed then this will be done * automatically. There is no need for users to explicitly * recompute the skeleton themselves. * * \pre This tetrahedron belongs to a triangulation (i.e., it * was created using NTriangulation::newTetrahedron() or added * using NTriangulation::addTetrahedron()). * * @return +1 or -1 according to the orientation of this tetrahedron. */ int orientation() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/ntriangulation.h" namespace regina { // Inline functions for NTetrahedron inline NTetrahedron::~NTetrahedron() { } inline const std::string& NTetrahedron::getDescription() const { return description; } inline void NTetrahedron::setDescription(const std::string& desc) { description = desc; } inline NTetrahedron* NTetrahedron::adjacentTetrahedron(int face) const { return tetrahedra[face]; } inline NTetrahedron* NTetrahedron::adjacentSimplex(int face) const { return tetrahedra[face]; } inline NTetrahedron* NTetrahedron::getAdjacentTetrahedron(int face) const { // Deprecated. return tetrahedra[face]; } inline int NTetrahedron::adjacentFace(int face) const { return tetrahedronPerm[face][face]; } inline int NTetrahedron::adjacentFacet(int facet) const { return tetrahedronPerm[facet][facet]; } inline int NTetrahedron::getAdjacentFace(int face) const { // Deprecated. return tetrahedronPerm[face][face]; } inline NPerm4 NTetrahedron::adjacentGluing(int face) const { return tetrahedronPerm[face]; } inline NPerm4 NTetrahedron::getAdjacentTetrahedronGluing(int face) const { // Deprecated! Finally. return tetrahedronPerm[face]; } inline NTriangulation* NTetrahedron::getTriangulation() const { return tri; } inline NComponent* NTetrahedron::getComponent() const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return component; } inline NVertex* NTetrahedron::getVertex(int vertex) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return vertices[vertex]; } inline NEdge* NTetrahedron::getEdge(int edge) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return edges[edge]; } inline NTriangle* NTetrahedron::getTriangle(int face) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return triangles[face]; } inline NTriangle* NTetrahedron::getFace(int face) const { return getTriangle(face); } inline NPerm4 NTetrahedron::getVertexMapping(int vertex) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return vertexMapping[vertex]; } inline NPerm4 NTetrahedron::getEdgeMapping(int edge) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return edgeMapping[edge]; } inline NPerm4 NTetrahedron::getTriangleMapping(int face) const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return triMapping[face]; } inline NPerm4 NTetrahedron::getFaceMapping(int face) const { return getTriangleMapping(face); } inline int NTetrahedron::orientation() const { if (! tri->calculatedSkeleton) tri->calculateSkeleton(); return tetOrientation; } inline void NTetrahedron::writeTextShort(std::ostream& out) const { out << "Tetrahedron"; if (description.length() > 0) out << ": " << description; } } // namespace regina #endif regina-4.95/engine/triangulation/ntriangle.cpp000644 000765 000024 00000012366 12234011536 021430 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "triangulation/nedge.h" #include "triangulation/ntriangle.h" namespace regina { const int NTriangle::TRIANGLE = 1; const int NTriangle::SCARF = 2; const int NTriangle::PARACHUTE = 3; const int NTriangle::CONE = 4; const int NTriangle::MOBIUS = 5; const int NTriangle::HORN = 6; const int NTriangle::DUNCEHAT = 7; const int NTriangle::L31 = 8; const NPerm4 NTriangle::ordering[4] = { NPerm4(1, 2, 3, 0), NPerm4(0, 2, 3, 1), NPerm4(0, 1, 3, 2), NPerm4(0, 1, 2, 3) }; int NTriangle::getType() { if (type) return type; subtype = -1; // Determine the triangle type. NVertex* v[3]; NEdge* e[3]; int i; for (i = 0; i < 3; i++) { v[i] = getVertex(i); e[i] = getEdge(i); } if (e[0] != e[1] && e[1] != e[2] && e[2] != e[0]) { // Three distinct edges. if (v[0] == v[1] && v[1] == v[2]) return (type = PARACHUTE); for (i = 0; i < 3; i++) if (v[(i+1)%3] == v[(i+2)%3]) { subtype = i; return (type = SCARF); } return (type = TRIANGLE); } if (e[0] == e[1] && e[1] == e[2]) { // All edges identified. if (getEdgeMapping(0).sign() == getEdgeMapping(1).sign() && getEdgeMapping(1).sign() == getEdgeMapping(2).sign()) return (type = L31); for (i = 0; i < 3; i++) if (getEdgeMapping((i+1)%3).sign() == getEdgeMapping((i+2)%3).sign()) { subtype = i; return (type = DUNCEHAT); } } // Two edges identified. for (i = 0; i < 3; i++) if (e[(i+1)%3] == e[(i+2)%3]) { subtype = i; if (getEdgeMapping((i+1)%3).sign() == getEdgeMapping((i+2)%3).sign()) return (type = MOBIUS); if (v[0] == v[1] && v[1] == v[2]) return (type = HORN); return (type = CONE); } // We should never reach this point. return 0; } NEdge* NTriangle::getEdge(int edge) const { NPerm4 p = embeddings[0]->getVertices(); return embeddings[0]->getTetrahedron()->getEdge( NEdge::edgeNumber[p[(edge + 1) % 3]][p[(edge + 2) % 3]]); } NPerm4 NTriangle::getEdgeMapping(int edge) const { NPerm4 triPerm = embeddings[0]->getVertices(); // Maps triangle -> tetrahedron NPerm4 edgePerm = embeddings[0]->getTetrahedron()->getEdgeMapping( NEdge::edgeNumber[triPerm[(edge + 1) % 3]][triPerm[(edge + 2) % 3]]); // Maps edge -> tetrahedron return NPerm4(triPerm.preImageOf(edgePerm[0]), triPerm.preImageOf(edgePerm[1]), edge, 3); } void NTriangle::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << "Appears as:" << std::endl; for (int i = 0; i < nEmbeddings; ++i) out << " " << embeddings[i]->getTetrahedron()->markedIndex() << " (" << embeddings[i]->getVertices().trunc3() << ')' << std::endl; } } // namespace regina regina-4.95/engine/triangulation/ntriangle.h000644 000765 000024 00000042154 12234011536 021073 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ntriangle.h * \brief Deals with triangles in a triangulation. */ #ifndef __NFACE_H #ifndef __DOXYGEN #define __NFACE_H #endif #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class NBoundaryComponent; class NComponent; class NEdge; class NTetrahedron; class NTriangulation; class NVertex; /** * \weakgroup triangulation * @{ */ /** * Details how a triangle in the skeleton forms part of an individual * tetrahedron. */ class REGINA_API NTriangleEmbedding { private: NTetrahedron* tetrahedron_; /**< The tetrahedron in which this triangle is contained. */ int tri_; /**< The face number of the tetrahedron that is this triangle. */ public: /** * Creates an embedding descriptor containing the given data. * * @param tet the tetrahedron in which this triangle is contained. * @param tri the face number of \a tet that is this triangle. */ NTriangleEmbedding(NTetrahedron* tet, int tri); /** * Creates an embedding descriptor containing the same data as * the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ NTriangleEmbedding(const NTriangleEmbedding& cloneMe); /** * Returns the tetrahedron in which this triangle is contained. * * @return the tetrahedron. */ NTetrahedron* getTetrahedron() const; /** * Returns the triangle number within getTetrahedron() that is * this triangle. * * @return the triangle number that is this triangle. */ int getTriangle() const; /** * A deprecated alias for getTriangle(). * * This routine returns the triangle number within getTetrahedron() * that is this triangle. See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @return the triangle number that is this triangle. */ int getFace() const; /** * Returns a mapping from vertices (0,1,2) of this triangle to the * corresponding vertex numbers in getTetrahedron(), as described * in NTetrahedron::getTriangleMapping(). * * @return a mapping from the vertices of this triangle to the * vertices of getTetrahedron(). */ NPerm4 getVertices() const; }; /** * Represents a triangle in the skeleton of a triangulation. * Triangles are highly temporary; once a triangulation changes, all its * triangle objects will be deleted and new ones will be created. */ class REGINA_API NTriangle : public ShareableObject, public NMarkedElement { public: static const int TRIANGLE; /**< Specifies a triangle with no identified vertices or edges. */ static const int SCARF; /**< Specifies a triangle with two identified vertices. */ static const int PARACHUTE; /**< Specifies a triangle with three identified vertices. */ static const int CONE; /**< Specifies a triangle with two edges identified to form a cone. */ static const int MOBIUS; /**< Specifies a triangle with two edges identified to form a mobius band. */ static const int HORN; /**< Specifies a triangle with two edges identified to form a cone with all three vertices identified. */ static const int DUNCEHAT; /**< Specifies a triangle with all three edges identified, some via orientable and some via non-orientable gluings. */ static const int L31; /**< Specifies a triangle with all three edges identified using non-orientable gluings. Note that this forms a spine for the Lens space L(3,1). */ /** * An array that maps triangle numbers within a tetrahedron * (i.e., face numbers) to the canonical ordering of the individual * tetrahedron vertices that form each triangle. * * This means that the vertices of triangle \a i in a tetrahedron * are, in canonical order, ordering[i][0..2]. As an * immediate consequence, we obtain ordering[i][3] == i. * * Regina defines canonical order to be \e increasing order. * That is, ordering[i][0] < ... < ordering[i][2]. * * This table does \e not describe the mapping from specific * triangles within a triangulation into individual tetrahedra (for * that, see NTetrahedron::getTriangleMapping() instead). This table * merely provides a neat and consistent way of listing the * vertices of any given tetrahedron face. * * This lookup table replaces the deprecated routine * regina::faceOrdering(). */ static const NPerm4 ordering[4]; private: NTriangleEmbedding* embeddings[2]; /**< An array of descriptors telling how this triangle forms a part of each individual tetrahedron that it belongs to. These embeddings will be automatically deleted when the triangle itself is deleted. */ int nEmbeddings; /**< The number of embedding descriptors stored in the embeddings array. */ NComponent* component; /**< The component that this triangle is a part of. */ NBoundaryComponent* boundaryComponent; /**< The boundary component that this triangle is a part of, or 0 if this triangle is internal. */ int type; /**< Specifies the triangle type according to one of the predefined triangle type constants in NTriangle, or 0 if type has not yet been determined. */ int subtype; /**< Specifies the vertex or edge that plays a special role for the triangle type specified by \a type. This is only relevant for some triangle types. */ public: /** * Default destructor. * All embedding descriptors stored in this triangle will be * automatically deleted. */ virtual ~NTriangle(); /** * Determines if this triangle lies entirely on the boundary of the * triangulation. * * @return \c true if and only if this triangle lies on the boundary. */ bool isBoundary() const; /** * Returns a description of the triangle type. * The triangle type describes how the edges and vertices of the * triangle are identified. * * @return one of the predefined triangle type constants in NTriangle. */ int getType(); /** * Return the triangle vertex or triangle edge that plays a special role * for the triangle type of this triangle. Note that this routine is * only relevant for some triangle types. The triangle type is * returned by getType(). * * @return The vertex or edge that plays a special role (this * will be 0, 1 or 2), or -1 if this triangle type has no special * vertex or edge. */ int getSubtype(); /** * Determines whether this triangle is wrapped up to form a Mobius band. * * Note that several different triangle types (as returned by * getType()) can produce this result. * Note also that a triangle can be both a Mobius band \a and a cone. * * @return \c true if and only if this triangle is a Mobius band. */ bool isMobiusBand(); /** * Determines whether this triangle is wrapped up to form a cone. * * Note that several different triangle types (as returned by * getType()) can produce this result. * Note also that a triangle can be both a Mobius band \a and a cone. * * @return \c true if and only if this triangle is a cone. */ bool isCone(); /** * Returns the number of descriptors available through getEmbedding(). * Note that this number will never be greater than two. * * @return the number of embedding descriptors. */ unsigned getNumberOfEmbeddings() const; /** * Returns the requested descriptor detailing how this triangle forms a * part of a particular tetrahedron in the triangulation. * Note that if this triangle represents multiple faces of a * particular tetrahedron, then there will be multiple embedding * descriptors available regarding that tetrahedron. * * @param index the index of the requested descriptor. This * should be between 0 and getNumberOfEmbeddings()-1 inclusive. * @return the requested embedding descriptor. */ const NTriangleEmbedding& getEmbedding(unsigned index) const; /** * Returns the triangulation to which this triangle belongs. * * @return the triangulation containing this triangle. */ NTriangulation* getTriangulation() const; /** * Returns the component of the triangulation to which this * triangle belongs. * * @return the component containing this triangle. */ NComponent* getComponent() const; /** * Returns the boundary component of the triangulation to which * this triangle belongs. * * @return the boundary component containing this triangle, or 0 if this * triangle does not lie entirely within the boundary of the * triangulation. */ NBoundaryComponent* getBoundaryComponent() const; /** * Returns the vertex of the triangulation that corresponds * to the given vertex of this triangle. * * Note that vertex \a i of a triangle is opposite edge \a i of the * triangle. * * @param vertex the vertex of this triangle to examine. This should * be 0, 1 or 2. * @return the corresponding vertex of the triangulation. */ NVertex* getVertex(int vertex) const; /** * Returns the edge of the triangulation that corresponds * to the given edge of this triangle. * * Note that edge \a i of a triangle is opposite vertex \a i of the * triangle. * * @param edge the edge of this triangle to examine. This should be * 0, 1 or 2. * @return the corresponding edge of the triangulation. */ NEdge* getEdge(int edge) const; /** * Examines the given edge of this triangle, and returns a mapping * from the "canonical" vertices of the corresponding edge of * the triangulation to the vertices of this triangle. * * This routine behaves much the same as * NTetrahedron::getEdgeMapping(), except that it maps the edge * vertices into a triangle, not into a tetrahedron. See * NTetrahedron::getEdgeMapping() for a more detailed * explanation of precisely what this mapping means. * * This routine differs from NTetrahedron::getEdgeMapping() in * how it handles the images of 2 and 3. This routine will * always map 2 to the remaining vertex of this triangle (which is * equal to the argument \a edge), and will always map 3 to itself. * * @param edge the edge of this triangle to examine. This should be * 0, 1 or 2. * @return a mapping from vertices (0,1) of the requested edge to * the vertices of this triangle. */ NPerm4 getEdgeMapping(int edge) const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new triangle and marks it as belonging to the * given triangulation component. * * @param myComponent the triangulation component to which this * triangle belongs. */ NTriangle(NComponent* myComponent); friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/ntetrahedron.h" namespace regina { // Inline functions for NTriangle inline NTriangle::NTriangle(NComponent* myComponent) : nEmbeddings(0), component(myComponent), boundaryComponent(0), type(0) { } inline NTriangle::~NTriangle() { if (nEmbeddings > 0) delete embeddings[0]; if (nEmbeddings > 1) delete embeddings[1]; } inline NTriangulation* NTriangle::getTriangulation() const { return embeddings[0]->getTetrahedron()->getTriangulation(); } inline NComponent* NTriangle::getComponent() const { return component; } inline NBoundaryComponent* NTriangle::getBoundaryComponent() const { return boundaryComponent; } inline NVertex* NTriangle::getVertex(int vertex) const { return embeddings[0]->getTetrahedron()->getVertex( embeddings[0]->getVertices()[vertex]); } inline bool NTriangle::isBoundary() const { return (boundaryComponent != 0); } inline int NTriangle::getSubtype() { getType(); return subtype; } inline bool NTriangle::isMobiusBand() { getType(); return (type == L31 || type == DUNCEHAT || type == MOBIUS); } inline bool NTriangle::isCone() { getType(); return (type == DUNCEHAT || type == CONE || type == HORN); } inline unsigned NTriangle::getNumberOfEmbeddings() const { return nEmbeddings; } inline const NTriangleEmbedding& NTriangle::getEmbedding(unsigned index) const { return *(embeddings[index]); } inline void NTriangle::writeTextShort(std::ostream& out) const { out << (isBoundary() ? "Boundary " : "Internal ") << "triangle"; } inline NTriangleEmbedding::NTriangleEmbedding(NTetrahedron* tet, int tri) : tetrahedron_(tet), tri_(tri) { } inline NTriangleEmbedding::NTriangleEmbedding( const NTriangleEmbedding& cloneMe) : tetrahedron_(cloneMe.tetrahedron_), tri_(cloneMe.tri_) { } inline NTetrahedron* NTriangleEmbedding::getTetrahedron() const { return tetrahedron_; } inline int NTriangleEmbedding::getTriangle() const { return tri_; } inline int NTriangleEmbedding::getFace() const { return tri_; } inline NPerm4 NTriangleEmbedding::getVertices() const { return tetrahedron_->getTriangleMapping(tri_); } } // namespace regina #endif regina-4.95/engine/triangulation/ntriangulation.cpp000644 000765 000024 00000057455 12236713237 022524 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include #include #include "foreign/snappea.h" #include "triangulation/ntriangulation.h" #include "utilities/xmlutils.h" // Property IDs: // #define PROPID_EXTRA_TOPOLOGY 1 -- Do not use! #define PROPID_H1 10 #define PROPID_H1REL 11 #define PROPID_H1BDRY 12 #define PROPID_H2 13 #define PROPID_FUNDAMENTALGROUP 14 // Property IDs for properties relating to normal surfaces: #define PROPID_ZEROEFFICIENT 201 #define PROPID_SPLITTINGSURFACE 202 namespace regina { NTriangulation::NTriangulation(const std::string& description) : calculatedSkeleton(false) { NTriangulation* attempt; if ((attempt = fromIsoSig(description))) { cloneFrom(*attempt); setPacketLabel(description); } else if ((attempt = rehydrate(description))) { cloneFrom(*attempt); setPacketLabel(description); } else if ((attempt = fromSnapPea(description))) { cloneFrom(*attempt); setPacketLabel(attempt->getPacketLabel()); } delete attempt; } void NTriangulation::addTetrahedron(NTetrahedron* t) { // Make this a no-op if the tetrahedron has already been added. if (t->tri == this) return; assert(t->tri == 0); ChangeEventSpan span(this); t->tri = this; tetrahedra.push_back(t); // Aggressively add neighbours of t (recursively). // First check whether this is even necessary. bool moreToAdd = false; int i; for (i = 0; i < 4; ++i) if (t->adjacentTetrahedron(i) && ! t->adjacentTetrahedron(i)->tri) { moreToAdd = true; break; } if (moreToAdd) { // Yep, it's necessary.. off we go. std::stack toFollow; toFollow.push(t); NTetrahedron* next; NTetrahedron* adj; while (! toFollow.empty()) { next = toFollow.top(); toFollow.pop(); for (i = 0; i < 4; ++i) { adj = next->adjacentTetrahedron(i); if (adj && ! adj->tri) { adj->tri = this; tetrahedra.push_back(adj); toFollow.push(adj); } } } } clearAllProperties(); } void NTriangulation::swapContents(NTriangulation& other) { ChangeEventSpan span1(this); ChangeEventSpan span2(&other); clearAllProperties(); other.clearAllProperties(); tetrahedra.swap(other.tetrahedra); TetrahedronIterator it; for (it = tetrahedra.begin(); it != tetrahedra.end(); ++it) (*it)->tri = this; for (it = other.tetrahedra.begin(); it != other.tetrahedra.end(); ++it) (*it)->tri = &other; } void NTriangulation::moveContentsTo(NTriangulation& dest) { ChangeEventSpan span1(this); ChangeEventSpan span2(&dest); clearAllProperties(); dest.clearAllProperties(); TetrahedronIterator it; for (it = tetrahedra.begin(); it != tetrahedra.end(); ++it) { // This is an abuse of NMarkedVector, since for a brief moment // each tetrahedron belongs to both vectors // tetrahedra and dest.tetrahedra. // However, the subsequent clear() operation does not touch the // tetrahedron markings (indices), and so we end up with the // correct result (i.e., the markings are correct for dest). (*it)->tri = &dest; dest.tetrahedra.push_back(*it); } tetrahedra.clear(); } void NTriangulation::clearAllProperties() { if (calculatedSkeleton) deleteSkeleton(); fundamentalGroup.clear(); H1.clear(); H1Rel.clear(); H1Bdry.clear(); H2.clear(); zeroEfficient.clear(); splittingSurface.clear(); twoSphereBoundaryComponents.clear(); negativeIdealBoundaryComponents.clear(); threeSphere.clear(); threeBall.clear(); solidTorus.clear(); irreducible.clear(); compressingDisc.clear(); haken.clear(); turaevViroCache.clear(); } void NTriangulation::writeTextLong(std::ostream& out) const { if (! calculatedSkeleton) calculateSkeleton(); out << "Size of the skeleton:\n"; out << " Tetrahedra: " << tetrahedra.size() << '\n'; out << " Triangles: " << triangles.size() << '\n'; out << " Edges: " << edges.size() << '\n'; out << " Vertices: " << vertices.size() << '\n'; out << '\n'; NTetrahedron* tet; NTetrahedron* adjTet; unsigned tetPos; int face, vertex, start, end; NPerm4 adjPerm; out << "Tetrahedron gluing:\n"; out << " Tet | glued to: (012) (013) (023) (123)\n"; out << " -----+-------------------------------------------------------\n"; for (tetPos=0; tetPos=0; face--) { out << " "; adjTet = tet->adjacentTetrahedron(face); if (! adjTet) out << " boundary"; else { adjPerm = tet->adjacentGluing(face); out << std::setw(3) << tetrahedronIndex(adjTet) << " ("; for (vertex=0; vertex<4; vertex++) { if (vertex == face) continue; out << adjPerm[vertex]; } out << ")"; } } out << '\n'; } out << '\n'; out << "Vertices:\n"; out << " Tet | vertex: 0 1 2 3\n"; out << " -----+--------------------------\n"; for (tetPos=0; tetPosgetVertex(vertex)); out << '\n'; } out << '\n'; out << "Edges:\n"; out << " Tet | edge: 01 02 03 12 13 23\n"; out << " -----+--------------------------------\n"; for (tetPos=0; tetPosgetEdge(NEdge::edgeNumber[start][end])); out << '\n'; } out << '\n'; out << "Triangles:\n"; out << " Tet | face: 012 013 023 123\n"; out << " -----+------------------------\n"; for (tetPos=0; tetPos=0; face--) out << ' ' << std::setw(3) << triangleIndex(tet->getTriangle(face)); out << '\n'; } out << '\n'; } void NTriangulation::writeXMLPacketData(std::ostream& out) const { using regina::xml::xmlEncodeSpecialChars; using regina::xml::xmlValueTag; // Write the tetrahedron gluings. TetrahedronIterator it; NTetrahedron* adjTet; int face; out << " \n"; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { out << " getDescription()) << "\"> "; for (face = 0; face < 4; face++) { adjTet = (*it)->adjacentTetrahedron(face); if (adjTet) { out << tetrahedronIndex(adjTet) << ' ' << static_cast((*it)-> adjacentGluing(face).getPermCode()) << ' '; } else out << "-1 -1 "; } out << "\n"; } out << " \n"; if (fundamentalGroup.known()) { out << " \n"; fundamentalGroup.value()->writeXMLData(out); out << " \n"; } if (H1.known()) { out << "

"; H1.value()->writeXMLData(out); out << "

\n"; } if (H1Rel.known()) { out << " "; H1Rel.value()->writeXMLData(out); out << "\n"; } if (H1Bdry.known()) { out << " "; H1Bdry.value()->writeXMLData(out); out << "\n"; } if (H2.known()) { out << "

"; H2.value()->writeXMLData(out); out << "

\n"; } if (twoSphereBoundaryComponents.known()) out << " " << xmlValueTag("twosphereboundarycomponents", twoSphereBoundaryComponents.value()) << '\n'; if (negativeIdealBoundaryComponents.known()) out << " " << xmlValueTag("negativeidealboundarycomponents", negativeIdealBoundaryComponents.value()) << '\n'; if (zeroEfficient.known()) out << " " << xmlValueTag("zeroeff", zeroEfficient.value()) << '\n'; if (splittingSurface.known()) out << " " << xmlValueTag("splitsfce", splittingSurface.value()) << '\n'; if (threeSphere.known()) out << " " << xmlValueTag("threesphere", threeSphere.value()) << '\n'; if (threeBall.known()) out << " " << xmlValueTag("threeball", threeBall.value()) << '\n'; if (solidTorus.known()) out << " " << xmlValueTag("solidtorus", solidTorus.value()) << '\n'; if (irreducible.known()) out << " " << xmlValueTag("irreducible", irreducible.value()) << '\n'; if (compressingDisc.known()) out << " " << xmlValueTag("compressingdisc", compressingDisc.value()) << '\n'; if (haken.known()) out << " " << xmlValueTag("haken", haken.value()) << '\n'; if (! turaevViroCache.empty()) { for (TuraevViroSet::const_iterator it = turaevViroCache.begin(); it != turaevViroCache.end(); it++) out << " \n"; } } NTriangulation* NTriangulation::enterTextTriangulation(std::istream& in, std::ostream& out) { NTriangulation* triang = new NTriangulation(); NTetrahedron* tet; long nTet; // Create new tetrahedra. out << "Number of tetrahedra: "; in >> nTet; while (nTet < 0) { out << "The number of tetrahedra must be non-negative.\n"; out << "Number of tetrahedra: "; in >> nTet; } out << '\n'; for (long i=0; inewTetrahedron(); // Read in the joins. long tetPos, altPos; int face, altFace; NTetrahedron* altTet; int vertices[6]; out << "Tetrahedra are numbered from 0 to " << nTet-1 << ".\n"; out << "Vertices are numbered from 0 to 3.\n"; out << "Enter in the face gluings one at a time.\n"; out << '\n'; while(1) { out << "Enter two tetrahedra to glue, separated by a space, or "; out << "-1 if finished: "; in >> tetPos; if (tetPos < 0) break; in >> altPos; if (altPos < 0) break; if (tetPos >= nTet || altPos >= nTet) { out << "Tetrahedron identifiers must be between 0 and " << nTet-1 << " inclusive.\n"; continue; } tet = triang->tetrahedra[tetPos]; altTet = triang->tetrahedra[altPos]; out << "Enter the three vertices of the first tetrahedron (" << tetPos << "), separated by spaces,\n"; out << " that will form one face of the gluing: "; in >> vertices[0] >> vertices[1] >> vertices[2]; out << "Enter the corresponding three vertices of the second tetrahedron (" << altPos << "): "; in >> vertices[3] >> vertices[4] >> vertices[5]; if (vertices[3] < 0 || vertices[3] > 3 || vertices[4] < 0 || vertices[4] > 3 || vertices[5] < 0 || vertices[5] > 3 || vertices[0] < 0 || vertices[0] > 3 || vertices[1] < 0 || vertices[1] > 3 || vertices[2] < 0 || vertices[2] > 3) { out << "Vertices must be between 0 and 3 inclusive.\n"; continue; } if (vertices[0] == vertices[1] || vertices[1] == vertices[2] || vertices[2] == vertices[0]) { out << "The three vertices for tetrahedron " << tetPos << " must be different.\n"; continue; } if (vertices[3] == vertices[4] || vertices[4] == vertices[5] || vertices[5] == vertices[3]) { out << "The three vertices for tetrahedron " << altPos << " must be different.\n"; continue; } face = 6 - vertices[0] - vertices[1] - vertices[2]; altFace = 6 - vertices[3] - vertices[4] - vertices[5]; if (face == altFace && tetPos == altPos) { out << "You cannot glue a face to itself.\n"; continue; } if (tet->adjacentTetrahedron(face) || altTet->adjacentTetrahedron(altFace)) { out << "One of these faces is already glued to something else.\n"; continue; } tet->joinTo(face, altTet, NPerm4(vertices[0], vertices[3], vertices[1], vertices[4], vertices[2], vertices[5], face, altFace)); out << '\n'; } out << "Finished reading gluings.\n"; out << "The triangulation has been successfully created.\n"; out << '\n'; // Return the completed triangulation. return triang; } long NTriangulation::getEulerCharManifold() const { // Begin with V - E + F - T. // This call to getEulerCharTri() also ensures that the skeleton has // been calculated. long ans = getEulerCharTri(); // Truncate any ideal vertices. for (BoundaryComponentIterator it = boundaryComponents.begin(); it != boundaryComponents.end(); ++it) if ((*it)->isIdeal()) ans += (*it)->getEulerCharacteristic() - 1; // If we have an invalid triangulation, we need to locate non-standard // boundary vertices and invalid edges, and truncate those unwanted bits // also. if (! valid) { for (VertexIterator it = vertices.begin(); it != vertices.end(); ++it) if ((*it)->getLink() == NVertex::NON_STANDARD_BDRY) ans += (*it)->getLinkEulerCharacteristic() - 1; for (EdgeIterator it = edges.begin(); it != edges.end(); ++it) if (! (*it)->isValid()) ++ans; } return ans; } void NTriangulation::deleteTetrahedra() { for_each(tetrahedra.begin(), tetrahedra.end(), FuncDelete()); tetrahedra.clear(); } void NTriangulation::deleteSkeleton() { // Now that skeletal destructors are private, we can't just use for_each. // How primitive. Loop through each list indivually. for (VertexIterator it = vertices.begin(); it != vertices.end(); ++it) delete *it; for (EdgeIterator it = edges.begin(); it != edges.end(); ++it) delete *it; for (TriangleIterator it = triangles.begin(); it != triangles.end(); ++it) delete *it; for (ComponentIterator it = components.begin(); it != components.end(); ++it) delete *it; for (BoundaryComponentIterator it = boundaryComponents.begin(); it != boundaryComponents.end(); ++it) delete *it; vertices.clear(); edges.clear(); triangles.clear(); components.clear(); boundaryComponents.clear(); calculatedSkeleton = false; } void NTriangulation::cloneFrom(const NTriangulation& X) { ChangeEventSpan span(this); removeAllTetrahedra(); TetrahedronIterator it; for (it = X.tetrahedra.begin(); it != X.tetrahedra.end(); it++) newTetrahedron((*it)->getDescription()); // Make the gluings. long tetPos, adjPos; NTetrahedron* tet; NTetrahedron* adjTet; NPerm4 adjPerm; int face; tetPos = 0; for (it = X.tetrahedra.begin(); it != X.tetrahedra.end(); it++) { tet = *it; for (face=0; face<4; face++) { adjTet = tet->adjacentTetrahedron(face); if (adjTet) { adjPos = X.tetrahedronIndex(adjTet); adjPerm = tet->adjacentGluing(face); if (adjPos > tetPos || (adjPos == tetPos && adjPerm[face] > face)) { tetrahedra[tetPos]->joinTo(face, tetrahedra[adjPos], adjPerm); } } } tetPos++; } // Properties: if (X.fundamentalGroup.known()) fundamentalGroup= new NGroupPresentation(*X.fundamentalGroup.value()); if (X.H1.known()) H1 = new NAbelianGroup(*(X.H1.value())); if (X.H1Rel.known()) H1Rel = new NAbelianGroup(*(X.H1Rel.value())); if (X.H1Bdry.known()) H1Bdry = new NAbelianGroup(*(X.H1Bdry.value())); if (X.H2.known()) H2 = new NAbelianGroup(*(X.H2.value())); twoSphereBoundaryComponents = X.twoSphereBoundaryComponents; negativeIdealBoundaryComponents = X.negativeIdealBoundaryComponents; zeroEfficient = X.zeroEfficient; splittingSurface = X.splittingSurface; threeSphere = X.threeSphere; threeBall = X.threeBall; solidTorus = X.solidTorus; irreducible = X.irreducible; compressingDisc = X.compressingDisc; haken = X.haken; turaevViroCache = X.turaevViroCache; } void NTriangulation::insertTriangulation(const NTriangulation& X) { ChangeEventSpan span(this); unsigned long norig = getNumberOfTetrahedra(); unsigned long nX = X.getNumberOfTetrahedra(); unsigned long tetPos; for (tetPos = 0; tetPos < nX; ++tetPos) newTetrahedron(X.tetrahedra[tetPos]->getDescription()); // Make the gluings. unsigned long adjPos; NTetrahedron* tet; NTetrahedron* adjTet; NPerm4 adjPerm; int face; for (tetPos = 0; tetPos < nX; ++tetPos) { tet = X.tetrahedra[tetPos]; for (face=0; face<4; face++) { adjTet = tet->adjacentTetrahedron(face); if (adjTet) { adjPos = X.tetrahedronIndex(adjTet); adjPerm = tet->adjacentGluing(face); if (adjPos > tetPos || (adjPos == tetPos && adjPerm[face] > face)) { tetrahedra[norig + tetPos]->joinTo(face, tetrahedra[norig + adjPos], adjPerm); } } } } } void NTriangulation::insertConstruction(unsigned long nTetrahedra, const int adjacencies[][4], const int gluings[][4][4]) { if (nTetrahedra == 0) return; ChangeEventSpan span(this); NTetrahedron** tet = new NTetrahedron*[nTetrahedra]; unsigned i, j; NPerm4 p; for (i = 0; i < nTetrahedra; i++) tet[i] = newTetrahedron(); for (i = 0; i < nTetrahedra; i++) for (j = 0; j < 4; j++) if (adjacencies[i][j] >= 0 && ! tet[i]->adjacentTetrahedron(j)) { p = NPerm4(gluings[i][j][0], gluings[i][j][1], gluings[i][j][2], gluings[i][j][3]); tet[i]->joinTo(j, tet[adjacencies[i][j]], p); } delete[] tet; } std::string NTriangulation::dumpConstruction() const { std::ostringstream ans; ans << "/**\n"; if (! getPacketLabel().empty()) ans << " * Triangulation: " << getPacketLabel() << "\n"; ans << " * Code automatically generated by dumpConstruction().\n" " */\n" "\n"; if (tetrahedra.empty()) { ans << "/* This triangulation is empty. No code is being generated. */\n"; return ans.str(); } ans << "/**\n" " * The following arrays describe the individual gluings of\n" " * tetrahedron faces.\n" " */\n" "\n"; unsigned long nTetrahedra = tetrahedra.size(); NTetrahedron* tet; NPerm4 p; unsigned long t; int f, i; ans << "const int adjacencies[" << nTetrahedra << "][4] = {\n"; for (t = 0; t < nTetrahedra; t++) { tet = tetrahedra[t]; ans << " { "; for (f = 0; f < 4; f++) { if (tet->adjacentTetrahedron(f)) { ans << tetrahedronIndex(tet->adjacentTetrahedron(f)); } else ans << "-1"; if (f < 3) ans << ", "; else if (t != nTetrahedra - 1) ans << "},\n"; else ans << "}\n"; } } ans << "};\n\n"; ans << "const int gluings[" << nTetrahedra << "][4][4] = {\n"; for (t = 0; t < nTetrahedra; t++) { tet = tetrahedra[t]; ans << " { "; for (f = 0; f < 4; f++) { if (tet->adjacentTetrahedron(f)) { p = tet->adjacentGluing(f); ans << "{ "; for (i = 0; i < 4; i++) { ans << p[i]; if (i < 3) ans << ", "; else ans << " }"; } } else ans << "{ 0, 0, 0, 0 }"; if (f < 3) ans << ", "; else if (t != nTetrahedra - 1) ans << " },\n"; else ans << " }\n"; } } ans << "};\n\n"; ans << "/**\n" " * The following code actually constructs a triangulation based on\n" " * the information stored in the arrays above.\n" " */\n" "\n" "NTriangulation tri;\n" "tri.insertConstruction(" << nTetrahedra << ", adjacencies, gluings);\n" "\n"; return ans.str(); } std::string NTriangulation::snapPea() const { std::ostringstream out; writeSnapPea(out, *this); return out.str(); } NTriangulation* NTriangulation::fromSnapPea(const std::string& snapPeaData) { std::istringstream in(snapPeaData); return readSnapPea(in); } } // namespace regina regina-4.95/engine/triangulation/ntriangulation.h000644 000765 000024 00000513522 12236713237 022161 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/ntriangulation.h * \brief Deals with triangulations. */ #ifndef __NTRIANGULATION_H #ifndef __DOXYGEN #define __NTRIANGULATION_H #endif #include #include #include #include #include "regina-core.h" #include "algebra/nabeliangroup.h" #include "algebra/ngrouppresentation.h" #include "generic/ngenerictriangulation.h" #include "packet/npacket.h" #include "utilities/nbooleans.h" #include "utilities/nmarkedvector.h" #include "utilities/nproperty.h" // The following headers are necessary so that std::auto_ptr can invoke // destructors where necessary. #include "triangulation/nisomorphism.h" // NOTE: More #includes follow after the class declarations. namespace regina { class NBoundaryComponent; class NComponent; class NEdge; class NTriangle; class NTetrahedron; class NVertex; class NGroupPresentation; class NIsomorphism; class NNormalSurface; class NTriangulation; class NXMLPacketReader; class NXMLTriangulationReader; /** * \addtogroup triangulation Triangulations * Triangulations of 3-manifolds. * @{ */ /** * Stores information about the 3-manifold triangulation packet. * See the general PacketInfo template notes for further details. * * \ifacespython Not present. */ template <> struct PacketInfo { typedef NTriangulation Class; inline static const char* name() { return "3-Manifold Triangulation"; } }; /** * Stores the triangulation of a 3-manifold along with its * various cellular structures and other information. * * When the triangulation is deleted, the corresponding * tetrahedra, the cellular structure and all other properties * will be deallocated. * * Triangles, edges, vertices and components are always temporary; * whenever a change * occurs with the triangulation, these will be deleted and a new * skeletal structure will be calculated. The same is true of various * other triangulation properties. * * The management of tetrahedra within a triangulation has become simpler and * safer as of Regina 4.90. In older versions (Regina 4.6 and earlier), * users were required to create tetrahedra, individually add them to * triangulations, and manually notify a triangulation whenever its tetrahedron * gluings changed. As of Regina 4.90, new tetrahedra are created using * NTriangulation::newTetrahedron() which automatically places them within * a triangulation, and all gluing changes are likewise communicated to the * triangulation automatically. These are part of a larger suite of changes * (all designed to help the user avoid inconsistent states and accidental * crashes); see the NTetrahedron class notes for further details. * * \testpart * * \todo \feature Is the boundary incompressible? * \todo \featurelong Am I obviously a handlebody? (Simplify and see * if there is nothing left). Am I obviously not a handlebody? * (Compare homology with boundary homology). * \todo \featurelong Is the triangulation Haken? * \todo \featurelong What is the Heegaard genus? * \todo \featurelong Have a subcomplex as a child packet of a * triangulation. Include routines to crush a subcomplex or to expand a * subcomplex to a normal surface. * \todo \featurelong Implement writeTextLong() for skeletal objects. */ class REGINA_API NTriangulation : public NPacket, public NGenericTriangulation<3> { REGINA_PACKET(NTriangulation, PACKET_TRIANGULATION) public: typedef std::vector::const_iterator TetrahedronIterator; /**< Used to iterate through tetrahedra. */ typedef std::vector::const_iterator TriangleIterator; /**< Used to iterate through triangles. */ typedef std::vector::const_iterator FaceIterator; /**< A deprecated alias for TriangleIterator. */ typedef std::vector::const_iterator EdgeIterator; /**< Used to iterate through edges. */ typedef std::vector::const_iterator VertexIterator; /**< Used to iterate through vertices. */ typedef std::vector::const_iterator ComponentIterator; /**< Used to iterate through components. */ typedef std::vector::const_iterator BoundaryComponentIterator; /**< Used to iterate through boundary components. */ typedef std::map, double> TuraevViroSet; /**< A map from (r, whichRoot) pairs to Turaev-Viro invariants. */ private: mutable bool calculatedSkeleton; /**< Has the skeleton been calculated? */ NMarkedVector tetrahedra; /**< The tetrahedra that form the triangulation. */ mutable NMarkedVector triangles; /**< The triangles in the triangulation skeleton. */ mutable NMarkedVector edges; /**< The edges in the triangulation skeleton. */ mutable NMarkedVector vertices; /**< The vertices in the triangulation skeleton. */ mutable NMarkedVector components; /**< The components that form the triangulation. */ mutable NMarkedVector boundaryComponents; /**< The components that form the boundary of the triangulation. */ mutable bool valid; /**< Is the triangulation valid? */ mutable bool ideal; /**< Is the triangulation ideal? */ mutable bool standard; /**< Is the triangulation standard? */ mutable bool orientable; /**< Is the triangulation orientable? */ mutable NProperty fundamentalGroup; /**< Fundamental group of the triangulation. */ mutable NProperty H1; /**< First homology group of the triangulation. */ mutable NProperty H1Rel; /**< Relative first homology group of the triangulation * with respect to the boundary. */ mutable NProperty H1Bdry; /**< First homology group of the boundary. */ mutable NProperty H2; /**< Second homology group of the triangulation. */ mutable NProperty twoSphereBoundaryComponents; /**< Does the triangulation contain any 2-sphere boundary components? */ mutable NProperty negativeIdealBoundaryComponents; /**< Does the triangulation contain any boundary components that are ideal and have negative Euler characteristic? */ mutable NProperty zeroEfficient; /**< Is the triangulation zero-efficient? */ mutable NProperty splittingSurface; /**< Does the triangulation have a normal splitting surface? */ mutable NProperty threeSphere; /**< Is this a triangulation of a 3-sphere? */ mutable NProperty threeBall; /**< Is this a triangulation of a 3-dimensional ball? */ mutable NProperty solidTorus; /**< Is this a triangulation of the solid torus? */ mutable NProperty irreducible; /**< Is this 3-manifold irreducible? */ mutable NProperty compressingDisc; /**< Does this 3-manifold contain a compressing disc? */ mutable NProperty haken; /**< Is this 3-manifold Haken? This property must only be stored for triangulations that are known to represent closed, connected, orientable, irreducible 3-manifolds. */ mutable TuraevViroSet turaevViroCache; /**< The set of Turaev-Viro invariants that have already been calculated. */ public: /** * \name Constructors and Destructors */ /*@{*/ /** * Default constructor. * * Creates an empty triangulation. */ NTriangulation(); /** * Copy constructor. * * Creates a new triangulation identical to the given triangulation. * The packet tree structure and packet label are \e not copied. * * @param cloneMe the triangulation to clone. */ NTriangulation(const NTriangulation& cloneMe); /** * "Magic" constructor that tries to find some way to interpret * the given string as a triangulation. * * At present, Regina understands the following types of strings * (and attempts to parse them in the following order): * * - isomorphism signatures (see fromIsoSig()); * - dehydration strings (see rehydrate()); * - the contents of a SnapPea data file (see fromSnapPea()). * * This list may grow in future versions of Regina. * * Regina will also set the packet label accordingly. * * If Regina cannot interpret the given string, this will be * left as the empty triangulation. * * @param description a string that describes a 3-manifold * triangulation. */ NTriangulation(const std::string& description); /** * Destroys this triangulation. * * The constituent tetrahedra, the cellular structure and all other * properties will also be deallocated. */ virtual ~NTriangulation(); /*@}*/ /** * \name Packet Administration */ /*@{*/ virtual void writeTextShort(std::ostream& out) const; virtual void writeTextLong(std::ostream& out) const; virtual bool dependsOnParent() const; /*@}*/ /** * \name Tetrahedra */ /*@{*/ /** * Returns the number of tetrahedra in the triangulation. * * @return the number of tetrahedra. */ unsigned long getNumberOfTetrahedra() const; /** * A dimension-agnostic alias for getNumberOfTetrahedra(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getNumberOfTetrahedra() for further information. */ unsigned long getNumberOfSimplices() const; /** * Returns all tetrahedra in the triangulation. * * The reference returned will remain valid * for as long as the triangulation exists, * always reflecting the tetrahedra currently in the * triangulation. * * \ifacespython This routine returns a python list. * * @return the list of all tetrahedra. */ const std::vector& getTetrahedra() const; /** * A dimension-agnostic alias for getTetrahedra(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getTetrahedra() for further information. */ const std::vector& getSimplices() const; /** * Returns the tetrahedron with the given index number in the * triangulation. * Note that tetrahedron indexing may change when a tetrahedron * is added or removed from the triangulation. * * @param index specifies which tetrahedron to return; this * value should be between 0 and getNumberOfTetrahedra()-1 * inclusive. * @return the indexth tetrahedron in the * triangulation. */ NTetrahedron* getTetrahedron(unsigned long index); /** * A dimension-agnostic alias for getTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getTetrahedron() for further information. */ NTetrahedron* getSimplex(unsigned long index); /** * Returns the tetrahedron with the given index number in the * triangulation. * Note that tetrahedron indexing may change when a tetrahedron * is added or removed from the triangulation. * * @param index specifies which tetrahedron to return; this * value should be between 0 and getNumberOfTetrahedra()-1 * inclusive. * @return the indexth tetrahedron in the * triangulation. */ const NTetrahedron* getTetrahedron(unsigned long index) const; /** * A dimension-agnostic alias for getTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See getTetrahedron() for further information. */ const NTetrahedron* getSimplex(unsigned long index) const; /** * Returns the index of the given tetrahedron in the * triangulation. * * Note that tetrahedron indexing may change when a tetrahedron * is added or removed from the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getTetrahedronIndex(). The name has been changed * because, unlike the old routine, it requires that the given * tetrahedron belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given tetrahedron is contained in this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. If you are passing the result of some other * routine that \e might return null (such as * NTetrahedron::adjacentTetrahedron), it might be worth explicitly * testing for null beforehand. * * @param tet specifies which tetrahedron to find in the * triangulation. * @return the index of the specified tetrahedron, where 0 is * the first tetrahedron, 1 is the second and so on. */ long tetrahedronIndex(const NTetrahedron* tet) const; /** * A dimension-agnostic alias for tetrahedronIndex(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See tetrahedronIndex() for further information. */ long simplexIndex(const NTetrahedron* tet) const; /** * Creates a new tetrahedron and adds it to this triangulation. * The new tetrahedron will have an empty description. * All four faces of the new tetrahedron will be boundary triangles. * * The new tetrahedron will become the last tetrahedron in this * triangulation. * * @return the new tetrahedron. */ NTetrahedron* newTetrahedron(); /** * A dimension-agnostic alias for newTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See newTetrahedron() for further information. */ NTetrahedron* newSimplex(); /** * Creates a new tetrahedron with the given description and adds * it to this triangulation. * All four faces of the new tetrahedron will be boundary triangles. * * @param desc the description to assign to the new tetrahedron. * @return the new tetrahedron. */ NTetrahedron* newTetrahedron(const std::string& desc); /** * A dimension-agnostic alias for newTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See newTetrahedron() for further information. */ NTetrahedron* newSimplex(const std::string& desc); /** * Inserts the given tetrahedron into the triangulation. * No face gluings anywhere will be examined or altered. * * The new tetrahedron will be assigned a higher index in the * triangulation than all tetrahedra already present. * * \pre The given tetrahedron does not already belong to a * different triangulation (though already belonging to \e this * triangulation is perfectly fine). * * \deprecated Users should create tetrahedra by calling * newTetrahedron() or newTetrahedron(const std::string&), which * will add the tetrahedron to the triangulation automatically. * * \warning As of Regina 4.90, this routine will also add any * neighbouring tetrahedra that do not yet belong to a * triangulation; moreover, this addition is recursive. This is done * to ensure that, whenever one tetrahedron belongs to a * triangulation, everything that it is joined to (directly or * indirectly) also belongs to that same triangulation. * See the NTetrahedron class notes for further details on how * tetrahedron management has changed in Regina 4.90 and above. * * \ifacespython Since this triangulation takes ownership * of the given tetrahedron, the python object containing the * given tetrahedron becomes a null object and should no longer * be used. * * @param tet the tetrahedron to insert. */ void addTetrahedron(NTetrahedron* tet); /** * Removes the given tetrahedron from the triangulation. * All faces glued to this tetrahedron will be unglued. * The tetrahedron will be deallocated. * * \pre The given tetrahedron exists in the triangulation. * * \warning This routine has changed behaviour as of Regina 4.90. * In older versions of Regina, the tetrahedron was returned to * the user. As of Regina 4.90, the tetrahedron is now destroyed * immediately. * * @param tet the tetrahedron to remove. */ void removeTetrahedron(NTetrahedron* tet); /** * A dimension-agnostic alias for removeTetrahedron(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See removeTetrahedron() for further information. */ void removeSimplex(NTetrahedron* tet); /** * Removes the tetrahedron with the given index number * from the triangulation. Note that tetrahedron indexing may * change when a tetrahedron is added or removed from the * triangulation. * * All faces glued to this tetrahedron will be unglued. * The tetrahedron will be deallocated. * * \warning This routine has changed behaviour as of Regina 4.90. * In older versions of Regina, the tetrahedron was returned to * the user. As of Regina 4.90, the tetrahedron is now destroyed * immediately. * * @param index specifies which tetrahedron to remove; this * should be between 0 and getNumberOfTetrahedra()-1 inclusive. */ void removeTetrahedronAt(unsigned long index); /** * A dimension-agnostic alias for removeTetrahedronAt(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See removeTetrahedronAt() for further information. */ void removeSimplexAt(unsigned long index); /** * Removes all tetrahedra from the triangulation. * All tetrahedra will be deallocated. */ void removeAllTetrahedra(); /** * A dimension-agnostic alias for removeAllTetrahedra(). * This is to assist with writing dimension-agnostic code that * can be reused to work in different dimensions. * * Here "simplex" refers to a top-dimensional simplex (which for * 3-manifold triangulations means a tetrahedron). * * See removeAllTetrahedra() for further information. */ void removeAllSimplices(); /** * Swaps the contents of this and the given triangulation. * That is, all tetrahedra that belong to this triangulation * will be moved to \a other, and all tetrahedra that belong to * \a other will be moved to this triangulation. * * All NTetrahedron pointers or references will remain valid. * * @param other the triangulation whose contents should be * swapped with this. */ void swapContents(NTriangulation& other); /** * Moves the contents of this triangulation into the given * destination triangulation, without destroying any pre-existing * contents. That is, all tetrahedra that currently belong to * \a dest will remain there, and all tetrahedra that belong to this * triangulation will be moved across as additional * tetrahedra in \a dest. * * All NTetrahedron pointers or references will remain valid. * After this operation, this triangulation will be empty. * * @param dest the triangulation to which tetrahedra should be * moved. */ void moveContentsTo(NTriangulation& dest); /** * This routine now does nothing, and should not be used. * * \deprecated In Regina versions 4.6 and earlier, this routine * was used to manually notify the triangulation that the gluings * of tetrahedra had changed. In Regina 4.90 and later this * notification is automatic. This routine now does nothing at * all, and can safely be removed from any existing code. */ void gluingsHaveChanged(); /*@}*/ /** * \name Skeletal Queries */ /*@{*/ /** * Returns the number of boundary components in this * triangulation. Note that each ideal vertex forms its own * boundary component. * * @return the number of boundary components. */ unsigned long getNumberOfBoundaryComponents() const; /** * Returns the number of components in this triangulation. * * @return the number of components. */ unsigned long getNumberOfComponents() const; /** * Returns the number of vertices in this triangulation. * * @return the number of vertices. */ unsigned long getNumberOfVertices() const; /** * Returns the number of edges in this triangulation. * * @return the number of edges. */ unsigned long getNumberOfEdges() const; /** * Returns the number of triangular faces in this triangulation. * * @return the number of triangles. */ unsigned long getNumberOfTriangles() const; /** * A deprecated alias for getNumberOfTriangles(). * * This routine returns the number of triangular faces in this * triangulation. See getNumberOfTriangles() for further details. * * Do not confuse this deprecated alias with the * (non-deprecated) tempate function getNumberOfFaces(). * * \deprecated This routine will be removed in a future version * of Regina. Please use getNumberOfTriangles() instead. * * @return the number of triangles. */ unsigned long getNumberOfFaces() const; /** * Returns the number of faces of the given dimension in this * triangulation. * * This template function is to assist with writing dimension-agnostic * code that can be reused to work in different dimensions. * * \pre the template argument \a dim is between 0 and 3 inclusive. * * \ifacespython Not present. * * @return the number of faces of the given dimension. */ template unsigned long getNumberOfFaces() const; /** * Returns all components of this triangulation. * * Bear in mind that each time the triangulation changes, the * components will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all components. */ const std::vector& getComponents() const; /** * Returns all boundary components of this triangulation. * Note that each ideal vertex forms its own boundary component. * * Bear in mind that each time the triangulation changes, the * boundary components will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all boundary components. */ const std::vector& getBoundaryComponents() const; /** * Returns all vertices of this triangulation. * * Bear in mind that each time the triangulation changes, the * vertices will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all vertices. */ const std::vector& getVertices() const; /** * Returns all edges of this triangulation. * * Bear in mind that each time the triangulation changes, the * edges will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all edges. */ const std::vector& getEdges() const; /** * Returns all triangular faces of this triangulation. * * Bear in mind that each time the triangulation changes, the * triangles will be deleted and replaced with new * ones. Thus the objects contained in this list should be * considered temporary only. * * This reference to the list however will remain valid and * up-to-date for as long as the triangulation exists. * * \ifacespython This routine returns a python list. * * @return the list of all triangles. */ const std::vector& getTriangles() const; /** * A deprecated alias for getTriangles(). * * This routine returns all triangular faces in this triangulation. * See getTriangles() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangles() instead. * * @return the list of all triangles. */ const std::vector& getFaces() const; /** * Returns the requested triangulation component. * * Bear in mind that each time the triangulation changes, the * components will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired component, ranging from 0 * to getNumberOfComponents()-1 inclusive. * @return the requested component. */ NComponent* getComponent(unsigned long index) const; /** * Returns the requested triangulation boundary component. * * Bear in mind that each time the triangulation changes, the * boundary components will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired boundary * component, ranging from 0 * to getNumberOfBoundaryComponents()-1 inclusive. * @return the requested boundary component. */ NBoundaryComponent* getBoundaryComponent(unsigned long index) const; /** * Returns the requested vertex in this triangulation. * * Bear in mind that each time the triangulation changes, the * vertices will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired vertex, ranging from 0 * to getNumberOfVertices()-1 inclusive. * @return the requested vertex. */ NVertex* getVertex(unsigned long index) const; /** * Returns the requested edge in this triangulation. * * Bear in mind that each time the triangulation changes, the * edges will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired edge, ranging from 0 * to getNumberOfEdges()-1 inclusive. * @return the requested edge. */ NEdge* getEdge(unsigned long index) const; /** * Returns the requested triangular face in this triangulation. * * Bear in mind that each time the triangulation changes, the * triangles will be deleted and replaced with new * ones. Thus this object should be considered temporary only. * * @param index the index of the desired triangle, ranging from 0 * to getNumberOfTriangles()-1 inclusive. * @return the requested triangle. */ NTriangle* getTriangle(unsigned long index) const; /** * A deprecated alias for getTriangle(). * * This routine returns the requested triangular face in the * triangulation. See getTriangle() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getTriangle() instead. * * @param index the index of the desired triangle, ranging from 0 * to getNumberOfTriangles()-1 inclusive. * @return the requested triangle. */ NTriangle* getFace(unsigned long index) const; /** * Returns the index of the given component in the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getComponentIndex(). The name has been changed * because, unlike the old routine, it requires that the given * component belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given component belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param component specifies which component to find in the * triangulation. * @return the index of the specified component, where 0 is the first * component, 1 is the second and so on. */ long componentIndex(const NComponent* component) const; /** * Returns the index of the given boundary component * in the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getBoundaryComponentIndex(). The name has been changed * because, unlike the old routine, it requires that the given * boundary component belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given boundary component belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param bc specifies which boundary component to find in the * triangulation. * @return the index of the specified boundary component, * where 0 is the first boundary component, 1 is the second and so on. */ long boundaryComponentIndex(const NBoundaryComponent* bc) const; /** * Returns the index of the given vertex in the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getVertexIndex(). The name has been changed * because, unlike the old routine, it requires that the given * vertex belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given vertex belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param vertex specifies which vertex to find in the * triangulation. * @return the index of the specified vertex, where 0 is the first * vertex, 1 is the second and so on. */ long vertexIndex(const NVertex* vertex) const; /** * Returns the index of the given edge in the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getEdgeIndex(). The name has been changed * because, unlike the old routine, it requires that the given * edge belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given edge belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param edge specifies which edge to find in the * triangulation. * @return the index of the specified edge, where 0 is the first * edge, 1 is the second and so on. */ long edgeIndex(const NEdge* edge) const; /** * Returns the index of the given triangle in the triangulation. * * This routine was introduced in Regina 4.5, and replaces the * old getFaceIndex(). The name has been changed * because, unlike the old routine, it requires that the given * triangle belongs to the triangulation (a consequence of * some significant memory optimisations). * * \pre The given triangle belongs to this triangulation. * * \warning Passing a null pointer to this routine will probably * crash your program. * * @param triangle specifies which triangle to find in the * triangulation. * @return the index of the specified triangle, where 0 is the first * triangle, 1 is the second and so on. */ long triangleIndex(const NTriangle* triangle) const; /** * A deprecated alias for triangleIndex(). * * This routine returns the index of the given triangle in the * triangulation. See triangleIndex() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use triangleIndex() instead. * * @param triangle specifies which triangle to find in the * triangulation. * @return the index of the specified triangle, where 0 is the first * triangle, 1 is the second and so on. */ long faceIndex(const NTriangle* triangle) const; /** * Determines if this triangulation contains any two-sphere * boundary components. * * @return \c true if and only if there is at least one * two-sphere boundary component. */ bool hasTwoSphereBoundaryComponents() const; /** * Determines if this triangulation contains any ideal boundary * components with negative Euler characteristic. * * @return \c true if and only if there is at least one such * boundary component. */ bool hasNegativeIdealBoundaryComponents() const; /*@}*/ /** * \name Isomorphism Testing */ /*@{*/ /** * Determines if this triangulation is combinatorially * isomorphic to the given triangulation. * * Specifically, this routine determines if there is a * one-to-one and onto boundary complete combinatorial * isomorphism from this triangulation to \a other. Boundary * complete isomorphisms are described in detail in the * NIsomorphism class notes. * * In particular, note that this triangulation and \a other must * contain the same number of tetrahedra for such an isomorphism * to exist. * * \todo \opt Improve the complexity by choosing a tetrahedron * mapping from each component and following gluings to * determine the others. * * If a boundary complete isomorphism is found, the details of * this isomorphism are returned. The isomorphism is newly * constructed, and so to assist with memory management is * returned as a std::auto_ptr. Thus, to test whether an * isomorphism exists without having to explicitly deal with the * isomorphism itself, you can call * if (isIsomorphicTo(other).get()) and the newly * created isomorphism (if it exists) will be automatically * destroyed. * * @param other the triangulation to compare with this one. * @return details of the isomorphism if the two triangulations * are combinatorially isomorphic, or a null pointer otherwise. */ std::auto_ptr isIsomorphicTo(const NTriangulation& other) const; /** * Determines if an isomorphic copy of this triangulation is * contained within the given triangulation, possibly as a * subcomplex of some larger component (or components). * * Specifically, this routine determines if there is a boundary * incomplete combinatorial isomorphism from this triangulation * to \a other. Boundary incomplete isomorphisms are described * in detail in the NIsomorphism class notes. * * In particular, note that boundary triangles of this triangulation * need not correspond to boundary triangles of \a other, and that * \a other can contain more tetrahedra than this triangulation. * * If a boundary incomplete isomorphism is found, the details of * this isomorphism are returned. The isomorphism is newly * constructed, and so to assist with memory management is * returned as a std::auto_ptr. Thus, to test whether an * isomorphism exists without having to explicitly deal with the * isomorphism itself, you can call * if (isContainedIn(other).get()) and the newly * created isomorphism (if it exists) will be automatically * destroyed. * * If more than one such isomorphism exists, only one will be * returned. For a routine that returns all such isomorphisms, * see findAllSubcomplexesIn(). * * @param other the triangulation in which to search for an * isomorphic copy of this triangulation. * @return details of the isomorphism if such a copy is found, * or a null pointer otherwise. */ std::auto_ptr isContainedIn(const NTriangulation& other) const; /** * Finds all ways in which an isomorphic copy of this triangulation * is contained within the given triangulation, possibly as a * subcomplex of some larger component (or components). * * This routine behaves identically to isContainedIn(), except * that instead of returning just one isomorphism (which may be * boundary incomplete and need not be onto), all such isomorphisms * are returned. * * See the isContainedIn() notes for additional information. * * The isomorphisms that are found will be inserted into the * given list. These isomorphisms will be newly created, and * the caller of this routine is responsible for destroying * them. The given list will not be emptied before the new * isomorphisms are inserted. * * \ifacespython Not present. * * @param other the triangulation in which to search for * isomorphic copies of this triangulation. * @param results the list in which any isomorphisms found will * be stored. * @return the number of isomorphisms that were found. */ unsigned long findAllSubcomplexesIn(const NTriangulation& other, std::list& results) const; /*@}*/ /** * \name Basic Properties */ /*@{*/ /** * Returns the Euler characteristic of this triangulation. * This will be evaluated strictly as \a V-E+F-T. * * Note that this routine handles cusps in a non-standard way. * Since it computes the Euler characteristic of the * triangulation (and not the underlying manifold), this routine * will treat each cusp as a single vertex, and \e not as * a surface boundary component. * * For a routine that handles cusps properly (i.e., treats them * as surface boundary components when computing the Euler * characteristic), see getEulerCharManifold() instead. * * This routine was previously called getEulerCharacteristic() in * Regina 4.3.1 and earlier. It was renamed in Regina 4.4 to * clarify the non-standard handling of cusps. * * @return the Euler characteristic of this triangulation. */ long getEulerCharTri() const; /** * Returns the Euler characteristic of the corresponding compact * 3-manifold. * * Instead of simply calculating \a V-E+F-T, this routine also: * * - treats ideal vertices as surface boundary components * (i.e., effectively truncates them); * - truncates invalid boundary vertices (i.e., boundary vertices * whose links are not discs); * - truncates the projective plane cusps at the midpoints of invalid * edges (edges identified with themselves in reverse). * * For ideal triangulations, this routine therefore computes * the proper Euler characteristic of the manifold (unlike * getEulerCharTri(), which does not). * * For triangulations whose vertex links are all spheres or discs, * this routine and getEulerCharTri() give identical results. * * @return the Euler characteristic of the corresponding compact * manifold. */ long getEulerCharManifold() const; /** * A deprecated alias for getEulerCharTri(). * * This routine calculates the Euler characteristic of this * triangulation. Since it treats cusps in a non-standard way, * it was renamed to getEulerCharTri() in Regina 4.4 to clarify * that this might differ from the Euler characteristic of the * corresponding compact manifold. * * See getEulerCharTri() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use getEulerCharTri() instead. * * @return the Euler characteristic of this triangulation. */ long getEulerCharacteristic() const; /** * Determines if this triangulation is valid. * A triangulation is valid unless there is some vertex whose * link has boundary but is not a disc (i.e., a vertex for which * NVertex::getLink() returns NVertex::NON_STANDARD_BDRY), * or unless there is some edge glued to itself in reverse * (i.e., an edge for which NEdge::isValid() returns \c false). * * @return \c true if and only if this triangulation is valid. */ bool isValid() const; /** * Determines if this triangulation is ideal. * This is the case if and only if one of the vertex links * is closed and not a 2-sphere. * Note that the triangulation is not required to be valid. * * @return \c true if and only if this triangulation is ideal. */ bool isIdeal() const; /** * Determines if this triangulation is standard. * This is the case if and only if every vertex is standard. * See NVertex::isStandard() for further details. * * @return \c true if and only if this triangulation is * standard. */ bool isStandard() const; /** * Determines if this triangulation has any boundary triangles. * * @return \c true if and only if there are boundary triangles. */ bool hasBoundaryTriangles() const; /** * A deprecated alias for hasBoundaryTriangles(). * * This routine determines whether this triangulation has any * boundary triangles. See hasBoundaryTriangles() for further details. * * \deprecated This routine will be removed in a future version * of Regina. Please use hasBoundaryTriangles() instead. * * @return \c true if and only if there are boundary triangles. */ bool hasBoundaryFaces() const; /** * Determines if this triangulation is closed. * This is the case if and only if it has no boundary. * Note that ideal triangulations are not closed. * * @return \c true if and only if this triangulation is closed. */ bool isClosed() const; /** * Determines if this triangulation is orientable. * * @return \c true if and only if this triangulation is * orientable. */ bool isOrientable() const; /** * Determines if this triangulation is oriented; that is, if * tetrahedron vertices are labelled in a way that preserves * orientation across adjacent tetrahedron faces. Specifically, this * routine returns \c true if and only if every gluing permutation * has negative sign. * * Note that \e orientable triangulations are not always \e oriented * by default. You can call orient() if you need the tetrahedra * to be oriented consistently as described above. * * A non-orientable triangulation can never be oriented. * * @return \c true if and only if all tetrahedra are oriented * consistently. * * @author Matthias Goerner */ bool isOriented() const; /** * Determines if this triangulation is ordered; that is, if * tetrahedron vertices are labelled so that all gluing * permutations are order-preserving on the tetrahedron faces. * Equivalently, this tests whether the edges of the triangulation * can all be oriented such that they induce a consistent ordering * on the vertices of each tetrahedron. * * Triangulations are not ordered by default, and indeed some * cannot be ordered at all. The routine order() will attempt * to relabel tetrahedron vertices to give an ordered triangulation. * * @return \c true if and only if all gluing permutations are * order preserving on the tetrahedron faces. * * @author Matthias Goerner */ bool isOrdered() const; /** * Determines if this triangulation is connected. * * @return \c true if and only if this triangulation is * connected. */ bool isConnected() const; /*@}*/ /** * \name Algebraic Properties */ /*@{*/ /** * Returns the fundamental group of this triangulation. * If this triangulation contains any ideal or non-standard * vertices, the fundamental group will be * calculated as if each such vertex had been truncated. * * If this triangulation contains any invalid edges, the * calculations will be performed without any truncation * of the corresponding projective plane cusp. Thus if a * barycentric subdivision is performed on the triangulation, the * result of getFundamentalGroup() will change. * * Bear in mind that each time the triangulation changes, the * fundamental group will be deleted. Thus the reference that is * returned from this routine should not be kept for later use. * Instead, getFundamentalGroup() should be called again; this will * be instantaneous if the group has already been calculated. * * Note that this triangulation is not required to be valid * (see isValid()). * * \pre This triangulation has at most one component. * * @return the fundamental group. */ const NGroupPresentation& getFundamentalGroup() const; /** * Notifies the triangulation that you have simplified the * presentation of its fundamental group. The old group * presentation will be destroyed, and this triangulation will * take ownership of the new (hopefully simpler) group that is * passed. * * This routine is useful for situations in which some external * body (such as GAP) has simplified the group presentation * better than Regina can. * * Regina does not verify that the new group presentation * is equivalent to the old, since this is - well, hard. * * If the fundamental group has not yet been calculated for this * triangulation, this routine will nevertheless take ownership * of the new group, under the assumption that you have worked * out the group through some other clever means without ever * having needed to call getFundamentalGroup() at all. * * Note that this routine will not fire a packet change event. * * @param newGroup a new (and hopefully simpler) presentation * of the fundamental group of this triangulation. */ void simplifiedFundamentalGroup(NGroupPresentation* newGroup); /** * Returns the first homology group for this triangulation. * If this triangulation contains any ideal or non-standard * vertices, the homology group will be * calculated as if each such vertex had been truncated. * * If this triangulation contains any invalid edges, the * calculations will be performed without any truncation * of the corresponding projective plane cusp. Thus if a * barycentric subdivision is performed on the triangulation, the * result of getHomologyH1() will change. * * Bear in mind that each time the triangulation changes, the * homology groups will be deleted. Thus the reference that is * returned from this routine should not be kept for later use. * Instead, getHomologyH1() should be called again; this will be * instantaneous if the group has already been calculated. * * Note that this triangulation is not required to be valid * (see isValid()). * * @return the first homology group. */ const NAbelianGroup& getHomologyH1() const; /** * Returns the relative first homology group with * respect to the boundary for this triangulation. * Note that ideal vertices are considered part of the boundary. * * Bear in mind that each time the triangulation changes, the * homology groups will be deleted. Thus the reference that is * returned from this routine should not be kept for later use. * Instead, getHomologyH1Rel() should be called again; this will be * instantaneous if the group has already been calculated. * * \pre This triangulation is valid. * * @return the relative first homology group with respect to the * boundary. */ const NAbelianGroup& getHomologyH1Rel() const; /** * Returns the first homology group of the * boundary for this triangulation. * Note that ideal vertices are considered part of the boundary. * * Bear in mind that each time the triangulation changes, the * homology groups will be deleted. Thus the reference that is * returned from this routine should not be kept for later use. * Instead, getHomologyH1Bdry() should be called again; this will be * instantaneous if the group has already been calculated. * * This routine is fairly fast, since it deduces the homology of * each boundary component through knowing what kind of surface * it is. * * \pre This triangulation is valid. * * @return the first homology group of the boundary. */ const NAbelianGroup& getHomologyH1Bdry() const; /** * Returns the second homology group for this triangulation. * If this triangulation contains any ideal vertices, * the homology group will be * calculated as if each such vertex had been truncated. * The algorithm used calculates various first homology groups * and uses homology and cohomology theorems to deduce the * second homology group. * * Bear in mind that each time the triangulation changes, the * homology groups will be deleted. Thus the reference that is * returned from this routine should not be kept for later use. * Instead, getHomologyH2() should be called again; this will be * instantaneous if the group has already been calculated. * * \pre This triangulation is valid. * * @return the second homology group. */ const NAbelianGroup& getHomologyH2() const; /** * Returns the second homology group with coefficients in Z_2 * for this triangulation. * If this triangulation contains any ideal vertices, * the homology group will be * calculated as if each such vertex had been truncated. * The algorithm used calculates the relative first homology group * with respect to the boundary and uses homology and cohomology * theorems to deduce the second homology group. * * This group will simply be the direct sum of several copies of * Z_2, so the number of Z_2 terms is returned. * * \pre This triangulation is valid. * * @return the number of Z_2 terms in the second homology group * with coefficients in Z_2. */ unsigned long getHomologyH2Z2() const; /** * Computes the Turaev-Viro state sum invariant of this * 3-manifold based upon the given initial data. * * The initial data is as described in the paper of Turaev and * Viro, "State sum invariants of 3-manifolds and quantum * 6j-symbols", Topology, vol. 31, no. 4, 1992, pp 865-902. * * In particular, Section 7 describes the initial data as * determined by an integer r >=3 and a root of unity q0 of * degree 2r for which q0^2 is a primitive root of unity of * degree r. * * These invariants, although computed in the complex field, * should all be reals. Thus the return type is an ordinary * double. * * \pre This triangulation is valid, closed and non-empty. * * @param r the integer r as described above; this must be at * least 3. * @param whichRoot determines q0 to be the root of unity * e^(2i * Pi * whichRoot / 2r); this argument must be strictly * between 0 and 2r and must have no common factors with r. * @return the requested Turaev-Viro invariant. * * @see allCalculatedTuraevViro */ double turaevViro(unsigned long r, unsigned long whichRoot) const; /** * Returns the set of all Turaev-Viro state sum invariants that * have already been calculated for this 3-manifold. * * Turaev-Viro invariants are described by an (r, whichRoot) * pair as described in the turaevViro() notes. The set * returned by this routine maps (r, whichRoot) pairs to the * corresponding invariant values. * * Each time turaevViro() is called, the result will be stored * in this set (as well as being returned to the user). This * set will be emptied whenever the triangulation is modified. * * \ifacespython Not present. * * @return the set of all Turaev-Viro invariants that have * already been calculated. * * @see turaevViro */ const TuraevViroSet& allCalculatedTuraevViro() const; /*@}*/ /** * \name Normal Surface Properties */ /*@{*/ /** * Determines if this triangulation is 0-efficient. * A triangulation is 0-efficient if its only normal spheres and * discs are vertex linking, and if it has no 2-sphere boundary * components. * * @return \c true if and only if this triangulation is * 0-efficient. */ bool isZeroEfficient(); /** * Is it already known whether or not this triangulation is * 0-efficient? * See isZeroEfficient() for further details. * * If this property is already known, future calls to * isZeroEfficient() will be very fast (simply returning the * precalculated value). * * \warning This routine does not actually tell you \e whether * this triangulation is 0-efficient; it merely tells you whether * the answer has already been computed. * * @return \c true if and only if this property is already known. */ bool knowsZeroEfficient() const; /** * Determines whether this triangulation has a normal splitting * surface. See NNormalSurface::isSplitting() for details * regarding normal splitting surfaces. * * \pre This triangulation is connected. If the triangulation * is not connected, this routine will still return a result but * that result will be unreliable. * * @return \c true if and only if this triangulation has a * normal splitting surface. */ bool hasSplittingSurface(); /** * Is it already known whether or not this triangulation has a * splitting surface? * See hasSplittingSurface() for further details. * * If this property is already known, future calls to * hasSplittingSurface() will be very fast (simply returning the * precalculated value). * * \warning This routine does not actually tell you \e whether * this triangulation has a splitting surface; it merely tells you * whether the answer has already been computed. * * @return \c true if and only if this property is already known. */ bool knowsSplittingSurface() const; /** * Searches for a non-vertex-linking normal sphere or disc * within this triangulation. If such a surface exists within * this triangulation, this routine is guaranteed to find one. * * Note that the surface returned (if any) depends upon this * triangulation, and so the surface must be destroyed before this * triangulation is destroyed. * * \warning This routine may, in some scenarios, temporarily modify the * packet tree by creating and then destroying a normal surface list. * * @return a newly allocated non-vertex-linking normal sphere or * disc, or 0 if none exists. */ NNormalSurface* hasNonTrivialSphereOrDisc(); /** * Searches for an octagonal almost normal 2-sphere within this * triangulation. If such a surface exists, this routine is * guaranteed to find one. * * Note that the surface returned (if any) depends upon this * triangulation, and so the surface must be destroyed before this * triangulation is destroyed. * * \pre This triangulation is valid, closed, orientable, connected, * and 0-efficient. These preconditions are almost certainly more * restrictive than they need to be, but we stay safe for now. * * \warning This routine may, in some scenarios, temporarily modify the * packet tree by creating and then destroying a normal surface list. * * @return a newly allocated non-vertex-linking normal sphere or * disc, or 0 if none exists. */ NNormalSurface* hasOctagonalAlmostNormalSphere(); /*@}*/ /** * \name Skeletal Transformations */ /*@{*/ /** * Produces a maximal forest in the 1-skeleton of the * triangulation boundary. * Both given sets will be emptied and the edges and vertices of * the maximal forest will be placed into them. * A vertex that forms its own boundary component (such as an * ideal vertex) will still be placed in \c vertexSet. * * Note that the edge and vertex pointers returned will become * invalid once the triangulation has changed. * * \ifacespython Not present. * * @param edgeSet the set to be emptied and into which the edges * of the maximal forest will be placed. * @param vertexSet the set to be emptied and into which the * vertices of the maximal forest will be placed. */ void maximalForestInBoundary(std::set& edgeSet, std::set& vertexSet) const; /** * Produces a maximal forest in the triangulation's 1-skeleton. * The given set will be emptied and will have the edges of the * maximal forest placed into it. It can be specified whether * or not different boundary components may be joined by the * maximal forest. * * An edge leading to an ideal vertex is still a * candidate for inclusion in the maximal forest. For the * purposes of this algorithm, any ideal vertex will be treated * as any other vertex (and will still be considered part of its * own boundary component). * * Note that the edge pointers returned will become * invalid once the triangulation has changed. * * \ifacespython Not present. * * @param edgeSet the set to be emptied and into which the edges * of the maximal forest will be placed. * @param canJoinBoundaries \c true if and only if different * boundary components are allowed to be joined by the maximal * forest. */ void maximalForestInSkeleton(std::set& edgeSet, bool canJoinBoundaries = true) const; /** * Produces a maximal forest in the triangulation's dual * 1-skeleton. The given set will be emptied and will have the * triangles corresponding to the edges of the maximal forest in the * dual 1-skeleton placed into it. * * Note that the triangle pointers returned will become invalid once * the triangulation has changed. * * \ifacespython Not present. * * @param triangleSet the set to be emptied and into which the * triangles representing the maximal forest will be placed. */ void maximalForestInDualSkeleton(std::set& triangleSet) const; /** * Attempts to simplify the triangulation as intelligently as * possible without further input. This routine will attempt to * reduce both the number of tetrahedra and the number of boundary * triangles (with the number of tetrahedra as its priority). * * Currently this routine uses simplifyToLocalMinimum() in * combination with random 4-4 moves, book opening moves and * book closing moves. * * \warning The specific behaviour of this routine may well * change between releases. * * \todo \opt Include random 2-3 moves to get out of wells. * * @return \c true if and only if the triangulation was changed. */ bool intelligentSimplify(); /** * Uses all known simplification moves to reduce the triangulation * monotonically to some local minimum number of tetrahedra. * Note that this will probably not give a globally minimal * triangulation; see intelligentSimplify() for further * assistance in achieving this goal. * * The moves used include 3-2, 2-0 (edge and vertex), * 2-1 and boundary shelling moves. * * Note that moves that do not reduce the number of tetrahedra * (such as 4-4 moves or book opening moves) are not used in this * routine. Such moves do however feature in intelligentSimplify(). * * \warning The specific behaviour of this routine is * very likely to change between releases. * * @param perform \c true if we are to perform the * simplifications, or \c false if we are only to investigate * whether simplifications are possible (defaults to \c true). * @return if \a perform is \c true, this routine returns * \c true if and only if the triangulation was changed to * reduce the number of tetrahedra; if \a perform is \c false, * this routine returns \c true if and only if it determines * that it is capable of performing such a change. */ bool simplifyToLocalMinimum(bool perform = true); /** * Checks the eligibility of and/or performs a 3-2 move * about the given edge. * This involves replacing the three tetrahedra joined at that * edge with two tetrahedra joined by a triangle. * This can be done iff (i) the edge is valid and non-boundary, * and (ii) the three tetrahedra are distinct. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a e) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool threeTwoMove(NEdge* e, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a 2-3 move * about the given triangle. * This involves replacing the two tetrahedra joined at that * triangle with three tetrahedra joined by an edge. * This can be done iff the two tetrahedra are distinct. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a f) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given triangle is a triangle of this triangulation. * * @param t the triangle about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool twoThreeMove(NTriangle* t, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a 4-4 move * about the given edge. * This involves replacing the four tetrahedra joined at that * edge with four tetrahedra joined along a different edge. * Consider the octahedron made up of the four original * tetrahedra; this has three internal axes. The initial four * tetrahedra meet along the given edge which forms one of these * axes; the new tetrahedra will meet along a different axis. * This move can be done iff (i) the edge is valid and non-boundary, * and (ii) the four tetrahedra are distinct. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a e) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge about which to perform the move. * @param newAxis Specifies which axis of the octahedron the new * tetrahedra should meet along; this should be 0 or 1. * Consider the four original tetrahedra in the order described * by NEdge::getEmbeddings(); call these tetrahedra 0, 1, 2 and * 3. If \a newAxis is 0, the new axis will separate tetrahedra * 0 and 1 from 2 and 3. If \a newAxis is 1, the new axis will * separate tetrahedra 1 and 2 from 3 and 0. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool fourFourMove(NEdge* e, int newAxis, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a 2-0 move * about the given edge of degree 2. * This involves taking the two tetrahedra joined at that edge * and squashing them flat. This can be done if: * * - the edge is valid and non-boundary; * * - the two tetrahedra are distinct; * * - the edges opposite \c e in each tetrahedron are distinct and * not both boundary; * * - if triangles \a f1 and \a f2 from one tetrahedron are to be * flattened onto triangles \a g1 and \a g2 of the other * respectively, then * (a) \a f1 and \a g1 are distinct, * (b) \a f2 and \a g2 are distinct, * (c) we do not have both \a f1 = \a g2 and \a g1 = \a f2, * (d) we do not have both \a f1 = \a f2 and \a g1 = \a g2, and * (e) we do not have two of the triangles boundary and the other * two identified. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a e) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool twoZeroMove(NEdge* e, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a 2-0 move * about the given vertex of degree 2. * This involves taking the two tetrahedra joined at that vertex * and squashing them flat. * This can be done if: * * - the vertex is non-boundary and has a 2-sphere vertex link; * * - the two tetrahedra are distinct; * * - the triangles opposite \c v in each tetrahedron are distinct and * not both boundary; * * - the two tetrahedra meet each other on all three faces touching * the vertex (as opposed to meeting each other on one face and * being glued to themselves along the other two). * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a v) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given vertex is a vertex of this triangulation. * * @param v the vertex about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool twoZeroMove(NVertex* v, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a 2-1 move * about the given edge. * This involves taking an edge meeting only one tetrahedron * just once and merging that tetrahedron with one of the * tetrahedra joining it. * * This can be done assuming the following conditions: * * - The edge must be valid and non-boundary. * * - The two remaining faces of the tetrahedron are not joined, and * the tetrahedron face opposite the given endpoint of the edge is * not boundary. * * - Consider the second tetrahedron to be merged (the one * joined along the face opposite the given endpoint of the edge). * Moreover, consider the two edges of this second tetrahedron * that run from the (identical) vertices of the original * tetrahedron not touching \c e to the vertex of the second * tetrahedron not touching the original tetrahedron. These edges * must be distinct and may not both be in the boundary. * * There are additional "distinct and not both boundary" conditions * on faces of the second tetrahedron, but those follow automatically * from the final condition above. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a e) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge about which to perform the move. * @param edgeEnd the end of the edge \e opposite that at * which the second tetrahedron (to be merged) is joined. * The end is 0 or 1, corresponding to the labelling (0,1) of * the vertices of the edge as described in * NEdgeEmbedding::getVertices(). * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool twoOneMove(NEdge* e, int edgeEnd, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a book opening move * about the given triangle. * This involves taking a triangle meeting the boundary along two * edges, and ungluing it to create two new boundary triangles * (thus exposing the tetrahedra it initially joined). * This move is the inverse of the closeBook() move, and is * used to open the way for new shellBoundary() moves. * * This move can be done if: * * - the triangle meets the boundary in precisely two edges (and thus * also joins two tetrahedra); * * - the vertex between these two edges is a standard boundary * vertex (its link is a disc); * * - the remaining edge of the triangle (which is internal to the * triangulation) is valid. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a f) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given triangle is a triangle of this triangulation. * * @param t the triangle about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool openBook(NTriangle* t, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a book closing move * about the given boundary edge. * This involves taking a boundary edge of the triangulation and * folding together the two boundary triangles on either side. This * move is the inverse of the openBook() move, and is used to * simplify the boundary of the triangulation. * This move can be done if: * * - the edge \a e is a boundary edge; * * - the two boundary triangles that it joins are distinct; * * - the two vertices opposite \a e in each of these boundary triangles * are valid and distinct; * * - if edges \a e1 and \a e2 of one boundary triangle are to be * folded onto edges \a f1 and \a f2 of the other boundary * triangle respectively, then we do not have both \a e1 = \a e2 * and \a f1 = \a f2. * * There are in fact several other "distinctness" conditions on * the edges \a e1, \a e2, \a f1 and \a f2, but they follow * automatically from the "distinct vertices" condition above. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a f) * can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge about which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool closeBook(NEdge* e, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a boundary shelling * move on the given tetrahedron. * This involves simply popping off a tetrahedron that touches * the boundary. * This can be done if: * * - all edges of the tetrahedron are valid; * * - precisely one, two or three faces of the tetrahedron lie in * the boundary; * * - if one face lies in the boundary, then the opposite vertex * does not lie in the boundary, and no two of the remaining three * edges are identified; * * - if two faces lie in the boundary, then the remaining edge does * not lie in the boundary, and the remaining two faces of the * tetrahedron are not identified. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects can no longer be used. * * \pre If the move is being performed and no * check is being run, it must be known in advance that the move * is legal. * \pre The given tetrahedron is a tetrahedron of this triangulation. * * @param t the tetrahedron upon which to perform the move. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the requested move may be performed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool shellBoundary(NTetrahedron* t, bool check = true, bool perform = true); /** * Checks the eligibility of and/or performs a collapse of * an edge in such a way that the topology of the manifold * does not change and the number of vertices of the triangulation * decreases by one. * * If the routine is asked to both check and perform, the move * will only be performed if the check shows it is legal. * * Note that after performing this move, all skeletal objects * (triangles, components, etc.) will be reconstructed, which means * any pointers to old skeletal objects (such as the argument \a e) * can no longer be used. * * The eligibility requirements for this move are somewhat * involved, and are discussed in detail in the collapseEdge() * source code for those who are interested. * * \pre If the move is being performed and no check is being run, * it must be known in advance that the move is legal. * \pre The given edge is an edge of this triangulation. * * @param e the edge to collapse. * @param check \c true if we are to check whether the move is * allowed (defaults to \c true). * @param perform \c true if we are to perform the move * (defaults to \c true). * @return If \a check is \c true, the function returns \c true * if and only if the given edge may be collapsed * without changing the topology of the manifold. If \a check * is \c false, the function simply returns \c true. */ bool collapseEdge(NEdge* e, bool check = true, bool perform = true); /** * Reorders the tetrahedra of this triangulation using a * breadth-first search, so that small-numbered tetrahedra are * adjacent to other small-numbered tetrahedra. * * Specifically, the reordering will operate as follows. * Tetrahedron 0 will remain tetrahedron 0. Its immediate * neighbours will be numbered 1, 2, 3 and 4 (though if these * neighbours are not distinct then of course fewer labels will * be required). Their immediate neighbours will in turn be * numbered 5, 6, and so on, ultimately following a breadth-first * search throughout the entire triangulation. * * If the optional argument \a reverse is \c true, then tetrahedron * numbers will be assigned in reverse order. That is, tetrahedron 0 * will become tetrahedron \a n-1, its neighbours will become * tetrahedra \a n-2 down to \a n-5, and so on. * * @param reverse \c true if the new tetrahedron numbers should * be assigned in reverse order, as described above. */ void reorderTetrahedraBFS(bool reverse = false); /** * Relabels tetrahedron vertices in this triangulation so that * all tetrahedra are oriented consistently, if possible. * * This routine works by flipping vertices 2 and 3 of each * tetrahedron with negative orientation. The result will be a * triangulation where the tetrahedron vertices are labelled in * a way that preserves orientation across adjacent tetrahedron faces. * In particular, every gluing permutation will have negative sign. * * If this triangulation includes both orientable and * non-orientable components, the orientable components will be * oriented as described above and the non-orientable * components will be left untouched. * * @author Matthias Goerner */ void orient(); /** * Relabels tetrahedron vertices in this triangulation to give * an ordered triangulation, if possible. * * To be an ordered triangulation, all face gluings (when restricted * to the tetrahedron face) must be order preserving. In other words, * it must be possible to orient all edges of the triangulation in * such a fashion that they are consistent with the ordering of the * vertices in each tetrahedron. * * If it is possible to order this triangulation, the vertices * of each tetrahedron will be relabelled accordingly and this * routine will return \c true. Otherwise, this routine will * return \c false and the triangulation will not be changed. * * \warning This routine may be slow, since it backtracks * through all possible edge orientations until a consistent one * has been found. * * @param forceOriented \c true if the triangulation must be * both ordered and \e oriented, in which case this routine will * return \c false if the triangulation cannot be oriented and * ordered at the same time. See orient() for further details. * @return \c true if the triangulation has been successfully ordered * as described above, or \c false if not. * * @author Matthias Goerner */ bool order(bool forceOriented = false); /*@}*/ /** * \name Decompositions */ /*@{*/ /** * Splits a disconnected triangulation into many smaller * triangulations, one for each component. The new component * triangulations will be inserted as children of the given * parent packet. The original triangulation will be left * unchanged. * * If the given parent packet is 0, the new component * triangulations will be inserted as children of this * triangulation. * * This routine can optionally assign unique (and sensible) * packet labels to each of the new component triangulations. * Note however that uniqueness testing may be slow, so this * assignment of labels should be disabled if the component * triangulations are only temporary objects used as part * of a larger routine. * * @param componentParent the packet beneath which the new * component triangulations will be inserted, or 0 if they * should be inserted directly beneath this triangulation. * @param setLabels \c true if the new component triangulations * should be assigned unique packet labels, or \c false if * they should be left without labels at all. * @return the number of new component triangulations * constructed. */ unsigned long splitIntoComponents(NPacket* componentParent = 0, bool setLabels = true); /** * Splits this triangulation into its connected sum * decomposition. The individual prime 3-manifold triangulations * that make up this decomposition will be inserted as children * of the given parent packet. The original triangulation will * be left unchanged. * * Note that this routine is currently only available for * closed orientable triangulations; see the list of * preconditions for full details. The 0-efficiency prime * decomposition algorithm of Jaco and Rubinstein is used. * * If the given parent packet is 0, the new prime summand * triangulations will be inserted as children of this * triangulation. * * This routine can optionally assign unique (and sensible) * packet labels to each of the new prime summand triangulations. * Note however that uniqueness testing may be slow, so this * assignment of labels should be disabled if the summand * triangulations are only temporary objects used as part * of a larger routine. * * If this is a triangulation of a 3-sphere, no prime summand * triangulations will be created at all. * * \warning The algorithms used in this routine rely on normal * surface theory and so can be very slow for larger triangulations. * For 3-sphere testing, see the routine isThreeSphere() which * uses faster methods where possible. * * \pre This triangulation is valid, closed, orientable and * connected. * * @param primeParent the packet beneath which the new prime * summand triangulations will be inserted, or 0 if they * should be inserted directly beneath this triangulation. * @param setLabels \c true if the new prime summand triangulations * should be assigned unique packet labels, or \c false if * they should be left without labels at all. * @return the number of prime summands created, 0 if this * triangulation is a 3-sphere or 0 if this triangulation does * not meet the preconditions described above. */ unsigned long connectedSumDecomposition(NPacket* primeParent = 0, bool setLabels = true); /** * Determines whether this is a triangulation of a 3-sphere. * * This routine relies upon a combination of Rubinstein's 3-sphere * recognition algorithm and Jaco and Rubinstein's 0-efficiency * prime decomposition algorithm. * * \warning The algorithms used in this routine rely on normal * surface theory and so can be very slow for larger * triangulations (although faster tests are used where possible). * The routine knowsThreeSphere() can be called to see if this * property is already known or if it happens to be very fast to * calculate for this triangulation. * * @return \c true if and only if this is a 3-sphere triangulation. */ bool isThreeSphere() const; /** * Is it already known (or trivial to determine) whether or not this * is a triangulation of a 3-sphere? See isThreeSphere() for * further details. * * If this property is indeed already known, future calls to * isThreeSphere() will be very fast (simply returning the * precalculated value). * * If this property is not already known, this routine will * nevertheless run some very fast preliminary tests to see if the * answer is obviously no. If so, it will store \c false as the * precalculated value for isThreeSphere() and this routine will * return \c true. * * Otherwise a call to isThreeSphere() may potentially require more * significant work, and so this routine will return \c false. * * \warning This routine does not actually tell you \e whether * this triangulation forms a 3-sphere; it merely tells you whether * the answer has already been computed (or is very easily computed). * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsThreeSphere() const; /** * Determines whether this is a triangulation of a 3-dimensional ball. * * This routine is based on isThreeSphere(), which in turn combines * Rubinstein's 3-sphere recognition algorithm with Jaco and * Rubinstein's 0-efficiency prime decomposition algorithm. * * \warning The algorithms used in this routine rely on normal * surface theory and so can be very slow for larger * triangulations (although faster tests are used where possible). * The routine knowsBall() can be called to see if this * property is already known or if it happens to be very fast to * calculate for this triangulation. * * @return \c true if and only if this is a triangulation of a * 3-dimensional ball. */ bool isBall() const; /** * Is it already known (or trivial to determine) whether or not this * is a triangulation of a 3-dimensional ball? See isBall() for * further details. * * If this property is indeed already known, future calls to isBall() * will be very fast (simply returning the precalculated value). * * If this property is not already known, this routine will * nevertheless run some very fast preliminary tests to see if the * answer is obviously no. If so, it will store \c false as the * precalculated value for isBall() and this routine will * return \c true. * * Otherwise a call to isBall() may potentially require more * significant work, and so this routine will return \c false. * * \warning This routine does not actually tell you \e whether * this triangulation forms a ball; it merely tells you whether * the answer has already been computed (or is very easily computed). * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsBall() const; /** * Converts this into a 0-efficient triangulation of the same * underlying 3-manifold. A triangulation is 0-efficient if its * only normal spheres and discs are vertex linking, and if it has * no 2-sphere boundary components. * * Note that this routine is currently only available for * closed orientable triangulations; see the list of * preconditions for details. The 0-efficiency algorithm of * Jaco and Rubinstein is used. * * If the underlying 3-manifold is prime, it can always be made * 0-efficient (with the exception of the special cases RP3 and * S2xS1 as noted below). In this case the original triangulation * will be modified directly and 0 will be returned. * * If the underyling 3-manifold is RP3 or S2xS1, it cannot * be made 0-efficient; in this case the original triangulation * will be reduced to a two-tetrahedron minimal triangulation * and 0 will again be returned. * * If the underlying 3-manifold is not prime, it cannot be made * 0-efficient. In this case the original triangulation will * remain unchanged and a new connected sum decomposition will * be returned. This will be presented as a newly allocated * container packet with one child triangulation for each prime * summand. * * \warning The algorithms used in this routine rely on normal * surface theory and so can be very slow for larger triangulations. * * \pre This triangulation is valid, closed, orientable and * connected. * * @return 0 if the underlying 3-manifold is prime (in which * case the original triangulation was modified directly), or * a newly allocated connected sum decomposition if the * underlying 3-manifold is composite (in which case the * original triangulation was not changed). */ NPacket* makeZeroEfficient(); /** * Determines whether this is a triangulation of the solid * torus; that is, the unknot complement. This routine can be * used on a triangulation with real boundary triangles, or on an * ideal triangulation (in which case all ideal vertices will * be assumed to be truncated). * * \warning The algorithms used in this routine rely on normal * surface theory and so might be very slow for larger * triangulations (although faster tests are used where possible). * The routine knowsSolidTorus() can be called to see if this * property is already known or if it happens to be very fast to * calculate for this triangulation. * * @return \c true if and only if this is either a real (compact) * or ideal (non-compact) triangulation of the solid torus. */ bool isSolidTorus() const; /** * Is it already known (or trivial to determine) whether or not this * is a triangulation of a solid torus (that is, the unknot * complement)? See isSolidTorus() for further details. * * If this property is indeed already known, future calls to * isSolidTorus() will be very fast (simply returning the * precalculated value). * * If this property is not already known, this routine will * nevertheless run some very fast preliminary tests to see if the * answer is obviously no. If so, it will store \c false as the * precalculated value for isSolidTorus() and this routine will * return \c true. * * Otherwise a call to isSolidTorus() may potentially require more * significant work, and so this routine will return \c false. * * \warning This routine does not actually tell you \e whether * this triangulation forms a solid torus; it merely tells you whether * the answer has already been computed (or is very easily computed). * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsSolidTorus() const; /** * Determines whether the underlying 3-manifold (which must be * closed) is irreducible. In other words, this routine determines * whether every embedded sphere in the underlying 3-manifold * bounds a ball. * * If the underlying 3-manifold is orientable, this routine will * use fast crushing and branch-and-bound methods. If the * underlying 3-manifold is non-orientable, it will use a * (much slower) full enumeration of vertex normal surfaces. * * \warning The algorithms used in this routine rely on normal * surface theory and might be slow for larger triangulations. * * \pre This triangulation is valid, closed, orientable and connected. * * @return \c true if and only if the underlying 3-manifold is * irreducible. */ bool isIrreducible() const; /** * Is it already known (or trivial to determine) whether or not the * underlying 3-manifold is irreducible? See isIrreducible() for * further details. * * If this property is indeed already known, future calls to * isIrreducible() will be very fast (simply returning the * precalculated value). * * \warning This routine does not actually tell you \e whether * the underlying 3-manifold is irreducible; it merely tells you whether * the answer has already been computed (or is very easily computed). * * \pre This triangulation is valid, closed, orientable and connected. * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsIrreducible() const; /** * Searches for a compressing disc within the underlying * 3-manifold. * * Let \a M be the underlying 3-manifold and let \a B be its * boundary. By a compressing disc, we mean a disc \a D * properly embedded in \a M, where the boundary of \a D * lies in \a B but does not bound a disc in \a B. * * This routine will first call the heuristic routine * hasSimpleCompressingDisc() in the hope of obtaining a fast * answer. If this fails, it will do one of two things: * * - If the triangulation is orientable and 1-vertex, it will * use the linear programming and crushing machinery outlined in * "Computing closed essential surfaces in knot complements", * Burton, Coward and Tillmann, SCG '13, p405-414, 2013. * This is often extremely fast, even for triangulations with * many tetrahedra. * * - If the triangulation is non-orientable or has multiple vertices * then it will run a full enumeration of * vertex normal surfaces, as described in "Algorithms for the * complete decomposition of a closed 3-manifold", * Jaco and Tollefson, Illinois J. Math. 39 (1995), 358-406. * As the number of tetrahedra grows, this can become extremely slow. * * This routine will work on a copy of this triangulation, not * the original. In particular, the copy will be simplified, which * means that there is no harm in calling this routine on an * unsimplified triangulation. * * If this triangulation has no boundary components, this * routine will simply return \c false. * * \pre This triangulation is valid and is not ideal. * \pre The underlying 3-manifold is irreducible. * * \warning This routine can be infeasibly slow for large * triangulations (particularly those that are non-orientable * or have multiple vertices), since it may need to perform a * full enumeration of vertex normal surfaces, and since it might * perform "large" operations on these surfaces such as cutting along * them. See hasSimpleCompressingDisc() for a "heuristic shortcut" * that is faster but might not give a definitive answer. * * @return \c true if the underlying 3-manifold contains a * compressing disc, or \c false if it does not. */ bool hasCompressingDisc() const; /** * Is it already known (or trivial to determine) whether or not * the underlying 3-manifold contains a compressing disc? * See hasCompressingDisc() for further details. * * If this property is indeed already known, future calls to * hasCompressingDisc() will be very fast (simply returning the * precalculated value). * * If this property is not already known, this routine will * nevertheless run some very fast preliminary tests to see if the * answer is obviously no. If so, it will store \c false as the * precalculated value for hasCompressingDisc() and this routine will * return \c true. * * Otherwise a call to hasCompressingDisc() may potentially require more * significant work, and so this routine will return \c false. * * \warning This routine does not actually tell you \e whether * the underlying 3-manifold has a compressing disc; it merely tells * you whether the answer has already been computed (or is very * easily computed). * * \pre This triangulation is valid and is not ideal. * \pre The underlying 3-manifold is irreducible. * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsCompressingDisc() const; /** * Determines whether the underlying 3-manifold (which * must be closed and orientable) is Haken. In other words, this * routine determines whether the underlying 3-manifold contains an * embedded closed two-sided incompressible surface. * * Currently Hakenness testing is available only for irreducible * manifolds. This routine will first test whether the manifold is * irreducible and, if it is not, will return \c false immediately. * * \pre This triangulation is valid, closed, orientable and connected. * * \warning This routine could be very slow for larger triangulations. * * @return \c true if and only if the underlying 3-manifold is * irreducible and Haken. */ bool isHaken() const; /** * Is it already known (or trivial to determine) whether or not the * underlying 3-manifold is Haken? See isHaken() for further details. * * If this property is indeed already known, future calls to * isHaken() will be very fast (simply returning the * precalculated value). * * \warning This routine does not actually tell you \e whether * the underlying 3-manifold is Haken; it merely tells you whether * the answer has already been computed (or is very easily computed). * * \pre This triangulation is valid, closed, orientable and connected. * * @return \c true if and only if this property is already known * or trivial to calculate. */ bool knowsHaken() const; /** * Searches for a "simple" compressing disc inside this * triangulation. * * Let \a M be the underlying 3-manifold and let \a B be its * boundary. By a compressing disc, we mean a disc \a D * properly embedded in \a M, where the boundary of \a D * lies in \a B but does not bound a disc in \a B. * * By a \a simple compressing disc, we mean a compressing disc * that has a very simple combinatorial structure (here "simple" * is subject to change; see the warning below). Examples * include the compressing disc inside a 1-tetrahedron solid * torus, or a compressing disc formed from a single internal triangle * surrounded by three boundary edges. * * The purpose of this routine is to avoid working with normal * surfaces within a large triangulation where possible. This * routine is relatively fast, and if it returns \c true then this * 3-manifold definitely contains a compressing disc. If this * routine returns \c false then there might or might not be a * compressing disc; the user will need to perform a full normal * surface enumeration using hasCompressingDisc() to be sure. * * This routine will work on a copy of this triangulation, not * the original. In particular, the copy will be simplified, which * means that there is no harm in calling this routine on an * unsimplified triangulation. * * If this triangulation has no boundary components, this * routine will simply return \c false. * * For further information on this test, see "The Weber-Seifert * dodecahedral space is non-Haken", Benjamin A. Burton, * J. Hyam Rubinstein and Stephan Tillmann, * Trans. Amer. Math. Soc. 364:2 (2012), pp. 911-932. * * \warning The definition of "simple" is subject to change in * future releases of Regina. That is, this routine may be * expanded over time to identify more types of compressing discs * (thus making it more useful as a "heuristic shortcut"). * * \pre This triangulation is valid and is not ideal. * * @return \c true if a simple compressing disc was found, * or \c false if not. Note that even with a return value of * \c false, there might still be a compressing disc (just not * one with a simple combinatorial structure). */ bool hasSimpleCompressingDisc() const; /*@}*/ /** * \name Subdivisions, Extensions and Covers */ /*@{*/ /** * Converts this triangulation into its double cover. * Each orientable component will be duplicated, and each * non-orientable component will be converted into its * orientable double cover. */ void makeDoubleCover(); /** * Converts an ideal triangulation into a finite triangulation. * All ideal or non-standard vertices are truncated and thus * converted into real boundary components made from unglued * faces of tetrahedra. * * Note that this operation is a loose converse of finiteToIdeal(). * * \warning Currently, this routine subdivides all tetrahedra as * if all vertices (not just some) were ideal. * This may lead to more tetrahedra than are necessary. * * \warning Currently, the presence of an invalid edge will force * the triangulation to be subdivided regardless of the value of * parameter \a forceDivision. The final triangulation will * still have the projective plane cusp caused by the invalid * edge. * * \todo \optlong Have this routine only use as many tetrahedra * as are necessary, leaving finite vertices alone. * * @param forceDivision specifies what to do if the triangulation * has no ideal or non-standard vertices. * If \c true, the triangulation will be * subdivided anyway, as if all vertices were ideal. If * \c false (the default), the triangulation will be left alone. * * @return \c true if and only if the triangulation was changed. * @author David Letscher */ bool idealToFinite(bool forceDivision = false); /** * Converts each real boundary component into a cusp (i.e., an ideal * vertex). Only boundary components formed from real * tetrahedron faces will be affected; ideal boundary components * are already cusps and so will not be changed. * * One side-effect of this operation is that all spherical * boundary components will be filled in with balls. * * This operation is performed by attaching a new tetrahedron to * each boundary triangle and then gluing these new tetrahedra * together in a way that mirrors the adjacencies of the * underlying boundary triangles. Each boundary component will * thereby be pushed up through the new tetrahedra and converted * into a cusp formed using vertices of these new tetrahedra. * * Note that this operation is a loose converse of idealToFinite(). * * \warning If a real boundary component contains vertices whose * links are not discs, this operation may have unexpected results. * * @return \c true if changes were made, or \c false if the * original triangulation contained no real boundary components. */ bool finiteToIdeal(); /** * Does a barycentric subdivision of the triangulation. * Each tetrahedron is divided into 24 tetrahedra by placing * an extra vertex at the centroid of each tetrahedron, the * centroid of each triangle and the midpoint of each edge. * * @author David Letscher */ void barycentricSubdivision(); /** * Drills out a regular neighbourhood of the given edge of the * triangulation. * * This is done by (i) performing two barycentric subdivisions, * (ii) removing all tetrahedra that touch the original edge, * and (iii) simplifying the resulting triangulation. * * \warning The second barycentric subdivision will multiply the * number of tetrahedra by 576; as a result this routine might * be slow, and the number of tetrahedra at the end might be * large (even taking the simplification into account). * * @param e the edge to drill out. */ void drillEdge(NEdge* e); /*@}*/ /** * \name Building Triangulations */ /*@{*/ /** * Performs a layering upon the given boundary edge of the * triangulation. See the NLayering class notes for further * details on what a layering entails. * * \pre The given edge is a boundary edge of this triangulation, * and the two boundary triangles on either side of it are distinct. * * @param edge the boundary edge upon which to layer. * @return the new tetrahedron provided by the layering. */ NTetrahedron* layerOn(NEdge* edge); /** * Inserts a new layered solid torus into the triangulation. * The meridinal disc of the layered solid torus will intersect * the three edges of the boundary torus in \a cuts0, \a cuts1 * and (\a cuts0 + \a cuts1) points respectively. * * The boundary torus will always consist of faces 012 and 013 of the * tetrahedron containing this boundary torus (this tetrahedron will be * returned). In face 012, edges 12, 02 and 01 will meet the meridinal * disc \a cuts0, \a cuts1 and (\a cuts0 + \a cuts1) times respectively. * The only exceptions are if these three intersection numbers are * (1,1,2) or (0,1,1), in which case edges 12, 02 and 01 will meet the * meridinal disc (1, 2 and 1) or (1, 1 and 0) times respectively. * * The new tetrahedra will be inserted at the end of the list of * tetrahedra in the triangulation. * * \pre 0 \<= \a cuts0 \<= \a cuts1; * \pre \a cuts1 is non-zero; * \pre gcd(\a cuts0, \a cuts1) = 1. * * @param cuts0 the smallest of the three desired intersection numbers. * @param cuts1 the second smallest of the three desired intersection * numbers. * @return the tetrahedron containing the boundary torus. * * @see NLayeredSolidTorus */ NTetrahedron* insertLayeredSolidTorus(unsigned long cuts0, unsigned long cuts1); /** * Inserts a new layered lens space L(p,q) into the triangulation. * The lens space will be created by gluing together two layered * solid tori in a way that uses the fewest possible tetrahedra. * * The new tetrahedra will be inserted at the end of the list of * tetrahedra in the triangulation. * * \pre \a p \> \a q \>= 0 unless (p,q) = (0,1); * \pre gcd(\a p, \a q) = 1. * * @param p a parameter of the desired lens space. * @param q a parameter of the desired lens space. * * @see NLayeredLensSpace */ void insertLayeredLensSpace(unsigned long p, unsigned long q); /** * Inserts a layered loop of the given length into this triangulation. * Layered loops are described in more detail in the NLayeredLoop * class notes. * * The new tetrahedra will be inserted at the end of the list of * tetrahedra in the triangulation. * * @param length the length of the new layered loop; this must * be strictly positive. * @param twisted \c true if the new layered loop should be twisted, * or \c false if it should be untwisted. * * @see NLayeredLoop */ void insertLayeredLoop(unsigned long length, bool twisted); /** * Inserts an augmented triangular solid torus with the given * parameters into this triangulation. Almost all augmented * triangular solid tori represent Seifert fibred spaces with three * or fewer exceptional fibres. Augmented triangular solid tori * are described in more detail in the NAugTriSolidTorus class notes. * * The resulting Seifert fibred space will be * SFS((a1,b1) (a2,b2) * (a3,b3) (1,1)), where the parameters * a1, ..., b3 are passed as arguments to this * routine. The three layered solid tori that are attached to * the central triangular solid torus will be * LST(|a1|, |b1|, |-a1-b1|), ..., * LST(|a3|, |b3|, |-a3-b3|). * * The new tetrahedra will be inserted at the end of the list of * tetrahedra in the triangulation. * * \pre gcd(\a a1, \a b1) = 1. * \pre gcd(\a a2, \a b2) = 1. * \pre gcd(\a a3, \a b3) = 1. * * @param a1 a parameter describing the first layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. * @param b1 a parameter describing the first layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. * @param a2 a parameter describing the second layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. * @param b2 a parameter describing the second layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. * @param a3 a parameter describing the third layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. * @param b3 a parameter describing the third layered solid * torus in the augmented triangular solid torus; this may be * either positive or negative. */ void insertAugTriSolidTorus(long a1, long b1, long a2, long b2, long a3, long b3); /** * Inserts an orientable Seifert fibred space with at most three * exceptional fibres over the 2-sphere into this triangulation. * * The inserted Seifert fibred space will be * SFS((a1,b1) (a2,b2) * (a3,b3) (1,1)), where the parameters * a1, ..., b3 are passed as arguments to this * routine. * * The three pairs of parameters (a,b) do not need * to be normalised, i.e., the parameters can be positive or * negative and b may lie outside the range [0..a). * There is no separate twisting parameter; each additional * twist can be incorporated into the existing parameters * by replacing some pair (a,b) with the pair * (a,a+b). For Seifert fibred * spaces with less than three exceptional fibres, some or all * of the parameter pairs may be (1,k) or even (1,0). * * The new tetrahedra will be inserted at the end of the list of * tetrahedra in the triangulation. * * \pre None of \a a1, \a a2 or \a a3 are 0. * \pre gcd(\a a1, \a b1) = 1. * \pre gcd(\a a2, \a b2) = 1. * \pre gcd(\a a3, \a b3) = 1. * * @param a1 a parameter describing the first exceptional fibre. * @param b1 a parameter describing the first exceptional fibre. * @param a2 a parameter describing the second exceptional fibre. * @param b2 a parameter describing the second exceptional fibre. * @param a3 a parameter describing the third exceptional fibre. * @param b3 a parameter describing the third exceptional fibre. */ void insertSFSOverSphere(long a1 = 1, long b1 = 0, long a2 = 1, long b2 = 0, long a3 = 1, long b3 = 0); /** * Inserts a copy of the given triangulation into this * triangulation. * * The new tetrahedra will be inserted into this triangulation * in the order in which they appear in the given triangulation, * and the numbering of their vertices (0-3) will not change. * They will be given the same descriptions as appear in the * given triangulation. * * @param source the triangulation whose copy will be inserted. */ void insertTriangulation(const NTriangulation& source); /** * Inserts the rehydration of the given string into this triangulation. * If you simply wish to convert a dehydration string into a * new triangulation, use the static routine rehydrate() instead. * See dehydrate() for more information on dehydration strings. * * This routine will first rehydrate the given string into a proper * triangulation. The tetrahedra from the rehydrated triangulation * will then be inserted into this triangulation in the same order in * which they appear in the rehydrated triangulation, and the * numbering of their vertices (0-3) will not change. * * The routine dehydrate() can be used to extract a dehydration * string from an existing triangulation. Dehydration followed * by rehydration might not produce a triangulation identical to * the original, but it is guaranteed to produce an isomorphic * copy. See dehydrate() for the reasons behind this. * * For a full description of the dehydrated triangulation * format, see A Census of Cusped Hyperbolic 3-Manifolds, * Callahan, Hildebrand and Weeks, Mathematics of Computation 68/225, * 1999. * * @param dehydration a dehydrated representation of the * triangulation to insert. Case is irrelevant; all letters * will be treated as if they were lower case. * @return \c true if the insertion was successful, or * \c false if the given string could not be rehydrated. * * @see dehydrate * @see rehydrate */ bool insertRehydration(const std::string& dehydration); /** * Inserts into this triangulation a set of tetrahedra and their * gluings as described by the given integer arrays. * * This routine is provided to make it easy to hard-code a * medium-sized triangulation in a C++ source file. All of the * pertinent data can be hard-coded into a pair of integer arrays at * the beginning of the source file, avoiding an otherwise tedious * sequence of many joinTo() calls. * * An additional \a nTetrahedra tetrahedra will be inserted into * this triangulation. The relationships between these tetrahedra * should be stored in the two arrays as follows. Note that the * new tetrahedra are numbered from 0 to (\a nTetrahedra - 1), and * individual tetrahedron faces are numbered from 0 to 3. * * The \a adjacencies array describes which tetrahedron faces are * joined to which others. Specifically, adjacencies[t][f] * should contain the number of the tetrahedron joined to face \a f * of tetrahedron \a t. If this face is to be left as a * boundary triangle, adjacencies[t][f] should be -1. * * The \a gluings array describes the particular gluing permutations * used when joining these tetrahedron faces together. Specifically, * gluings[t][f][0..3] should describe the permutation * used to join face \a f of tetrahedron \a t to its adjacent * tetrahedron. These four integers should be 0, 1, 2 and 3 in some * order, so that gluings[t][f][i] contains the image of * \a i under this permutation. If face \a f of tetrahedron \a t * is to be left as a boundary triangle, gluings[t][f][0..3] * may contain anything (and will be duly ignored). * * It is the responsibility of the caller of this routine to * ensure that the given arrays are correct and consistent. * No error checking will be performed by this routine. * * Note that, for an existing triangulation, dumpConstruction() * will output a pair of C++ arrays that can be copied into a * source file and used to reconstruct the triangulation via * this routine. * * \ifacespython Not present. * * @param nTetrahedra the number of additional tetrahedra to insert. * @param adjacencies describes which of the new tetrahedron * faces are to be identified. This array must have initial * dimension at least \a nTetrahedra. * @param gluings describes the specific gluing permutations by * which these new tetrahedron faces should be identified. This * array must also have initial dimension at least \a nTetrahedra. */ void insertConstruction(unsigned long nTetrahedra, const int adjacencies[][4], const int gluings[][4][4]); /*@}*/ /** * \name Exporting Triangulations */ /*@{*/ /** * Dehydrates this triangulation into an alphabetical string. * * A dehydration string is a compact text representation * of a triangulation, introduced by Callahan, Hildebrand and Weeks * for their cusped hyperbolic census (see below). The dehydration * string of an n-tetrahedron triangulation consists of * approximately (but not precisely) 5n/2 lower-case letters. * * Dehydration strings come with some restrictions: * - They rely on the triangulation being "canonical" in some * combinatorial sense. This is not enforced here; instead * a combinatorial isomorphism is applied to make the * triangulation canonical, and this isomorphic triangulation * is dehydrated instead. Note that the original triangulation * is not changed. * - They require the triangulation to be connected. * - They require the triangulation to have no boundary triangles * (though ideal triangulations are fine). * - They can only support triangulations with at most 25 tetrahedra. * * The routine rehydrate() can be used to recover a * triangulation from a dehydration string. Note that the * triangulation recovered might not be identical to the * original, but it is guaranteed to be an isomorphic copy. * * For a full description of the dehydrated triangulation * format, see A Census of Cusped Hyperbolic 3-Manifolds, * Callahan, Hildebrand and Weeks, Mathematics of Computation 68/225, * 1999. * * @return a dehydrated representation of this triangulation * (or an isomorphic variant of this triangulation), or the * empty string if dehydration is not possible because the * triangulation is disconnected, has boundary triangles or contains * too many tetrahedra. * * @see rehydrate * @see insertRehydration */ std::string dehydrate() const; /** * Constructs the isomorphism signature for this triangulation. * * An isomorphism signature is a compact text representation * of a triangulation. Unlike dehydrations, an isomorphism signature * uniquely determines a triangulation up to combinatorial isomorphism. * That is, two triangulations are combinatorially isomorphic if * and only if their isomorphism signatures are the same. * * The isomorphism signature is constructed entirely of * printable characters, and has length proportional to * n log n, where \a n is the number of tetrahedra. * * Isomorphism signatures are more general than dehydrations: * they can be used with any triangulation (including closed, ideal, * bounded, invalid and/or disconnected triangulations, as well * as triangulations with large numbers of tetrahedra). * * The time required to construct the isomorphism signature of a * triangulation is O(n^2 log^2 n). * * The routine fromIsoSig() can be used to recover a * triangulation from an isomorphism signature. The triangulation * recovered might not be identical to the original, but it will be * combinatorially isomorphic. * * If \a relabelling is non-null (i.e., it points to some * NIsomorphism pointer \a p), then it will be modified to point * to a new NIsomorphism that describes the precise relationship * between this triangulation and the reconstruction from fromIsoSig(). * Specifically, the triangulation that is reconstructed from * fromIsoSig() will be combinatorially identical to * relabelling.apply(this). * * For a full and precise description of the isomorphism signature * format, see Simplification paths in the Pachner graphs of * closed orientable 3-manifold triangulations, Burton, 2011, * arXiv:1110.6080. * * \ifacespython The isomorphism argument is not present. * Instead there are two routines: fromIsoSig(), which returns a * string only, and fromIsoSigDetail(), which returns a pair * (signature, relabelling). * * \pre If \a relabelling is non-null, then this triangulation * must be non-empty and connected. The facility to return a * relabelling for disconnected triangulations may be added to * Regina in a later release. * * @param relabelling if non-null, this will be modified to point to a * new isomorphism describing the relationship between this * triangulation and that reconstructed from fromIsoSig(), as * described above. * @return the isomorphism signature of this triangulation. * * @see fromIsoSig */ std::string isoSig(NIsomorphism** relabelling = 0) const; /** * Returns C++ code that can be used with insertConstruction() * to reconstruct this triangulation. * * The code produced will consist of the following: * * - the declaration and initialisation of two integer arrays, * describing the tetrahedron gluings in this trianguation; * - two additional lines that declare a new NTriangulation and * call insertConstruction() to rebuild this triangulation. * * The main purpose of this routine is to generate the two integer * arrays, which can be tedious and error-prone to code up by hand. * * Note that the number of lines of code produced grows linearly * with the number of tetrahedra. If this triangulation is very * large, the returned string will be very large as well. * * @return the C++ code that was generated. */ std::string dumpConstruction() const; /** * Returns a string containing the full contents of a SnapPea * data file that describes this triangulation. This string * can, for instance, be used to pass the triangulation to SnapPy * without writing to the filesystem. * * If you wish to export a triangulation to a SnapPea \e file, * you should use the global function writeSnapPea() instead * (which has better performance, and does not require you to * construct an enormous intermediate string). * * For details on how the SnapPea file will be constructed and * what will be included, see the documentation for writeSnapPea(). * * \pre This triangulation is not invalid, and does not contain * any boundary triangles. * * @return a string containing the contents of the corresponding * SnapPea data file. */ std::string snapPea() const; /*@}*/ /** * \name Importing Triangulations */ /*@{*/ /** * Allows the user to interactively enter a triangulation in * plain text. Prompts will be sent to the given output stream * and information will be read from the given input stream. * * \ifacespython This routine is a member of class Engine. * It takes no parameters; \a in and \a out are always assumed * to be standard input and standard output respectively. * * @param in the input stream from which text will be read. * @param out the output stream to which prompts will be * written. * @return the triangulation entered in by the user. */ static NTriangulation* enterTextTriangulation(std::istream& in, std::ostream& out); /** * Rehydrates the given alphabetical string into a new triangulation. * See dehydrate() for more information on dehydration strings. * * This routine will rehydrate the given string into a new * triangulation, and return this new triangulation. * * The converse routine dehydrate() can be used to extract a * dehydration string from an existing triangulation. Dehydration * followed by rehydration might not produce a triangulation identical * to the original, but it is guaranteed to produce an isomorphic * copy. See dehydrate() for the reasons behind this. * * For a full description of the dehydrated triangulation * format, see A Census of Cusped Hyperbolic 3-Manifolds, * Callahan, Hildebrand and Weeks, Mathematics of Computation 68/225, * 1999. * * @param dehydration a dehydrated representation of the * triangulation to construct. Case is irrelevant; all letters * will be treated as if they were lower case. * @return a newly allocated triangulation if the rehydration was * successful, or null if the given string could not be rehydrated. * * @see dehydrate * @see insertRehydration */ static NTriangulation* rehydrate(const std::string& dehydration); /** * Recovers a full triangulation from an isomorphism signature. * See isoSig() for more information on isomorphism signatures. * * The triangulation that is returned will be newly created. * * Calling isoSig() followed by fromIsoSig() is not guaranteed to * produce an identical triangulation to the original, but it * \e is guaranteed to produce a combinatorially isomorphic * triangulation. * * For a full and precise description of the isomorphism signature * format, see Simplification paths in the Pachner graphs of * closed orientable 3-manifold triangulations, Burton, 2011, * arXiv:1110.6080. * * @param signature the isomorphism signature of the * triangulation to construct. Note that, unlike dehydration * strings, case is important for isomorphism signatures. * @return a newly allocated triangulation if the reconstruction was * successful, or null if the given string was not a valid * isomorphism signature. */ static NTriangulation* fromIsoSig(const std::string& signature); using NGenericTriangulation<3>::isoSigComponentSize; /** * Extracts a triangulation from a string that contains the * full contents of a SnapPea data file. This routine could, * for instance, be used to receive a triangulation from SnapPy * without writing to the filesystem. * * If you wish to read a triangulation from a SnapPea \e file, * you should use the global function readSnapPea() instead * (which has better performance, and does not require you to * construct an enormous intermediate string). * * For details on how the triangulation will be extracted, * see the documentation for readSnapPea(). * * The triangulation that is returned will be newly created. * If the SnapPea data is not in the correct format, this * routine will return 0 instead. * * \pre The first two lines of the SnapPea file each contain at * most 1000 characters. * * @param snapPeaData a string containing the full contents of a * SnapPea data file. * @return a new triangulation extracted from the given data, * or 0 on error. */ static NTriangulation* fromSnapPea(const std::string& snapPeaData); /*@}*/ static NXMLPacketReader* getXMLReader(NPacket* parent, NXMLTreeResolver& resolver); protected: virtual NPacket* internalClonePacket(NPacket* parent) const; virtual void writeXMLPacketData(std::ostream& out) const; /** * Turns this triangulation into a clone of the given * triangulation. * The tree structure and label of this triangulation are not * touched. * * @param from the triangulation from which this triangulation * will be cloned. */ void cloneFrom(const NTriangulation& from); private: void deleteTetrahedra(); /**< Deallocates all tetrahedra and empties the list. */ void deleteSkeleton(); /**< Deallocates all skeletal objects and empties all corresponding lists. */ /** * Clears any calculated properties and declares them all * unknown. All dynamic memory used for storing known * properties is deallocated. * * In most cases this routine is followed immediately by firing * a packet change event. */ virtual void clearAllProperties(); /** * Checks that the permutations on face gluings are valid and * that adjacent face gluings match. That is, if face \a A of * tetrahedron \a X is glued to face \a B of tetrahedron \a Y, * then face \a B of tetrahedron \a Y is also glued to face \a A * of tetrahedron \a X using the inverse permutation. * * If any of these tests fail, this routine writes a message to * standard error and marks this triangulations as invalid. * Any such failure indicates a likely programming error, since * adjacent face gluings should always match if the NTetrahedron * gluing routines have been used correctly. * * @author Matthias Goerner */ void checkPermutations() const; /** * Recalculates vertices, edges, triangles, components and * boundary components, as well as various other skeletal * properties such as validity and vertex links. * All appropriate lists are filled. * * \pre All skeletal lists are empty. */ void calculateSkeleton() const; /** * Calculates the triangulation components and associated * properties. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateComponents() const; void labelComponent(NTetrahedron*, NComponent*) const; /**< Internal to calculateComponents(). */ /** * Calculates the triangulation vertices and associated * properties. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateVertices() const; void labelVertex(NTetrahedron*, int, NVertex*) const; /**< Internal to calculateVertices(). */ /** * Calculates the triangulation edges and associated * properties. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateEdges() const; void labelEdge(NTetrahedron*, int, NEdge*) const; /**< Internal to calculateEdges(). */ /** * Calculates the triangles of this triangulation and associated * properties. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateTriangles() const; /** * Calculates the triangulation boundary components and * properties of these boundary components. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateBoundary() const; void labelBoundaryTriangle(NTriangle*, NBoundaryComponent*) const; /**< Internal to calculateBoundary(). */ /** * Calculates the triangulation vertex links and associated * properties. * * \warning This should only be called from within * calculateSkeleton(). */ void calculateVertexLinks() const; /** * Calculates all properties of the triangulation relating to * its boundary components. */ void calculateBoundaryProperties() const; /** * Determines if an isomorphic copy of this triangulation is * contained within the given triangulation. * * If the argument \a completeIsomorphism is \c true, the * isomorphism must be onto and boundary complete. * That is, this triangulation must be combinatorially * isomorphic to the given triangulation. * * If the argument \a completeIsomorphism is \c false, the * isomorphism may be boundary incomplete and may or may not be * onto. That is, this triangulation must appear as a * subcomplex of the given triangulation, possibly with some * original boundary triangles joined to new tetrahedra. * * See the NIsomorphism class notes for further details * regarding boundary complete and boundary incomplete * isomorphisms. * * The isomorphisms found, if any, will be appended to the * list \a results. This list will not be emptied before * calculations begin. All isomorphisms will be newly created, * and the caller of this routine is responsible for destroying * them. * * If \a firstOnly is passed as \c true, only the first * isomorphism found (if any) will be returned, after which the * routine will return immediately. Otherwise all isomorphisms * will be returned. * * @param other the triangulation in which to search for an * isomorphic copy of this triangulation. * @param results the list in which any isomorphisms found will * be stored. * @param completeIsomorphism \c true if isomorphisms must be * onto and boundary complete, or \c false if neither of these * restrictions should be imposed. * @param firstOnly \c true if only one isomorphism should be * returned (if any), or \c false if all isomorphisms should be * returned. * @return the total number of isomorphisms found. */ unsigned long findIsomorphisms(const NTriangulation& other, std::list& results, bool completeIsomorphism, bool firstOnly) const; /** * Internal to findIsomorphisms(). * * Examines properties of the given tetrahedra to find any * immediate evidence that \a src may not map to \a dest in a * boundary complete isomorphism (in which the vertices of \a src * are mapped to the vertices of \a dest according to the * permutation \a p). * * In particular, properties such as edge degrees and vertex links * are examined. * * @param src the first of the two tetrahedra to examine. * @param dest the second of the two tetrahedra to examine. * @param p the permutation under which the vertices of \a src * must map to the vertices of \a dest. * @return \c true if no immediate incompatibilities between the * tetrahedra were found, or \c false if properties of the * tetrahedra were found that differ between \a src and \a dest. */ static bool compatibleTets(NTetrahedron* src, NTetrahedron* dest, NPerm4 p); void stretchBoundaryForestFromVertex(NVertex*, std::set&, std::set&) const; /**< Internal to maximalForestInBoundary(). */ bool stretchForestFromVertex(NVertex*, std::set&, std::set&, std::set&) const; /**< Internal to maximalForestInSkeleton(). */ void stretchDualForestFromTet(NTetrahedron*, std::set&, std::set&) const; /**< Internal to maximalForestInDualSkeleton(). */ friend class regina::NTetrahedron; friend class regina::NXMLTriangulationReader; }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/ntetrahedron.h" #include "triangulation/ntriangle.h" #include "triangulation/nedge.h" #include "triangulation/nvertex.h" #include "triangulation/ncomponent.h" #include "triangulation/nboundarycomponent.h" namespace regina { // Inline functions for NTriangulation inline NTriangulation::NTriangulation() : calculatedSkeleton(false) { } inline NTriangulation::NTriangulation(const NTriangulation& cloneMe) : NPacket(), calculatedSkeleton(false) { cloneFrom(cloneMe); } inline NTriangulation::~NTriangulation() { clearAllProperties(); deleteTetrahedra(); } inline NPacket* NTriangulation::internalClonePacket(NPacket*) const { return new NTriangulation(*this); } inline bool NTriangulation::dependsOnParent() const { return false; } inline unsigned long NTriangulation::getNumberOfTetrahedra() const { return tetrahedra.size(); } inline unsigned long NTriangulation::getNumberOfSimplices() const { return tetrahedra.size(); } inline NTetrahedron* NTriangulation::getTetrahedron(unsigned long index) { return tetrahedra[index]; } inline NTetrahedron* NTriangulation::getSimplex(unsigned long index) { return tetrahedra[index]; } inline const NTetrahedron* NTriangulation::getTetrahedron(unsigned long index) const { return tetrahedra[index]; } inline const NTetrahedron* NTriangulation::getSimplex(unsigned long index) const { return tetrahedra[index]; } inline long NTriangulation::tetrahedronIndex(const NTetrahedron* tet) const { return tet->markedIndex(); } inline long NTriangulation::simplexIndex(const NTetrahedron* tet) const { return tet->markedIndex(); } inline NTetrahedron* NTriangulation::newTetrahedron() { ChangeEventSpan span(this); NTetrahedron* tet = new NTetrahedron(); tet->tri = this; tetrahedra.push_back(tet); clearAllProperties(); return tet; } inline NTetrahedron* NTriangulation::newSimplex() { return newTetrahedron(); } inline NTetrahedron* NTriangulation::newTetrahedron(const std::string& desc) { ChangeEventSpan span(this); NTetrahedron* tet = new NTetrahedron(desc); tet->tri = this; tetrahedra.push_back(tet); clearAllProperties(); return tet; } inline NTetrahedron* NTriangulation::newSimplex(const std::string& desc) { return newTetrahedron(desc); } inline void NTriangulation::removeTetrahedronAt(unsigned long index) { ChangeEventSpan span(this); NTetrahedron* ans = tetrahedra[index]; ans->isolate(); tetrahedra.erase(tetrahedra.begin() + index); delete ans; clearAllProperties(); } inline void NTriangulation::removeSimplexAt(unsigned long index) { removeTetrahedronAt(index); } inline void NTriangulation::removeTetrahedron(NTetrahedron* tet) { ChangeEventSpan span(this); tet->isolate(); tetrahedra.erase(tetrahedra.begin() + tetrahedronIndex(tet)); delete tet; clearAllProperties(); } inline void NTriangulation::removeSimplex(NTetrahedron* tet) { removeTetrahedron(tet); } inline void NTriangulation::removeAllTetrahedra() { ChangeEventSpan span(this); deleteTetrahedra(); clearAllProperties(); } inline void NTriangulation::removeAllSimplices() { removeAllTetrahedra(); } inline void NTriangulation::gluingsHaveChanged() { } inline unsigned long NTriangulation::getNumberOfBoundaryComponents() const { if (! calculatedSkeleton) calculateSkeleton(); return boundaryComponents.size(); } inline unsigned long NTriangulation::getNumberOfComponents() const { if (! calculatedSkeleton) calculateSkeleton(); return components.size(); } inline unsigned long NTriangulation::getNumberOfVertices() const { if (! calculatedSkeleton) calculateSkeleton(); return vertices.size(); } inline unsigned long NTriangulation::getNumberOfEdges() const { if (! calculatedSkeleton) calculateSkeleton(); return edges.size(); } inline unsigned long NTriangulation::getNumberOfTriangles() const { if (! calculatedSkeleton) calculateSkeleton(); return triangles.size(); } inline unsigned long NTriangulation::getNumberOfFaces() const { return getNumberOfTriangles(); } template <> inline unsigned long NTriangulation::getNumberOfFaces<0>() const { return getNumberOfVertices(); } template <> inline unsigned long NTriangulation::getNumberOfFaces<1>() const { return getNumberOfEdges(); } template <> inline unsigned long NTriangulation::getNumberOfFaces<2>() const { return getNumberOfTriangles(); } template <> inline unsigned long NTriangulation::getNumberOfFaces<3>() const { return getNumberOfTetrahedra(); } inline long NTriangulation::getEulerCharTri() const { if (! calculatedSkeleton) calculateSkeleton(); // Cast away the unsignedness of std::vector::size(). return static_cast(vertices.size()) - static_cast(edges.size()) + static_cast(triangles.size()) - static_cast(tetrahedra.size()); } inline long NTriangulation::getEulerCharacteristic() const { return getEulerCharTri(); } inline const std::vector& NTriangulation::getTetrahedra() const { return (const std::vector&)(tetrahedra); } inline const std::vector& NTriangulation::getSimplices() const { return (const std::vector&)(tetrahedra); } inline const std::vector& NTriangulation::getBoundaryComponents() const { if (! calculatedSkeleton) calculateSkeleton(); return (const std::vector&)(boundaryComponents); } inline const std::vector& NTriangulation::getComponents() const { if (! calculatedSkeleton) calculateSkeleton(); return (const std::vector&)(components); } inline const std::vector& NTriangulation::getVertices() const { if (! calculatedSkeleton) calculateSkeleton(); return (const std::vector&)(vertices); } inline const std::vector& NTriangulation::getEdges() const { if (! calculatedSkeleton) calculateSkeleton(); return (const std::vector&)(edges); } inline const std::vector& NTriangulation::getTriangles() const { if (! calculatedSkeleton) calculateSkeleton(); return (const std::vector&)(triangles); } inline const std::vector& NTriangulation::getFaces() const { return getTriangles(); } inline NComponent* NTriangulation::getComponent(unsigned long index) const { if (! calculatedSkeleton) calculateSkeleton(); return components[index]; } inline NBoundaryComponent* NTriangulation::getBoundaryComponent( unsigned long index) const { if (! calculatedSkeleton) calculateSkeleton(); return boundaryComponents[index]; } inline NVertex* NTriangulation::getVertex(unsigned long index) const { if (! calculatedSkeleton) calculateSkeleton(); return vertices[index]; } inline NEdge* NTriangulation::getEdge(unsigned long index) const { if (! calculatedSkeleton) calculateSkeleton(); return edges[index]; } inline NTriangle* NTriangulation::getTriangle(unsigned long index) const { if (! calculatedSkeleton) calculateSkeleton(); return triangles[index]; } inline NTriangle* NTriangulation::getFace(unsigned long index) const { return getTriangle(index); } inline long NTriangulation::componentIndex(const NComponent* component) const { return component->markedIndex(); } inline long NTriangulation::boundaryComponentIndex( const NBoundaryComponent* boundaryComponent) const { return boundaryComponent->markedIndex(); } inline long NTriangulation::vertexIndex(const NVertex* vertex) const { return vertex->markedIndex(); } inline long NTriangulation::edgeIndex(const NEdge* edge) const { return edge->markedIndex(); } inline long NTriangulation::triangleIndex(const NTriangle* tri) const { return tri->markedIndex(); } inline long NTriangulation::faceIndex(const NTriangle* tri) const { return tri->markedIndex(); } inline bool NTriangulation::hasTwoSphereBoundaryComponents() const { if (! twoSphereBoundaryComponents.known()) calculateBoundaryProperties(); return twoSphereBoundaryComponents.value(); } inline bool NTriangulation::hasNegativeIdealBoundaryComponents() const { if (! negativeIdealBoundaryComponents.known()) calculateBoundaryProperties(); return negativeIdealBoundaryComponents.value(); } inline bool NTriangulation::isValid() const { if (! calculatedSkeleton) calculateSkeleton(); return valid; } inline bool NTriangulation::isIdeal() const { if (! calculatedSkeleton) calculateSkeleton(); return ideal; } inline bool NTriangulation::isStandard() const { if (! calculatedSkeleton) calculateSkeleton(); return standard; } inline bool NTriangulation::hasBoundaryTriangles() const { if (! calculatedSkeleton) calculateSkeleton(); return (triangles.size() > 2 * tetrahedra.size()); } inline bool NTriangulation::hasBoundaryFaces() const { return hasBoundaryTriangles(); } inline bool NTriangulation::isClosed() const { if (! calculatedSkeleton) calculateSkeleton(); return boundaryComponents.empty(); } inline bool NTriangulation::isOrientable() const { if (! calculatedSkeleton) calculateSkeleton(); return orientable; } inline bool NTriangulation::isConnected() const { if (! calculatedSkeleton) calculateSkeleton(); return (components.size() <= 1); } inline void NTriangulation::simplifiedFundamentalGroup( NGroupPresentation* newGroup) { fundamentalGroup = newGroup; } inline bool NTriangulation::knowsZeroEfficient() const { return zeroEfficient.known(); } inline bool NTriangulation::knowsSplittingSurface() const { return splittingSurface.known(); } inline unsigned long NTriangulation::getHomologyH2Z2() const { return getHomologyH1Rel().getRank() + getHomologyH1Rel().getTorsionRank(2); } inline const NTriangulation::TuraevViroSet& NTriangulation::allCalculatedTuraevViro() const { return turaevViroCache; } inline void NTriangulation::writeTextShort(std::ostream& out) const { out << "Triangulation with " << tetrahedra.size() << (tetrahedra.size() == 1 ? " tetrahedron" : " tetrahedra"); } inline std::string NTriangulation::isoSig(NIsomorphism** relabelling) const { return NGenericTriangulation<3>::isoSig(*this, relabelling); } inline NTriangulation* NTriangulation::fromIsoSig( const std::string& signature) { return NGenericTriangulation<3>::fromIsoSig(signature); } } // namespace regina #endif regina-4.95/engine/triangulation/nvertex.cpp000644 000765 000024 00000014301 12234011536 021127 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "dim2/dim2triangulation.h" #include "maths/permconv.h" #include "triangulation/nisomorphism.h" #include "triangulation/nvertex.h" #include namespace regina { const int NVertex::SPHERE = 1; const int NVertex::DISC = 2; const int NVertex::TORUS = 3; const int NVertex::KLEIN_BOTTLE = 4; const int NVertex::NON_STANDARD_CUSP = 5; const int NVertex::NON_STANDARD_BDRY = 6; NVertex::~NVertex() { delete linkTri; } void NVertex::writeTextShort(std::ostream& out) const { switch(link) { case SPHERE: out << "Internal "; break; case DISC: out << "Boundary "; break; case TORUS: out << "Torus cusp "; break; case KLEIN_BOTTLE: out << "Klein bottle cusp "; break; case NON_STANDARD_CUSP: out << "Non-standard cusp "; break; case NON_STANDARD_BDRY: out << "Non-standard boundary "; break; } out << "vertex of degree " << getNumberOfEmbeddings(); } void NVertex::writeTextLong(std::ostream& out) const { writeTextShort(out); out << std::endl; out << "Appears as:" << std::endl; std::vector::const_iterator it; for (it = embeddings.begin(); it != embeddings.end(); ++it) out << " " << it->getTetrahedron()->markedIndex() << " (" << it->getVertex() << ')' << std::endl; } Dim2Triangulation* NVertex::buildLinkDetail(bool labels, NIsomorphism** inclusion) const { // Build the triangulation. Dim2Triangulation* ans = new Dim2Triangulation(); NPacket::ChangeEventSpan span(ans); if (inclusion) *inclusion = new NIsomorphism(embeddings.size()); std::vector::const_iterator it, adjIt; Dim2Triangle* tTri; int i; for (it = embeddings.begin(), i = 0; it != embeddings.end(); ++it, ++i) { tTri = ans->newTriangle(); if (labels) { std::stringstream s; s << it->getTetrahedron()->markedIndex() << " (" << it->getVertex() << ')'; tTri->setDescription(s.str()); } if (inclusion) { (*inclusion)->tetImage(i) = it->getTetrahedron()->markedIndex(); (*inclusion)->facetPerm(i) = it->getTetrahedron()-> getTriangleMapping(it->getVertex()); } } NTetrahedron *tet, *adj; int exitTri, v; int edgeInLink; int adjIndex; int adjVertex; for (it = embeddings.begin(), i = 0; it != embeddings.end(); ++it, ++i) { tet = it->getTetrahedron(); v = it->getVertex(); for (exitTri = 0; exitTri < 4; ++exitTri) { if (exitTri == v) continue; adj = tet->adjacentTetrahedron(exitTri); if (! adj) continue; edgeInLink = tet->getTriangleMapping(v).preImageOf(exitTri); if (ans->getTriangle(i)->adjacentTriangle(edgeInLink)) { // We've already made this gluing in the vertex link // from the other side. continue; } adjVertex = tet->adjacentGluing(exitTri)[v]; // TODO: We need to find which *embedding* corresponds to // the adjacent tetrahedron/vertex pair. // Currently we do a simple linear scan, which makes the // overall link construction quadratic. This can surely be // made linear(ish) with the right data structure and/or algorithm. for (adjIt = embeddings.begin(), adjIndex = 0; adjIt != embeddings.end(); ++adjIt, ++adjIndex) if (adjIt->getTetrahedron() == adj && adjIt->getVertex() == adjVertex) break; // Sets adjIndex to the right value. ans->getTriangle(i)->joinTo(edgeInLink, ans->getTriangle(adjIndex), perm4to3(adj->getTriangleMapping(adjVertex).inverse() * tet->adjacentGluing(exitTri) * tet->getTriangleMapping(v))); } } return ans; } } // namespace regina regina-4.95/engine/triangulation/nvertex.h000644 000765 000024 00000051251 12234011536 020601 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nvertex.h * \brief Deals with vertices in a triangulation. */ #ifndef __NVERTEX_H #ifndef __DOXYGEN #define __NVERTEX_H #endif #include #include "regina-core.h" #include "shareableobject.h" #include "maths/nperm4.h" #include "utilities/nmarkedvector.h" // NOTE: More #includes follow after the class declarations. namespace regina { class Dim2Triangulation; class NBoundaryComponent; class NComponent; class NIsomorphism; class NTetrahedron; class NTriangulation; /** * \weakgroup triangulation * @{ */ /** * Details how a vertex in the skeleton forms part of an individual * tetrahedron. */ class REGINA_API NVertexEmbedding { private: NTetrahedron* tetrahedron; /**< The tetrahedron in which this vertex is contained. */ int vertex; /**< The vertex number of the tetrahedron that is this vertex. */ public: /** * Default constructor. The embedding descriptor created is * unusable until it has some data assigned to it using * operator =. * * \ifacespython Not present. */ NVertexEmbedding(); /** * Creates an embedding descriptor containing the given data. * * @param newTet the tetrahedron in which this vertex is * contained. * @param newVertex the vertex number of \a newTet that is this vertex. */ NVertexEmbedding(NTetrahedron* newTet, int newVertex); /** * Creates an embedding descriptor containing the same data as * the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ NVertexEmbedding(const NVertexEmbedding& cloneMe); /** * Assigns to this embedding descriptor the same data as is * contained in the given embedding descriptor. * * @param cloneMe the embedding descriptor to clone. */ NVertexEmbedding& operator =(const NVertexEmbedding& cloneMe); /** * Returns the tetrahedron in which this vertex is contained. * * @return the tetrahedron. */ NTetrahedron* getTetrahedron() const; /** * Returns the vertex number within getTetrahedron() that is * this vertex. * * @return the vertex number that is this vertex. */ int getVertex() const; /** * Returns a permutation that maps 0 to the vertex number within * getTetrahedron() that is this vertex. The real point of this * routine is that (1,2,3) maps to the three remaining tetrahedron * vertices in a manner that preserves orientation as you walk around * the vertex (assuming this is actually possible). See * NTetrahedron::getVertexMapping() for details. * * @return a permutation that maps 0 to the vertex number that * is this vertex. */ NPerm4 getVertices() const; }; /** * Represents a vertex in the skeleton of a triangulation. * Vertices are highly temporary; once a triangulation changes, all its * vertex objects will be deleted and new ones will be created. * * \testpart */ class REGINA_API NVertex : public ShareableObject, public NMarkedElement { public: static const int SPHERE; /**< Specifies a vertex link that is a sphere. */ static const int DISC; /**< Specifies a vertex link that is a disc. */ static const int TORUS; /**< Specifies a vertex link that is a torus. */ static const int KLEIN_BOTTLE; /**< Specifies a vertex link that is a Klein bottle. */ static const int NON_STANDARD_CUSP; /**< Specifies a vertex link that is closed and is not a sphere, torus or Klein bottle. */ static const int NON_STANDARD_BDRY; /**< Specifies a vertex link that has boundary and is not a disc. */ private: std::vector embeddings; /**< A list of descriptors telling how this vertex forms a part of each individual tetrahedron that it belongs to. */ NComponent* component; /**< The component that this vertex is a part of. */ NBoundaryComponent* boundaryComponent; /**< The boundary component that this vertex is a part of, or 0 if this vertex is internal. */ int link; /**< Specifies the link of the vertex according to one of the predefined vertex link constants in NVertex. */ bool linkOrientable; /**< Specifies whether the vertex link is orientable. */ long linkEulerCharacteristic; /**< Specifies the Euler characteristic of the vertex link. */ Dim2Triangulation* linkTri; /**< A triangulation of the vertex link. This will only be constructed on demand; until then it will be null. */ public: /** * Default destructor. */ virtual ~NVertex(); /** * Returns the list of descriptors detailing how this vertex forms a * part of various tetrahedra in the triangulation. * Note that if this vertex represents multiple vertices of a * particular tetrahedron, then there will be multiple embedding * descriptors in the list regarding that tetrahedron. * * \ifacespython This routine returns a python list. * * @return the list of embedding descriptors. * @see NVertexEmbedding */ const std::vector& getEmbeddings() const; /** * Returns the number of descriptors in the list returned by * getEmbeddings(). Note that this is identical to getDegree(). * * @return the number of embedding descriptors. */ unsigned long getNumberOfEmbeddings() const; /** * Returns the requested descriptor from the list returned by * getEmbeddings(). * * @param index the index of the requested descriptor. This * should be between 0 and getNumberOfEmbeddings()-1 inclusive. * @return the requested embedding descriptor. */ const NVertexEmbedding& getEmbedding(unsigned long index) const; /** * Returns the triangulation to which this vertex belongs. * * @return the triangulation containing this vertex. */ NTriangulation* getTriangulation() const; /** * Returns the component of the triangulation to which this * vertex belongs. * * @return the component containing this vertex. */ NComponent* getComponent() const; /** * Returns the boundary component of the triangulation to which * this vertex belongs. * * See the note in the NBoundaryComponent overview regarding what * happens if the vertex link is a multiply punctured surface. * Note that this makes the vertex non-standard and the * triangulation invalid. * * An ideal vertex will have its own individual boundary * component to which it belongs. * * @return the boundary component containing this vertex, * or 0 if this vertex is not on the boundary of the triangulation * as determined by isBoundary(). */ NBoundaryComponent* getBoundaryComponent() const; /** * Returns the degree of this vertex. Note that this is * identical to getNumberOfEmbeddings(). * * @return the degree of this vertex. */ unsigned long getDegree() const; /** * Returns a description of the link of the vertex. * * This routine does not require a full triangulation of the * vertex link, and so can be much faster than analysing the * result of buildLink(). * * @return one of the predefined link constants in NVertex. */ int getLink() const; /** * Returns a full 2-manifold triangulation describing * the link of this vertex. * * This routine is fast (it uses a pre-computed triangulation if * possible). The downside is that the triangulation is read-only, * and does not contain any information on how the triangles in the * link correspond to tetrahedra in the original triangulation * (though this is easily deduced; see below). * If you want a writable triangulation, or one with this extra * information, then call buildLinkDetail() instead. * * The triangulation of the vertex link is built as follows. * Let \a i lie between 0 and getDegree()-1 inclusive, let * \a tet represent getEmbedding(i).getTetrahedron(), * and let \a v represent getEmbedding(i).getVertex(). * Then buildLink()->getTriangle(i) is the triangle * in the vertex link that "slices off" vertex \a v from * tetrahedron \a tet. In other words, * buildLink()->getTriangle(i) in the vertex link * is parallel to triangle tet->getTriangle(v) in the * surrounding 3-manifold triangulation. * * The vertices of each triangle in the vertex link are * numbered as follows. Following the discussion above, * suppose that buildLink()->getTriangle(i) sits within * \c tet and is parallel to tet->getTriangle(v). * Then vertices 0,1,2 of the triangle in the link will be * parallel to vertices 0,1,2 of the corresponding NTriangle. * The permutation tet->getTriangleMapping(v) will map * vertices 0,1,2 of the triangle in the link to the * corresponding vertices of \c tet (those opposite \c v), * and will map 3 to \c v itself. * * This NVertex object will retain ownership of the triangulation * that is returned. If you wish to edit the triangulation, you * should make a new clone and edit the clone instead. * * @return the read-only triangulated link of the vertex. */ const Dim2Triangulation* buildLink() const; /** * Returns a full 2-manifold triangulation describing * the link of this vertex. * * This routine is heavyweight (it computes a new triangulation * each time). The benefit is that the triangulation is writeable, * and optionally contain detailed information on how the triangles * in the link correspond to tetrahedra in the original triangulation. * If you do not need this extra information, consider using the * faster buildLink() instead. * * See the buildLink() documentation for an explanation of * exactly how the triangulation will be constructed. * * If \a labels is passed as \c true, each triangle of the new * vertex link will be given a text description of the form * t (v), where \c t is the index of the tetrahedron * the triangle is from, and \c v is the vertex of that tetrahedron * that this triangle links. * * If \a inclusion is non-null (i.e., it points to some * NIsomorphism pointer \a p), then it will be modified to * point to a new NIsomorphism that describes in detail how the * individual triangles of the link sit within tetrahedra of * the original triangulation. Specifically, after this routine * is called, p->tetImage(i) will indicate which tetrahedron * \a tet of the 3-manifold triangulation contains the ith * triangle of the link. Moreover, p->facePerm(i) will * indicate exactly where the ith triangle sits within \a tet: * it will send 3 to the vertex of \a t that the triangle links, * and it will send 0,1,2 to the vertices of \a tet that are * parallel to vertices 0,1,2 of this triangle. * * The triangulation that is returned, as well as the isomorphism * if one was requested, will be newly allocated. The caller of * this routine is responsible for destroying these objects. * * Strictly speaking, this is an abuse of the NIsomorphism class * (the domain is a triangulation of the wrong dimension, and * the map is not 1-to-1 into the range tetrahedra). We use * it anyway, but you should not attempt to call any high-level * routines (such as NIsomorphism::apply). * * \ifacespython The second (isomorphism) argument is not present. * Instead this routine returns a pair (triangulation, isomorphism). * As a side-effect, the isomorphism will always be constructed * (i.e., it is not optional). * * @return a newly constructed triangulation of the link of this vertex. */ Dim2Triangulation* buildLinkDetail(bool labels = true, NIsomorphism** inclusion = 0) const; /** * Determines if the link of this vertex is closed. * * @return \c true if and only if the link of this vertex is * closed. */ bool isLinkClosed() const; /** * Determines if this vertex is an ideal vertex. * This requires the vertex link to be closed and not a * 2-sphere. * * @return \c true if and only if this is an ideal vertex. */ bool isIdeal() const; /** * Determines if this vertex lies on the boundary of the * triangulation. Ideal vertices are included as * being on the boundary. In fact, the only vertices not * considered as on the boundary are those whose links are * spheres. * * @return \c true if and only if this vertex lies on the boundary. * @see isIdeal() */ bool isBoundary() const; /** * Determines if this vertex is standard. * This requires the vertex link to be a sphere, disc, torus or * Klein bottle. * * @return \c true if and only if this vertex is standard. */ bool isStandard() const; /** * Determines if the vertex link is orientable. * * This routine does not require a full triangulation of the * vertex link, and so can be much faster than calling * buildLink().isOrientable(). * * @return \c true if and only if the vertex link is orientable. */ bool isLinkOrientable() const; /** * Returns the Euler characteristic of the vertex link. * * This routine does not require a full triangulation of the * vertex link, and so can be much faster than calling * buildLink().getEulerChar(). * * @return the Euler characteristic of the vertex link. */ long getLinkEulerCharacteristic() const; void writeTextShort(std::ostream& out) const; void writeTextLong(std::ostream& out) const; private: /** * Creates a new vertex and marks it as belonging to the * given triangulation component. * * @param myComponent the triangulation component to which this * vertex belongs. */ NVertex(NComponent* myComponent); friend class NTriangulation; /**< Allow access to private members. */ }; /*@}*/ } // namespace regina // Some more headers that are required for inline functions: #include "triangulation/ntetrahedron.h" namespace regina { // Inline functions for NVertex inline NVertex::NVertex(NComponent* myComponent) : component(myComponent), boundaryComponent(0), linkOrientable(true), linkEulerCharacteristic(0), linkTri(0) { } inline NTriangulation* NVertex::getTriangulation() const { return embeddings.front().getTetrahedron()->getTriangulation(); } inline NComponent* NVertex::getComponent() const { return component; } inline NBoundaryComponent* NVertex::getBoundaryComponent() const { return boundaryComponent; } inline unsigned long NVertex::getDegree() const { return embeddings.size(); } inline int NVertex::getLink() const { return link; } inline const Dim2Triangulation* NVertex::buildLink() const { if (! linkTri) { // This is a construct-on-demand member: cast away constness to // set it here. const_cast(this)->linkTri = buildLinkDetail(false, 0); } return linkTri; } inline bool NVertex::isLinkClosed() const { return (link != DISC && link != NON_STANDARD_BDRY); } inline bool NVertex::isIdeal() const { return (link == TORUS || link == KLEIN_BOTTLE || link == NON_STANDARD_CUSP); } inline bool NVertex::isBoundary() const { return (boundaryComponent != 0); } inline bool NVertex::isStandard() const { return (link != NON_STANDARD_CUSP && link != NON_STANDARD_BDRY); } inline bool NVertex::isLinkOrientable() const { return linkOrientable; } inline long NVertex::getLinkEulerCharacteristic() const { return linkEulerCharacteristic; } inline const std::vector& NVertex::getEmbeddings() const { return embeddings; } inline unsigned long NVertex::getNumberOfEmbeddings() const { return embeddings.size(); } inline const NVertexEmbedding& NVertex::getEmbedding(unsigned long index) const { return embeddings[index]; } inline NVertexEmbedding::NVertexEmbedding() : tetrahedron(0) { } inline NVertexEmbedding::NVertexEmbedding(NTetrahedron* newTet, int newVertex) : tetrahedron(newTet), vertex(newVertex) { } inline NVertexEmbedding::NVertexEmbedding(const NVertexEmbedding& cloneMe) : tetrahedron(cloneMe.tetrahedron), vertex(cloneMe.vertex) { } inline NVertexEmbedding& NVertexEmbedding::operator = (const NVertexEmbedding& cloneMe) { tetrahedron = cloneMe.tetrahedron; vertex = cloneMe.vertex; return *this; } inline NTetrahedron* NVertexEmbedding::getTetrahedron() const { return tetrahedron; } inline int NVertexEmbedding::getVertex() const { return vertex; } inline NPerm4 NVertexEmbedding::getVertices() const { return tetrahedron->getVertexMapping(vertex); } } // namespace regina #endif regina-4.95/engine/triangulation/nxmltrireader.cpp000644 000765 000024 00000025266 12236524106 022334 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "algebra/nxmlalgebrareader.h" #include "triangulation/nxmltrireader.h" #include "utilities/stringutils.h" namespace regina { /** * A unique namespace containing various specific-task packet readers. */ namespace { /** * Reads a single tetrahedron and its name and gluings. */ class NTetrahedronReader : public NXMLElementReader { private: NTriangulation* tri; NTetrahedron* tet; public: NTetrahedronReader(NTriangulation* newTri, unsigned whichTet) : tri(newTri), tet(tri->getTetrahedra()[whichTet]) { } virtual void startElement(const std::string&, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { tet->setDescription(props.lookup("desc")); } virtual void initialChars(const std::string& chars) { std::vector tokens; if (basicTokenise(back_inserter(tokens), chars) != 8) return; long tetIndex, permCode; NPerm4 perm; NTetrahedron* adjTet; int adjFace; for (int k = 0; k < 4; k ++) { if (! valueOf(tokens[2 * k], tetIndex)) continue; if (! valueOf(tokens[2 * k + 1], permCode)) continue; if (tetIndex < 0 || tetIndex >= static_cast(tri->getNumberOfTetrahedra())) continue; if (! NPerm4::isPermCode( static_cast(permCode))) continue; perm.setPermCode(static_cast(permCode)); adjTet = tri->getTetrahedra()[tetIndex]; adjFace = perm[k]; if (adjTet == tet && adjFace == k) continue; if (tet->adjacentTetrahedron(k)) continue; if (adjTet->adjacentTetrahedron(adjFace)) continue; tet->joinTo(k, adjTet, perm); } } }; /** * Reads an entire set of tetrahedra with their names and gluings. */ class NTetrahedraReader : public NXMLElementReader { private: NTriangulation* tri; unsigned readTets; public: NTetrahedraReader(NTriangulation* newTri) : tri(newTri), readTets(0) { } virtual void startElement(const std::string& /* tagName */, const regina::xml::XMLPropertyDict& props, NXMLElementReader*) { long nTets; if (valueOf(props.lookup("ntet"), nTets)) for ( ; nTets > 0; nTets--) tri->newTetrahedron(); } virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "tet") { if (readTets < tri->getNumberOfTetrahedra()) return new NTetrahedronReader(tri, readTets++); else return new NXMLElementReader(); } else return new NXMLElementReader(); } }; /** * Reads an abelian group property. */ class NAbelianGroupPropertyReader : public NXMLElementReader { public: typedef NProperty PropType; private: PropType& prop; public: NAbelianGroupPropertyReader(PropType& newProp) : prop(newProp) { } virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "abeliangroup") if (! prop.known()) return new NXMLAbelianGroupReader(); return new NXMLElementReader(); } virtual void endSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "abeliangroup") { NAbelianGroup* ans = dynamic_cast(subReader)-> getGroup(); if (ans) prop = ans; } } }; /** * Reads a group presentation property. */ class NGroupPresentationPropertyReader : public NXMLElementReader { public: typedef NProperty PropType; private: PropType& prop; public: NGroupPresentationPropertyReader(PropType& newProp) : prop(newProp) { } virtual NXMLElementReader* startSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict&) { if (subTagName == "group") if (! prop.known()) return new NXMLGroupPresentationReader(); return new NXMLElementReader(); } virtual void endSubElement(const std::string& subTagName, NXMLElementReader* subReader) { if (subTagName == "group") { NGroupPresentation* ans = dynamic_cast(subReader)-> getGroup(); if (ans) prop = ans; } } }; } NXMLElementReader* NXMLTriangulationReader::startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& props) { // We don't read boundary component properties since they're stored // across multiple property tags and they're easy to calculate // anyway. if (subTagName == "tetrahedra") return new NTetrahedraReader(tri); else if (subTagName == "zeroeff") { bool b; if (valueOf(props.lookup("value"), b)) tri->zeroEfficient = b; } else if (subTagName == "splitsfce") { bool b; if (valueOf(props.lookup("value"), b)) tri->splittingSurface = b; } else if (subTagName == "threesphere") { bool b; if (valueOf(props.lookup("value"), b)) tri->threeSphere = b; } else if (subTagName == "threeball") { bool b; if (valueOf(props.lookup("value"), b)) tri->threeBall = b; } else if (subTagName == "solidtorus") { bool b; if (valueOf(props.lookup("value"), b)) tri->solidTorus = b; } else if (subTagName == "irreducible") { bool b; if (valueOf(props.lookup("value"), b)) tri->irreducible = b; } else if (subTagName == "compressingdisc") { bool b; if (valueOf(props.lookup("compressingdisc"), b)) tri->compressingDisc = b; } else if (subTagName == "haken") { bool b; if (valueOf(props.lookup("haken"), b)) tri->haken = b; } else if (subTagName == "H1") return new NAbelianGroupPropertyReader(tri->H1); else if (subTagName == "H1Rel") return new NAbelianGroupPropertyReader(tri->H1Rel); else if (subTagName == "H1Bdry") return new NAbelianGroupPropertyReader(tri->H1Bdry); else if (subTagName == "H2") return new NAbelianGroupPropertyReader(tri->H2); else if (subTagName == "fundgroup") return new NGroupPresentationPropertyReader(tri->fundamentalGroup); else if (subTagName == "turaevviro") { unsigned long r, root; double value; if (valueOf(props.lookup("r"), r) && valueOf(props.lookup("root"), root) && valueOf(props.lookup("value"), value)) tri->turaevViroCache[std::make_pair(r, root)] = value; } return new NXMLElementReader(); } void NXMLTriangulationReader::endContentSubElement(const std::string&, NXMLElementReader*) { } NXMLPacketReader* NTriangulation::getXMLReader(NPacket*, NXMLTreeResolver& resolver) { return new NXMLTriangulationReader(resolver); } } // namespace regina regina-4.95/engine/triangulation/nxmltrireader.h000644 000765 000024 00000007451 12236524106 021775 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file triangulation/nxmltrireader.h * \brief Deals with parsing XML data for triangulation packets. */ #ifndef __NXMLTRIREADER_H #ifndef __DOXYGEN #define __NXMLTRIREADER_H #endif #include "regina-core.h" #include "packet/nxmlpacketreader.h" #include "triangulation/ntriangulation.h" namespace regina { /** * \weakgroup triangulation * @{ */ /** * An XML packet reader that reads a single triangulation. * * \ifacespython Not present. */ class REGINA_API NXMLTriangulationReader : public NXMLPacketReader { private: NTriangulation* tri; /**< The triangulation currently being read. */ public: /** * Creates a new triangulation reader. * * @param resolver the master resolver that will be used to fix * dangling packet references after the entire XML file has been read. */ NXMLTriangulationReader(NXMLTreeResolver& resolver); virtual NPacket* getPacket(); virtual NXMLElementReader* startContentSubElement( const std::string& subTagName, const regina::xml::XMLPropertyDict& subTagProps); virtual void endContentSubElement(const std::string& subTagName, NXMLElementReader* subReader); }; /*@}*/ // Inline functions for NXMLTriangulationReader inline NXMLTriangulationReader::NXMLTriangulationReader( NXMLTreeResolver& resolver) : NXMLPacketReader(resolver), tri(new NTriangulation()) { } inline NPacket* NXMLTriangulationReader::getPacket() { return tri; } } // namespace regina #endif regina-4.95/engine/triangulation/reorder.cpp000644 000765 000024 00000031346 12234011536 021106 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "maths/nperm4.h" #include "triangulation/ntriangulation.h" #include "triangulation/nedge.h" #include "triangulation/nisomorphism.h" #include #include namespace regina { namespace { // Begin anonymous namespace void reorder_fatal_error(const char* msg) { std::cerr << "ERROR: " << msg << std::endl; std::exit(1); } // Given is a tetrahedron with an ordering inducing edge orientations. // edge_orientations_on_tet[i] == -1 means that we intend to flip the // edge orientation of the i-th edge of the tetrahedron. // perm_from_edges returns the permutation that needs to be applied to the // tetrahedron to achieve this. NPerm4 perm_from_edges(const int edge_orientations_on_tet[6]) { // p[i] = number of edge_orientations pointing to vertex i int p[4] = {0, 0, 0, 0}; for(int i = 0; i < 6; i++) if(edge_orientations_on_tet[i] == +1) p[NEdge::edgeVertex[i][1]]++; else p[NEdge::edgeVertex[i][0]]++; // Consistency check for(int i = 0; i < 4; i++) { if(p[i] > 3 || p[i] < 0) reorder_fatal_error("bad permutation in reorder.cpp"); for(int j = i+1; j < 4; j++) if(p[i] == p[j]) reorder_fatal_error("bad permutation in reorder.cpp"); } return NPerm4(p[0],p[1],p[2],p[3]); } // edge_orientations[i] is the edge orientation of the i-th edge in // the triangulation // edge_orientations_tet[i] is the edge orientation of the i-th edge of // the tetrahedron // edge_orientations_tet[i] == +1 means that the edge is oriented pointing // from the lower to the higher vertex // writes the result into edge_orientations_tet void edge_orientations_on_tet(const NTriangulation &trig, const std::vector &edge_orientations, const NTetrahedron *tet, int edge_orientations_tet[6]) { for(int i = 0; i < 6; i++) { // to get the edge orientation on a tetrahedron's edge // look it up in edge_orientations int orientation = edge_orientations[trig.edgeIndex(tet->getEdge(i))]; // a tetrahedron's edge might be identified with the edge in the // triangulation in a way that the edge orientation is not consistent NPerm4 perm = tet->getEdgeMapping(i); if(perm[0] > perm[1]) orientation = - orientation; edge_orientations_tet[i] = orientation; } } // edge_orientations_tet are as above // v0, v1, v2 are the vertices of the face // It is assumed that v0 < v1 < v2 inline bool check_consistency_on_face(const int edge_orientations_tet[6], int v0, int v1, int v2) { // There are only two ways to get a cyclic orientation of edges on a face if( (edge_orientations_tet[NEdge::edgeNumber[v0][v1]] == +1) && (edge_orientations_tet[NEdge::edgeNumber[v1][v2]] == +1) && (edge_orientations_tet[NEdge::edgeNumber[v0][v2]] == -1)) return false; if( (edge_orientations_tet[NEdge::edgeNumber[v0][v1]] == -1) && (edge_orientations_tet[NEdge::edgeNumber[v1][v2]] == -1) && (edge_orientations_tet[NEdge::edgeNumber[v0][v2]] == +1)) return false; return true; } // edge_orientations[i] == 0 means that the edge_orientation has not been // assigned yet, so ignore it for testing // checks that the edge_orientations give an ordering of the triangulation bool check_consistency_on_tet(const NTriangulation &trig, const std::vector &edge_orientations, const NTetrahedron *tet, bool force_oriented) { int edge_orientations_tet[6]; // compute how the assignment of orientations to edges of the triangulation // look on the tetrahedron edge_orientations_on_tet(trig,edge_orientations,tet,edge_orientations_tet); // check that edge orientations are acyclic on each face of tet if(!check_consistency_on_face(edge_orientations_tet,1,2,3)) return false; if(!check_consistency_on_face(edge_orientations_tet,0,2,3)) return false; if(!check_consistency_on_face(edge_orientations_tet,0,1,3)) return false; if(!check_consistency_on_face(edge_orientations_tet,0,1,2)) return false; // if we do not need to check for consistent orientation, we are done: if(!force_oriented) return true; // orientation can't be determined until all edge_orientations are assigned for(int i = 0; i < 6; i++) if(edge_orientations_tet[i] == 0) return true; // check for valid orientation NPerm4 p = perm_from_edges(edge_orientations_tet); if(p.sign() * tet->orientation() == -1) return false; return true; } // checks that the edge orientations give a valid ordering of the triangulation bool check_consistency_around_edge(const NTriangulation &trig, const std::vector &edge_orientations, int edge_index, bool force_oriented) { typedef std::deque NEmbed; typedef NEmbed::const_iterator NEmbedIterator; NEdge* edge = trig.getEdge(edge_index); const NEmbed & edge_embeddings = edge -> getEmbeddings(); NEmbedIterator it; // iterate through all tetrahedra around an edge for(it = edge_embeddings.begin(); it != edge_embeddings.end(); ++it) if(!check_consistency_on_tet(trig, edge_orientations, it->getTetrahedron(), force_oriented)) return false; return true; } // construct isomorphism from edge orientations NIsomorphism* iso_from_edges(const NTriangulation &trig, const std::vector & edge_orientations, bool force_oriented) { NIsomorphism* iso = new NIsomorphism(trig.getNumberOfTetrahedra()); // iterate through all tetrahedra for(unsigned i = 0; i < trig.getNumberOfTetrahedra(); i++) { // consistency check if(!check_consistency_on_tet(trig, edge_orientations, trig.getTetrahedron(i), force_oriented)) reorder_fatal_error("Inconsistent edge orientations in reorder.cpp"); int edge_orientations_tet[6]; // compute how the edge orientations look on the tetrahedron edge_orientations_on_tet(trig, edge_orientations, trig.getTetrahedron(i), edge_orientations_tet); // derive the permutation iso->facePerm(i) = perm_from_edges(edge_orientations_tet); iso->tetImage(i) = i; } return iso; } // Find edge orientations (through back tracking) such that they induce a valid // ordering on each tetrahedron (and if force_oriented also a consistent // orientation on each tetrahedron). // If the function succeeds the isomorphism turning the triangulation into an // ordered triangulation is returned. NIsomorphism* ordering_iso(const NTriangulation &trig, bool force_oriented) { std::vector edge_orientations(trig.getNumberOfEdges(),0); int i = 0; while(true) { if(i < 0) return NULL; if(i >= static_cast(trig.getNumberOfEdges())) return iso_from_edges(trig, edge_orientations, force_oriented); if(edge_orientations[i] == 0) { edge_orientations[i] = +1; if(check_consistency_around_edge(trig,edge_orientations, i,force_oriented)) ++i; } else if(edge_orientations[i] == +1) { edge_orientations[i] = -1; if(check_consistency_around_edge(trig,edge_orientations, i,force_oriented)) ++i; } else { edge_orientations[i] = 0; --i; } } return NULL; } } // End anonymous namespace bool NTriangulation::isOriented() const { TetrahedronIterator it; // Calling isOrientable() will force a skeletal calculation if this // has not been done already. if(!isOrientable()) return false; for(it = tetrahedra.begin(); it != tetrahedra.end(); ++it) if( (*it) -> tetOrientation != 1) return false; return true; } void NTriangulation::orient() { if (! calculatedSkeleton) calculateSkeleton(); NIsomorphism flip_tets_iso(getNumberOfTetrahedra()); TetrahedronIterator it; int t; for (t = 0, it = tetrahedra.begin(); it != tetrahedra.end(); ++it, ++t) { flip_tets_iso.tetImage(t) = t; if ((*it)->tetOrientation == 1 || ! (*it)->getComponent()->isOrientable()) flip_tets_iso.facePerm(t) = NPerm4(); // Identity else flip_tets_iso.facePerm(t) = NPerm4(2,3); } flip_tets_iso.applyInPlace(this); } bool NTriangulation::isOrdered() const { TetrahedronIterator it; for(it = tetrahedra.begin(); it != tetrahedra.end(); ++it) for(int face = 0; face < 4; face++) if((*it)->tetrahedra[face]) { NPerm4 perm = (*it) -> tetrahedronPerm[face]; // check that the permutation is order preserving on the face int last = -1; for(int k = 0; k < 4; ++k) if( k != face ) { if(perm[k] < last) return false; last = perm[k]; } } return true; } bool NTriangulation::order(bool force_oriented) { if(!calculatedSkeleton) calculateSkeleton(); if(force_oriented && !isOrientable()) return false; // find the isomorphism to order (and orient) the triangulation NIsomorphism* iso = ordering_iso(*this, force_oriented); if(!iso) return false; // apply the isomorphism iso -> applyInPlace(this); delete iso; // consistency check if(! isOrdered()) reorder_fatal_error("NTriangulation::order returned unordered triangulation in reorder.cpp"); if(force_oriented && ! isOriented()) reorder_fatal_error("NTriangulation::order returned unoriented triangulation in reorder.cpp"); return true; } } regina-4.95/engine/triangulation/simplify.cpp000644 000765 000024 00000122702 12234011536 021275 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "triangulation/ntriangulation.h" namespace regina { namespace { // Mapping from vertices (0,1,2) of each external triangular face of a new // tetrahedron to the vertices of this new tetrahedron in a 3-2 move. // Each new tetrahedron has its vertices numbered so that the corresponding // embedding permutation for the internal triangle is the identity. // Also, threeTwoVertices[i] refers to face i of the new tetrahedron for // each i. const NPerm4 threeTwoVertices[3] = { NPerm4(3,1,2,0), NPerm4(3,2,0,1), NPerm4(3,0,1,2) }; // Mapping from vertices (0,1,2) of each external triangular face of a new // tetrahedron to the vertices of this new tetrahedron in a 2-3 move. // Each new tetrahedron has its vertices numbered so that the corresponding // embedding permutation for the internal edge is the identity. // Also, twoThreeVertices[i] refers to face i of the new tetrahedron for // each i. const NPerm4 twoThreeVertices[2] = { NPerm4(1,2,3,0), NPerm4(0,2,3,1) }; // A helper routine that uses union-find to test whether a graph // contains cycles. This is used by collapseEdge(). // // This routine returns true if the given edge connects two distinct // components of the graph, or false if both endpoints of the edge // are already in the same component (i.e., a cycle has been created). bool unionFindInsert(long* parent, long* depth, long vtx1, long vtx2) { // Find the root of the tree containing vtx1 and vtx2. long top1, top2; for (top1 = vtx1; parent[top1] >= 0; top1 = parent[top1]) ; for (top2 = vtx2; parent[top2] >= 0; top2 = parent[top2]) ; // Are both vertices in the same component? if (top1 == top2) return false; // Join the two components. // Insert the shallower tree beneath the deeper tree. if (depth[top1] < depth[top2]) { parent[top1] = top2; } else { parent[top2] = top1; if (depth[top1] == depth[top2]) ++depth[top1]; } return true; } } bool NTriangulation::threeTwoMove(NEdge* e, bool check, bool perform) { const std::deque& embs = e->getEmbeddings(); if (check) { if (e->isBoundary() || ! e->isValid()) return false; if (embs.size() != 3) return false; } // Find the unwanted tetrahedra. NTetrahedron* oldTet[3]; NPerm4 oldVertexPerm[3]; std::set oldTets; int oldPos = 0; for (std::deque::const_iterator it = embs.begin(); it != embs.end(); it++) { oldTet[oldPos] =(*it).getTetrahedron(); if (check) if (! oldTets.insert(oldTet[oldPos]).second) return false; oldVertexPerm[oldPos] = (*it).getVertices(); oldPos++; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 3-2 move\n"; #endif // Perform the move. ChangeEventSpan span(this); int oldPos2, newPos, newPos2; // Create the new tetrahedra. NTetrahedron* newTet[2]; for (newPos = 0; newPos < 2; newPos++) newTet[newPos] = newTetrahedron(); // Find the gluings from (0,1,2) of the new tetrahedron faces // to the vertices of the old tetrahedra. NPerm4 gluings[2][3]; for (oldPos = 0; oldPos < 3; oldPos++) for (newPos = 0; newPos < 2; newPos++) gluings[newPos][oldPos] = oldVertexPerm[oldPos] * twoThreeVertices[newPos]; // Find the tetrahedra to which the old tetrahedron faces are glued, // store the gluings from (0,1,2) of the new tetrahedron faces to the // vertices of these adjacent tetrahedra, and unjoin the tetrahedra. NTetrahedron* adjTet[2][3]; int adjFace; int oldFace; for (oldPos = 0; oldPos < 3; oldPos++) for (newPos = 0; newPos < 2; newPos++) { // Note that gluings[n][o][3] == oldVertexPerm[o][n], since // twoThreeVertices[i][3] == i. // oldFace = gluings[newPos][oldPos][3]; oldFace = oldVertexPerm[oldPos][newPos]; adjTet[newPos][oldPos] = oldTet[oldPos]->adjacentTetrahedron(oldFace); if (adjTet[newPos][oldPos]) { for (oldPos2 = 0; oldPos2 < 3; oldPos2++) { if (adjTet[newPos][oldPos] == oldTet[oldPos2]) { adjFace = oldTet[oldPos]->adjacentFace(oldFace); for (newPos2 = 0; newPos2 < 2; newPos2++) // if (gluings[newPos2][oldPos2][3] == adjFace) { if (oldVertexPerm[oldPos2][newPos2] == adjFace) { // Face oldFace of oldTet[oldPos] is glued to // face adjFace of oldTet[oldPos2] and should be // glued to face oldPos2 of newTet[newPos2]. if ((oldPos2 < oldPos) || (oldPos2 == oldPos && newPos2 < newPos)) { // We've already seen this gluing from // the other direction and // gluings[newPos2][oldPos2] has already // been modified. We'll have to leave // this gluing to be made from the // other direction. adjTet[newPos][oldPos] = 0; } else { adjTet[newPos][oldPos] = newTet[newPos2]; gluings[newPos][oldPos] = threeTwoVertices[oldPos2] * gluings[newPos2][oldPos2].inverse() * oldTet[oldPos]-> adjacentGluing(oldFace) * gluings[newPos][oldPos]; } break; } break; } } if (oldPos2 >= 3) gluings[newPos][oldPos] = oldTet[oldPos]->adjacentGluing(oldFace) * gluings[newPos][oldPos]; oldTet[oldPos]->unjoin(oldFace); } } // Remove the old tetrahedra from the triangulation. for (oldPos = 0; oldPos < 3; oldPos++) removeTetrahedron(oldTet[oldPos]); // Glue the faces of the new tetrahedra. for (oldPos = 0; oldPos < 3; oldPos++) for (newPos = 0; newPos < 2; newPos++) if (adjTet[newPos][oldPos]) newTet[newPos]->joinTo(oldPos, adjTet[newPos][oldPos], gluings[newPos][oldPos] * threeTwoVertices[oldPos].inverse()); newTet[0]->joinTo(3, newTet[1], NPerm4()); // Done!. return true; } bool NTriangulation::twoThreeMove(NTriangle* f, bool check, bool perform) { if (check) { if (f->getNumberOfEmbeddings() != 2) return false; // We now know that the given triangle is not on the boundary. } // Find the unwanted tetrahedra. NTetrahedron* oldTet[2]; NPerm4 oldVertexPerm[2]; int oldPos; for (oldPos = 0; oldPos < 2; oldPos++) { oldTet[oldPos] = f->getEmbedding(oldPos).getTetrahedron(); oldVertexPerm[oldPos] = f->getEmbedding(oldPos).getVertices(); } if (check) if (oldTet[0] == oldTet[1]) return false; if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 2-3 move\n"; #endif // Actually perform the move. ChangeEventSpan span(this); int oldPos2, newPos, newPos2; // Allocate the new tetrahedra. NTetrahedron* newTet[3]; for (newPos = 0; newPos < 3; newPos++) newTet[newPos] = newTetrahedron(); // Find the gluings from (0,1,2) of the new tetrahedron faces // to the vertices of the old tetrahedra. NPerm4 gluings[3][2]; for (oldPos = 0; oldPos < 2; oldPos++) for (newPos = 0; newPos < 3; newPos++) gluings[newPos][oldPos] = oldVertexPerm[oldPos] * threeTwoVertices[newPos]; // Find the tetrahedra to which the old tetrahedron faces are glued, // store the gluings from (0,1,2) of the new tetrahedron faces to the // vertices of these adjacent tetrahedra, and unjoin the tetrahedra. NTetrahedron* adjTet[3][2]; int adjFace; int oldFace; for (oldPos = 0; oldPos < 2; oldPos++) for (newPos = 0; newPos < 3; newPos++) { // Note that gluings[n][o][3] == oldVertexPerm[o][n], since // threeTwoVertices[i][3] == i. // oldFace = gluings[newPos][oldPos][3]; oldFace = oldVertexPerm[oldPos][newPos]; adjTet[newPos][oldPos] = oldTet[oldPos]->adjacentTetrahedron(oldFace); if (adjTet[newPos][oldPos]) { for (oldPos2 = 0; oldPos2 < 2; oldPos2++) { if (adjTet[newPos][oldPos] == oldTet[oldPos2]) { adjFace = oldTet[oldPos]->adjacentFace(oldFace); for (newPos2 = 0; newPos2 < 3; newPos2++) // if (gluings[newPos2][oldPos2][3] == adjFace) { if (oldVertexPerm[oldPos2][newPos2] == adjFace) { // Face oldFace of oldTet[oldPos] is glued to // face adjFace of oldTet[oldPos2] and should be // glued to face oldPos2 of newTet[newPos2]. if ((oldPos2 < oldPos) || (oldPos2 == oldPos && newPos2 < newPos)) { // We've already seen this gluing from // the other direction and // gluings[newPos2][oldPos2] has already // been modified. We'll have to leave // this gluing to be made from the // other direction. adjTet[newPos][oldPos] = 0; } else { adjTet[newPos][oldPos] = newTet[newPos2]; gluings[newPos][oldPos] = twoThreeVertices[oldPos2] * gluings[newPos2][oldPos2].inverse() * oldTet[oldPos]-> adjacentGluing(oldFace) * gluings[newPos][oldPos]; } break; } break; } } if (oldPos2 >= 2) gluings[newPos][oldPos] = oldTet[oldPos]->adjacentGluing(oldFace) * gluings[newPos][oldPos]; oldTet[oldPos]->unjoin(oldFace); } } // Remove the old tetrahedra from the triangulation. for (oldPos = 0; oldPos < 2; oldPos++) removeTetrahedron(oldTet[oldPos]); // Glue the faces of the new tetrahedra. for (oldPos = 0; oldPos < 2; oldPos++) for (newPos = 0; newPos < 3; newPos++) if (adjTet[newPos][oldPos]) newTet[newPos]->joinTo(oldPos, adjTet[newPos][oldPos], gluings[newPos][oldPos] * twoThreeVertices[oldPos].inverse()); NPerm4 internalPerm = NPerm4(0,1,3,2); newTet[0]->joinTo(2, newTet[1], internalPerm); newTet[1]->joinTo(2, newTet[2], internalPerm); newTet[2]->joinTo(2, newTet[0], internalPerm); // Done! return true; } bool NTriangulation::fourFourMove(NEdge* e, int newAxis, bool check, bool perform) { const std::deque& embs = e->getEmbeddings(); if (check) { if (e->isBoundary() || ! e->isValid()) return false; if (embs.size() != 4) return false; } // Find the unwanted tetrahedra. NTetrahedron* oldTet[4]; std::set oldTets; int oldPos = 0; for (std::deque::const_iterator it = embs.begin(); it != embs.end(); it++) { oldTet[oldPos] =(*it).getTetrahedron(); if (check) if (! oldTets.insert(oldTet[oldPos]).second) return false; oldPos++; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 4-4 move\n"; #endif // Perform the 4-4 move as a 2-3 move followed by a 3-2 move. ChangeEventSpan span(this); NTriangle* tri23 = (newAxis == 0 ? oldTet[0]->getTriangle(embs[0].getVertices()[2]) : oldTet[1]->getTriangle(embs[1].getVertices()[2])); int edge32 = embs[3].getEdge(); twoThreeMove(tri23, false, true); threeTwoMove(oldTet[3]->getEdge(edge32), false, true); // Done! return true; } bool NTriangulation::twoZeroMove(NEdge* e, bool check, bool perform) { if (check) { if (e->isBoundary() || ! e->isValid()) return false; if (e->getNumberOfEmbeddings() != 2) return false; } NTetrahedron* tet[2]; NPerm4 perm[2]; int i = 0; for (std::deque::const_iterator it = e->getEmbeddings().begin(); it != e->getEmbeddings().end(); it++) { tet[i] = (*it).getTetrahedron(); perm[i] = (*it).getVertices(); i++; } if (check) if (tet[0] == tet[1]) return false; if (check) { NEdge* edge[2]; NTriangle* triangle[2][2]; // triangle[i][j] will be on tetrahedron i opposite vertex j of the // internal edge. for (i=0; i<2; i++) { edge[i] = tet[i]->getEdge( NEdge::edgeNumber[perm[i][2]][perm[i][3]]); triangle[i][0] = tet[i]->getTriangle(perm[i][0]); triangle[i][1] = tet[i]->getTriangle(perm[i][1]); } if (edge[0] == edge[1]) return false; if (edge[0]->isBoundary() && edge[1]->isBoundary()) return false; if (triangle[0][0] == triangle[1][0]) return false; if (triangle[0][1] == triangle[1][1]) return false; // The cases with two pairs of identified triangles and with one // pair of identified triangles plus one pair of boundary triangles are // all covered by the following check. if (tet[0]->getComponent()->getNumberOfTetrahedra() == 2) return false; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 2-0 move about edge\n"; #endif // Actually perform the move. ChangeEventSpan span(this); // Unglue faces from the doomed tetrahedra and glue them to each // other. NPerm4 crossover = tet[0]->adjacentGluing(perm[0][2]); NPerm4 gluing; NTetrahedron* top; NTetrahedron* bottom; int topFace; for (i=0; i<2; i++) { top = tet[0]->adjacentTetrahedron(perm[0][i]); bottom = tet[1]->adjacentTetrahedron(perm[1][i]); if (! top) { // Bottom triangle becomes boundary. tet[1]->unjoin(perm[1][i]); } else if (! bottom) { // Top triangle becomes boundary. tet[0]->unjoin(perm[0][i]); } else { // Bottom and top triangles join. topFace = tet[0]->adjacentFace(perm[0][i]); gluing = tet[1]->adjacentGluing(perm[1][i]) * crossover * top->adjacentGluing(topFace); tet[0]->unjoin(perm[0][i]); tet[1]->unjoin(perm[1][i]); top->joinTo(topFace, bottom, gluing); } } // Finally remove and dispose of the tetrahedra. removeTetrahedron(tet[0]); removeTetrahedron(tet[1]); // Tidy up. // Properties have already been cleared in removeTetrahedron(). return true; } bool NTriangulation::twoZeroMove(NVertex* v, bool check, bool perform) { if (check) { if (v->getLink() != NVertex::SPHERE) return false; if (v->getNumberOfEmbeddings() != 2) return false; } NTetrahedron* tet[2]; int vertex[2]; std::vector::const_iterator it; int i = 0; for (it = v->getEmbeddings().begin(); it != v->getEmbeddings().end(); it++) { tet[i] = (*it).getTetrahedron(); vertex[i] = (*it).getVertex(); i++; } if (check) { if (tet[0] == tet[1]) return false; NTriangle* triangle[2]; for (i = 0; i < 2; i++) triangle[i] = tet[i]->getTriangle(vertex[i]); if (triangle[0] == triangle[1]) return false; if (triangle[0]->isBoundary() && triangle[1]->isBoundary()) return false; // Check that the tetrahedra are joined along all three triangles. for (i = 0; i < 4; i++) { if (i == vertex[0]) continue; if (tet[0]->adjacentTetrahedron(i) != tet[1]) return false; } } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 2-0 move about vertex\n"; #endif // Actually perform the move. ChangeEventSpan span(this); // Unglue faces from the doomed tetrahedra and glue them to each // other. NTetrahedron* top = tet[0]->adjacentTetrahedron(vertex[0]); NTetrahedron* bottom = tet[1]->adjacentTetrahedron(vertex[1]); if (! top) { tet[1]->unjoin(vertex[1]); } else if (! bottom) { tet[0]->unjoin(vertex[0]); } else { NPerm4 crossover; if (vertex[0] == 0) crossover = tet[0]->adjacentGluing(1); else crossover = tet[0]->adjacentGluing(0); int topFace = tet[0]->adjacentFace(vertex[0]); NPerm4 gluing = tet[1]->adjacentGluing(vertex[1]) * crossover * top->adjacentGluing(topFace); tet[0]->unjoin(vertex[0]); tet[1]->unjoin(vertex[1]); top->joinTo(topFace, bottom, gluing); } // Finally remove and dispose of the tetrahedra. removeTetrahedron(tet[0]); removeTetrahedron(tet[1]); // Tidy up. // Properties have already been cleared in removeTetrahedron(). return true; } bool NTriangulation::twoOneMove(NEdge* e, int edgeEnd, bool check, bool perform) { // edgeEnd is the end opposite where the action is. if (check) { if (e->isBoundary() || ! e->isValid()) return false; if (e->getNumberOfEmbeddings() != 1) return false; } const NEdgeEmbedding& emb = e->getEmbeddings().front(); NTetrahedron* oldTet = emb.getTetrahedron(); NPerm4 oldVertices = emb.getVertices(); NTetrahedron* top = oldTet->adjacentTetrahedron(oldVertices[edgeEnd]); int otherEdgeEnd = 1 - edgeEnd; if (check) if (! top) return false; NTriangle* centreTri = oldTet->getTriangle(oldVertices[edgeEnd]); NTriangle* bottomTri = oldTet->getTriangle(oldVertices[otherEdgeEnd]); NPerm4 bottomToTop = oldTet->adjacentGluing(oldVertices[edgeEnd]); int topGlued[2]; NEdge* flatEdge[2]; int i; for (i=0; i<2; i++) { topGlued[i] = bottomToTop[oldVertices[i + 2]]; flatEdge[i] = top->getEdge( NEdge::edgeNumber[topGlued[i]][bottomToTop[oldVertices[edgeEnd]]]); } if (check) { if (centreTri == bottomTri) return false; if (flatEdge[0] == flatEdge[1]) return false; if (flatEdge[0]->isBoundary() && flatEdge[1]->isBoundary()) return false; // This next test should follow from the two edges being distinct, // but we'll do it anyway. if (top->getTriangle(topGlued[0]) == top->getTriangle(topGlued[1])) return false; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing 2-1 move\n"; #endif // Go ahead and perform the move. ChangeEventSpan span(this); // First glue together the two faces that will be flattened. NTetrahedron* adjTet[2]; adjTet[0] = top->adjacentTetrahedron(topGlued[0]); adjTet[1] = top->adjacentTetrahedron(topGlued[1]); if (! adjTet[0]) top->unjoin(topGlued[1]); else if (! adjTet[1]) top->unjoin(topGlued[0]); else { int adjFace[2]; adjFace[0] = top->adjacentFace(topGlued[0]); adjFace[1] = top->adjacentFace(topGlued[1]); NPerm4 gluing = top->adjacentGluing(topGlued[1]) * NPerm4(topGlued[0], topGlued[1]) * adjTet[0]->adjacentGluing(adjFace[0]); top->unjoin(topGlued[0]); top->unjoin(topGlued[1]); adjTet[0]->joinTo(adjFace[0], adjTet[1], gluing); } // Now make the new tetrahedron and glue it to itself. NTetrahedron* newTet = newTetrahedron(); newTet->joinTo(2, newTet, NPerm4(2,3)); // Glue the new tetrahedron into the remaining structure. if (oldTet->adjacentTetrahedron(oldVertices[otherEdgeEnd]) == top) { // The top of the new tetrahedron must be glued to the bottom. int topFace = bottomToTop[oldVertices[otherEdgeEnd]]; NPerm4 bottomFacePerm = NPerm4(oldVertices[edgeEnd], oldVertices[otherEdgeEnd], oldVertices[2], oldVertices[3]); NPerm4 gluing = bottomFacePerm.inverse() * top->adjacentGluing(topFace) * bottomToTop * bottomFacePerm * NPerm4(0,1); top->unjoin(topFace); newTet->joinTo(0, newTet, gluing); } else { int bottomFace = oldVertices[otherEdgeEnd]; int topFace = bottomToTop[bottomFace]; NTetrahedron* adjTop = top->adjacentTetrahedron(topFace); NTetrahedron* adjBottom = oldTet->adjacentTetrahedron(bottomFace); NPerm4 bottomFacePerm = NPerm4(oldVertices[edgeEnd], oldVertices[otherEdgeEnd], oldVertices[2], oldVertices[3]); if (adjTop) { NPerm4 topGluing = top->adjacentGluing(topFace) * bottomToTop * bottomFacePerm * NPerm4(0,1); top->unjoin(topFace); newTet->joinTo(0, adjTop, topGluing); } if (adjBottom) { NPerm4 bottomGluing = oldTet->adjacentGluing(bottomFace) * bottomFacePerm; oldTet->unjoin(bottomFace); newTet->joinTo(1, adjBottom, bottomGluing); } } // Finally remove and dispose of the unwanted tetrahedra. removeTetrahedron(oldTet); removeTetrahedron(top); // Tidy up. // Properties have already been cleared in removeTetrahedron(). return true; } bool NTriangulation::openBook(NTriangle* f, bool check, bool perform) { const NTriangleEmbedding& emb = f->getEmbedding(0); NTetrahedron* tet = emb.getTetrahedron(); NPerm4 vertices = emb.getVertices(); // Check that the triangle has exactly two boundary edges. // Note that this will imply that the triangle joins two tetrahedra. if (check) { int fVertex = -1; int nBdry = 0; if (tet->getEdge(NEdge::edgeNumber[vertices[0]][vertices[1]])-> isBoundary()) nBdry++; else fVertex = 2; if (tet->getEdge(NEdge::edgeNumber[vertices[1]][vertices[2]])-> isBoundary()) nBdry++; else fVertex = 0; if (tet->getEdge(NEdge::edgeNumber[vertices[2]][vertices[0]])-> isBoundary()) nBdry++; else fVertex = 1; if (nBdry != 2) return false; if (tet->getVertex(vertices[fVertex])->getLink() != NVertex::DISC) return false; if (! f->getEdge(fVertex)->isValid()) return false; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing open book move\n"; #endif // Actually perform the move. // Don't bother with a block since this is so simple. tet->unjoin(emb.getTriangle()); return true; } bool NTriangulation::closeBook(NEdge* e, bool check, bool perform) { if (check) { if (! e->isBoundary()) return false; } // Find the two triangles on either side of edge e. const NEdgeEmbedding& front = e->getEmbeddings().front(); const NEdgeEmbedding& back = e->getEmbeddings().back(); NTetrahedron* t0 = front.getTetrahedron(); NTetrahedron* t1 = back.getTetrahedron(); NPerm4 p0 = front.getVertices(); NPerm4 p1 = back.getVertices(); if (check) { if (t0->getTriangle(p0[3]) == t1->getTriangle(p1[2])) return false; if (t0->getVertex(p0[2]) == t1->getVertex(p1[3])) return false; if (t0->getVertex(p0[2])->getLink() != NVertex::DISC || t1->getVertex(p1[3])->getLink() != NVertex::DISC) return false; NEdge* e1 = t0->getEdge(NEdge::edgeNumber[p0[0]][p0[2]]); NEdge* e2 = t0->getEdge(NEdge::edgeNumber[p0[1]][p0[2]]); NEdge* f1 = t1->getEdge(NEdge::edgeNumber[p1[0]][p1[3]]); NEdge* f2 = t1->getEdge(NEdge::edgeNumber[p1[1]][p1[3]]); if (e1 == e2 && f1 == f2) return false; } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing close book move\n"; #endif // Actually perform the move. // Don't bother with a block since this is so simple. t0->joinTo(p0[3], t1, p1 * NPerm4(2, 3) * p0.inverse()); return true; } bool NTriangulation::shellBoundary(NTetrahedron* t, bool check, bool perform) { // To perform the move we don't even need a skeleton. if (check) { if (! calculatedSkeleton) calculateSkeleton(); int nBdry = 0; int i, j; int bdry[4]; for (i=0; i<4; i++) if (t->getTriangle(i)->isBoundary()) bdry[nBdry++] = i; if (nBdry < 1 || nBdry > 3) return false; if (nBdry == 1) { if (t->getVertex(bdry[0])->isBoundary()) return false; NEdge* internal[3]; j = 0; for (i = 0; i < 4; ++i) if (i != bdry[0]) internal[j++] = t->getEdge(NEdge::edgeNumber[bdry[0]][i]); if (! (internal[0]->isValid() && internal[1]->isValid() && internal[2]->isValid())) return false; if (internal[0] == internal[1] || internal[1] == internal[2] || internal[2] == internal[0]) return false; } else if (nBdry == 2) { i = NEdge::edgeNumber[bdry[0]][bdry[1]]; if (t->getEdge(i)->isBoundary()) return false; if (! t->getEdge(i)->isValid()) return false; if (t->adjacentTetrahedron(NEdge::edgeVertex[5 - i][0]) == t) return false; } } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing shell boundary move\n"; #endif // Actually perform the move. // Don't bother with a block since this is so simple. removeTetrahedron(t); return true; } bool NTriangulation::collapseEdge(NEdge* e, bool check, bool perform) { // Find the tetrahedra to remove. const std::deque& embs = e->getEmbeddings(); std::deque::const_iterator it; NTetrahedron* tet = 0; NPerm4 p; if (check) { // Note: We never check whether the edge is valid, but this // comes automatically from the other tests. In particular, an // invalid edge must join the same vertex to itself. // CHECK 0: The tetrahedra around the edge must be distinct. // We check this as follows: // // - None of the triangles containing edge e must contain e twice. // We throw this into check 2 below (see point [0a]). // // - The only remaining bad case is where a tetrahedron contains // e as two opposite edges. In this case one can prove that // we have a bad chain of bigons, which will be picked up in // check 2 below. // CHECK 1: Can we collapse the edge to a point (creating bigons and // pillows with bigon boundaries)? // The vertices must be distinct. if (e->getVertex(0) == e->getVertex(1)) return false; // If both vertices are in the boundary then we must be collapsing a // boundary edge, and both vertices must have plain old disc links. // Recall that ideal vertices return isBoundary() == true. if (e->getVertex(0)->isBoundary() && e->getVertex(1)->isBoundary()) { if (! e->isBoundary()) return false; if (e->getVertex(0)->getLink() != NVertex::DISC) return false; if (e->getVertex(1)->getLink() != NVertex::DISC) return false; } // CHECK 2: Can we flatten each bigon to an edge (leaving // triangular pillows behind)? // // This is trickier. Even if every individual bigon is okay, we // don't want a _chain_ of bigons together to crush a sphere or // projective plane. // // The way we do this is as follows. Consider each NEdge* to be // a vertex of some graph G, and consider each bigon to be an edge // in this graph G. The vertices at either end of the edge in G // are the (NEdge*)s that bound the bigon. // // We can happily flatten each bigon if and only if the graph G // contains no cycles. We shall test this using union-find, // which should have log-linear complexity. // // We deal with boundary edges and invalid edges as follows. // All boundary and/or invalid edges become the *same* vertex in // the graph G. This means, for instance, that a bigon joining two // distinct boundary edges is not allowed. Invalid edges are // included here because each invalid edge contains a projective // plane cusp at its centre. // // If edge e is itself a boundary edge, things become more // interesting again. In this case, the two *boundary* bigons // are not subject to the same restrictions -- crushing bigons // along the boundary does no harm, *unless* the boundary bigon // edges themselves form a cycle. This is essentially the same // dilemma as before but one dimension down. We can detect this // because it implies either: // // - two edges of the same bigon are identified, and hence the // two vertices of edge e are identified (which has already // been disallowed in check 1 above); // // - the four edges of the two boundary bigons are identified in // pairs, which means the entire boundary component consists // of the two bigons and nothing else. // // What does this mean in a practical sense? If edge e is a // boundary edge, we: // // - verify that the boundary component has more than two triangles; // // - then ignore both boundary bigons from here onwards. // // Quite pleasant to deal with in the end. if (e->isBoundary()) if (e->getBoundaryComponent()->getNumberOfTriangles() == 2) return false; { long nEdges = edges.size(); // The parent of each edge in the union-find tree, or -1 if // an edge is at the root of a tree. // // This array is indexed by edge number in the triangulation. // Although we might not use many of these edges, it's fast // and simple. The "unified boundary" is assigned the edge // number nEdges. long* parent = new long[nEdges + 1]; std::fill(parent, parent + nEdges + 1, -1); // The depth of each subtree in the union-find tree. long* depth = new long[nEdges + 1]; std::fill(depth, depth + nEdges + 1, 0); NEdge *upper, *lower; long id1, id2; // Run through all triangles containing e. it = embs.begin(); for ( ; it != embs.end(); ++it) { tet = it->getTetrahedron(); p = it->getVertices(); upper = tet->getEdge(NEdge::edgeNumber[p[0]][p[2]]); lower = tet->getEdge(NEdge::edgeNumber[p[1]][p[2]]); if (upper == e || lower == e) { // [0a]: Check 0 fails (see explanation earlier). delete[] depth; delete[] parent; return false; } // Now that we've run check 0, skip the first (boundary) // triangle if e is a boundary edge. We will skip the // last boundary triangle automatically, since for a boundary // edge there are k+1 triangles but only k embeddings. // // We do not need to worry about missing check 0 for // the last boundary triangle, since if it fails there then // it must also fail for the first. if (e->isBoundary() && it == embs.begin()) continue; id1 = ((upper->isBoundary() || ! upper->isValid()) ? nEdges : upper->markedIndex()); id2 = ((lower->isBoundary() || ! lower->isValid()) ? nEdges : lower->markedIndex()); // This bigon joins nodes id1 and id2 in the graph G. if (! unionFindInsert(parent, depth, id1, id2)) { delete[] depth; delete[] parent; return false; } } // No bad chains of bigons! delete[] depth; delete[] parent; } // CHECK 3: Can we flatten each triangular pillow to a triangle? // // Again, even if each individual pillow is okay, we don't want // a chain of pillows together to completely crush away a // 3-manifold component. // // This means no cycles of pillows, and no chains of pillows // that run from boundary to boundary. // // Test this in the same way that we tested edges. It's kind of // overkill, since each vertex in the corresponding graph G will // have degree <= 2, but it's fast so we'll do it. { long nTriangles = triangles.size(); // The parent of each triangle in the union-find tree, or -1 if // a triangle is at the root of a tree. // // This array is indexed by triangle number in the triangulation. // The "unified boundary" is assigned the triangle number // nTriangles. long* parent = new long[nTriangles + 1]; std::fill(parent, parent + nTriangles + 1, -1); // The depth of each subtree in the union-find tree. long* depth = new long[nTriangles + 1]; std::fill(depth, depth + nTriangles + 1, 0); NTriangle *upper, *lower; long id1, id2; for (it = embs.begin(); it != embs.end(); ++it) { tet = it->getTetrahedron(); p = it->getVertices(); upper = tet->getTriangle(p[0]); lower = tet->getTriangle(p[1]); id1 = (upper->isBoundary() ? nTriangles : upper->markedIndex()); id2 = (lower->isBoundary() ? nTriangles : lower->markedIndex()); // This pillow joins nodes id1 and id2 in the graph G. if (! unionFindInsert(parent, depth, id1, id2)) { delete[] depth; delete[] parent; return false; } } // No bad chains of bigons! delete[] depth; delete[] parent; } } if (! perform) return true; #ifdef DEBUG std::cerr << "Performing edge collapse move\n"; #endif // Perform the move. ChangeEventSpan span(this); NPerm4 topPerm, botPerm; NTetrahedron *top, *bot; // Clone the edge embeddings because we cannot rely on skeletal // objects once we start changing the triangulation. unsigned long degree = embs.size(); NTetrahedron** embTet = new NTetrahedron*[degree]; NPerm4* embVertices = new NPerm4[degree]; unsigned i; for (i = 0, it = embs.begin(); it != embs.end(); ++i, ++it) { embTet[i] = (*it).getTetrahedron(); embVertices[i] = (*it).getVertices(); } for (i = 0; i < degree; ++i) { top = embTet[i]->adjacentTetrahedron(embVertices[i][0]); topPerm = embTet[i]->adjacentGluing(embVertices[i][0]); bot = embTet[i]->adjacentTetrahedron(embVertices[i][1]); botPerm = embTet[i]->adjacentGluing(embVertices[i][1]); embTet[i]->isolate(); if (top && bot) top->joinTo(topPerm[embVertices[i][0]], bot, botPerm * NPerm4(embVertices[i][0], embVertices[i][1]) * topPerm.inverse()); removeTetrahedron(embTet[i]); } delete[] embVertices; delete[] embTet; return true; } void NTriangulation::reorderTetrahedraBFS(bool reverse) { unsigned long n = getNumberOfTetrahedra(); if (n == 0) return; ChangeEventSpan span(this); // Run a breadth-first search over all tetrahedra. NTetrahedron** ordered = new NTetrahedron*[n]; bool* used = new bool[n]; std::fill(used, used + n, false); unsigned long filled = 0; /* Placed in ordered[]. */ unsigned long processed = 0; /* All neighbours placed in ordered[]. */ unsigned long nextTet = 0; /* Used to search for connected components. */ unsigned i; NTetrahedron *tet, *adj; while (processed < n) { if (filled == processed) { // Look for the next connected component. while (used[nextTet]) ++nextTet; ordered[filled++] = tetrahedra[nextTet]; used[nextTet] = true; ++nextTet; } tet = ordered[processed]; // Add all neighbours of tet to the queue. for (i = 0; i < 4; ++i) if ((adj = tet->adjacentTetrahedron(i))) if (! used[adj->markedIndex()]) { ordered[filled++] = adj; used[adj->markedIndex()] = true; } ++processed; } // Flush the tetrahedra from the triangulation, and reinsert them in // the order in which they were found during the breadth-first search. tetrahedra.clear(); unsigned long j; if (reverse) { for (j = n; j > 0; ) tetrahedra.push_back(ordered[--j]); } else { for (j = 0; j < n; ) tetrahedra.push_back(ordered[j++]); } delete[] used; delete[] ordered; } } // namespace regina regina-4.95/engine/triangulation/simplifyglobal.cpp000644 000765 000024 00000030543 12234011536 022457 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "triangulation/ntriangulation.h" // Affects the number of random 4-4 moves attempted during simplification. #define COEFF_4_4 5 namespace regina { bool NTriangulation::intelligentSimplify() { bool changed; { // Begin scope for change event block. ChangeEventSpan span(this); // Reduce to a local minimum. changed = simplifyToLocalMinimum(true); // Clone to work with when we might want to roll back changes. NTriangulation* use; // Variables used for selecting random 4-4 moves. std::vector > fourFourAvailable; std::pair fourFourChoice; unsigned long fourFourAttempts; unsigned long fourFourCap; NEdge* edge; EdgeIterator eit; int axis; while (true) { // --- Random 4-4 moves --- // Clone the triangulation and start making changes that might or // might not lead to a simplification. // If we've already simplified then there's no need to use a // separate clone since we won't need to undo further changes. use = (changed ? this : new NTriangulation(*this)); // Make random 4-4 moves. fourFourAttempts = fourFourCap = 0; while (true) { // Calculate the list of available 4-4 moves. fourFourAvailable.clear(); // Use getEdges() to ensure the skeleton has been calculated. for (eit = use->getEdges().begin(); eit != use->getEdges().end(); eit++) { edge = *eit; for (axis = 0; axis < 2; axis++) if (use->fourFourMove(edge, axis, true, false)) fourFourAvailable.push_back( std::make_pair(edge, axis)); } // Increment fourFourCap if needed. if (fourFourCap < COEFF_4_4 * fourFourAvailable.size()) fourFourCap = COEFF_4_4 * fourFourAvailable.size(); // Have we tried enough 4-4 moves? if (fourFourAttempts >= fourFourCap) break; // Perform a random 4-4 move on the clone. fourFourChoice = fourFourAvailable[ static_cast(rand()) % fourFourAvailable.size()]; use->fourFourMove(fourFourChoice.first, fourFourChoice.second, false, true); // See if we can simplify now. if (use->simplifyToLocalMinimum(true)) { // We have successfully simplified! // Start all over again. fourFourAttempts = fourFourCap = 0; } else fourFourAttempts++; } // Sync the real triangulation with the clone if appropriate. if (use != this) { // At this point, changed == false. if (use->getNumberOfTetrahedra() < getNumberOfTetrahedra()) { // The 4-4 moves were successful; accept them. cloneFrom(*use); changed = true; } delete use; } // At this point we have decided that 4-4 moves will help us // no more. // --- Open book and close book moves --- if (hasBoundaryTriangles()) { // Clone again, always -- we don't want to create gratuitous // boundary triangles if they won't be of any help. use = new NTriangulation(*this); // Perform every book opening move we can find. TriangleIterator fit; bool opened = false; bool openedNow = true; while (openedNow) { openedNow = false; for (fit = use->getTriangles().begin(); fit != use->getTriangles().end(); ++fit) if (use->openBook(*fit, true, true)) { opened = openedNow = true; break; } } // If we're lucky, we now have an edge that we can collapse. if (opened) { if (use->simplifyToLocalMinimum(true)) { // Yay! cloneFrom(*use); changed = true; } else { // No good. // Ditch use and don't open anything. opened = false; } } delete use; // If we did any book opening stuff, start all over again. if (opened) continue; // If we've made it this far then there seems to be // nothing left to do. // // Perform book *closing* moves to simplify the boundary // of the triangulation, even if this does not actually // reduce the number of tetrahedra. // // Since we always want to simplify the boundary, make // the changes directly to this triangulation. bool closed = false; EdgeIterator eit; for (eit = getEdges().begin(); eit != getEdges().end(); ++eit) if (closeBook(*eit, true, true)) { closed = true; changed = true; // We don't actually care whether we reduce the // number of tetrahedra or not. Ignore the // return value from simplifyToLocalMinimum(). simplifyToLocalMinimum(true); break; } // If we *did* manage to close a book, there might be // further internal simplifications that we can now do. // Back to the top. if (closed) continue; } // Nothing more we can do here. break; } } // End scope for change event span. return changed; } bool NTriangulation::simplifyToLocalMinimum(bool perform) { EdgeIterator eit; VertexIterator vit; BoundaryComponentIterator bit; NEdge* edge; unsigned long nTriangles; unsigned long iTriangle; // unsigned long nEdges; // unsigned long iEdge; // std::deque::const_iterator embit, embbeginit, embendit; bool changed = false; // Has anything changed ever (for return value)? bool changedNow = true; // Did we just change something (for loop control)? { // Begin scope for change event span. ChangeEventSpan span(this); while (changedNow) { changedNow = false; if (! calculatedSkeleton) { calculateSkeleton(); } // Crush edges if we can. if (vertices.size() > components.size() && vertices.size() > boundaryComponents.size()) { for (eit = edges.begin(); eit != edges.end(); ++eit) { edge = *eit; if (collapseEdge(edge, true, perform)) { changedNow = changed = true; break; } } if (changedNow) { if (perform) continue; else return true; } } // Look for internal simplifications. for (eit = edges.begin(); eit != edges.end(); eit++) { edge = *eit; if (threeTwoMove(edge, true, perform)) { changedNow = changed = true; break; } if (twoZeroMove(edge, true, perform)) { changedNow = changed = true; break; } if (twoOneMove(edge, 0, true, perform)) { changedNow = changed = true; break; } if (twoOneMove(edge, 1, true, perform)) { changedNow = changed = true; break; } } if (changedNow) { if (perform) continue; else return true; } for (vit = vertices.begin(); vit != vertices.end(); vit++) { if (twoZeroMove(*vit, true, perform)) { changedNow = changed = true; break; } } if (changedNow) { if (perform) continue; else return true; } // Look for boundary simplifications. if (hasBoundaryTriangles()) { for (bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) { // Run through triangles of this boundary component looking // for shell boundary moves. nTriangles = (*bit)->getNumberOfTriangles(); for (iTriangle = 0; iTriangle < nTriangles; iTriangle++) { if (shellBoundary((*bit)->getTriangle(iTriangle)-> getEmbedding(0).getTetrahedron(), true, perform)) { changedNow = changed = true; break; } } if (changedNow) break; } if (changedNow) { if (perform) continue; else return true; } } } } // End scope for change event span. return changed; } } // namespace regina regina-4.95/engine/triangulation/skeleton.cpp000644 000765 000024 00000057000 12234011536 021263 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include "maths/nrational.h" #include "triangulation/ntriangulation.h" #include namespace regina { void NTriangulation::calculateSkeleton() const { ideal = false; valid = true; orientable = true; standard = true; #if 0 checkPermutations(); // Sets valid to false if gluings are mismatched (which should // never happen if the NTetrahedron gluing routines have been // used correctly) #endif // Set this now so that any tetrahedron query routines do not try to // recursively recompute the skeleton again. calculatedSkeleton = true; calculateComponents(); // Sets components, orientable, NComponent.orientable, // NTetrahedron.component calculateTriangles(); // Sets triangles, NTriangle.component calculateVertices(); // Sets vertices, NVertex.component, NVertex.linkOrientable. calculateEdges(); // Sets edges, NEdge.component, valid, NEdge.valid calculateBoundary(); // Sets boundaryComponents, NTriangle.boundaryComponent, // NEdge.boundaryComponent, NVertex.boundaryComponent, // NComponent.boundaryComponents calculateVertexLinks(); // Sets valid, ideal, NVertex.link, // NVertex.linkEulerCharacteristic, NComponent.ideal, // boundaryComponents, NVertex.boundaryComponent } void NTriangulation::checkPermutations() const { TetrahedronIterator it; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) for (int face = 0; face < 4; face++) { NTetrahedron * adjacent = (*it) -> adjacentTetrahedron(face); if (adjacent) { NPerm4 perm = (*it) -> adjacentGluing(face); NPerm4 adj_perm = adjacent -> adjacentGluing(perm[face]); if (!(perm*adj_perm).isIdentity()) { valid = false; // This printing statement is temporary code // to be removed once enough people have tested it std::cerr << "ERROR: Permutations of adjacent faces " "do not match in skeleton.cpp" << std::endl; } if ((*it) != adjacent -> adjacentTetrahedron(perm[face])) { valid = false; // This printing statement is temporary code // to be removed once enough people have tested it std::cerr << "ERROR: Adjacency relations do not match" " in skeleton.cpp" << std::endl; } } } } void NTriangulation::calculateComponents() const { TetrahedronIterator it; NComponent* label; NTetrahedron* tet; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) (*it)->component = 0; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; if (tet->component == 0) { label = new NComponent(); labelComponent(tet, label); components.push_back(label); } } } void NTriangulation::labelComponent(NTetrahedron* firstTet, NComponent* component) const { // Now non-recursive; uses a queue instead. // The queue contains tetrahedra from which we need to propagate // component labelling. // Use plain C arrays for the queue. Since each tetrahedron is pushed // on at most once, the array size does not need to be very large. // Note that we have >= 1 tetrahedron, since firstTet != 0. NTetrahedron** queue = new NTetrahedron*[tetrahedra.size()]; firstTet->component = component; component->tetrahedra.push_back(firstTet); firstTet->tetOrientation = 1; unsigned queueStart = 0, queueEnd = 1; queue[0] = firstTet; NTetrahedron* tet; NTetrahedron* adjTet; int face; int yourOrientation; while (queueStart < queueEnd) { tet = queue[queueStart++]; for (face=0; face<4; face++) { adjTet = tet->adjacentTetrahedron(face); if (adjTet) { yourOrientation = (tet->adjacentGluing(face). sign() == 1 ? -tet->tetOrientation : tet->tetOrientation); if (adjTet->component) { if (yourOrientation != adjTet->tetOrientation) { orientable = false; component->orientable = false; } } else { adjTet->component = component; component->tetrahedra.push_back(adjTet); adjTet->tetOrientation = yourOrientation; queue[queueEnd++] = adjTet; } } } } delete[] queue; } void NTriangulation::calculateVertices() const { TetrahedronIterator it; int vertex; NTetrahedron* tet; NVertex* label; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (vertex=0; vertex<4; vertex++) tet->vertices[vertex] = 0; } for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (vertex=0; vertex<4; vertex++) if (! tet->vertices[vertex]) { label = new NVertex(tet->component); tet->component->vertices.push_back(label); labelVertex(tet, vertex, label); vertices.push_back(label); } } } void NTriangulation::labelVertex(NTetrahedron* firstTet, int firstVertex, NVertex* label) const { // Create a queue using simple arrays. // Since each tetrahedron vertex is pushed on at most once, the // array size does not need to be very large. // Note that we have >= 1 tetrahedron, since firstTet != 0. NTetrahedron** queueTet = new NTetrahedron*[tetrahedra.size() * 4]; int* queueVtx = new int[tetrahedra.size() * 4]; firstTet->vertices[firstVertex] = label; firstTet->vertexMapping[firstVertex] = NPerm4(0, firstVertex); firstTet->tmpOrientation[firstVertex] = 1; label->embeddings.push_back(NVertexEmbedding(firstTet, firstVertex)); unsigned queueStart = 0, queueEnd = 1; queueTet[0] = firstTet; queueVtx[0] = firstVertex; NTetrahedron* tet; NTetrahedron* adjTet; int vertex; int adjVertex; int adjOrientation; int face; NPerm4 adjMap; while (queueStart < queueEnd) { tet = queueTet[queueStart]; vertex = queueVtx[queueStart]; queueStart++; for (face=0; face<4; face++) { if (face == vertex) continue; adjTet = tet->adjacentTetrahedron(face); if (adjTet) { // When we choose an adjacent gluing map, throw in a // swap to preserve the "orientation" of the cycle // formed by the images of 1, 2 and 3. Note that this // only becomes meaningful if the vertex link is an // orientable surface (otherwise there is no consistent // way to orient these cycles at all). adjMap = tet->adjacentGluing(face) * tet->vertexMapping[vertex] * NPerm4(1, 2); adjVertex = adjMap[0]; // We should actually be inverting NTriangle::ordering[adjVertex]. // However, all we care about is the sign of the permutation, // so let's save ourselves those extra few CPU cycles. if ((NTriangle::ordering[adjVertex] * tet->adjacentGluing(face) * NTriangle::ordering[vertex]).sign() > 0) adjOrientation = -(tet->tmpOrientation[vertex]); else adjOrientation = tet->tmpOrientation[vertex]; if (adjTet->vertices[adjVertex]) { if (adjTet->tmpOrientation[adjVertex] != adjOrientation) label->linkOrientable = false; } else { adjTet->vertices[adjVertex] = label; adjTet->vertexMapping[adjVertex] = adjMap; adjTet->tmpOrientation[adjVertex] = adjOrientation; label->embeddings.push_back(NVertexEmbedding(adjTet, adjVertex)); queueTet[queueEnd] = adjTet; queueVtx[queueEnd] = adjVertex; queueEnd++; } } } } delete[] queueTet; delete[] queueVtx; } void NTriangulation::calculateEdges() const { TetrahedronIterator it; int edge; NTetrahedron* tet; NEdge* label; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (edge=0; edge<6; edge++) tet->edges[edge] = 0; } for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (edge=0; edge<6; edge++) if (! tet->edges[edge]) { label = new NEdge(tet->component); tet->component->edges.push_back(label); labelEdge(tet, edge, label); edges.push_back(label); } } } void NTriangulation::labelEdge(NTetrahedron* firstTet, int firstEdge, NEdge* label) const { // Since tetrahedron edges are joined together in a loop, the depth-first // search is really just a straight line in either direction. // We therefore do away with the usual stack/queue and just keep track // of the next edge to process in the current direction. firstTet->edges[firstEdge] = label; firstTet->edgeMapping[firstEdge] = NEdge::ordering[firstEdge]; label->embeddings.push_back(NEdgeEmbedding(firstTet, firstEdge)); // The last tetrahedron edge that was successfully processed. NTetrahedron* tet; NPerm4 tetVertices; int exitFace; NPerm4 exitPerm; // The next tetrahedron edge around from this. NTetrahedron* nextTet; int nextEdge; NPerm4 nextVertices; for (int dir = 0; dir < 2; dir++) { // Start at the start and walk in one particular direction. tet = firstTet; tetVertices = tet->edgeMapping[firstEdge]; while (true) { // Move through to the next tetrahedron. exitFace = tetVertices[dir == 0 ? 2 : 3]; nextTet = tet->adjacentTetrahedron(exitFace); if (! nextTet) break; exitPerm = tet->adjacentGluing(exitFace); nextVertices = exitPerm * tetVertices * NPerm4(2, 3); nextEdge = NEdge::edgeNumber[nextVertices[0]][nextVertices[1]]; if (nextTet->edges[nextEdge]) { // We looped right around. // Check that we're not labelling the edge in reverse. if (nextTet->edgeMapping[nextEdge][0] != nextVertices[0]) { // The edge is being labelled in reverse! label->valid = false; valid = false; } break; } // We have a new tetrahedron edge; this needs to be labelled. nextTet->edges[nextEdge] = label; nextTet->edgeMapping[nextEdge] = nextVertices; if (dir == 0) label->embeddings.push_back(NEdgeEmbedding(nextTet, nextEdge)); else label->embeddings.push_front(NEdgeEmbedding(nextTet, nextEdge)); tet = nextTet; tetVertices = nextVertices; } } } void NTriangulation::calculateTriangles() const { TetrahedronIterator it; int face; NTetrahedron* tet; NTetrahedron* adjTet; NTriangle* label; NPerm4 adjVertices; int adjFace; for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (face=0; face<4; face++) tet->triangles[face] = 0; } for (it = tetrahedra.begin(); it != tetrahedra.end(); it++) { tet = *it; for (face=3; face>=0; face--) if (! tet->triangles[face]) { label = new NTriangle(tet->component); tet->component->triangles.push_back(label); tet->triangles[face] = label; tet->triMapping[face] = NTriangle::ordering[face]; label->embeddings[0] = new NTriangleEmbedding(tet, face); label->nEmbeddings = 1; adjTet = tet->adjacentTetrahedron(face); if (adjTet) { // Triangle is not on the boundary. adjFace = tet->adjacentFace(face); adjVertices = (tet->adjacentGluing(face))* tet->triMapping[face]; adjTet->triangles[adjFace] = label; adjTet->triMapping[adjFace] = adjVertices; label->embeddings[1] = new NTriangleEmbedding(adjTet, adjFace); label->nEmbeddings = 2; } triangles.push_back(label); } } } void NTriangulation::calculateBoundary() const { // Sets boundaryComponents, NTriangle.boundaryComponent, // NEdge.boundaryComponent, NVertex.boundaryComponent, // NComponent.boundaryComponents TriangleIterator it; NTriangle* triangle; NBoundaryComponent* label; for (it = triangles.begin(); it != triangles.end(); it++) { triangle = *it; if (triangle->nEmbeddings < 2) if (triangle->boundaryComponent == 0) { label = new NBoundaryComponent(); label->orientable = true; labelBoundaryTriangle(triangle, label); boundaryComponents.push_back(label); triangle->component->boundaryComponents.push_back(label); } } } void NTriangulation::labelBoundaryTriangle(NTriangle* firstTriangle, NBoundaryComponent* label) const { std::queue triangleQueue; NTriangleEmbedding* emb; emb = firstTriangle->embeddings[0]; firstTriangle->boundaryComponent = label; label->triangles.push_back(firstTriangle); emb->getTetrahedron()->tmpOrientation[emb->getTriangle()] = 1; triangleQueue.push(firstTriangle); NTetrahedron* tet; NPerm4 tetVertices; int tetFace; int i,j; NVertex* vertex; NEdge* edge; NTriangle* triangle; NTriangle* nextTriangle; int nextFaceNumber; NPerm4 nextFacePerm; NTetrahedron* nextTet; int followFromFace; NPerm4 switchPerm; int yourOrientation; while (! triangleQueue.empty()) { triangle = triangleQueue.front(); triangleQueue.pop(); // Run through the edges and vertices on this triangle. emb = triangle->embeddings[0]; tet = emb->getTetrahedron(); tetFace = emb->getTriangle(); tetVertices = tet->triMapping[tetFace]; // Run through the vertices. for (i=0; i<3; i++) { vertex = tet->vertices[tetVertices[i]]; if (vertex->boundaryComponent != label) { // A vertex in an invalid triangulation might end up in // more than one boundary component. Push it into all // of the relevant boundary components' lists. vertex->boundaryComponent = label; label->vertices.push_back(vertex); } } // Run through the edges. for (i=0; i<3; i++) for (j=i+1; j<3; j++) { edge = tet->edges[NEdge::edgeNumber[tetVertices[i]] [tetVertices[j]]]; if (! (edge->boundaryComponent)) { edge->boundaryComponent = label; label->edges.push_back(edge); } // Label the adjacent boundary triangle with the same label. followFromFace = 6 - tetVertices[i] - tetVertices[j] - tetFace; switchPerm = NPerm4(followFromFace, tetFace); nextFaceNumber = followFromFace; nextFacePerm = NPerm4(); nextTet = tet; while (nextTet->adjacentTetrahedron(nextFaceNumber)) { nextFacePerm = nextTet->adjacentGluing( nextFaceNumber) * nextFacePerm * switchPerm; nextTet = nextTet->adjacentTetrahedron(nextFaceNumber); nextFaceNumber = nextFacePerm[followFromFace]; } nextTriangle = nextTet->triangles[nextFaceNumber]; // Find the expected orientation of the next triangle. yourOrientation = (nextTet->triMapping[nextFaceNumber].inverse() * nextFacePerm * switchPerm * tet->triMapping[tetFace]) .sign() == 1 ? -tet->tmpOrientation[tetFace] : tet->tmpOrientation[tetFace]; if (nextTriangle->boundaryComponent) { // Check the orientation. if (yourOrientation != nextTet->tmpOrientation[nextFaceNumber]) label->orientable = false; } else { // Add this adjacent triangle to the queue. nextTriangle->boundaryComponent = label; label->triangles.push_back(nextTriangle); nextTet->tmpOrientation[nextFaceNumber] = yourOrientation; triangleQueue.push(nextTriangle); } } } } void NTriangulation::calculateVertexLinks() const { // Begin by calculating Euler characteristics. // Here we use the formula: chi = (2 v_int + v_bdry - f) / 2, which // is easily proven with a little arithmetic. // Note that NVertex::linkEulerCharacteristic is initialised to 0 in // the NVertex constructor. // Begin by calculating (2 v_int + v_bdry) for each vertex link. NEdge* e; NVertex* end0; NVertex* end1; NTetrahedron* tet; for (EdgeIterator eit = edges.begin(); eit != edges.end(); eit++) { e = *eit; // Try to compute e->getVertex(0) and e->getVertex(1), but // without calling e->getVertex() which will recursively try to // recompute the skeleton. const NEdgeEmbedding& emb = e->getEmbeddings().front(); tet = emb.getTetrahedron(); end0 = tet->vertices[tet->edgeMapping[emb.getEdge()][0]]; end1 = tet->vertices[tet->edgeMapping[emb.getEdge()][1]]; if (e->isBoundary()) { // Contribute to v_bdry. end0->linkEulerCharacteristic++; if (e->valid) end1->linkEulerCharacteristic++; } else { // Contribute to 2 v_int. end0->linkEulerCharacteristic += 2; if (e->valid) end1->linkEulerCharacteristic += 2; } } // Run through each vertex and finalise Euler characteristic, link // and more. NVertex* vertex; for (VertexIterator it = vertices.begin(); it != vertices.end(); it++) { vertex = *it; // Fix the Euler characteristic (subtract f, divide by two). vertex->linkEulerCharacteristic = (vertex->linkEulerCharacteristic - static_cast(vertex->getEmbeddings().size())) / 2; if (vertex->isBoundary()) { // We haven't added ideal vertices to the boundary list yet, // so this must be real boundary. if (vertex->linkEulerCharacteristic == 1) vertex->link = NVertex::DISC; else { vertex->link = NVertex::NON_STANDARD_BDRY; valid = false; standard = false; } } else { if (vertex->linkEulerCharacteristic == 2) vertex->link = NVertex::SPHERE; else { if (vertex->linkEulerCharacteristic == 0) vertex->link = (vertex->isLinkOrientable() ? NVertex::TORUS : NVertex::KLEIN_BOTTLE); else { vertex->link = NVertex::NON_STANDARD_CUSP; standard = false; } ideal = true; vertex->component->ideal = true; NBoundaryComponent* bc = new NBoundaryComponent(vertex); bc->orientable = vertex->isLinkOrientable(); vertex->boundaryComponent = bc; boundaryComponents.push_back(bc); vertex->component->boundaryComponents.push_back(bc); } } } } void NTriangulation::calculateBoundaryProperties() const { // Make sure the skeleton has been calculated! if (! calculatedSkeleton) calculateSkeleton(); bool localTwoSphereBoundaryComponents = false; bool localNegativeIdealBoundaryComponents = false; for (BoundaryComponentIterator it = boundaryComponents.begin(); it != boundaryComponents.end(); it++) { if ((*it)->getEulerCharacteristic() == 2) localTwoSphereBoundaryComponents = true; else if ((*it)->isIdeal() && (*it)->getEulerCharacteristic() < 0) localNegativeIdealBoundaryComponents = true; // Stop the search if we've found everything we're looking for. if (localTwoSphereBoundaryComponents && localNegativeIdealBoundaryComponents) break; } twoSphereBoundaryComponents = localTwoSphereBoundaryComponents; negativeIdealBoundaryComponents = localNegativeIdealBoundaryComponents; } } // namespace regina regina-4.95/engine/triangulation/subdivide.cpp000644 000765 000024 00000035115 12234011536 021420 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include "triangulation/ntriangulation.h" #include "utilities/memutils.h" #include "utilities/stlutils.h" namespace regina { void NTriangulation::barycentricSubdivision() { // Rewritten for Regina 4.94 to use a more sensible labelling scheme. // IMPORTANT: If this code is ever rewritten (and in particular, if // the labelling of new tetrahedra ever changes), then the // drillEdge() code must be rewritten as well (since it relies on // the specific labelling scheme that we use here). unsigned long nOldTet = tetrahedra.size(); if (nOldTet == 0) return; NTriangulation staging; ChangeEventSpan span1(&staging); NTetrahedron** newTet = new NTetrahedron*[nOldTet * 24]; NTetrahedron* oldTet; // A tetrahedron in the subdivision is uniquely defined by the // permutation (face, edge, vtx, corner) of (0, 1, 2, 3). // This is the tetrahedron that: // - meets the boundary in the face opposite vertex "face"; // - meets that face in the edge opposite vertex "edge"; // - meets that edge in the vertex opposite vertex "vtx"; // - directly touches vertex "corner". unsigned long tet; for (tet = 0; tet < 24 * nOldTet; ++tet) newTet[tet] = staging.newTetrahedron(); // Do all of the internal gluings int permIdx; NPerm4 perm, glue; for (tet=0; tet < nOldTet; ++tet) for (permIdx = 0; permIdx < 24; ++permIdx) { perm = NPerm4::S4[permIdx]; // (0, 1, 2, 3) -> (face, edge, vtx, corner) // Internal gluings within the old tetrahedron: newTet[24 * tet + permIdx]->joinTo(perm[3], newTet[24 * tet + (perm * NPerm4(3, 2)).S4Index()], NPerm4(perm[3], perm[2])); newTet[24 * tet + permIdx]->joinTo(perm[2], newTet[24 * tet + (perm * NPerm4(2, 1)).S4Index()], NPerm4(perm[2], perm[1])); newTet[24 * tet + permIdx]->joinTo(perm[1], newTet[24 * tet + (perm * NPerm4(1, 0)).S4Index()], NPerm4(perm[1], perm[0])); // Adjacent gluings to the adjacent tetrahedron: oldTet = getTetrahedron(tet); if (! oldTet->adjacentTetrahedron(perm[0])) continue; // This hits a boundary triangle. if (newTet[24 * tet + permIdx]->adjacentTetrahedron(perm[0])) continue; // We've already done this gluing from the other side. glue = oldTet->adjacentGluing(perm[0]); newTet[24 * tet + permIdx]->joinTo(perm[0], newTet[24 * tetrahedronIndex( oldTet->adjacentTetrahedron(perm[0])) + (glue * perm).S4Index()], glue); } // Delete the existing tetrahedra and put in the new ones. ChangeEventSpan span2(this); removeAllTetrahedra(); swapContents(staging); delete[] newTet; } void NTriangulation::drillEdge(NEdge* e) { // Recall from the barycentric subdivision code above that // a tetrahedron in the subdivision is uniquely defined by the // permutation (face, edge, vtx, corner) of (0, 1, 2, 3). // // For an edge (i,j) opposite vertices (k,l), the tetrahedra that // meet it are: // // (k,l,i,j) and (l,k,i,j), both containing the half-edge touching vertex j; // (k,l,j,i) and (l,k,j,i), both containing the half-edge touching vertex i. // // In each case the corresponding edge number in the new tetrahedron // equals the edge number from the original tetrahedron. int edgeNum = e->getEmbedding(0).getEdge(); long tetNum = tetrahedronIndex(e->getEmbedding(0).getTetrahedron()); int oldToNew[2]; // Identifies two of the 24 tetrahedra in a subdivision // that contain the two corresponding half-edges. oldToNew[0] = NPerm4( NEdge::edgeVertex[5 - edgeNum][0], NEdge::edgeVertex[5 - edgeNum][1], NEdge::edgeVertex[edgeNum][0], NEdge::edgeVertex[edgeNum][1]). S4Index(); oldToNew[1] = NPerm4( NEdge::edgeVertex[5 - edgeNum][0], NEdge::edgeVertex[5 - edgeNum][1], NEdge::edgeVertex[edgeNum][1], NEdge::edgeVertex[edgeNum][0]). S4Index(); ChangeEventSpan span(this); barycentricSubdivision(); barycentricSubdivision(); std::set toRemove; int i, j, k; unsigned long finalTet; NVertex* finalVertex; std::vector::const_iterator it; for (i = 0; i < 2; ++i) for (j = 0; j < 2; ++j) { finalTet = 24 * (24 * tetNum + oldToNew[i]) + oldToNew[j]; // Remove all tetrahedra that touch each endpoint of the // resulting edge in the second barycentric subdivision. for (k = 0; k < 2; ++k) { finalVertex = tetrahedra[finalTet]->getEdge(edgeNum)-> getVertex(k); for (it = finalVertex->getEmbeddings().begin(); it != finalVertex->getEmbeddings().end(); ++it) toRemove.insert(tetrahedronIndex(it->getTetrahedron())); } } // Make sure we remove tetrahedra in reverse order, so the numbering // doesn't change. for (std::set::reverse_iterator rit = toRemove.rbegin(); rit != toRemove.rend(); ++rit) removeTetrahedronAt(*rit); // We have lots of tetrahedra now. Simplify. intelligentSimplify(); } bool NTriangulation::idealToFinite(bool forceDivision) { // The call to isValid() ensures the skeleton has been calculated. if (isValid() && ! isIdeal()) if (! forceDivision) return false; int i,j,k,l; long numOldTet = tetrahedra.size(); if (! numOldTet) return false; NTriangulation staging; ChangeEventSpan span1(&staging); NTetrahedron **newTet = new NTetrahedron*[32*numOldTet]; for (i=0; i<32*numOldTet; i++) newTet[i] = staging.newTetrahedron(); int tip[4]; int interior[4]; int edge[4][4]; int vertex[4][4]; int nDiv = 0; for (j=0; j<4; j++) { tip[j] = nDiv++; interior[j] = nDiv++; for (k=0; k<4; k++) if (j != k) { edge[j][k] = nDiv++; vertex[j][k] = nDiv++; } } // First glue all of the tetrahedra inside the same // old tetrahedron together. for (i=0; ijoinTo(j, newTet[interior[j] + i * nDiv], NPerm4()); // Glue the interior tetrahedra to the others. for (j=0; j<4; j++) { for (k=0; k<4; k++) if (j != k) { newTet[interior[j] + i * nDiv]->joinTo(k, newTet[vertex[k][j] + i * nDiv], NPerm4()); } } // Glue the edge tetrahedra to the others. for (j=0; j<4; j++) for (k=0; k<4; k++) if (j != k) { newTet[edge[j][k] + i * nDiv]->joinTo(j, newTet[edge[k][j] + i * nDiv], NPerm4(j,k)); for (l=0; l<4; l++) if ( (l != j) && (l != k) ) newTet[edge[j][k] + i * nDiv]->joinTo(l, newTet[vertex[j][l] + i * nDiv], NPerm4(k,l)); } } // Now deal with the gluings between the pieces inside adjacent tetrahedra. NTetrahedron *ot; long oppTet; NPerm4 p; for (i=0; iadjacentTetrahedron(j)) { oppTet = tetrahedronIndex(ot->adjacentTetrahedron(j)); p = ot->adjacentGluing(j); // First deal with the tip tetrahedra. for (k=0; k<4; k++) if (j != k) newTet[tip[k] + i * nDiv]->joinTo(j, newTet[tip[p[k]] + oppTet * nDiv], p); // Next the edge tetrahedra. for (k=0; k<4; k++) if (j != k) newTet[edge[j][k] + i * nDiv]->joinTo(k, newTet[edge[p[j]][p[k]] + oppTet * nDiv], p); // Finally, the vertex tetrahedra. for (k=0; k<4; k++) if (j != k) newTet[vertex[j][k] + i * nDiv]->joinTo(k, newTet[vertex[p[j]][p[k]] + oppTet * nDiv], p); } } ChangeEventSpan span2(this); removeAllTetrahedra(); swapContents(staging); calculateSkeleton(); // Remove the tetrahedra that meet any of the non-standard or // ideal vertices. // First we make a list of the tetrahedra. std::vector tetList; std::vector::const_iterator vembit; for (VertexIterator vIter = vertices.begin(); vIter != vertices.end(); vIter++) if ((*vIter)->isIdeal() || ! (*vIter)->isStandard()) for (vembit = (*vIter)->getEmbeddings().begin(); vembit != (*vIter)->getEmbeddings().end(); vembit++) tetList.push_back((*vembit).getTetrahedron()); // Now remove the tetrahedra. // For each tetrahedron, remove it and delete it. for_each(tetList.begin(), tetList.end(), std::bind1st(std::mem_fun(&NTriangulation::removeTetrahedron), this)); delete[] newTet; return true; } bool NTriangulation::finiteToIdeal() { if (! hasBoundaryTriangles()) return false; // Make a list of all boundary triangles, indexed by triangle number, // and create the corresponding new tetrahedra. unsigned long nTriangles = getNumberOfTriangles(); NTriangulation staging; NTetrahedron** bdry = new NTetrahedron*[nTriangles]; NPerm4* bdryPerm = new NPerm4[nTriangles]; NTetrahedron** newTet = new NTetrahedron*[nTriangles]; ChangeEventSpan span1(&staging); TriangleIterator fit; unsigned i; for (i = 0, fit = triangles.begin(); fit != triangles.end(); ++i, ++fit) { if (! (*fit)->isBoundary()) { bdry[i] = newTet[i] = 0; continue; } bdry[i] = (*fit)->getEmbedding(0).getTetrahedron(); bdryPerm[i] = (*fit)->getEmbedding(0).getVertices(); } // Add the new tetrahedra one boundary component at a time, so that // the tetrahedron labels are compatible with previous versions of // regina (<= 4.6). BoundaryComponentIterator bit; for (bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) for (i = 0; i < (*bit)->getNumberOfTriangles(); ++i) newTet[(*bit)->getTriangle(i)->markedIndex()] = staging.newTetrahedron(); // Glue the new tetrahedra to each other. NEdge* edge; unsigned long tetTriangle1, tetTriangle2; NPerm4 t1Perm, t2Perm; for (bit = boundaryComponents.begin(); bit != boundaryComponents.end(); bit++) for (i = 0; i < (*bit)->getNumberOfEdges(); i++) { edge = (*bit)->getEdge(i); // This must be a valid boundary edge. // Find the boundary triangles at either end. NEdgeEmbedding e1 = edge->getEmbeddings().front(); NEdgeEmbedding e2 = edge->getEmbeddings().back(); tetTriangle1 = e1.getTetrahedron()->getTriangle( e1.getVertices()[3])->markedIndex(); tetTriangle2 = e2.getTetrahedron()->getTriangle( e2.getVertices()[2])->markedIndex(); t1Perm = bdryPerm[tetTriangle1].inverse() * e1.getVertices(); t2Perm = bdryPerm[tetTriangle2].inverse() * e2.getVertices() * NPerm4(2, 3); newTet[tetTriangle1]->joinTo(t1Perm[2], newTet[tetTriangle2], t2Perm * t1Perm.inverse()); } // Now join the new tetrahedra to the boundary triangles of the original // triangulation. // Set up a change block, since here we start changing the original // triangulation. ChangeEventSpan span2(this); staging.moveContentsTo(*this); for (i = 0; i < nTriangles; ++i) if (newTet[i]) newTet[i]->joinTo(3, bdry[i], bdryPerm[i]); // Clean up and return. delete[] newTet; delete[] bdryPerm; delete[] bdry; return true; } } // namespace regina regina-4.95/engine/triangulation/surfaces.cpp000644 000765 000024 00000026543 12236247215 021271 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "enumerate/ntreetraversal.h" #include "triangulation/ntriangulation.h" #include "surfaces/nnormalsurfacelist.h" namespace regina { /** * When testing 0-efficiency, to prove that a normal 2-sphere must occur * at a vertex we use Euler characteristic arguments. One issue that * arises for non-orientable 3-manifolds is whether a non-vertex normal * 2-sphere can be decomposed into two-sided projective planes and other * surfaces of non-positive Euler characteristic. On this issue, Jaco * writes: * * "Remember that in any 3-manifold, regular curves of intersection between * normal surfaces are orientation preserving; thus if you add a two-sided * projective plane to any other surface, the curves of intersection must * be trivial curves on the projective plane - thus the result must be * nonorientable." */ /** * In the general case, 0-efficiency must be tested for in standard * triangle-quad coordinates. For example, the triangulation with * isosig dLQacccbnjk (which is ideal with one torus cusp) is * not 0-efficient, but the non-trivial sphere does not appear as a * vertex in quad coordinates. */ /** * Splitting surfaces must also be tested for in standard triangle-quad * coordinates. See the triangulation J_{1|3,-5} (chained triangular * solid torus of major type) of S^3 / Q_32 x Z_3 an an example of a * triangulation with a splitting surface having chi=-1 that can be * decomposed in quad space as the sum of two vertex normal tori minus a * vertex link. */ NNormalSurface* NTriangulation::hasNonTrivialSphereOrDisc() { // Get the empty triangulation out of the way now. if (tetrahedra.empty()) return 0; // Do we already know the answer? if (zeroEfficient.known() && zeroEfficient.value()) return 0; // Use combinatorial optimisation if we can. if (isValid() && vertices.size() == 1) { // For now, just use the safe arbitrary-precision NInteger type. NTreeSingleSoln tree(this, NS_STANDARD); if (tree.find()) { NNormalSurface* s = tree.buildSurface(); if (! ((! s->hasRealBoundary()) && (s->getEulerCharacteristic() == 1) && s->isTwoSided())) return s; // Looks like we've found a two-sided projective plane. // Fall through to a full enumeration of vertex surfaces. delete s; } else return 0; } // Fall back to a slow-but-general method: enumerate all vertex surfaces. // For valid, non-ideal triangulations we can do this in quad // coordinates (where a non-trivial sphere or disc is guaranteed to // appear as a vertex surface). Otherwise fall back to standard coords. NNormalSurfaceList* surfaces = NNormalSurfaceList::enumerate(this, (isValid() && ! isIdeal()) ? NS_QUAD : NS_STANDARD); const NNormalSurface* s; NNormalSurface* ans = 0; for (size_t i = 0; i < surfaces->getNumberOfSurfaces() && ! ans; ++i) { s = surfaces->getSurface(i); // These are vertex surfaces, so we know they must be connected. // Because we are either (i) using standard coordinates, or // (ii) working with a non-ideal triangulation; we know the // vertex surfaces are compact also. if (s->isVertexLinking()) continue; // Now they are compact, connected and non-vertex-linking. // We just need to pick out spheres and discs. if (s->getEulerCharacteristic() == 2) { // Must be a sphere; no bounded surface has chi=2. ans = s->clone(); } else if (s->getEulerCharacteristic() == 1) { if (s->hasRealBoundary()) { // Must be a disc. ans = s->clone(); } else if (! s->isTwoSided()) { // A projective plane that doubles to a sphere. ans = s->doubleSurface(); } } } delete surfaces; return ans; } NNormalSurface* NTriangulation::hasOctagonalAlmostNormalSphere() { // Get the empty triangulation out of the way now. if (tetrahedra.empty()) return 0; // Use combinatorial optimisation if we can. // This is good for large problems, but for small problems a full // enumeration is usually faster. Still, the big problems are the // ones we need to be more fussy about. if (vertices.size() == 1) { // For now, just use the safe arbitrary-precision NInteger type. NTreeSingleSoln tree(this, NS_AN_STANDARD); if (tree.find()) { // Since our preconditions ensure the triangulation is // closed, orientable and 0-efficient, there are no // non-vertex-linking normal surfaces with positive Euler // characteristic. Our optimisation asks for (Euler - #octs) > 0, // which then implies that our surface here is almost normal // with exactly 1 octagon and Euler = 2. This is exactly // what we're looking for. NNormalSurface* s = tree.buildSurface(); return s; } else return 0; } // Fall back to a slow-but-general method: enumerate all vertex surfaces. // Given our preconditions, we can do this in quadrilateral-octagon // coordinates; for details see "Quadrilateral-octagon coordinates for // almost normal surfaces", B.B., Experiment. Math. 19 (2010), 285-315. NNormalSurfaceList* surfaces = NNormalSurfaceList::enumerate(this, NS_AN_QUAD_OCT); // Our vertex surfaces are guaranteed to be in smallest possible // integer coordinates, with at most one non-zero octagonal coordinate. const NNormalSurface* s; NNormalSurface* ans = 0; unsigned long tet; unsigned oct; bool found, broken; NLargeInteger coord; for (size_t i = 0; i < surfaces->getNumberOfSurfaces() && ! ans; ++i) { s = surfaces->getSurface(i); // These are vertex surfaces, so we know they must be connected. // Because we are working with a non-ideal triangulation, we know the // vertex surfaces are compact. // Hunt for spheres with exactly one octagon. // Note that 1-sided projective planes are no good here, // since when doubled they give too many octagonal discs. if (s->getEulerCharacteristic() == 2) { // Euler char = 2 implies no real boundary. found = false; // At least one octagon found so far? broken = false; // More than one octagon found so far? for (tet = 0; tet < tetrahedra.size() && ! broken; ++tet) for (oct = 0; oct < 3; ++oct) { coord = s->getOctCoord(tet, oct); if (coord > 1) { broken = true; break; } else if (coord == 1) { if (found) { broken = true; break; } else found = true; } } if (found && ! broken) { // This is it! ans = s->clone(); } } } delete surfaces; return ans; } bool NTriangulation::isZeroEfficient() { if (! zeroEfficient.known()) { if (hasTwoSphereBoundaryComponents()) zeroEfficient = false; else { // Operate on a clone of this triangulation, to avoid // changing the real packet tree. NTriangulation clone(*this); NNormalSurface* s = clone.hasNonTrivialSphereOrDisc(); if (s) { zeroEfficient = false; delete s; } else { zeroEfficient = true; // Things implied by 0-efficiency: if (isValid() && isClosed() && isConnected()) irreducible = true; } } } return zeroEfficient.value(); } bool NTriangulation::hasSplittingSurface() { // Splitting surfaces must unfortunately be calculated using // tri-quad coordinates. if (splittingSurface.known()) return splittingSurface.value(); // Create a normal surface list. // // Work on a clone of this triangulation so we don't trigger any // changes to the packet tree. NTriangulation working(*this); NNormalSurfaceList* surfaces = NNormalSurfaceList::enumerate(&working, NS_STANDARD); // Run through all vertex surfaces. unsigned long nSurfaces = surfaces->getNumberOfSurfaces(); const NNormalSurface* s; NLargeInteger chi; for (unsigned long i = 0; i < nSurfaces; i++) { s = surfaces->getSurface(i); if (! splittingSurface.known()) if (s->isSplitting()) splittingSurface = true; // See if there is no use running through the rest of the list. if (splittingSurface.known()) break; } // Done! if (! splittingSurface.known()) splittingSurface = false; // The stack will clean things up for us automatically. return splittingSurface.value(); } } // namespace regina regina-4.95/engine/triangulation/turaevviro.cpp000644 000765 000024 00000031534 12234011536 021651 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include "regina-config.h" #include "maths/approx.h" #include "maths/numbertheory.h" #include "triangulation/ntriangulation.h" namespace regina { namespace { /** * Allows calculation of [n]! for arbitrary n. * Values are cached as they are calculated. */ class BracketFactorial { private: double* fact; /**< The cached values [0]!, [1]!, ..., [r-1]! . */ double* inv; /**< The cached inverses of the values stored in fact[]. */ double angle; /**< The angle arg(q0). */ unsigned long r; /**< The integer r, for which 2r * angle is some integer multiple of 2 * Pi. */ public: /** * Precalculate all values [0]!, ..., [r-1]!. * * Requires r >= 3. */ BracketFactorial(double newAngle, unsigned long newR) : fact(new double[newR]), inv(new double[newR]), angle(newAngle), r(newR) { fact[0] = fact[1] = inv[0] = inv[1] = 1.0; for (unsigned long i = 2; i < r; i++) { fact[i] = fact[i - 1] * sin(angle * i) / sin(angle); inv[i] = inv[i - 1] * sin(angle) / sin(angle * i); } } /** * Clean up memory. */ ~BracketFactorial() { delete[] fact; delete[] inv; } /** * Calculates the single value [n] (note that there is no * factorial symbol included). * These values are individually easy to calculate and so * are not cached. */ double bracket(unsigned long index) const { if (index == 0 || index == 1) return 1; return sin(angle * index) / sin(angle); } /** * Returns the value [index]!. */ double operator [] (unsigned long index) const { return (index < r ? fact[index] : 0.0); } /** * Returns the value [index]! ^ -1. * * Requires index < r. */ double inverse(unsigned long index) const { return inv[index]; } }; /** * Represents the initial data as described in Section 7 of Turaev * and Viro's paper. */ struct InitialData { unsigned long r; /**< The integer r. */ double angle; /**< The angle arg(q0). */ BracketFactorial fact; /**< The cached values [n]!. */ double vertexContrib; /**< The vertex-based contribution to the Turaev-Viro invariant; this is the inverse square of the distinguished value w. */ InitialData(unsigned long newR, double newAngle) : r(newR), angle(newAngle), fact(angle, r) { vertexContrib = 2.0 * sin(angle) * sin(angle) / r; } /** * Determines whether (i/2, j/2, k/2) is an admissible triple. */ bool isAdmissible(unsigned long i, unsigned long j, unsigned long k) const { return ((i + j + k) % 2 == 0) && (i <= j + k) && (j <= i + k) && (k <= i + j) && (i + j + k <= 2 * (r - 2)); } /** * Determines the triangle-based contribution to the Turaev-Viro * invariant. This corresponds to +/- Delta(i/2, j/2, k/2)^2. */ std::complex triContrib(unsigned long i, unsigned long j, unsigned long k) const { // By admissibility, (i + j + k) is guaranteed to be even. std::complex ans = fact[(i + j - k) / 2] * fact[(j + k - i) / 2] * fact[(k + i - j) / 2] * fact.inverse((i + j + k + 2) / 2); return ((i + j + k) % 4 == 0 ? ans : -ans); } /** * Determines the edge-based contribution to the Turaev-Viro * invariant. This corresponds to w(i/2)^2. */ std::complex edgeContrib(unsigned long i) const { return (i % 2 == 0 ? fact.bracket(i + 1) : - fact.bracket(i + 1)); } /** * Determines the tetrahedron-based contribution to the Turaev-Viro * invariant. This combines with the square roots of the triangle-based * contributions for the four tetrahedron faces to give the symbol * * | i/2 j/2 k/2 | * | l/2 m/2 n/2 | . */ std::complex tetContrib(unsigned long i, unsigned long j, unsigned long k, unsigned long l, unsigned long m, unsigned long n) { std::complex ans = 0.0; unsigned long minZ = i + j + k; if (minZ < i + m + n) minZ = i + m + n; if (minZ < j + l + n) minZ = j + l + n; if (minZ < k + l + m) minZ = k + l + m; unsigned long maxZ = i + j + l + m; if (maxZ > i + k + l + n) maxZ = i + k + l + n; if (maxZ > j + k + m + n) maxZ = j + k + m + n; double term; for (unsigned long z = minZ; z <= maxZ; z++) { if (z % 2 != 0) continue; // We are guaranteed that z / 2 is an integer. term = fact[(z + 2) / 2] * fact.inverse((z - i - j - k) / 2) * fact.inverse((z - i - m - n) / 2) * fact.inverse((z - j - l - n) / 2) * fact.inverse((z - k - l - m) / 2) * fact.inverse((i + j + l + m - z) / 2) * fact.inverse((i + k + l + n - z) / 2) * fact.inverse((j + k + m + n - z) / 2); if (z % 4 == 0) ans += term; else ans -= term; } return ans; } }; } double NTriangulation::turaevViro(unsigned long r, unsigned long whichRoot) const { // Have we already calculated this invariant? std::pair tvParams(r, whichRoot); TuraevViroSet::const_iterator it = turaevViroCache.find(tvParams); if (it != turaevViroCache.end()) return (*it).second; // Do some basic parameter checks. if (r < 3) return 0; if (whichRoot >= 2 * r) return 0; if (gcd(r, whichRoot) > 1) return 0; // Set up our initial data. double angle = (M_PI * whichRoot) / r; InitialData init(r, angle); // Run through all admissible colourings. std::complex ans = 0.0; unsigned long nEdges = getNumberOfEdges(); unsigned long nTriangles = getNumberOfTriangles(); unsigned long* colour = new unsigned long[nEdges]; std::fill(colour, colour + nEdges, 0); long curr = 0; std::complex valColour; bool admissible; std::deque::const_iterator embit; long index1, index2; unsigned long i; while (curr >= 0) { // Have we found an admissible colouring? if (curr >= static_cast(nEdges)) { // Increment ans appropriately. valColour = 1.0; for (i = 0; i < vertices.size(); i++) valColour *= init.vertexContrib; for (i = 0; i < nEdges; i++) valColour *= init.edgeContrib(colour[i]); for (i = 0; i < nTriangles; i++) valColour *= init.triContrib( colour[edgeIndex(triangles[i]->getEdge(0))], colour[edgeIndex(triangles[i]->getEdge(1))], colour[edgeIndex(triangles[i]->getEdge(2))]); for (i = 0; i < tetrahedra.size(); i++) valColour *= init.tetContrib( colour[edgeIndex(tetrahedra[i]->getEdge(0))], colour[edgeIndex(tetrahedra[i]->getEdge(1))], colour[edgeIndex(tetrahedra[i]->getEdge(3))], colour[edgeIndex(tetrahedra[i]->getEdge(5))], colour[edgeIndex(tetrahedra[i]->getEdge(4))], colour[edgeIndex(tetrahedra[i]->getEdge(2))] ); ans += valColour; // Step back down one level. curr--; if (curr >= 0) colour[curr]++; continue; } // Have we run out of values to try at this level? if (colour[curr] > r - 2) { colour[curr] = 0; curr--; if (curr >= 0) colour[curr]++; continue; } // Does the current value for colour[curr] preserve admissibility? admissible = true; const std::deque& embs(edges[curr]->getEmbeddings()); for (embit = embs.begin(); embit != embs.end(); embit++) { index1 = edgeIndex((*embit).getTetrahedron()->getEdge( NEdge::edgeNumber[(*embit).getVertices()[0]] [(*embit).getVertices()[2]])); index2 = edgeIndex((*embit).getTetrahedron()->getEdge( NEdge::edgeNumber[(*embit).getVertices()[1]] [(*embit).getVertices()[2]])); if (index1 <= curr && index2 <= curr) { // We've decided upon colours for all three edges of // this triangle containing the current edge. if (! init.isAdmissible(colour[index1], colour[index2], colour[curr])) { admissible = false; break; } } } // Use the current value for colour[curr] if appropriate; // otherwise step forwards to the next value. if (admissible) curr++; else colour[curr]++; } delete[] colour; if (isNonZero(ans.imag())) { // This should never happen, since the Turaev-Viro invariant is the // square of the modulus of the Witten invariant for sl_2. std::cerr << "WARNING: The Turaev-Viro invariant has an imaginary component.\n" " This should never happen.\n" " Please report this (along with the 3-manifold that" " was used) to " << PACKAGE_BUGREPORT << "." << std::endl; } turaevViroCache[tvParams] = ans.real(); return ans.real(); } } // namespace regina regina-4.95/engine/utilities/base64.cpp000644 000765 000024 00000036143 12234011536 017663 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "utilities/base64.h" namespace regina { /** * These routines are taken and modified from the Base64 project at * base64.sourceforge.net. The front matter from the original b64.c is * included below. */ /*********************************************************************\ MODULE NAME: b64.c AUTHOR: Bob Trower 08/04/01 PROJECT: Crypt Data Packaging COPYRIGHT: Copyright (c) Trantor Standard Systems Inc., 2001 NOTES: This source code may be used as you wish, subject to the MIT license. See the LICENCE section below. Canonical source should be at: http://base64.sourceforge.net DESCRIPTION: This little utility implements the Base64 Content-Transfer-Encoding standard described in RFC1113 (http://www.faqs.org/rfcs/rfc1113.html). This is the coding scheme used by MIME to allow binary data to be transferred by SMTP mail. Groups of 3 bytes from a binary stream are coded as groups of 4 bytes in a text stream. The input stream is 'padded' with zeros to create an input that is an even multiple of 3. A special character ('=') is used to denote padding so that the stream can be decoded back to its exact size. Encoded output is formatted in lines which should be a maximum of 72 characters to conform to the specification. This program defaults to 72 characters, but will allow more or less through the use of a switch. The program enforces a minimum line size of 4 characters. Example encoding: The stream 'ABCD' is 32 bits long. It is mapped as follows: ABCD A (65) B (66) C (67) D (68) (None) (None) 01000001 01000010 01000011 01000100 16 (Q) 20 (U) 9 (J) 3 (D) 17 (R) 0 (A) NA (=) NA (=) 010000 010100 001001 000011 010001 000000 000000 000000 QUJDRA== Decoding is the process in reverse. A 'decode' lookup table has been created to avoid string scans. DESIGN GOALS: Specifically: Code is a stand-alone utility to perform base64 encoding/decoding. It should be genuinely useful when the need arises and it meets a need that is likely to occur for some users. Code acts as sample code to show the author's design and coding style. Generally: This program is designed to survive: Everything you need is in a single source file. It compiles cleanly using a vanilla ANSI C compiler. It does its job correctly with a minimum of fuss. The code is not overly clever, not overly simplistic and not overly verbose. Access is 'cut and paste' from a web page. Terms of use are reasonable. VALIDATION: Non-trivial code is never without errors. This file likely has some problems, since it has only been tested by the author. It is expected with most source code that there is a period of 'burn-in' when problems are identified and corrected. That being said, it is possible to have 'reasonably correct' code by following a regime of unit test that covers the most likely cases and regression testing prior to release. This has been done with this code and it has a good probability of performing as expected. Unit Test Cases: case 0:empty file: CASE0.DAT -> -> (Zero length target file created on both encode and decode.) case 1:One input character: CASE1.DAT A -> QQ== -> A case 2:Two input characters: CASE2.DAT AB -> QUI= -> AB case 3:Three input characters: CASE3.DAT ABC -> QUJD -> ABC case 4:Four input characters: case4.dat ABCD -> QUJDRA== -> ABCD case 5:All chars from 0 to ff, linesize set to 50: AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX 2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7 /P3+/w== case 6:Mime Block from e-mail: (Data same as test case 5) case 7: Large files: Tested 28 MB file in/out. case 8: Random Binary Integrity: This binary program (b64.exe) was encoded to base64, back to binary and then executed. case 9 Stress: All files in a working directory encoded/decoded and compared with file comparison utility to ensure that multiple runs do not cause problems such as exhausting file handles, tmp storage, etc. ------------- Syntax, operation and failure: All options/switches tested. Performs as expected. case 10: No Args -- Shows Usage Screen Return Code 1 (Invalid Syntax) case 11: One Arg (invalid) -- Shows Usage Screen Return Code 1 (Invalid Syntax) case 12: One Arg Help (-?) -- Shows detailed Usage Screen. Return Code 0 (Success -- help request is valid). case 13: One Arg Help (-h) -- Shows detailed Usage Screen. Return Code 0 (Success -- help request is valid). case 14: One Arg (valid) -- Uses stdin/stdout (filter) Return Code 0 (Sucess) case 15: Two Args (invalid file) -- shows system error. Return Code 2 (File Error) case 16: Encode non-existent file -- shows system error. Return Code 2 (File Error) case 17: Out of disk space -- shows system error. Return Code 3 (File I/O Error) case 18: Too many args -- shows system error. Return Code 1 (Invalid Syntax) ------------- Compile/Regression test: gcc compiled binary under Cygwin Microsoft Visual Studio under Windows 2000 Microsoft Version 6.0 C under Windows 2000 DEPENDENCIES: None LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. VERSION HISTORY: Bob Trower 08/04/01 -- Create Version 0.00.00B Bob Trower 08/17/01 -- Correct documentation, messages. -- Correct help for linesize syntax. -- Force error on too many arguments. Bob Trower 08/19/01 -- Add sourceforge.net reference to help screen prior to release. Bob Trower 10/22/04 -- Cosmetics for package/release. Bob Trower 02/28/08 -- More Cosmetics for package/release. Bob Trower 02/14/11 -- Cast altered to fix warning in VS6. \******************************************************************* */ /* ** Translation Table as described in RFC1113 */ static const char cb64[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /* ** Translation Table to decode (created by author) */ static const char cd64[]="|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; void base64Encode(const char* in, size_t inlen, char* out, size_t outlen) { unsigned char inc[3]; unsigned char outc[4]; int i, len; *inc = (unsigned char) 0; *outc = (unsigned char) 0; while( inlen ) { for( len = 0; len < 3 && inlen; len++ ) { inc[len] = *in++; --inlen; } for (i = len; i < 3; ++i) inc[i] = 0; // encode 3 8-bit binary bytes as 4 '6-bit' characters outc[0] = (unsigned char) cb64[ (int)(inc[0] >> 2) ]; outc[1] = (unsigned char) cb64[ (int)(((inc[0] & 0x03) << 4) | ((inc[1] & 0xf0) >> 4)) ]; outc[2] = (unsigned char) (len > 1 ? cb64[(int)(((inc[1] & 0x0f) << 2) | ((inc[2] & 0xc0) >> 6)) ] : '='); outc[3] = (unsigned char) (len > 2 ? cb64[(int)(inc[2] & 0x3f)] : '='); for( i = 0; i < 4 && outlen; i++ ) { *out++ = outc[i]; --outlen; } } // Add a terminating null. if (outlen) *out++ = 0; } size_t base64Encode(const char* in, size_t inlen, char** out) { size_t outlen = 1 + base64Length(inlen); // Check for overflow. if (inlen > outlen) { *out = 0; return 0; } *out = new char[outlen]; base64Encode(in, inlen, *out, outlen); return outlen - 1; } bool base64Decode (const char* in, size_t inlen, char* out, size_t *outlen) { unsigned char inc[4]; unsigned char outc[3]; int v; int i, len; size_t outsize = *outlen; *outlen = 0; // The input may end with padding of '=' or '=='. if (inlen && in[inlen - 1] == '=') --inlen; if (inlen && in[inlen - 1] == '=') --inlen; bool unbroken = true; while( inlen && unbroken ) { for( len = 0; len < 4 && inlen; ++len ) { v = *in++; --inlen; v = ((v < 43 || v > 122) ? 0 : (int) cd64[ v - 43 ]); if( v != 0 ) v = ((v == (int)'$') ? 0 : v - 61); if (v == 0) { // Invalid character. unbroken = false; break; } inc[ len ] = (unsigned char) (v - 1); } // decode 4 '6-bit' characters into 3 8-bit binary bytes outc[ 0 ] = (unsigned char ) (inc[0] << 2 | inc[1] >> 4); outc[ 1 ] = (unsigned char ) (inc[1] << 4 | inc[2] >> 2); outc[ 2 ] = (unsigned char ) (((inc[2] << 6) & 0xc0) | inc[3]); for( i = 0; i < len - 1; i++ ) { if (outsize) { out[(*outlen)++] = outc[i]; --outsize; } else return false; // output buffer exhausted. } } return unbroken; } bool base64Decode(const char* in, size_t inlen, char** out, size_t* outlen) { // This may allocate one or two bytes too much, but never too little. // Dividing before multiplying avoids the possibility of overflow. size_t needlen = 3 * (inlen / 4) + 2; *out = new char[needlen]; if (!base64Decode(in, inlen, *out, &needlen)) { delete[] *out; *out = 0; return false; } if (outlen) *outlen = needlen; return true; } } // namespace regina regina-4.95/engine/utilities/base64.h000644 000765 000024 00000026023 12234011536 017324 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/base64.h * \brief Routines for base64 encoding and decoding taken and modified * from the \a Base64 project at base64.sourceforge.net. * * The \a Base64 copyright notice is as follows: * * Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the * Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall * be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __BASE64_H #ifndef __DOXYGEN #define __BASE64_H #endif #include "regina-core.h" #include // for size_t namespace regina { /** * \weakgroup utilities * @{ */ /** * Returns the number of base64 characters required to encode the given * number of bytes. This is the number of characters used (excluding the * null terminator) by the routine base64Encode(const char*, size_t, char**). * * \ifacespython Not present. * * @param bytes the number of raw input bytes. * @return the corresponding number of base64 printable output characters. */ inline REGINA_API size_t base64Length(size_t bytes) { return (((bytes + 2) / 3) * 4); } /** * Determines whether the given character is a base64 printable character as * used by the base64 routines in Regina. The base64 printable characters * are the letters (both upper-case and lower-case), digits, plus (+), and * forward slash (/). * * Note that the equals sign (=) is padding, and is not considered by * this routine to be a base64 printable character. * * \ifacespython Not present. * * @param ch any character. * @return \c true if the given character is one of the base64 printable * characters used in Regina, or \c false if it is not. */ inline REGINA_API bool isBase64(char ch) { return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '+' || ch == '/'); } /** * Encodes the given sequence of raw bytes in base64, and writes the * results into a preallocated output buffer. * * The length of the output buffer is passed as the argument \a outlen. * If the number of base64 characters required is less than \a outlen, * a terminating \c null will be written to the end of the output sequence. * If the number of base64 characters is \a outlen or greater, this * routine will output as many base64 characters as possible, up to a * maximum of \a outlen. * * The routine base64Length() can be used to precalculate precisely how * many output characters will be required. * * \ifacespython Not present. * * \testpart * * @param in the sequence of input bytes; this does not need to be * terminated in any special way. * @param inlen the length of the input sequence. * @param out the output buffer into which the resulting base64 * characters will be written. * @param outlen the length of the output buffer. * * @author This routine is based on the \a Base64 project at * base64.sourceforge.net. The original was written by Bob Trower, and is * licensed under the MIT license. See the base64.h notes for details. */ REGINA_API void base64Encode(const char* in, size_t inlen, char* out, size_t outlen); /** * Encodes the given sequence of raw bytes in base64, and passes back a * newly allocated array containing the results. The \a out pointer will * be set to this new array, which will be null-terminated. This array will * be allocated using \c new[], and the caller is responsible for destroying * it using \c delete[]. * * If the output array is too large (in particular, the expected size * will overflow a \c size_t), the \a out pointer will be set to \c null. * * \ifacespython Not present. * * \testpart * * @param in the sequence of input bytes; this does not need to be * terminated in any special way. * @param inlen the length of the input sequence. * @param out the address of a pointer which will be set to the output * array of base64 characters. * @return the length of the output array, not counting the terminating null. * * @author This routine is based on the \a Base64 project at * base64.sourceforge.net. The original was written by Bob Trower, and is * licensed under the MIT license. See the base64.h notes for details. */ REGINA_API size_t base64Encode(const char* in, size_t inlen, char** out); /** * Decodes the given sequence of base64 characters, and writes the * resulting raw bytes into a preallocated output buffer. * * The given base64 sequence should not contain any unexpected characters; * even whitespace will cause the decoding procedure to abort. * * The length of the output buffer is passed as the argument \a outlen. * If an unexpected or invalid character is found, or the output * buffer is exhausted, this routine will write as many output bytes as * it can and then return \c false. Otherwise (on success) it will return * \c true. Either way, it will reset \a outlen to the total number of * bytes that were written. * * The total number of output bytes is important to know, since the output * array is not terminated in any special way. * * \ifacespython Not present. * * \testpart * * @param in the input sequence of base64 characters; this does not need * to be terminated in any special way. * @param inlen the length of the input sequence. * @param out the output buffer into which the resulting raw bytes * will be written. * @param outlen must contain the length of the output buffer on entry, and * on exit contains the number of output bytes that were successfully written. * @return \c true if decoding was successful, or \c false if the output * buffer was exhausted or an unexpected input character was found. * * @author This routine is based on the \a Base64 project at * base64.sourceforge.net. The original was written by Bob Trower, and is * licensed under the MIT license. See the base64.h notes for details. */ REGINA_API bool base64Decode(const char* in, size_t inlen, char* out, size_t* outlen); /** * Decodes the given sequence of base64 characters, and passes back a * newly allocated array containing the results. The \a out pointer * will be set to this new array, and \a outlen will be set to the * number of raw bytes in this output array. This array will be * allocated using \c new[], and the caller is responsible for * destroying it using \c delete[]. * * The given base64 sequence should not contain any unexpected characters; * even whitespace will cause the decoding procedure to abort. * * The length of the output buffer is passed as the argument \a outlen. * If an unexpected or invalid character is found or the output * buffer is exhausted, this routine will return \c false, set \a out * to \c null, and leave \a outlen undefined. Otherwise (on success) it * will return \c true and set \a outlen to the total number of output bytes. * * If the user is not interested in the length of the output array, a * null pointer may be passed in the \a outlen argument. Note however * that the output array is not terminated in any special way. * * \ifacespython Not present. * * \testpart * * @param in the input sequence of base64 characters; this does not need * to be terminated in any special way. * @param inlen the length of the input sequence. * @param out the address of a pointer which will be set to the output * array of raw bytes (or which will be set to \c null on failure). * @param outlen the address of an integer which will be set to the * length of the output array (or which will be left undefined on failure). * @return \c true if decoding was successful, or \c false if an unexpected * input character was found or some other error occurred. * * @author This routine is based on the \a Base64 project at * base64.sourceforge.net. The original was written by Bob Trower, and is * licensed under the MIT license. See the base64.h notes for details. */ REGINA_API bool base64Decode(const char* in, size_t inlen, char** out, size_t* outlen); /*@}*/ } // namespace regina #endif regina-4.95/engine/utilities/bitmanip.h000644 000765 000024 00000024371 12234011536 020047 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/bitmanip.h * \brief Optimised classes for bitwise analysis and manipulation of * native data types. */ #ifndef __BITMANIP_H #ifndef __DOXYGEN #define __BITMANIP_H #endif #include "regina-core.h" #include "regina-config.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * A generic class for bitwise analysis and manipulation of native data types. * * This class is not optimised for common data types; its * sole functionality is to provide default implementations for some * operations that are not yet optimised (or do not need to be). * * End users should use BitManipulator instead, which is better * optimised and which has all the functionality of this class. * * See BitManipulator for further information. * * \pre Type \a T is an unsigned integral numeric type. * \pre The argument \a size is a power of two, and is at most sizeof(\a T). * * \ifacespython Not present. */ template class GenericBitManipulator { public: /** * Returns the index of the first \c true bit in the given * integer, or -1 if the given integer is zero. * Bits are indexed from 0 upwards, starting at the least * significant bit. * * @param x the integer of type \a T to examine. * @return the position of the first \c true bit, or -1 if there * are no \c true bits. */ inline static int firstBit(T x) { if (! x) return -1; // This binary search will break if size is not a power of two. unsigned chunkSize = size << 3; // 8 * size unsigned chunkStart = 0; while (chunkSize > 1) { chunkSize >>= 1; if (! (x & (((T(1) << chunkSize) - T(1)) << chunkStart))) chunkStart += chunkSize; } return chunkStart; } /** * Returns the index of the last \c true bit in the given * integer, or -1 if the given integer is zero. * Bits are indexed from 0 upwards, starting at the least * significant bit. * * @param x the integer of type \a T to examine. * @return the position of the last \c true bit, or -1 if there * are no \c true bits. */ inline static int lastBit(T x) { if (! x) return -1; // This binary search will break if size is not a power of two. unsigned chunkSize = size << 3; // 8 * size unsigned chunkStart = 0; while (chunkSize > 1) { chunkSize >>= 1; if (x & (((T(1) << chunkSize) - T(1)) << (chunkStart + chunkSize))) chunkStart += chunkSize; } return chunkStart; } }; /** * An optimised class for bitwise analysis and manipulation of native * data types. * * The class BitManipulator is used to manipulate the lowest * \a size bytes of type \a T, which must be an unsigned native integer * type such as unsigned char, unsigned int, or unsigned long long. * * The generic implementation of BitManipulator is here for completeness. * All or most native types \a T have template specialisations * that are carefully optimised (precisely what gets specialised depends * upon properties of the compiler). * * Like this class, all specialisations either inherit or override the * default implementations from the base class GenericBitManipulator. * * \pre Type \a T is an unsigned integral numeric type. * \pre The argument \a size is a power of two, and is at most sizeof(\a T). * * \ifacespython Not present. */ template class BitManipulator : public GenericBitManipulator { public: enum { /** * Indicates whether this class is a template specialisation * of BitManipulator with extra optimisations. * * This compile-time constant is set to 0 for the generic * implementation of BitManipulator, and 1 for all specialisations. */ specialised = 0 }; /** * Returns the number of bits that are set in the given integer. * * Specifically, this routine returns the number of bits set to 1 * from amongst the lowest \a size bytes of \a x. * * @param x the integer of type \a T to examine. * @return the number of bits that are set. */ inline static int bits(T x) { return BitManipulator> 1)>::bits(x) + BitManipulator> 1)>::bits(x >> (4 * size)); } }; // Specialisations for individual type sizes. // Hide these from doxygen. #ifndef __DOXYGEN template class BitManipulator : public GenericBitManipulator { public: enum { specialised = 1 }; inline static int bits(T x) { x = (x & T(0x55)) + ((x & T(0xAA)) >> 1); x = (x & T(0x33)) + ((x & T(0xCC)) >> 2); return (x & T(0x0F)) + ((x & T(0xF0)) >> 4); } }; template class BitManipulator : public GenericBitManipulator { public: enum { specialised = 1 }; inline static int bits(T x) { x = (x & T(0x5555)) + ((x & T(0xAAAA)) >> 1); x = (x & T(0x3333)) + ((x & T(0xCCCC)) >> 2); x = (x & T(0x0F0F)) + ((x & T(0xF0F0)) >> 4); return (x & T(0x00FF)) + ((x & T(0xFF00)) >> 8); } }; template class BitManipulator : public GenericBitManipulator { public: enum { specialised = 1 }; inline static int bits(T x) { x = (x & T(0x55555555)) + ((x & T(0xAAAAAAAA)) >> 1); x = (x & T(0x33333333)) + ((x & T(0xCCCCCCCC)) >> 2); x = (x & T(0x0F0F0F0F)) + ((x & T(0xF0F0F0F0)) >> 4); x = (x & T(0x00FF00FF)) + ((x & T(0xFF00FF00)) >> 8); return (x & T(0x0000FFFF)) + ((x & T(0xFFFF0000)) >> 16); } }; // Support 64-bit processing natively if we can; otherwise we will fall // back to the generic two-lots-of-32-bit implementation. #if defined(NUMERIC_64_FOUND) template class BitManipulator : public GenericBitManipulator { public: enum { specialised = 1 }; inline static int bits(T x) { x = (x & T(0x5555555555555555)) + ((x & T(0xAAAAAAAAAAAAAAAA)) >> 1); x = (x & T(0x3333333333333333)) + ((x & T(0xCCCCCCCCCCCCCCCC)) >> 2); x = (x & T(0x0F0F0F0F0F0F0F0F)) + ((x & T(0xF0F0F0F0F0F0F0F0)) >> 4); x = (x & T(0x00FF00FF00FF00FF)) + ((x & T(0xFF00FF00FF00FF00)) >> 8); x = (x & T(0x0000FFFF0000FFFF)) + ((x & T(0xFFFF0000FFFF0000)) >> 16); return (x & T(0x00000000FFFFFFFF)) + ((x & T(0xFFFFFFFF00000000)) >> 32); } }; #elif defined(NUMERIC_64_LL_FOUND) template class BitManipulator : public GenericBitManipulator { public: enum { specialised = 1 }; inline static int bits(T x) { x = (x & T(0x5555555555555555LL)) + ((x & T(0xAAAAAAAAAAAAAAAALL)) >> 1); x = (x & T(0x3333333333333333LL)) + ((x & T(0xCCCCCCCCCCCCCCCCLL)) >> 2); x = (x & T(0x0F0F0F0F0F0F0F0FLL)) + ((x & T(0xF0F0F0F0F0F0F0F0LL)) >> 4); x = (x & T(0x00FF00FF00FF00FFLL)) + ((x & T(0xFF00FF00FF00FF00LL)) >> 8); x = (x & T(0x0000FFFF0000FFFFLL)) + ((x & T(0xFFFF0000FFFF0000LL)) >> 16); return (x & T(0x00000000FFFFFFFFLL)) + ((x & T(0xFFFFFFFF00000000LL)) >> 32); } }; #endif // End the hide-from-doxygen block. #endif /*@}*/ } // namespace regina #endif regina-4.95/engine/utilities/boostutils.h000644 000765 000024 00000023136 12234011536 020451 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/boostutils.h * \brief Miscellaneous utility classes taken or modified from the * Boost C++ libraries. * * The Boost copyright notices are as follows. * * Type Traits: * * (C) Copyright Steve Cleary, Beman Dawes, Howard Hinnant & John Maddock 2000. * * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. * * Reference Wrappers: * * Copyright (C) 1999, 2000 Jaakko Järvi (jaakko.jarvi@cs.utu.fi)
* Copyright (C) 2001, 2002 Peter Dimov
* Copyright (C) 2002 David Abrahams * * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. * * Next, Prior and Non-Copyable: * * Contributed by Dave Abrahams * * (C) Copyright Boost.org 1999-2003. * Permission to copy, use, modify, sell and distribute this software * is granted provided this copyright notice appears in all copies. * This software is provided "as is" without express or implied * warranty, and with no claim as to its suitability for any purpose. */ #ifndef __BOOSTUTILS_H #ifndef __DOXYGEN #define __BOOSTUTILS_H #endif #include "regina-core.h" namespace regina { /** * Miscellaneous utility classes taken or modified from the Boost C++ * libraries. * * See the boostutils.h file documentation for Boost license details. */ namespace boost { /** * \weakgroup utilities * @{ */ /** * A template class used to remove the indirection from a pointer type. * * If T is a pointer type, then * \code remove_pointer::type \endcode * removes the top-level indirection from T; otherwise * T remains unchanged. For example int* becomes * int, but int& remains unchanged. * * \ifacespython Not present. * * @author This class was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template struct remove_pointer { typedef T type; /**< The template argument with the top-level indirection * removed if it is a pointer type. */ }; #ifndef __DOXYGEN /** * Specialisations of remove_pointer: */ template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; template struct remove_pointer { typedef T type; }; #endif /** * A wrapper allowing references to be passed through generic functions. * This class is for use with the Standard Template Library. * * The primary advantage of this class is its implicit conversion to * type T&. Thus it can be passed to routines expecting * references to T but can also be passed by reference itself. * * See global routines ::ref() and ::cref() for simple creation of these * wrappers. * * \ifacespython Not present. * * @author This class was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template class reference_wrapper { public: typedef T type; /**< The data type being referenced by this wrapper. */ private: T* t_; /**< A pointer to the object being referenced. */ public: /** * Creates a new wrapper to reference the given object. * * @param t the object to be referenced. */ explicit reference_wrapper(type& t) : t_(&t) { } /** * Returns a reference to the object being referenced. * This routine provides an implicit conversion to type * T&. * * @return the corresonding reference. */ operator type& () const { return *t_; } /** * Returns a reference to the object being referenced. * * @return the corresonding reference. */ type& get() const { return *t_; } /** * Returns a pointer to the object being referenced. * * @return the corresponding pointer. */ type* get_pointer() const { return t_; } }; /** * Returns a wrapper for the given reference. See reference_wrapper for * further details. * * \ifacespython Not present. * * @param t the reference to wrap. * @return the corresponding wrapper. * * @author This routine was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template inline reference_wrapper const ref(T& t) { return reference_wrapper(t); } /** * Returns a wrapper for the given const reference. See reference_wrapper for * further details. * * \ifacespython Not present. * * @param t the reference to wrap. * @return the corresponding wrapper. * * @author This routine was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template inline reference_wrapper const cref(T const& t) { return reference_wrapper(t); } /** * Returns the iterator prior to the given iterator. * This function avoids having to explicitly create a temporary to * decrement. * * Only the decrement operator --it needs to be defined. * * \ifacespython Not present. * * @param it the iterator to examine. * @return the iterator prior to the given iterator. * * @author This routine was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template inline T prior(T it) { return --it; } /** * Returns the iterator following the given iterator. * This function avoids having to explicitly create a temporary to * increment. * * Only the increment operator ++it needs to be defined. * * \ifacespython Not present. * * @param it the iterator to examine. * @return the iterator following the given iterator. * * @author This routine was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ template inline T next(T it) { return ++it; } /** * A base class that guarantees that derived classes cannot be copied. * This is done by defining a private copy constructor and a private * copy assignment operator. * * \ifacespython Not present. * * @author This class was taken and modified from the Boost C++ libraries * (http://www.boost.org/). */ class REGINA_API noncopyable { protected: /** * A constructor which does nothing. */ noncopyable() {} /** * A destructor which does nothing. */ ~noncopyable() {} private: /** * An inaccessable copy constructor. */ noncopyable(const noncopyable&) {} /** * An inaccessible copy assignment operator. */ const noncopyable& operator = (const noncopyable&) { return *this; } }; /*@}*/ } } // namespace regina::boost #endif regina-4.95/engine/utilities/CMakeLists.txt000644 000765 000024 00000001644 12236247215 020640 0ustar00babstaff000000 000000 # utilities # Files to compile SET ( FILES base64 i18nutils nbooleans nthread osutils stringutils xmlutils zstream ) # Prepend folder name FOREACH ( SOURCE_FILE ${FILES} ) SET ( SOURCES ${SOURCES} utilities/${SOURCE_FILE}) ENDFOREACH(SOURCE_FILE) # Set the variable in the parent directory SET( SOURCES ${SOURCES} PARENT_SCOPE) if (${REGINA_INSTALL_DEV}) INSTALL(FILES base64.h bitmanip.h boostutils.h flags.h i18nutils.h intutils.h memutils.h nbitmask.h nbooleans.h nlistoncall.h nmarkedvector.h nmatrix2.h nmpi.h nproperty.h nqitmask.h nrational.h nthread.h ntrieset.h ntritmask.h osutils.h ptrutils.h registryutils.h stlutils.h stringutils.h stringutils-impl.h stringutils.tcc xmlutils.h zstream.h DESTINATION ${INCLUDEDIR}/utilities COMPONENT Development) endif (${REGINA_INSTALL_DEV}) regina-4.95/engine/utilities/flags.h000644 000765 000024 00000040162 12234011536 017334 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/flags.h * \brief A template class for handling bitwise combinations of enum flags. */ #ifndef __FLAGS_H #ifndef __DOXYGEN #define __FLAGS_H #endif namespace regina { /** * \weakgroup utilities * @{ */ /** * A class representing a bitwise combination of flags defined by an * enumeration type. * * The enumeration type is given in the template parameter \a T. * This class allows the user to form and test bitwise combinations of the * individual enum values, without losing type safety. * * \ifacespython Present only for some particular enumeration types \a T, * when explicitly noted in the corresponding enum documentation. */ template class Flags { public: /** * The underlying enumeration type. */ typedef T Enum; private: /** * The combination of flags that we are storing. */ int value_; public: /** * Creates an empty flag set, with no flags set at all. */ inline Flags() : value_(0) { } /** * Creates a flag set initialised to the given value. * * @param init the initial value of this flag set. */ inline Flags(T init) : value_(init) { } /** * Creates a clone of the given flag set. * * @param init the flag set to clone. */ inline Flags(const Flags& init) : value_(init.value_) { } /** * Returns the integer representation of this set. * This is suitable for file input and/or output. * * \warning This function should not be used widely, since it * effectively works around inbuilt type safety mechanisms. * * @return the integer value of this set. */ int intValue() const { return static_cast(value_); } /** * Returns the set corresponding to the given integer value. * This is suitable for file input and/or output. * * \warning This function should not be used widely, since it * effectively works around inbuilt type safety mechanisms. * * @return the set corresponding to the given integer value. */ inline static Flags fromInt(int value) { return Flags(value); } /** * Returns whether the given flag is set. * * This requires \e all of the bits of the given flag to be set. * The test is equivalent to (*this & flag) == flag. * * @param flag the flag whose presence will be tested. * @return \c true if and only if all of the bits of the given * flag are set. */ bool has(T flag) const { return (value_ & flag) == flag; } /** * Returns whether all of the flags in the given set are set. * * This requires \e all of the bits of all of the flags in the * given set to be present in this set. * The test is equivalent to (*this & rhs) == rhs. * * @param rhs the set whose presence will be tested. * @return \c true if and only if all of the bits of the given * set are present in this set. */ bool has(const Flags& rhs) const { return (value_ & rhs.value_) == rhs.value_; } /** * Determines whether this set is precisely equal to the given flag. * * @param rhs the flag to test this against. * @return \c true if and only if this and the given flag are identical. */ inline bool operator == (T rhs) const { return (value_ == rhs); } /** * Determines whether this set is precisely equal to the given * flag set. * * @param rhs the flag set to test this against. * @return \c true if and only if this and the given flag set are * identical. */ inline bool operator == (const Flags& rhs) const { return (value_ == rhs.value_); } /** * Determines whether this set is not equal to the given flag. * * @param rhs the flag to test this against. * @return \c true if and only if this and the given flag are not * identical. */ inline bool operator != (T rhs) const { return (value_ != rhs); } /** * Determines whether this set is not equal to the given flag set. * * @param rhs the flag to test this against. * @return \c true if and only if this and the given flag set are not * identical. */ inline bool operator != (const Flags& rhs) const { return (value_ != rhs.value_); } /** * Sets this flag set to contain precisely the given flag only. * * @param rhs the new value of this flag set. * @return a reference to this flag set. */ inline Flags& operator = (T rhs) { value_ = rhs; return *this; } /** * Sets this flag set to contain a copy of the given flag set. * * @param rhs the new value of this flag set. * @return a reference to this flag set. */ inline Flags& operator = (const Flags& rhs) { value_ = rhs.value_; return *this; } /** * Changes this flag set by taking a bitwise OR with the given * flag. * * @param rhs the flag to combine with this set. * @return a reference to this flag set. */ inline Flags& operator |= (T rhs) { value_ |= rhs; return *this; } /** * Changes this flag set by taking a bitwise OR with the given * flag set. * * @param rhs the flag set to combine with this set. * @return a reference to this flag set. */ inline Flags& operator |= (const Flags& rhs) { value_ |= rhs.value_; return *this; } /** * Changes this flag set by taking a bitwise AND with the given * flag. * * @param rhs the flag to combine with this set. * @return a reference to this flag set. */ inline Flags& operator &= (T rhs) { value_ &= rhs; return *this; } /** * Changes this flag set by taking a bitwise AND with the given * flag set. * * @param rhs the flag set to combine with this set. * @return a reference to this flag set. */ inline Flags& operator &= (const Flags& rhs) { value_ &= rhs.value_; return *this; } /** * Changes this flag set by taking a bitwise XOR with the given * flag. * * @param rhs the flag to combine with this set. * @return a reference to this flag set. */ inline Flags& operator ^= (T rhs) { value_ ^= rhs; return *this; } /** * Changes this flag set by taking a bitwise XOR with the given * flag set. * * @param rhs the flag set to combine with this set. * @return a reference to this flag set. */ inline Flags& operator ^= (const Flags& rhs) { value_ ^= rhs.value_; return *this; } /** * Returns the bitwise OR of this set and the given flag. * This flag set is not changed. * * @param rhs the flag to combine with this set. * @return the combination of this set and the given flag. */ inline Flags operator | (T rhs) const { return Flags(value_ | rhs); } /** * Returns the bitwise OR of this and the given flag set. * This flag set is not changed. * * @param rhs the flag set to combine with this set. * @return the combination of this and the given flag set. */ inline Flags operator | (const Flags& rhs) const { return Flags(value_ | rhs.value_); } /** * Returns the bitwise AND of this set and the given flag. * This flag set is not changed. * * @param rhs the flag to combine with this set. * @return the combination of this set and the given flag. */ inline Flags operator & (T rhs) const { return Flags(value_ & rhs); } /** * Returns the bitwise AND of this and the given flag set. * This flag set is not changed. * * @param rhs the flag set to combine with this set. * @return the combination of this and the given flag set. */ inline Flags operator & (const Flags& rhs) const { return Flags(value_ & rhs.value_); } /** * Returns the bitwise XOR of this set and the given flag. * This flag set is not changed. * * @param rhs the flag to combine with this set. * @return the combination of this set and the given flag. */ inline Flags operator ^ (T rhs) const { return Flags(value_ ^ rhs); } /** * Returns the bitwise XOR of this and the given flag set. * This flag set is not changed. * * @param rhs the flag set to combine with this set. * @return the combination of this and the given flag set. */ inline Flags operator ^ (const Flags& rhs) const { return Flags(value_ ^ rhs.value_); } /** * Clears all bits from this set that appear in the given flag. * * @param rhs the flag to clear from this set. */ inline void clear(T rhs) { value_ |= rhs; value_ ^= rhs; } /** * Clears all bits from this set that appear in the given set. * * @param rhs identifies the bits to clear from this set. */ inline void clear(const Flags& rhs) { value_ |= rhs.value_; value_ ^= rhs.value_; } /** * Adjust this set so that exactly one and only one of the two * given flags are included. * * If neither flag is present or both flags are present, this * set will be adjusted so that \a default_ is present and \a other * is not. * * \pre Both \a default_ and \a other are each single-bit flags. * * \ifacespython Not present, even for flag types that are * exposed to Python. * * @param default_ the flag that will be set if any adjustments * need to be made. * @param other the flag that will be cleared if any adjustments * need to be made. */ inline void ensureOne(T default_, T other) { if (! ((value_ & default_) || (value_ & other))) value_ |= default_; else if ((value_ & default_) && (value_ & other)) value_ ^= other; } /** * Adjust this set so that exactly one and only one of the three * given flags are included. * * If neither flag is present, then \a default_ will be used. * If multiple flags are present, then the flag that appears * \e earlier in the argument list for this routine will be used. * * \pre Each of the given flags is single-bit. * * \ifacespython Not present, even for flag types that are * exposed to Python. * * @param default_ the highest-priority flag. * @param second the second-highest-priority flag. * @param last the lowest-priority flag. */ inline void ensureOne(T default_, T second, T last) { // Cast to int, because (T | T) is overloaded to return Flags. if (! (value_ & (static_cast(default_) | second | last))) value_ = default_; else if (value_ & default_) clear(second | last); else if (value_ & second) clear(last); } /** * Adjust this set so that exactly one and only one of the four * given flags are included. * * If neither flag is present, then \a default_ will be used. * If multiple flags are present, then the flag that appears * \e earlier in the argument list for this routine will be used. * * \pre Each of the given flags is single-bit. * * \ifacespython Not present, even for flag types that are * exposed to Python. * * @param default_ the highest-priority flag. * @param second the second-highest-priority flag. * @param third the third-highest-priority flag. * @param last the lowest-priority flag. */ inline void ensureOne(T default_, T second, T third, T last) { // Cast to int, because (T | T) is overloaded to return Flags. if (! (value_ & (static_cast(default_) | second | third | last))) value_ = default_; else if (value_ & default_) clear(second | third | last); else if (value_ & second) clear(third | last); else if (value_ & third) clear(last); } private: /** * Constructs a new flag set with the given internal value. * * @param init the new internal value. */ inline Flags(int init) : value_(init) { } }; /*@}*/ } // namespace regina #endif regina-4.95/engine/utilities/i18nutils.cpp000644 000765 000024 00000016316 12234011536 020437 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include #include #include #include #include "regina-config.h" #include "utilities/i18nutils.h" #ifdef LANGINFO_FOUND #include #endif namespace regina { namespace i18n { bool Locale::initialised = false; const iconv_t IConvStreamBuffer::cdNone((iconv_t)(-1)); #ifdef LANGINFO_FOUND const char* Locale::codeset() { if (! initialised) { ::setlocale(LC_ALL, ""); initialised = true; } return nl_langinfo(CODESET); } #else namespace { const char* noLanginfoCodeset = "UTF-8"; } const char* Locale::codeset() { return noLanginfoCodeset; } #endif IConvStreamBuffer* IConvStreamBuffer::open(std::ostream& dest, const char* srcCode, const char* destCode) { if (sink) if (! close()) return 0; sink = &dest; cd = iconv_open(destCode, srcCode); if (cd == cdNone) { if (errno != EINVAL) return 0; // The given encodings are not supported. // This is fine; we'll just pass data through to sink untranslated. } // When we give the buffer to std::streambuf, leave space for an // extra overflow character; this will make the implementation of // overflow() simpler. setp(preBuffer, preBuffer + (sizeof(preBuffer) - 1)); return this; } IConvStreamBuffer* IConvStreamBuffer::close() throw() { sync(); if (cd == cdNone) { // We're passing data through untranslated; nothing more to do. return this; } // Close down the internal iconv workings. if (iconv_close(cd) == 0) { cd = cdNone; return this; } else return 0; } IConvStreamBuffer::int_type IConvStreamBuffer::overflow( IConvStreamBuffer::int_type c) { // Are we even open? if (sink == 0) return traits_type::eof(); // Add the extra character to the end of the buffer before processing. if (c != traits_type::eof()) { *pptr() = c; pbump(1); } // Do we know how to translate between encodings? If not, just // send the data straight through to the destination stream. if (cd == cdNone) { ptrdiff_t n = pptr() - preBuffer; sink->write(preBuffer, n); pbump(-n); if (sink->fail()) return traits_type::eof(); else return 0; } // Convert the data through iconv(). // We might need more than one run through this. while (pptr() > preBuffer) { size_t inBytes = pptr() - preBuffer; size_t outBytes = sizeof(postBuffer); ICONV_CONST char* inPtr = preBuffer; char* outPtr = postBuffer; ::iconv(cd, &inPtr, &inBytes, &outPtr, &outBytes); int iconvErr = errno; errno = 0; // If we got any output, write it to the destination stream. if (outPtr > postBuffer) { sink->write(postBuffer, outPtr - postBuffer); if (sink->fail()) return traits_type::eof(); } // Are we completely finished? if (inBytes == 0) { // Yes! pbump(- (inPtr - preBuffer)); return 0; } // Something went wrong. if (iconvErr == E2BIG) { // The output buffer filled up. This shouldn't happen, but // anyway; move the leftover input to the front of the input // buffer and try again. ::memmove(preBuffer, inPtr, inBytes); pbump(- (inPtr - preBuffer)); continue; } if (iconvErr == EINVAL) { // We hit an incomplete multibyte sequence. Move the // leftover input to the front of the buffer and stop, since // we need more input before we can continue translating. ::memmove(preBuffer, inPtr, inBytes); pbump(- (inPtr - preBuffer)); return 0; } if (iconvErr == EILSEQ) { // We hit an invalid multibyte sequence. // Try to recover gracefully by just skipping over it. ::memmove(preBuffer, inPtr + 1, inBytes - 1); pbump(- (inPtr + 1 - preBuffer)); sink->write("?", 1); if (sink->fail()) return traits_type::eof(); continue; } // We should never reach this point, since it indicates an error // state that iconv() should not set. std::cerr << "ERROR: Unexpected state after call to iconv().\n"; std::cerr << "Please report this as a bug to the Regina author(s).\n"; std::cerr.flush(); return traits_type::eof(); } // We can never reach this point, but keep the compiler happy. return 0; } int IConvStreamBuffer::sync() { if (sink) { IConvStreamBuffer::int_type ret = overflow(traits_type::eof()); sink->flush(); return (ret == traits_type::eof() || sink->fail()) ? -1 : 0; } else return -1; } } } // namespace regina::i18n regina-4.95/engine/utilities/i18nutils.h000644 000765 000024 00000025507 12234011536 020106 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/i18nutils.h * \brief Various classes and routines for working with internationalisation * and character encodings. */ #ifndef __I18NUTILS_H #ifndef __DOXYGEN #define __I18NUTILS_H #endif #include "regina-core.h" #include "regina-config.h" #include #include namespace regina { /** * Various classes and routines for working with internationalisation and * character encodings. */ namespace i18n { /** * \weakgroup utilities * @{ */ /** * A simple class with static routines for querying information about the * current locale. * * These routines use ::setlocale() to determine the current locale, * which means they respect environment variables such as LANG and LC_ALL. */ class REGINA_API Locale { private: static bool initialised; /**< Have we determined the current locale yet? This is done the first time a query routine is called. */ public: /** * Returns the character encoding used in the current locale. * This is a plain string, such as "UTF-8" or "ISO-8859-1". * * @return the character encoding for the current locale. */ static const char* codeset(); private: /** * Disable the default constructor, since all routines are static. */ Locale(); }; /** * An output stream buffer that translates between character encodings. * The \e iconv library is used to do the real work. * * Users should not normally instantiate this class directly; instead * see IConvStream for a higher-level interface to character conversion. * * This class will still work if \e iconv is not supported on the build * machine, though in this case it will simply pass data through without * performing any translations. * * \ifacespython Not included. * * @author Parts of this code are modified from the cxxtools library * (http://www.tntnet.org/cxxutils.html), which is * copyright (c) 2003 by Tommi Maekitalo, and covered by the GNU Lesser * General Public License. */ class REGINA_API IConvStreamBuffer : public std::streambuf { private: std::ostream* sink; /**< The destination output stream, or 0 if it has not yet been initialised. */ char preBuffer[16]; /**< The internal pre-conversion character buffer. */ char postBuffer[64]; /**< The internal post-conversion character buffer. Allow characters to blow out to four times their size. */ iconv_t cd; /**< The iconv conversion descriptor, or \a cdNone if no conversion descriptor is currently active. */ static const iconv_t cdNone; /**< Signifies that no iconv conversion descriptor is currently active. */ public: /** * Creates a new stream buffer. */ IConvStreamBuffer(); /** * Destroys this stream buffer. This stream buffer will be * closed, but the destination output stream will not be. */ ~IConvStreamBuffer(); /** * Opens a new stream buffer that wraps around the given output * stream. If this stream buffer is already open, it will be * closed and then reopened with the given parameters. * * Any data that is sent to this stream buffer will be * translated from \e srcCode to \e destCode and passed on to the * given output stream. * * If the given encodings are invalid, this stream will still * forward data to the given output stream but no conversion * will take place. * * See the \e iconv documentation for information on what * encodings are supported. For the GNU C library implementation, * valid encodings can be found by running iconv --list. * * \pre The destination output stream is already open. * * @param dest the destination output stream. * @param srcCode the character encoding for data that is to be * written into this stream buffer. * @param destCode the character encoding for the translated data * that will subsequently be written to the destination output stream. * @return this stream buffer on success, or 0 on error. */ IConvStreamBuffer* open(std::ostream& dest, const char* srcCode, const char* destCode); /** * Closes this stream buffer. * * @return this stream buffer on success, or 0 on error. */ IConvStreamBuffer* close() throw(); /** * Sends buffered data to the destination output stream, * converting between character sets en route. * * The buffer will be flushed as far as possible, and any * invalid characters will be replaced with one or more question * marks. If the buffer ends in an incomplete multibyte character, * this incomplete character will be held back (since it presumably * needs to be combined with later input). * * @param c an extra character to send that did not fit in the * internal buffer, or EOF if we simply wish to flush the buffer. * @return 0 on success, or EOF on error. */ int_type overflow(int_type c); /** * Simply returns EOF (since this is not an input stream). * * @return EOF. */ int_type underflow(); /** * Flushes all output buffers. The buffers for both this stream * and the destination output stream will be flushed. * * @return 0 on success, or -1 on error. */ int sync(); }; /** * An output stream that converts between character encodings. * The \e iconv library does all the work behind the scenes. * * An IConvStream acts as a wrapper around some other destination * output stream (for instance, std::cout). To use an IConvStream: * * - Construct it, passing the destination output stream and the to/from * character encodings to the class constructor; * * - Write data to this IConvStream, which will be converted and * forwarded on to the destination output stream; * * - Destroy this IConvStream. The destination output stream will * remain open. * * This class will still work if \e iconv is not supported on the build * machine, though in this case it will simply pass data straight through * to the destination output stream without any conversion. * * \ifacespython Not present. * * @author Parts of this code are modified from the cxxtools library * (http://www.tntnet.org/cxxutils.html), which is * copyright (c) 2003 by Tommi Maekitalo, and covered by the GNU Lesser * General Public License. */ class REGINA_API IConvStream : public std::ostream { private: IConvStreamBuffer buf; /**< The IConvStreamBuffer that does all the actual work. */ public: /** * Creates a new IConvStream; see the class notes for details. * * If the given encodings are invalid, this stream will still * forward data to the given output stream but no conversion * will take place. * * See the \e iconv documentation for information on what * encodings are supported. For the GNU C library implementation, * valid encodings can be found by running iconv --list. * * \pre The destination output stream is already open. * * @param dest the destination output stream. * @param srcCode the character encoding for data that is to be * written into this IConvStream. * @param destCode the character encoding for the translated data * that will subsequently be written to the destination output stream. */ IConvStream(std::ostream& dest, const char* srcCode, const char* destCode); }; // Inline functions for Locale inline Locale::Locale() { } // Inline functions for IConvStreamBuffer inline IConvStreamBuffer::IConvStreamBuffer() : sink(0), cd(cdNone) { } inline IConvStreamBuffer::~IConvStreamBuffer() { close(); } inline IConvStreamBuffer::int_type IConvStreamBuffer::underflow() { return traits_type::eof(); } // Inline functions for IConvStream inline IConvStream::IConvStream(std::ostream& dest, const char* srcCode, const char* destCode) : std::ostream(&buf) { if (buf.open(dest, srcCode, destCode) == 0) setstate(std::ios::failbit); } } } // namespace regina::i18n #endif regina-4.95/engine/utilities/intutils.h000644 000765 000024 00000007655 12234011536 020125 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/intutils.h * \brief Miscellaneous utility classes for analysing the built-in * integer types at compile time. */ #ifndef __INTUTILS_H #ifndef __DOXYGEN #define __INTUTILS_H #endif #include "regina-core.h" #include "regina-config.h" #include namespace regina { /** * Gives access to k-byte integer types, where \a k is a constant that * is not known until compile time. */ template struct IntOfSize { /** * A signed integer type with \a k bytes, where \a k is the template * parameter. * * The default is \c void, which indicates that Regina does not know * how to access an integer type of the requested size. */ typedef void type; /** * An unsigned integer type with \a k bytes, where \a k is the template * parameter. * * The default is \c void, which indicates that Regina does not know * how to access an integer type of the requested size. */ typedef void utype; }; #ifndef __DOXYGEN template <> struct IntOfSize<1> { typedef int8_t type; typedef uint8_t utype; }; template <> struct IntOfSize<2> { typedef int16_t type; typedef uint16_t utype; }; template <> struct IntOfSize<4> { typedef int32_t type; typedef uint32_t utype; }; template <> struct IntOfSize<8> { typedef int64_t type; typedef uint64_t utype; }; template <> struct IntOfSize<16> { #if defined(__INT128_T_FOUND) typedef __int128_t type; typedef __uint128_t utype; #elif defined(INT128_T_FOUND) typedef int128_t type; typedef uint128_t utype; #else typedef void type; typedef void utype; #endif }; #endif // __DOXYGEN } // namespace regina #endif regina-4.95/engine/utilities/memutils.h000644 000765 000024 00000016161 12234011536 020101 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/memutils.h * \brief Provides object creation and deletion functions for use with * the Standard Template Library. */ #ifndef __MEMUTILS_H #ifndef __DOXYGEN #define __MEMUTILS_H #endif #include "regina-core.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * An adaptable generator used to create objects using default constructors. * This class is for use with the Standard Template Library. * * Note that the template argument need not be a pointer class. If the * template argument is T, this generator will return a * \e pointer to a newly created object of type T. * * \ifacespython Not present. */ template struct FuncNew { typedef T* result_type; /**< The return type for this generator. */ /** * Creates a new object using the default constructor. * * @return the newly created object. */ T* operator() () const { return new T; } }; /** * An adaptable unary function used to create objects using copy constructors. * This class is for use with the Standard Template Library. * * Note that the template argument need not be a pointer class. If the * template argument is T, this unary function will accept * a \e pointer to T, dereference this pointer and pass it to the * copy constructor to return a pointer to a newly created object of * type T. * * \ifacespython Not present. */ template struct FuncNewCopyPtr { typedef const T* argument_type; /**< The argument type for this unary function. */ typedef T* result_type; /**< The return type for this unary function. */ /** * Creates a new object using the copy constructor. * * @param ptr the pointer whose data should (after dereferencing) be * passed to the copy constructor. * @return the newly created object. */ T* operator() (const T* ptr) const { return new T(*ptr); } }; /** * An adaptable unary function used to create objects using copy constructors. * This class is for use with the Standard Template Library. * * Note that the template argument need not be a pointer class. If the * template argument is T, this unary function will accept * a \e reference to T and pass it directly to the * copy constructor to return a pointer to a newly created object of * type T. * * \ifacespython Not present. */ template struct FuncNewCopyRef { typedef const T& argument_type; /**< The argument type for this unary function. */ typedef T* result_type; /**< The return type for this unary function. */ /** * Creates a new object using the copy constructor. * * @param obj the object whose data should be passed to the copy * constructor. * @return the newly created object. */ T* operator() (const T& obj) const { return new T(obj); } }; /** * An adaptable unary function used to create objects using the * clone() method. * This class is for use with the Standard Template Library. * * Note that the template argument need not be a pointer class. If the * template argument is T, this unary function will accept * a \e pointer to T and call clone() upon the * corresponding object, returning a pointer to the newly created clone * of type T. * * \pre Type T has method T* clone() const. The * declared return type may be different, but the result \e must * be castable to T*. * * \ifacespython Not present. */ template struct FuncNewClonePtr { typedef const T* argument_type; /**< The argument type for this unary function. */ typedef T* result_type; /**< The return type for this unary function. */ /** * Creates a new object using the clone() method. * * @param ptr the pointer whose corresponding object should be cloned. * @return the newly created clone. */ T* operator() (const T* ptr) const { return dynamic_cast(ptr->clone()); } }; /** * An adaptable unary function used to deallocate objects. * This class is for use with the Standard Template Library. * * Note that the template argument need not be a pointer class. If the * template argument is T, this unary function will accept (and * call \c delete upon) \e pointers to T. * * \ifacespython Not present. */ template struct FuncDelete { typedef T* argument_type; /**< The argument type for this unary function. */ typedef void result_type; /**< The return type for this unary function. */ /** * Calls \c delete upon the given pointer. * * @param ptr the pointer whose data should be deleted. */ void operator() (T* ptr) const { delete ptr; } }; /*@}*/ } // namespace regina #endif regina-4.95/engine/utilities/nbitmask.h000644 000765 000024 00000161636 12234011536 020062 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/nbitmask.h * \brief Provides optimised bitmasks of arbitrary length. */ #ifndef __NBITMASK_H #ifndef __DOXYGEN #define __NBITMASK_H #endif #include #include #include "regina-core.h" #include "regina-config.h" #include "utilities/bitmanip.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * A bitmask that can store arbitrarily many true-or-false bits. * * This bitmask packs the bits together, so that (unlike an array of bools) * many bits can be stored in a single byte. As a result, operations on * this class are fast because the CPU can work on many bits simultaneously. * * Nevertheless, this class still has overhead because the bits must be * allocated on the heap, and because every operation requires looping * through the individual bytes. For reasonably small bitmasks, see the * highly optimised NBitmask1 and NBitmask2 classes instead. * * Once a bitmask is created, the only way its length (the number of bits) * can be changed is by calling reset(size_t). * * The length of the bitmask is not actually stored in this structure. * This means that, upon construction (or reset), the length will be * automatically rounded up to the next "raw unit of storage". * * \todo \opt Insist that sizeof(Piece) is a power of two, and replace * expensive division/mod operations with cheap bit operations. * * \warning Because this class may increase the length of the bitmask * (rounding up to the next unit of storage), bitwise computations may * not give the results that you expect. In particular, flip() may set * additional \c true bits in the "dead space" between the intended length * and the actual length, and this may have a flow-on effect for other * operations (such as subset testing, bit counting and so on). Be * careful! * * \testpart * * \ifacespython Not present. */ class REGINA_API NBitmask { private: typedef unsigned Piece; /**< The types of the machine-native pieces into which this bitmask is split. */ size_t pieces; /**< The number of machine-native pieces into which this bitmask is split. */ Piece* mask; /**< The array of pieces, each of which stores 8 * sizeof(Piece) individual bits. */ public: /** * Creates a new invalid bitmask. You must call the one-argument * reset(size_t) or use the assignment operator to give the * bitmask a length before it can be used. * * Use of this default constructor is discouraged. The only * reason it exists is to support arrays and containers of * bitmasks, where the bitmasks must be created in bulk and then * individually assigned lengths. * * \warning No other routines can be used with this bitmask * until it has been assigned a length via reset(size_t) or * the assignment operator. As the single exception, the class * destructor is safe to use even if a bitmask has never been * initialised. */ NBitmask(); /** * Creates a new bitmask of the given length with all bits set to * \c false. * * @param length the number of bits stored in this bitmask; this must * be at least one. */ NBitmask(size_t length); /** * Creates a clone of the given bitmask. * * It is fine if the given bitmask is invalid (but in this case, * the new bitmask will be invalid also). Invalid bitmasks must be * assigned a length using reset(size_t) or the assignment operator. * * @param cloneMe the bitmask to clone. */ NBitmask(const NBitmask& cloneMe); /** * Destroys this bitmask. */ ~NBitmask(); /** * Returns the value of the given bit of this bitmask. * * @param index indicates which bit to query; this must be at least * zero and strictly less than the length of this bitmask. * @return the value of the (\a index)th bit. */ bool get(size_t index) const; /** * Sets the given bit of this bitmask to the given value. * * @param index indicates which bit to set; this must be at least zero * and strictly less than the length of this bitmask. * @param value the value that will be assigned to the (\a index)th bit. */ void set(size_t index, bool value); /** * Sets all bits in the given sorted list to the given value. * * This is a convenience routine for setting many bits at once. * The indices of the bits to set should be sorted and stored in * some container, such as a std::set or a C-style array. This * routine takes iterators over this container, and sets the * bits at the corresponding indices to the given value. * * For example, the following code would set bits 3, 5 and 6 * to \c true: * * \code * std::vector indices; * indices.push(3); indices.push(5); indices.push(6); * bitmask.set(indices.begin(), indices.end(), true); * \endcode * * Likewise, the following code would set bits 1, 4 and 7 to \c false: * * \code * unsigned indices[3] = { 1, 4, 7 }; * bitmask.set(indices, indices + 3, false); * \endcode * * All other bits of this bitmask are unaffected by this routine. * * \pre \a ForwardIterator is a forward iterator type that iterates * over integer values. * \pre The list of indices described by these iterators is * in \e sorted order. This is to allow optimisations for * larger bitmask types. * \pre All indices in the given list are at least zero and * strictly less than the length of this bitmask. * * @param indexBegin the beginning of the iterator range * containing the sorted indices of the bits to set. * @param indexEnd the end of the iterator range containing the * sorted indices of the bits to set. * @param value the value that will be assigned to each of the * corresponding bits. */ template void set(ForwardIterator indexBegin, ForwardIterator indexEnd, bool value) { Piece* base = mask; size_t offset = 0; size_t diff; for ( ; indexBegin != indexEnd; ++indexBegin) { // INV: offset = (base - mask) * 8 * sizeof(Piece) // INV: *indexBegin >= offset if (*indexBegin >= offset + (8 * sizeof(Piece))) { diff = ((*indexBegin - offset) / (8 * sizeof(Piece))); base += diff; offset += (8 * sizeof(Piece) * diff); } *base |= (Piece(1) << (*indexBegin - offset)); if (! value) *base ^= (Piece(1) << (*indexBegin - offset)); } } /** * Sets all bits of this bitmask to \c false. * * \warning The length of this bitmask must already have been * initialised. In particular, if the default constructor was * used, you must call the one-argument reset(size_t) before you * can use this routine. */ void reset(); /** * Resizes this bitmask to the given length and sets all bits to * \c false. * * This routine can be used to change the length (number of * bits) of the bitmask if desired. * * @param length the number of bits to store in this bitmask; this * must be at least one. */ void reset(size_t length); /** * Sets this bitmask to a copy of the given bitmask. * * If this bitmask is invalid, this assignment operator can be * used to initialise it with a length. * * If this bitmask has already been initialised to a different * length from that of the given bitmask, the length of this * bitmask will be changed accordingly. * * If the given bitmask is invalid, this bitmask will become * invalid also. Invalid bitmasks must be assigned a length using * reset(size_t) or this assignment operator. * * @param other the bitmask to clone. * @return a reference to this bitmask. */ NBitmask& operator = (const NBitmask& other); /** * Leaves the first \a numBits bits of this bitmask intact, but * sets all subsequent bits to \c false. In other words, this * routine "truncates" this bitmask to the given number of bits. * * This routine does not change the \e length of this bitmask * (as passed to the contructor or to reset()). * * \pre \a numBits is at most the length of this bitmask. * * @param numBits the number of bits that will \e not be cleared. */ void truncate(size_t numBits); /** * Sets this to the intersection of this and the given bitmask. * Every bit that is unset in \a other will be unset in this bitmask. * * \pre This and the given bitmask have the same length. * * @param other the bitmask to intersect with this. * @return a reference to this bitmask. */ NBitmask& operator &= (const NBitmask& other); /** * Sets this to the union of this and the given bitmask. * Every bit that is set in \a other will be set in this bitmask. * * \pre This and the given bitmask have the same length. * * @param other the bitmask to union with this. * @return a reference to this bitmask. */ NBitmask& operator |= (const NBitmask& other); /** * Sets this to the exclusive disjunction (XOR) of this and the * given bitmask. Every bit that is set in \a other will be * flipped in this bitmask. * * \pre This and the given bitmask have the same length. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ NBitmask& operator ^= (const NBitmask& other); /** * Sets this to the set difference of this and the given bitmask. * Every bit that is set in \a other will be cleared in this bitmask. * * \pre This and the given bitmask have the same length. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ NBitmask& operator -= (const NBitmask& other); /** * Negates every bit in this bitmask. All \c true bits will be * set to \c false and vice versa. * * \warning Because this class may increase the bitmask length * (rounding up to the next unit of storage), flip() may set * additional \c true bits in the "dead space" between the intended * length and the actual length. This may cause unexpected * results for routines such as subset testing, bit counting and * so on. Be careful! */ void flip(); /** * Determines whether this and the given bitmask are identical. * * \pre This and the given bitmask have the same length. * * @param other the bitmask to compare against this. * @return \c true if and only if this and the given bitmask are * identical. */ bool operator == (const NBitmask& other) const; /** * Determines whether this bitmask appears strictly before the given * bitmask when bitmasks are sorted in lexicographical order. * Here the bit at index 0 is least significant, and the bit at * index \a length-1 is most significant. * * \pre This and the given bitmask have the same length. * * \warning We do not use < for this operation, since <= * represents the subset operation. * * @param other the bitmask to compare against this. * @return \c true if and only if this is lexicographically * strictly smaller than the given bitmask. */ bool lessThan(const NBitmask& other) const; /** * Determines whether this bitmask is entirely contained within * the given bitmask. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in the given bitmask. * * \pre This and the given bitmask have the same length. * * \warning This operation does not compare bitmasks * lexicographically; moreover, it only describes a partial * order, not a total order. To compare bitmasks * lexicographically, use lessThan() instead. * * @param other the bitmask to compare against this. * @return \c true if and only if this bitmask is entirely contained * within the given bitmask. */ bool operator <= (const NBitmask& other) const; /** * Determines whether this bitmask is entirely contained within * the union of the two given bitmasks. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in either \a x or \a y. * * \pre Both \a x and \a y are the same length as this bitmask. * * @param x the first bitmask used to form the union. * @param y the first bitmask used to form the union. * @return \c true if and only if this bitmask is entirely contained * within the union of \a x and \a y. */ bool inUnion(const NBitmask& x, const NBitmask& y) const; /** * Determines whether this bitmask contains the intersection of * the two given bitmasks. * * For this routine to return \c true, every bit that is set in * \e both \a x and \a y must be set in this bitmask also. * * \pre Both \a x and \a y are the same length as this bitmask. * * @param x the first bitmask used to form the intersection. * @param y the first bitmask used to form the intersection. * @return \c true if and only if this bitmask entirely contains * the intersection of \a x and \a y. */ bool containsIntn(const NBitmask& x, const NBitmask& y) const; /** * Returns the number of bits currently set to \c true in this * bitmask. * * @return the number of \c true bits. */ size_t bits() const; /** * Returns the index of the first \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the first \c true bit. */ long firstBit() const; /** * Returns the index of the last \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the last \c true bit. */ long lastBit() const; /** * Determines whether at most one bit is set to \c true in this * bitmask. * * If this bitmask is entirely \c false or if only one bit is set * to \c true, then this routine will return \c true. Otherwise * this routine will return \c false. * * @return \c true if and only if at most one bit is set to \c true. */ bool atMostOneBit() const; friend std::ostream& operator << (std::ostream& out, const NBitmask& mask); }; /** * Writes the given bitmask to the given output stream as a sequence of * zeroes and ones. * * Since the length of the bitmask is not stored, the number of bits * written might be greater than the length initially assigned to this * bitmask (specifically, the length will be rounded up to the next "raw * unit of storage"). * * \ifacespython Not present. * * @param out the output stream to which to write. * @param mask the bitmask to write. * @return a reference to the given output stream. */ REGINA_API std::ostream& operator << (std::ostream& out, const NBitmask& mask); /** * A small but extremely fast bitmask class that can store up to * 8 * sizeof(\a T) true-or-false bits. * * This bitmask packs all of the bits together into a single variable of * type \a T. This means that operations on bitmasks are extremely * fast, because all of the bits can be processed at once. * * The downside of course is that the number of bits that can be stored * is limited to 8 * sizeof(\a T), where \a T must be a native unsigned * integer type (such as unsigned char, unsigned int, or unsigned long * long). * * For another extremely fast bitmask class that can store twice as * many bits, see NBitmask2. For a bitmask class that can store * arbitrarily many bits, see NBitmask. * * \pre Type \a T is an unsigned integral numeric type. * * \testpart * * \ifacespython Not present. */ template class NBitmask1 { private: T mask; /**< Contains all 8 * sizeof(\a T) bits of this bitmask. */ public: /** * Creates a new bitmask with all bits set to \c false. */ inline NBitmask1() : mask(0) { } /** * Creates a new bitmask with all bits set to \c false. * * The integer argument is merely for compatibility with * the NBitmask constructor, and will be ignored. * * \warning This is \e not a constructor that initialises the * bitmask to a given pattern. */ inline NBitmask1(size_t) : mask(0) { } /** * Creates a clone of the given bitmask. * * @param cloneMe the bitmask to clone. */ inline NBitmask1(const NBitmask1& cloneMe) : mask(cloneMe.mask) { } /** * Sets all bits of this bitmask to \c false. */ inline void reset() { mask = 0; } /** * Sets all bits of this bitmask to \c false. * * The integer argument is merely for compatibility with * NBitmask::reset(size_t), and will be ignored. */ inline void reset(size_t) { mask = 0; } /** * Sets this bitmask to a copy of the given bitmask. * * @param other the bitmask to clone. * @return a reference to this bitmask. */ NBitmask1& operator = (const NBitmask1& other) { mask = other.mask; return *this; } /** * Leaves the first \a numBits bits of this bitmask intact, but * sets all subsequent bits to \c false. In other words, this * routine "truncates" this bitmask to the given number of bits. * * This routine does not change the \e length of this bitmask * (as passed to the contructor or to reset()). * * @param numBits the number of bits that will \e not be cleared. */ inline void truncate(size_t numBits) { if (numBits < 8 * sizeof(T)) mask &= ((T(1) << numBits) - T(1)); } /** * Returns the value of the given bit of this bitmask. * * @param index indicates which bit to query; this must be between * 0 and (8 * sizeof(\a T) - 1) inclusive. * @return the value of the (\a index)th bit. */ inline bool get(size_t index) const { return (mask & (T(1) << index)); } /** * Sets the given bit of this bitmask to the given value. * * @param index indicates which bit to set; this must be between * 0 and (8 * sizeof(\a T) - 1) inclusive. * @param value the value that will be assigned to the (\a index)th bit. */ inline void set(size_t index, bool value) { mask |= (T(1) << index); if (! value) mask ^= (T(1) << index); } /** * Sets all bits in the given sorted list to the given value. * * This is a convenience routine for setting many bits at once. * The indices of the bits to set should be sorted and stored in * some container, such as a std::set or a C-style array. This * routine takes iterators over this container, and sets the * bits at the corresponding indices to the given value. * * For example, the following code would set bits 3, 5 and 6 * to \c true: * * \code * std::vector indices; * indices.push(3); indices.push(5); indices.push(6); * bitmask.set(indices.begin(), indices.end(), true); * \endcode * * Likewise, the following code would set bits 1, 4 and 7 to \c false: * * \code * unsigned indices[3] = { 1, 4, 7 }; * bitmask.set(indices, indices + 3, false); * \endcode * * All other bits of this bitmask are unaffected by this routine. * * \pre \a ForwardIterator is a forward iterator type that iterates * over integer values. * \pre The list of indices described by these iterators is * in \e sorted order. This is to allow optimisations for * larger bitmask types. * \pre All indices in the given list are between * 0 and (8 * sizeof(\a T) - 1) inclusive. * * @param indexBegin the beginning of the iterator range * containing the sorted indices of the bits to set. * @param indexEnd the end of the iterator range containing the * sorted indices of the bits to set. * @param value the value that will be assigned to each of the * corresponding bits. */ template void set(ForwardIterator indexBegin, ForwardIterator indexEnd, bool value) { for ( ; indexBegin != indexEnd; ++indexBegin) { mask |= (T(1) << *indexBegin); if (! value) mask ^= (T(1) << *indexBegin); } } /** * Sets this to the intersection of this and the given bitmask. * Every bit that is unset in \a other will be unset in this bitmask. * * @param other the bitmask to intersect with this. * @return a reference to this bitmask. */ inline NBitmask1& operator &= (const NBitmask1& other) { mask &= other.mask; return *this; } /** * Sets this to the union of this and the given bitmask. * Every bit that is set in \a other will be set in this bitmask. * * @param other the bitmask to union with this. * @return a reference to this bitmask. */ inline NBitmask1& operator |= (const NBitmask1& other) { mask |= other.mask; return *this; } /** * Sets this to the exclusive disjunction (XOR) of this and the * given bitmask. Every bit that is set in \a other will be * flipped in this bitmask. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ inline NBitmask1& operator ^= (const NBitmask1& other) { mask ^= other.mask; return *this; } /** * Sets this to the set difference of this and the given bitmask. * Every bit that is set in \a other will be cleared in this bitmask. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ inline NBitmask1& operator -= (const NBitmask1& other) { mask |= other.mask; mask ^= other.mask; return *this; } /** * Negates every bit in this bitmask. All \c true bits will be * set to \c false and vice versa. * * Unlike the more generic NBitmask, this optimised bitmask * class does not store a length. This means that all * 8 * sizeof(\a T) possible bits will be negated. */ inline void flip() { mask = ~mask; } /** * Determines whether this and the given bitmask are identical. * * @param other the bitmask to compare against this. * @return \c true if and only if this and the given bitmask are * identical. */ inline bool operator == (const NBitmask1& other) const { return (mask == other.mask); } /** * Determines whether this bitmask appears strictly before the given * bitmask when bitmasks are sorted in lexicographical order. * Here the bit at index 0 is least significant, and the bit at * index \a length-1 is most significant. * * \warning We do not use < for this operation, since <= * represents the subset operation. * * @param other the bitmask to compare against this. * @return \c true if and only if this is lexicographically * strictly smaller than the given bitmask. */ inline bool lessThan(const NBitmask1& other) const { return (mask < other.mask); } /** * Determines whether this bitmask is entirely contained within * the given bitmask. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in the given bitmask. * * \warning This operation does not compare bitmasks * lexicographically; moreover, it only describes a partial * order, not a total order. To compare bitmasks * lexicographically, use lessThan() instead. * * @param other the bitmask to compare against this. * @return \c true if and only if this bitmask is entirely contained * within the given bitmask. */ inline bool operator <= (const NBitmask1& other) const { return ((mask | other.mask) == other.mask); } /** * Determines whether this bitmask is entirely contained within * the union of the two given bitmasks. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in either \a x or \a y. * * @param x the first bitmask used to form the union. * @param y the first bitmask used to form the union. * @return \c true if and only if this bitmask is entirely contained * within the union of \a x and \a y. */ inline bool inUnion(const NBitmask1& x, const NBitmask1& y) const { return ((mask & (x.mask | y.mask)) == mask); } /** * Determines whether this bitmask contains the intersection of * the two given bitmasks. * * For this routine to return \c true, every bit that is set in * \e both \a x and \a y must be set in this bitmask also. * * @param x the first bitmask used to form the intersection. * @param y the first bitmask used to form the intersection. * @return \c true if and only if this bitmask entirely contains * the intersection of \a x and \a y. */ inline bool containsIntn(const NBitmask1& x, const NBitmask1& y) const { return ((mask | (x.mask & y.mask)) == mask); } /** * Returns the number of bits currently set to \c true in this * bitmask. * * @return the number of \c true bits. */ inline size_t bits() const { return BitManipulator::bits(mask); } /** * Returns the index of the first \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the first \c true bit. */ inline long firstBit() const { return BitManipulator::firstBit(mask); } /** * Returns the index of the last \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the last \c true bit. */ inline long lastBit() const { return BitManipulator::lastBit(mask); } /** * Determines whether at most one bit is set to \c true in this * bitmask. * * If this bitmask is entirely \c false or if only one bit is set * to \c true, then this routine will return \c true. Otherwise * this routine will return \c false. * * @return \c true if and only if at most one bit is set to \c true. */ inline bool atMostOneBit() const { return BitManipulator::bits(mask) <= 1; } template friend std::ostream& operator << (std::ostream& out, const NBitmask1& mask); }; /** * Writes the given bitmask to the given output stream as a sequence of * zeroes and ones. * * Since the length of the bitmask is not stored, the number of bits * written will be 8 * sizeof(\a T). * * \ifacespython Not present. * * @param out the output stream to which to write. * @param mask the bitmask to write. * @return a reference to the given output stream. */ template std::ostream& operator << (std::ostream& out, const NBitmask1& mask) { for (T bit = 1; bit; bit <<= 1) out << ((mask.mask & bit) ? '1' : '0'); return out; } /** * A small but extremely fast bitmask class that can store up to * 8 * sizeof(\a T) + 8 * sizeof(\a U) true-or-false bits. * * This bitmask packs all of the bits together into a single variable of * type \a T and a single variable of type \a U. This means that operations * on entire bitmasks are extremely fast, because all of the bits can be * processed in just two "native" operations. * * The downside of course is that the number of bits that can be stored * is limited to 8 * sizeof(\a T) + 8 * sizeof(\a U), where \a T and \a U * must be native unsigned integer types (such as unsigned char, unsigned int, * or unsigned long long). * * For an even faster bitmask class that can only store half as many bits, * see NBitmask1. For a bitmask class that can store arbitrarily many bits, * see NBitmask. * * \pre Types \a T and \a U are unsigned integral numeric types. * * \testpart * * \ifacespython Not present. */ template class NBitmask2 { private: T low; /**< Contains the first 8 * sizeof(\a T) bits of this bitmask. */ U high; /**< Contains the final 8 * sizeof(\a U) bits of this bitmask. */ public: /** * Creates a new bitmask with all bits set to \c false. */ inline NBitmask2() : low(0), high(0) { } /** * Creates a new bitmask with all bits set to \c false. * * The integer argument is merely for compatibility with * the NBitmask constructor, and will be ignored. * * \warning This is \e not a constructor that initialises the * bitmask to a given pattern. */ inline NBitmask2(size_t) : low(0), high(0) { } /** * Creates a clone of the given bitmask. * * @param cloneMe the bitmask to clone. */ inline NBitmask2(const NBitmask2& cloneMe) : low(cloneMe.low), high(cloneMe.high) { } /** * Sets all bits of this bitmask to \c false. */ inline void reset() { low = 0; high = 0; } /** * Sets all bits of this bitmask to \c false. * * The integer argument is merely for compatibility with * NBitmask::reset(size_t), and will be ignored. */ inline void reset(size_t) { low = 0; high = 0; } /** * Sets this bitmask to a copy of the given bitmask. * * @param other the bitmask to clone. * @return a reference to this bitmask. */ NBitmask2& operator = (const NBitmask2& other) { low = other.low; high = other.high; return *this; } /** * Leaves the first \a numBits bits of this bitmask intact, but * sets all subsequent bits to \c false. In other words, this * routine "truncates" this bitmask to the given number of bits. * * This routine does not change the \e length of this bitmask * (as passed to the contructor or to reset()). * * @param numBits the number of bits that will \e not be cleared. */ inline void truncate(size_t numBits) { if (numBits < 8 * sizeof(T)) { low &= ((T(1) << numBits) - T(1)); high = 0; } else { numBits -= 8 * sizeof(T); if (numBits < 8 * sizeof(U)) high &= ((U(1) << numBits) - U(1)); } } /** * Returns the value of the given bit of this bitmask. * * @param index indicates which bit to query; this must be between * 0 and (8 * sizeof(\a T) + 8 * sizeof(\a U) - 1) inclusive. * @return the value of the (\a index)th bit. */ inline bool get(size_t index) const { if (index < 8 * sizeof(T)) return (low & (T(1) << index)); else return (high & (U(1) << (index - 8 * sizeof(T)))); } /** * Sets the given bit of this bitmask to the given value. * * @param index indicates which bit to set; this must be between * 0 and (8 * sizeof(\a T) + 8 * sizeof(\a U) - 1) inclusive. * @param value the value that will be assigned to the (\a index)th bit. */ inline void set(size_t index, bool value) { if (index < 8 * sizeof(T)) { low |= (T(1) << index); if (! value) low ^= (T(1) << index); } else { high |= (U(1) << (index - 8 * sizeof(T))); if (! value) high ^= (U(1) << (index - 8 * sizeof(T))); } } /** * Sets all bits in the given sorted list to the given value. * * This is a convenience routine for setting many bits at once. * The indices of the bits to set should be sorted and stored in * some container, such as a std::set or a C-style array. This * routine takes iterators over this container, and sets the * bits at the corresponding indices to the given value. * * For example, the following code would set bits 3, 5 and 6 * to \c true: * * \code * std::vector indices; * indices.push(3); indices.push(5); indices.push(6); * bitmask.set(indices.begin(), indices.end(), true); * \endcode * * Likewise, the following code would set bits 1, 4 and 7 to \c false: * * \code * unsigned indices[3] = { 1, 4, 7 }; * bitmask.set(indices, indices + 3, false); * \endcode * * All other bits of this bitmask are unaffected by this routine. * * \pre \a ForwardIterator is a forward iterator type that iterates * over integer values. * \pre The list of indices described by these iterators is * in \e sorted order. This is to allow optimisations for * larger bitmask types. * \pre All indices in the given list are between * 0 and (8 * sizeof(\a T) + 8 * sizeof(\a U) - 1) inclusive. * * @param indexBegin the beginning of the iterator range * containing the sorted indices of the bits to set. * @param indexEnd the end of the iterator range containing the * sorted indices of the bits to set. * @param value the value that will be assigned to each of the * corresponding bits. */ template void set(ForwardIterator indexBegin, ForwardIterator indexEnd, bool value) { // First deal with the bits stored in low. for ( ; indexBegin != indexEnd && *indexBegin < 8 * sizeof(T); ++indexBegin) { low |= (T(1) << *indexBegin); if (! value) low ^= (T(1) << *indexBegin); } // Now deal with the bits stored in high. for ( ; indexBegin != indexEnd; ++indexBegin) { high |= (U(1) << ((*indexBegin) - 8 * sizeof(T))); if (! value) high ^= (U(1) << ((*indexBegin) - 8 * sizeof(T))); } } /** * Sets this to the intersection of this and the given bitmask. * Every bit that is unset in \a other will be unset in this bitmask. * * @param other the bitmask to intersect with this. * @return a reference to this bitmask. */ inline NBitmask2& operator &= (const NBitmask2& other) { low &= other.low; high &= other.high; return *this; } /** * Sets this to the union of this and the given bitmask. * Every bit that is set in \a other will be set in this bitmask. * * @param other the bitmask to union with this. * @return a reference to this bitmask. */ inline NBitmask2& operator |= (const NBitmask2& other) { low |= other.low; high |= other.high; return *this; } /** * Sets this to the exclusive disjunction (XOR) of this and the * given bitmask. Every bit that is set in \a other will be * flipped in this bitmask. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ inline NBitmask2& operator ^= (const NBitmask2& other) { low ^= other.low; high ^= other.high; return *this; } /** * Sets this to the set difference of this and the given bitmask. * Every bit that is set in \a other will be cleared in this bitmask. * * @param other the bitmask to XOR with this. * @return a reference to this bitmask. */ inline NBitmask2& operator -= (const NBitmask2& other) { low |= other.low; low ^= other.low; high |= other.high; high ^= other.high; return *this; } /** * Negates every bit in this bitmask. All \c true bits will be * set to \c false and vice versa. * * Unlike the more generic NBitmask, this optimised bitmask * class does not store a length. This means that all * 8 * sizeof(\a T) + 8 * sizeof(\a U) possible bits will be negated. */ inline void flip() { low = ~low; high = ~high; } /** * Determines whether this and the given bitmask are identical. * * @param other the bitmask to compare against this. * @return \c true if and only if this and the given bitmask are * identical. */ inline bool operator == (const NBitmask2& other) const { return (low == other.low && high == other.high); } /** * Determines whether this bitmask appears strictly before the given * bitmask when bitmasks are sorted in lexicographical order. * Here the bit at index 0 is least significant, and the bit at * index \a length-1 is most significant. * * \warning We do not use < for this operation, since <= * represents the subset operation. * * @param other the bitmask to compare against this. * @return \c true if and only if this is lexicographically * strictly smaller than the given bitmask. */ inline bool lessThan(const NBitmask2& other) const { return (high < other.high || (high == other.high && low < other.low)); } /** * Determines whether this bitmask is entirely contained within * the given bitmask. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in the given bitmask. * * \warning This operation does not compare bitmasks * lexicographically; moreover, it only describes a partial * order, not a total order. To compare bitmasks * lexicographically, use lessThan() instead. * * @param other the bitmask to compare against this. * @return \c true if and only if this bitmask is entirely contained * within the given bitmask. */ inline bool operator <= (const NBitmask2& other) const { return ((low | other.low) == other.low && (high | other.high) == other.high); } /** * Determines whether this bitmask is entirely contained within * the union of the two given bitmasks. * * For this routine to return \c true, every bit that is set * in this bitmask must also be set in either \a x or \a y. * * @param x the first bitmask used to form the union. * @param y the first bitmask used to form the union. * @return \c true if and only if this bitmask is entirely contained * within the union of \a x and \a y. */ inline bool inUnion(const NBitmask2& x, const NBitmask2& y) const { return ((low & (x.low | y.low)) == low && (high & (x.high | y.high)) == high); } /** * Determines whether this bitmask contains the intersection of * the two given bitmasks. * * For this routine to return \c true, every bit that is set in * \e both \a x and \a y must be set in this bitmask also. * * @param x the first bitmask used to form the intersection. * @param y the first bitmask used to form the intersection. * @return \c true if and only if this bitmask entirely contains * the intersection of \a x and \a y. */ inline bool containsIntn(const NBitmask2& x, const NBitmask2& y) const { return ((low | (x.low & y.low)) == low && (high | (x.high & y.high)) == high); } /** * Returns the number of bits currently set to \c true in this * bitmask. * * @return the number of \c true bits. */ inline size_t bits() const { return BitManipulator::bits(low) + BitManipulator::bits(high); } /** * Returns the index of the first \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the first \c true bit. */ inline long firstBit() const { // -1 case does not work out of the box in the second IF branch // due to the 8 * sizeof(T). if (low) return BitManipulator::firstBit(low); else if (high) return 8 * sizeof(T) + BitManipulator::firstBit(high); else return -1; } /** * Returns the index of the last \c true bit in this bitmask, * or -1 if there are no \c true bits. * * @return the index of the last \c true bit. */ inline long lastBit() const { // -1 case works out of the box in the second IF branch. if (high) return 8 * sizeof(T) + BitManipulator::lastBit(high); else return BitManipulator::lastBit(low); } /** * Determines whether at most one bit is set to \c true in this * bitmask. * * If this bitmask is entirely \c false or if only one bit is set * to \c true, then this routine will return \c true. Otherwise * this routine will return \c false. * * @return \c true if and only if at most one bit is set to \c true. */ inline bool atMostOneBit() const { return (BitManipulator::bits(low) + BitManipulator::bits(high)) <= 1; } template friend std::ostream& operator << (std::ostream& out, const NBitmask2& mask); }; /** * Writes the given bitmask to the given output stream as a sequence of * zeroes and ones. * * Since the length of the bitmask is not stored, the number of bits * written will be 8 * sizeof(\a T) + 8 * sizeof(\a U). * * \ifacespython Not present. * * @param out the output stream to which to write. * @param mask the bitmask to write. * @return a reference to the given output stream. */ template std::ostream& operator << (std::ostream& out, const NBitmask2& mask) { for (T bit = 1; bit; bit <<= 1) out << ((mask.low & bit) ? '1' : '0'); for (U bit = 1; bit; bit <<= 1) out << ((mask.high & bit) ? '1' : '0'); return out; } #ifndef __DOXYGEN /** * An internal template that helps choose the correct bitmask type for * a given (hard-coded) number of bits. * * Please do not use this class directly, since this template is internal * and subject to change in future versions of Regina. Instead please * use the convenience typedefs NBitmaskLen8, NBitmaskLen16, NBitmaskLen32 * and NBitmaskLen64. * * The reason this template exists is to circumvent the fact that we cannot * use sizeof() in a #if statement. The boolean argument to this template * should always be left as the default. */ template = 4)> struct InternalBitmaskLen32; template <> struct InternalBitmaskLen32 { typedef NBitmask1 Type; }; template <> struct InternalBitmaskLen32 { // The standard guarantees that sizeof(long) >= 4. typedef NBitmask1 Type; }; /** * An internal template that helps choose the correct bitmask type for * a given (hard-coded) number of bits. * * Please do not use this class directly, since this template is internal * and subject to change in future versions of Regina. Instead please * use the convenience typedefs NBitmaskLen8, NBitmaskLen16, NBitmaskLen32 * and NBitmaskLen64. * * The reason this template exists is to circumvent the fact that we cannot * use sizeof() in a #if statement. The boolean argument to this template * should always be left as the default. */ template = 8)> struct InternalBitmaskLen64; template <> struct InternalBitmaskLen64 { typedef NBitmask1 Type; }; template <> struct InternalBitmaskLen64 { #ifdef LONG_LONG_FOUND // The C standard guarantees that sizeof(long long) >= 8. // However, the C++ standard does not require long long to exist at // all (hence the LONG_LONG_FOUND test). typedef NBitmask1 Type; #else // The standard guarantees that sizeof(long) >= 4. // Therefore two longs will be enough for 64 bits. typedef NBitmask2 Type; #endif }; #endif // End block for doxygen to ignore. /** * A convenience typedef that gives a small and extremely fast bitmask * class capable of holding at least 8 true-or-false bits. * * This bitmask class is guaranteed to be an instantiation of the * template class NBitmask1. * * The particular instantiation is subject to change between different * platforms, different compilers and/or different versions of Regina. * * \ifacespython Not present. */ typedef NBitmask1 NBitmaskLen8; /** * A convenience typedef that gives a small and extremely fast bitmask * class capable of holding at least 16 true-or-false bits. * * This bitmask class is guaranteed to be an instantiation of the * template class NBitmask1. * * The particular instantiation is subject to change between different * platforms, different compilers and/or different versions of Regina. * * \ifacespython Not present. */ typedef NBitmask1 NBitmaskLen16; /** * A convenience typedef that gives a small and extremely fast bitmask * class capable of holding at least 32 true-or-false bits. * * This bitmask class is guaranteed to be an instantiation of the * template class NBitmask1. * * The particular instantiation is subject to change between different * platforms, different compilers and/or different versions of Regina. * * \ifacespython Not present. */ typedef InternalBitmaskLen32<>::Type NBitmaskLen32; /** * A convenience typedef that gives a small and extremely fast bitmask * class capable of holding at least 64 true-or-false bits. * * This bitmask class is guaranteed to be an instantiation of * \e either the template class NBitmask1 or the template class NBitmask2. * * The particular instantiation is subject to change between different * platforms, different compilers and/or different versions of Regina. * * \ifacespython Not present. */ typedef InternalBitmaskLen64<>::Type NBitmaskLen64; /*@}*/ // Inline functions for NBitmask inline NBitmask::NBitmask() : pieces(0), mask(0) { } inline NBitmask::NBitmask(size_t length) : pieces((length - 1) / (8 * sizeof(Piece)) + 1), mask(new Piece[pieces]) { std::fill(mask, mask + pieces, 0); } inline NBitmask::NBitmask(const NBitmask& cloneMe) : pieces(cloneMe.pieces), mask(new Piece[cloneMe.pieces]) { std::copy(cloneMe.mask, cloneMe.mask + pieces, mask); } inline NBitmask::~NBitmask() { delete[] mask; } inline void NBitmask::reset() { std::fill(mask, mask + pieces, 0); } inline void NBitmask::reset(size_t length) { delete[] mask; pieces = (length - 1) / (8 * sizeof(Piece)) + 1; mask = new Piece[pieces]; std::fill(mask, mask + pieces, 0); } inline NBitmask& NBitmask::operator = (const NBitmask& other) { if (pieces != other.pieces) { delete[] mask; pieces = other.pieces; mask = new Piece[pieces]; } if (pieces) std::copy(other.mask, other.mask + pieces, mask); return *this; } inline void NBitmask::truncate(size_t numBits) { size_t skip = numBits / (8 * sizeof(Piece)); numBits = numBits % (8 * sizeof(Piece)); Piece* piece = mask + skip; if (piece < mask + pieces) { (*piece) &= ((Piece(1) << numBits) - Piece(1)); for (++piece; piece < mask + pieces; ++piece) *piece = 0; } } inline bool NBitmask::get(size_t index) const { return (mask[index / (8 * sizeof(Piece))] & (Piece(1) << (index % (8 * sizeof(Piece))))); } inline void NBitmask::set(size_t index, bool value) { mask[index / (8 * sizeof(Piece))] |= (Piece(1) << (index % (8 * sizeof(Piece)))); if (! value) mask[index / (8 * sizeof(Piece))] ^= (Piece(1) << (index % (8 * sizeof(Piece)))); } inline NBitmask& NBitmask::operator &= (const NBitmask& other) { for (size_t i = 0; i < pieces; ++i) mask[i] &= other.mask[i]; return *this; } inline NBitmask& NBitmask::operator |= (const NBitmask& other) { for (size_t i = 0; i < pieces; ++i) mask[i] |= other.mask[i]; return *this; } inline NBitmask& NBitmask::operator ^= (const NBitmask& other) { for (size_t i = 0; i < pieces; ++i) mask[i] ^= other.mask[i]; return *this; } inline NBitmask& NBitmask::operator -= (const NBitmask& other) { for (size_t i = 0; i < pieces; ++i) { mask[i] |= other.mask[i]; mask[i] ^= other.mask[i]; } return *this; } inline void NBitmask::flip() { for (size_t i = 0; i < pieces; ++i) mask[i] = ~mask[i]; } inline bool NBitmask::operator == (const NBitmask& other) const { return std::equal(mask, mask + pieces, other.mask); } inline bool NBitmask::lessThan(const NBitmask& other) const { for (long i = pieces - 1; i >= 0; --i) if (mask[i] < other.mask[i]) return true; else if (mask[i] > other.mask[i]) return false; return false; } inline bool NBitmask::operator <= (const NBitmask& other) const { for (size_t i = 0; i < pieces; ++i) if ((mask[i] | other.mask[i]) != other.mask[i]) return false; return true; } inline bool NBitmask::inUnion(const NBitmask& x, const NBitmask& y) const { for (size_t i = 0; i < pieces; ++i) if ((mask[i] & (x.mask[i] | y.mask[i])) != mask[i]) return false; return true; } inline bool NBitmask::containsIntn(const NBitmask& x, const NBitmask& y) const { for (size_t i = 0; i < pieces; ++i) if ((mask[i] | (x.mask[i] & y.mask[i])) != mask[i]) return false; return true; } inline size_t NBitmask::bits() const { size_t ans = 0; for (size_t i = 0; i < pieces; ++i) ans += BitManipulator::bits(mask[i]); return ans; } inline long NBitmask::firstBit() const { for (size_t i = 0; i < pieces; ++i) if (mask[i]) return 8 * sizeof(Piece) * i + BitManipulator::firstBit(mask[i]); return -1; } inline long NBitmask::lastBit() const { for (long i = pieces - 1; i >= 0; --i) if (mask[i]) return 8 * sizeof(Piece) * i + BitManipulator::lastBit(mask[i]); return -1; } inline bool NBitmask::atMostOneBit() const { unsigned bits = 0; for (size_t i = 0; i < pieces; ++i) { bits += BitManipulator::bits(mask[i]); if (bits > 1) return false; } return true; } inline std::ostream& operator << (std::ostream& out, const NBitmask& mask) { NBitmask::Piece bit; for (size_t i = 0; i < mask.pieces; ++i) for (bit = 1; bit; bit <<= 1) out << ((bit & mask.mask[i]) ? '1' : '0'); return out; } } // namespace regina #endif regina-4.95/engine/utilities/nbooleans.cpp000644 000765 000024 00000006352 12234011536 020556 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #include "utilities/nbooleans.h" #include namespace regina { const NTriBool NTriBool::True(codeTrue); const NTriBool NTriBool::False(codeFalse); const NTriBool NTriBool::Unknown(codeUnknown); const unsigned char NBoolSet::eltTrue = 1; const unsigned char NBoolSet::eltFalse = 2; const NBoolSet NBoolSet::sNone; const NBoolSet NBoolSet::sTrue(true); const NBoolSet NBoolSet::sFalse(false); const NBoolSet NBoolSet::sBoth(true, true); std::ostream& operator << (std::ostream& out, const NTriBool& tri) { if (tri.isTrue()) out << "true"; else if (tri.isFalse()) out << "false"; else out << "unknown"; return out; } std::ostream& operator << (std::ostream& out, const NBoolSet& set) { if (set == NBoolSet::sNone) out << "{ }"; else if (set == NBoolSet::sTrue) out << "{ true }"; else if (set == NBoolSet::sFalse) out << "{ false }"; else out << "{ true, false }"; return out; } } // namespace regina regina-4.95/engine/utilities/nbooleans.h000644 000765 000024 00000101146 12234011536 020220 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/nbooleans.h * \brief Provides various types that extend the standard boolean. */ #ifndef __NBOOLEANS_H #ifndef __DOXYGEN #define __NBOOLEANS_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * A three-way extension of the boolean type. * Three-way booleans can be true, false or unknown. * * \deprecated NTriBool has now been replaced with native (two-way) bool * throughout Regina, and will be removed in some future release. */ class REGINA_API NTriBool { private: enum Code { codeTrue = 1, codeFalse = -1, codeUnknown = 0 }; /**< Internal codes for the three possible values. */ Code code; /**< The internal code for this particular three-way boolean. */ public: static const NTriBool True; /**< A global instance of the true value. */ static const NTriBool False; /**< A global instance of the false value. */ static const NTriBool Unknown; /**< A global instance of the unknown value. */ public: /** * Creates a new three-way boolean initialised to the unknown value. */ NTriBool(); /** * Creates a copy of the given three-way boolean. * * @param cloneMe the three-way boolean to clone. */ NTriBool(const NTriBool& cloneMe); /** * Creates a new three-way boolean whose initial value is the * given standard boolean. * * @param value the value (true or false) to assign to the new * three-way boolean. */ NTriBool(bool value); /** * Is the value of this three-way boolean equal to true? * * @return whether or not this object has the value of true. */ bool isTrue() const; /** * Is the value of this three-way boolean equal to false? * * @return whether or not this object has the value of false. */ bool isFalse() const; /** * Is the value of this three-way boolean equal to unknown? * * @return whether or not this object has the value of unknown. */ bool isUnknown() const; /** * Does this three-way boolean take one of the explicit values * true or false? * * Note that this routine is the negation of isUnknown(). * * @return whether or not this object has an explicit true or * false value. */ bool isKnown() const; /** * Sets this three-way boolean to true. */ void setTrue(); /** * Sets this three-way boolean to false. */ void setFalse(); /** * Sets this three-way boolean to unknown. */ void setUnknown(); /** * Determines whether this and the given three-way boolean are equal. * This routine makes a straightforward comparison of states. * That is, true is equal to true, false is equal to false and * unknown is equal to unknown. * * @param other the three-way boolean to compare with this. * @return \c true if and only if this and the given value are * equal. */ bool operator == (const NTriBool& other) const; /** * Determines whether this and the given standard boolean are equal. * This routine makes a straightforward comparison of states. * That is, true is equal to true and false is equal to false. * Unknown is equal to neither true nor false. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the standard boolean to compare with this. * @return \c true if and only if this and the given value are * equal. */ bool operator == (bool other) const; /** * Determines whether this and the given three-way boolean are * different. * This routine makes a straightforward comparison of states. * That is, true is equal to true, false is equal to false and * unknown is equal to unknown. * * @param other the three-way boolean to compare with this. * @return \c true if and only if this and the given value are * different. */ bool operator != (const NTriBool& other) const; /** * Determines whether this and the given standard boolean are * different. * This routine makes a straightforward comparison of states. * That is, true is equal to true and false is equal to false. * Unknown is equal to neither true nor false. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the standard boolean to compare with this. * @return \c true if and only if this and the given value are * different. */ bool operator != (bool other) const; /** * Sets this three-way boolean to be identical to the given * three-way boolean. * * @param cloneMe the value to assign to this object. * @return a reference to this object. */ NTriBool& operator = (const NTriBool& cloneMe); /** * Sets this three-way boolean to be identical to the given * standard boolean. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param cloneMe the value to assign to this object. * @return a reference to this object. */ NTriBool& operator = (bool cloneMe); /** * Sets this to be the disjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown or true) is true, and (unknown or false) is unknown. * * @param other the value to combine with this value. * @return a reference to this object. */ NTriBool& operator |= (const NTriBool& other); /** * Sets this to be the disjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown or true) is true, and (unknown or false) is unknown. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the value to combine with this value. * @return a reference to this object. */ NTriBool& operator |= (bool other); /** * Sets this to be the conjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown and true) is unknown, and (unknown and false) is false. * * @param other the value to combine with this value. * @return a reference to this object. */ NTriBool& operator &= (const NTriBool& other); /** * Sets this to be the conjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown and true) is unknown, and (unknown and false) is false. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the value to combine with this value. * @return a reference to this object. */ NTriBool& operator &= (bool other); /** * Returns the disjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown or true) is true, and (unknown or false) is unknown. * * @param other the value to combine with this value. * @return the disjunction of this and the given value. */ NTriBool operator | (const NTriBool& other) const; /** * Returns the disjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown or true) is true, and (unknown or false) is unknown. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the value to combine with this value. * @return the disjunction of this and the given value. */ NTriBool operator | (bool other) const; /** * Returns the conjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown and true) is unknown, and (unknown and false) is false. * * @param other the value to combine with this value. * @return the conjunction of this and the given value. */ NTriBool operator & (const NTriBool& other) const; /** * Returns the conjunction of this and the given value. * Unknown is handled in the intuitive way. For instance, * (unknown and true) is unknown, and (unknown and false) is false. * * \ifacespython Not present, to avoid accidental integer * conversions in Python. * * @param other the value to combine with this value. * @return the conjunction of this and the given value. */ NTriBool operator & (bool other) const; /** * Returns the negation of this value. * The negation of unknown is unknown. * * @return the negation of this value. */ NTriBool operator ~ () const; private: /** * Creates a new three-way boolean corresponding to the given internal * code. * * @param newCode the internal code representing the new three-way * boolean. */ explicit NTriBool(Code newCode); /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool(int); /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ bool operator == (int) const; /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ bool operator != (int) const; /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool& operator = (int); /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool& operator |= (int); /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool& operator &= (int); /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool operator | (int) const; /** * A private routine that should never be used. * This routine is defined so that illegal integer operations * don't automatically convert to boolean operations. */ NTriBool operator & (int) const; }; /** * Writes the given three-way boolean to the given output stream. * The value will be written in the form * true, false or unknown. * * @param out the output stream to which to write. * @param set the three-way boolean to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NTriBool& set); /** * An exception thrown when an illegal integer conversion is attempted * with NTriBool. */ class REGINA_API NTriBool_Illegal_Integer_Conversion { }; /** * A set of booleans. Note that there are only four possible such sets. * NBoolSet objects are small enough to pass about by value instead of * by reference. */ class REGINA_API NBoolSet { private: unsigned char elements; /**< The first two bits of this character represent whether or not \c true or \c false belongs to this set. */ static const unsigned char eltTrue; /**< A character with only the \c true member bit set. */ static const unsigned char eltFalse; /**< A character with only the \c false member bit set. */ public: static const NBoolSet sNone; /**< The empty set. */ static const NBoolSet sTrue; /**< The set containing only \c true. */ static const NBoolSet sFalse; /**< The set containing only \c false. */ static const NBoolSet sBoth; /**< The set containing both \c true and \c false. */ public: /** * Creates a new empty set. */ NBoolSet(); /** * Creates a set containing a single member as given. * * @param member the single element to include in this set. */ NBoolSet(bool member); /** * Creates a set equal to the given set. * * @param cloneMe the set upon which we will base the new set. */ NBoolSet(const NBoolSet& cloneMe); /** * Creates a set specifying whether \c true and/or \c false * should be a member. * * @param insertTrue should the new set include the element * true? * @param insertFalse should the new set include the element * false? */ NBoolSet(bool insertTrue, bool insertFalse); /** * Determines if \c true is a member of this set. * * @return \c true if and only if \c true is a member of this * set. */ bool hasTrue() const; /** * Determines if \c false is a member of this set. * * @return \c true if and only if \c false is a member of this * set. */ bool hasFalse() const; /** * Determines if the given boolean is a member of this set. * * @param value the boolean to search for in this set. * @return \c true if and only if the given boolean is a member * of this set. */ bool contains(bool value) const; /** * Inserts \c true into this set if it is not already present. */ void insertTrue(); /** * Inserts \c false into this set if it is not already present. */ void insertFalse(); /** * Removes \c true from this set if it is present. */ void removeTrue(); /** * Removes \c false from this set if it is present. */ void removeFalse(); /** * Removes all elements from this set. */ void empty(); /** * Places both \c true and \c false into this set if they are * not already present. */ void fill(); /** * Determines if this set is equal to the given set. * * @param other the set to compare with this. * @return \c true if and only if this and the given set are * equal. */ bool operator == (const NBoolSet& other) const; /** * Determines if this set is not equal to the given set. * * @param other the set to compare with this. * @return \c true if and only if this and the given set are * not equal. */ bool operator != (const NBoolSet& other) const; /** * Determines if this set is a proper subset of the given set. * * @param other the set to compare with this. * @return \c true if and only if this is a proper subset of the * given set. */ bool operator < (const NBoolSet& other) const; /** * Determines if this set is a proper superset of the given set. * * @param other the set to compare with this. * @return \c true if and only if this is a proper superset of the * given set. */ bool operator > (const NBoolSet& other) const; /** * Determines if this set is a subset of (possibly equal to) * the given set. * * @param other the set to compare with this. * @return \c true if and only if this is a subset of the * given set. */ bool operator <= (const NBoolSet& other) const; /** * Determines if this set is a superset of (possibly equal to) * the given set. * * @param other the set to compare with this. * @return \c true if and only if this is a superset of the * given set. */ bool operator >= (const NBoolSet& other) const; /** * Sets this set to be identical to the given set. * * @param cloneMe the set whose value this set will take. * @return a reference to this set. */ NBoolSet& operator = (const NBoolSet& cloneMe); /** * Sets this set to the single member set containing the given * element. * * @param member the single element to include in this set. * @return a reference to this set. */ NBoolSet& operator = (bool member); /** * Sets this set to be the union of this and the given set. * The result is a set containing precisely the elements that * belong to either of the original sets. * Note that this set will be modified. * * @param other the set to union with this set. * @return a reference to this set. */ NBoolSet& operator |= (const NBoolSet& other); /** * Sets this set to be the intersection of this and the given set. * The result is a set containing precisely the elements that * belong to both original sets. * Note that this set will be modified. * * @param other the set to intersect with this set. * @return a reference to this set. */ NBoolSet& operator &= (const NBoolSet& other); /** * Sets this set to be the symmetric difference of this and the * given set. * The result is a set containing precisely the elements that * belong to one but not both of the original sets. * Note that this set will be modified. * * @param other the set whose symmetric difference with this set * is to be found. * @return a reference to this set. */ NBoolSet& operator ^= (const NBoolSet& other); /** * Returns the union of this set with the given set. * The result is a set containing precisely the elements that * belong to either of the original sets. * This set is not changed. * * @param other the set to union with this set. * @return the union of this and the given set. */ NBoolSet operator | (const NBoolSet& other) const; /** * Returns the intersection of this set with the given set. * The result is a set containing precisely the elements that * belong to both original sets. * This set is not changed. * * @param other the set to intersect with this set. * @return the intersection of this and the given set. */ NBoolSet operator & (const NBoolSet& other) const; /** * Returns the symmetric difference of this set and the given set. * The result is a set containing precisely the elements that * belong to one but not both of the original sets. * This set is not changed. * * @param other the set whose symmetric difference with this set * is to be found. * @return the symmetric difference of this and the given set. */ NBoolSet operator ^ (const NBoolSet& other) const; /** * Returns the complement of this set. * The result is a set containing precisely the elements that * this set does not contain. * This set is not changed. * * @return the complement of this set. */ NBoolSet operator ~ () const; /** * Returns the byte code representing this boolean set. * The byte code is sufficient to reconstruct the set * and is thus a useful means for passing boolean sets to and * from the engine. * * The lowest order bit of the byte code is 1 if and only if * \c true is in the set. The next lowest order bit is 1 if and * only if \c false is in the set. All other bits are 0. * Thus sets NBoolSet::sNone, NBoolSet::sTrue, NBoolSet::sFalse * and NBoolSet::sBoth have byte codes 0, 1, 2 and 3 respectively. * * @return the byte code representing this set. */ unsigned char getByteCode() const; /** * Sets this boolean set to that represented by the given byte * code. See getByteCode() for more information on byte codes. * * \pre \a code is 0, 1, 2 or 3. * * @param code the byte code that will determine the new value * of this set. */ void setByteCode(unsigned char code); /** * Creates a boolean set from the given byte code. * See getByteCode() for more information on byte codes. * * \pre \a code is 0, 1, 2 or 3. * * @param code the byte code from which the new set will be * created. */ static NBoolSet fromByteCode(unsigned char code); friend std::ostream& operator << (std::ostream& out, const NBoolSet& set); }; /** * Writes the given boolean set to the given output stream. * The set will be written in the form * { true, false }, { true }, * { false } or { }. * * @param out the output stream to which to write. * @param set the boolean set to write. * @return a reference to \a out. */ REGINA_API std::ostream& operator << (std::ostream& out, const NBoolSet& set); /*@}*/ // Inline functions for NTriBool inline NTriBool::NTriBool() : code(codeUnknown) { } inline NTriBool::NTriBool(const NTriBool& cloneMe) : code(cloneMe.code) { } inline NTriBool::NTriBool(bool value) : code(value ? codeTrue : codeFalse) { } inline bool NTriBool::isTrue() const { return (code == codeTrue); } inline bool NTriBool::isFalse() const { return (code == codeFalse); } inline bool NTriBool::isUnknown() const { return (code == codeUnknown); } inline bool NTriBool::isKnown() const { return (code != codeUnknown); } inline void NTriBool::setTrue() { code = codeTrue; } inline void NTriBool::setFalse() { code = codeFalse; } inline void NTriBool::setUnknown() { code = codeUnknown; } inline bool NTriBool::operator == (const NTriBool& other) const { return (code == other.code); } inline bool NTriBool::operator == (bool other) const { return (other && (code == codeTrue)) || ((! other) && (code == codeFalse)); } inline bool NTriBool::operator != (const NTriBool& other) const { return (code != other.code); } inline bool NTriBool::operator != (bool other) const { return (other && (code != codeTrue)) || ((! other) && (code != codeFalse)); } inline NTriBool& NTriBool::operator = (const NTriBool& other) { code = other.code; return *this; } inline NTriBool& NTriBool::operator = (bool other) { code = (other ? codeTrue : codeFalse); return *this; } inline NTriBool& NTriBool::operator |= (const NTriBool& other) { if (other.code == codeTrue) code = codeTrue; if (other.code == codeUnknown && code == codeFalse) code = codeUnknown; return *this; } inline NTriBool& NTriBool::operator |= (bool other) { if (other) code = codeTrue; return *this; } inline NTriBool& NTriBool::operator &= (const NTriBool& other) { if (other.code == codeFalse) code = codeFalse; if (other.code == codeUnknown && code == codeTrue) code = codeUnknown; return *this; } inline NTriBool& NTriBool::operator &= (bool other) { if (! other) code = codeFalse; return *this; } inline NTriBool NTriBool::operator | (const NTriBool& other) const { if (code == codeTrue || other.code == codeTrue) return True; if (code == codeFalse && other.code == codeFalse) return False; return Unknown; } inline NTriBool NTriBool::operator | (bool other) const { return (other ? True : *this); } inline NTriBool NTriBool::operator & (const NTriBool& other) const { if (code == codeTrue && other.code == codeTrue) return True; if (code == codeFalse || other.code == codeFalse) return False; return Unknown; } inline NTriBool NTriBool::operator & (bool other) const { return (other ? *this : False); } inline NTriBool NTriBool::operator ~ () const { switch (code) { case codeTrue : return False; case codeFalse : return True; default : return Unknown; } } inline NTriBool::NTriBool(Code newCode) : code((newCode == codeTrue || newCode == codeFalse) ? newCode : codeUnknown) { } inline NTriBool::NTriBool(int) { throw NTriBool_Illegal_Integer_Conversion(); } inline bool NTriBool::operator == (int) const { throw NTriBool_Illegal_Integer_Conversion(); } inline bool NTriBool::operator != (int) const { throw NTriBool_Illegal_Integer_Conversion(); } inline NTriBool& NTriBool::operator = (int) { throw NTriBool_Illegal_Integer_Conversion(); } inline NTriBool& NTriBool::operator |= (int) { throw NTriBool_Illegal_Integer_Conversion(); } inline NTriBool& NTriBool::operator &= (int) { throw NTriBool_Illegal_Integer_Conversion(); } inline NTriBool NTriBool::operator | (int) const { throw NTriBool_Illegal_Integer_Conversion(); } inline NTriBool NTriBool::operator & (int) const { throw NTriBool_Illegal_Integer_Conversion(); } // Inline functions for NBoolSet inline NBoolSet::NBoolSet() : elements(0) { } inline NBoolSet::NBoolSet(bool member) : elements(member ? eltTrue : eltFalse) { } inline NBoolSet::NBoolSet(const NBoolSet& cloneMe) : elements(cloneMe.elements) { } inline NBoolSet::NBoolSet(bool insertTrue, bool insertFalse) : elements(0) { if (insertTrue) elements = static_cast(elements | eltTrue); if (insertFalse) elements = static_cast(elements | eltFalse); } inline bool NBoolSet::hasTrue() const { return (elements & eltTrue); } inline bool NBoolSet::hasFalse() const { return (elements & eltFalse); } inline bool NBoolSet::contains(bool value) const { return (elements & (value ? eltTrue : eltFalse)); } inline void NBoolSet::insertTrue() { elements = static_cast(elements | eltTrue); } inline void NBoolSet::insertFalse() { elements = static_cast(elements | eltFalse); } inline void NBoolSet::removeTrue() { elements = static_cast(elements & eltFalse); } inline void NBoolSet::removeFalse() { elements = static_cast(elements & eltTrue); } inline void NBoolSet::empty() { elements = 0; } inline void NBoolSet::fill() { elements = static_cast(eltTrue | eltFalse); } inline bool NBoolSet::operator == (const NBoolSet& other) const { return (elements == other.elements); } inline bool NBoolSet::operator != (const NBoolSet& other) const { return (elements != other.elements); } inline bool NBoolSet::operator < (const NBoolSet& other) const { return ((elements & other.elements) == elements) && (elements != other.elements); } inline bool NBoolSet::operator > (const NBoolSet& other) const { return ((elements & other.elements) == other.elements) && (elements != other.elements); } inline bool NBoolSet::operator <= (const NBoolSet& other) const { return ((elements & other.elements) == elements); } inline bool NBoolSet::operator >= (const NBoolSet& other) const { return ((elements & other.elements) == other.elements); } inline NBoolSet& NBoolSet::operator = (const NBoolSet& cloneMe) { elements = cloneMe.elements; return *this; } inline NBoolSet& NBoolSet::operator = (bool member) { elements = (member ? eltTrue : eltFalse); return *this; } inline NBoolSet& NBoolSet::operator |= (const NBoolSet& other) { elements = static_cast(elements | other.elements); return *this; } inline NBoolSet& NBoolSet::operator &= (const NBoolSet& other) { elements = static_cast(elements & other.elements); return *this; } inline NBoolSet& NBoolSet::operator ^= (const NBoolSet& other) { elements = static_cast(elements ^ other.elements); return *this; } inline NBoolSet NBoolSet::operator | (const NBoolSet& other) const { NBoolSet ans; ans.elements = static_cast(elements | other.elements); return ans; } inline NBoolSet NBoolSet::operator & (const NBoolSet& other) const { NBoolSet ans; ans.elements = static_cast(elements & other.elements); return ans; } inline NBoolSet NBoolSet::operator ^ (const NBoolSet& other) const { NBoolSet ans; ans.elements = static_cast(elements ^ other.elements); return ans; } inline NBoolSet NBoolSet::operator ~ () const { return NBoolSet(! hasTrue(), ! hasFalse()); } inline unsigned char NBoolSet::getByteCode() const { return elements; } inline void NBoolSet::setByteCode(unsigned char code) { elements = code; } inline NBoolSet NBoolSet::fromByteCode(unsigned char code) { return NBoolSet(code & eltTrue, code & eltFalse); } } // namespace regina #endif regina-4.95/engine/utilities/nlistoncall.h000644 000765 000024 00000015745 12234011536 020573 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/nlistoncall.h * \brief Provides lists of expensive objects that are only created * when required. */ #ifndef __NLISTONCALL_H #ifndef __DOXYGEN #define __NLISTONCALL_H #endif #include "regina-core.h" #include "utilities/boostutils.h" #include namespace regina { /** * \weakgroup utilities * @{ */ /** * Offers a hard-coded list of expensive objects that should only be * created if they are required. An example might include a large * hard-coded list of triangulations (which are expensive to construct) * that will only be required in special scenarios, and not in everyday * use of the software. * * A particular hard-coded list should be defined by a subclass of * NListOnCall. The list should be filled within the pure virtual * routine initialise(), which must be overridden. * * A static list of this type is relatively cheap to create. The list * will not actually be filled (and the expensive objects will not be * created) until the first time the list is traversed. Specifically, * the list will be filled on the first call to begin(). * * Only an extremely simple form of list traversal is offered: Routines * begin() and end() are defined, which return forward iterators that * can be used to run through the list contents. * * Lists of this type are designed to be constant. Aside from the initial * list population in the initialise() routine and the final list destruction * (in which all of the stored objects will also be destroyed), the list and * its objects should never be changed. Because of this, the iterator type * returns only constant pointers to list objects. * * Note that \a T is the expensive object type, not a pointer type to such * an object. * * \ifacespython Not present. */ template class NListOnCall : public regina::boost::noncopyable { public: /** * An iterator over this list. This operates as a forward * iterator in a manner consistent with the standard C++ library. * It does not allow either the list or its individual objects * to be changed. */ typedef typename std::list::const_iterator iterator; private: std::list items; /**< The internal list of items. */ bool initialised; /**< Has the list been filled yet? */ public: /** * Creates a new list structure. The list will not be filled * with items; this does not happen until the first time that * begin() is called. */ NListOnCall() : initialised(false) { } /** * Destroys this list and all of the items it contains. */ virtual ~NListOnCall() { for (iterator it = items.begin(); it != items.end(); it++) delete const_cast(*it); } /** * Returns an iterator pointing to the first item in this list. * * If the list has not yet been filled with items, this will * take place now. Thus the first call to begin() will be * expensive, but subsequent calls will be extremely cheap. * * @return an iterator pointing to the first item. */ iterator begin() const { if (! initialised) { const_cast*>(this)->initialise(); const_cast*>(this)->initialised = true; } return items.begin(); } /** * Returns an iterator pointing past the end of this list (i.e., * just after the last item). * * \pre The begin() routine has been called at least once * (otherwise the list will not yet have been filled). * * @return a past-the-end iterator. */ iterator end() const { return items.end(); } protected: /** * Adds the given item to the end of this list. This routine should * only ever be called from within a subclass implementation of * initialise(). * * The given item will be owned by this list, and will be * destroyed when this list is destroyed. * * @param item the new item to insert. */ void insert(T* item) { items.push_back(item); } /** * Fills this list with items. The particular set of items to * use will typically depend on the particular subclass of * NListOnCall that is being defined. * * This routine will be run the first time that begin() is * called on each list, and will not be run again. */ virtual void initialise() = 0; }; /*@}*/ } // namespace regina #endif regina-4.95/engine/utilities/nmarkedvector.h000644 000765 000024 00000024045 12234011536 021106 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/nmarkedvector.h * \brief Provides space-efficient arrays with fast object-to-index lookup. */ #ifndef __NMARKEDVECTOR_H #ifndef __DOXYGEN #define __NMARKEDVECTOR_H #endif #include #include "regina-core.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * A base class for elements of NMarkedVector. * * NMarkedVector is a vector class that provides fast, space-efficient * reverse lookup of array indices (i.e., given an object, find the * index at which it is stored). The way it does this is to store the * array indices in the object themselves. * * As a result, any type to be stored in an NMarkedVector must be derived * from this class. This class provides a space to store the * corresponding array index, and looks after access control so that * only NMarkedVector can edit this index. * * Since indices are stored with the objects, it is possible to perform * this reverse lookup by querying the object alone, without any * reference to the actual vector. This can be done through the public * routine markedIndex(). * * The trade-off of course is that any object may not belong to more * than one NMarkedVector at a time. Any attempt to do this will result * in undefined behaviour. * * See NMarkedVector for further information. * * \ifacespython Not present. */ class REGINA_API NMarkedElement { private: long marking; /**< The index in the NMarkedVector at which this object is stored. If the object does not belong to an NMarkedVector, the value of this field is undefined. */ public: /** * Returns the index at which this object is stored in an * NMarkedVector. If this object does not belong to an * NMarkedVector, the return value is undefined. * * @return the index at which this object is stored. */ inline long markedIndex() const; template friend class NMarkedVector; /**< Allow only NMarkedVector to edit the array index. */ }; /** * A vector of objects with fast, space-efficient reverse lookup of * array indices. * * This class derives from std::vector, and so provides fast forward * lookups from array indices to objects. What NMarkedVector provides * in addition to this is fast reverse lookups from objects back to * array indices. * * The way this class is able to provide fast constant-time reverse * lookups without consuming a great deal of space is by storing array indices * inside the objects themselves. As a result, there are two * significant constraints: * * - This class can only store objects derived from NMarkedElement * (which provides space for storing the array indices and handles * their access control). In particular, it cannot store native types * such as \c int or predefined types such as \c std::string. * * - An object can only belong to one NMarkedVector at a time. Any * attempt to insert an object into more than one NMarkedVector at the * same time results in undefined behaviour. * * Using this class is fairly simple. The class provides a restricted * subset of the std::vector functionality, including \a iterator, * \a const_iterator, \a begin, \a end, \a size, \a empty, \a front, * \a back, operator [], and \a clear (this subset may grow over * time if required). In addition, any const method of std::vector can * be accessed through an explicit cast to const std::vector&. To * perform a reverse lookup (find the index at which an array is stored), * simply call the object's inherited method NMarkedElement::markedIndex(). * * Note that, like its parent std::vector, this class performs no memory * management. In particular, elements (which are pointers to real objects) * are not destroyed when they are removed from a vector or when the vector * is eventually destroyed. * * \pre The type \a T is a class derived from NMarkedElement. * * \ifacespython Not present. */ template class NMarkedVector : private std::vector { public: using typename std::vector::iterator; using typename std::vector::const_iterator; using typename std::vector::size_type; using std::vector::begin; using std::vector::end; using std::vector::size; using std::vector::empty; using std::vector::operator []; using std::vector::front; using std::vector::back; using std::vector::clear; /** * Constructs a new empty vector. */ inline NMarkedVector() {} /** * Casts this vector to a const std::vector, thus providing * access to the entire const functionality of std::vector. * * @return a reference to this vector, cast as a const std::vector. */ inline const std::vector& operator ()() const { return *this; } /** * Pushes the given item onto the end of this vector. The array * index stored inside \a item will be modified accordingly. * * The caller retains reponsibility for the ownership of \a item. * This class will make no attempt to deallocate \a item when it * is removed or when this vector is eventually destroyed. * * \pre The given item does not already belong to some other * NMarkedVector. * * @param item the item to add to this vector. */ inline void push_back(T* item) { item->marking = size(); std::vector::push_back(item); } /** * Erases the item at the given position in this vector. * * The item will not be destroyed, and the (now irrelevant) * index stored inside it will not be modified. * * \pre The given iterator points to an element of this vector. * * @param pos an iterator pointing to the element to erase. * @return an iterator pointing to the element immediately * after the element that was erased. */ inline typename std::vector::iterator erase( typename std::vector::iterator pos) { typename std::vector::iterator it = pos; for (++it; it != end(); ++it) --((*it)->marking); return std::vector::erase(pos); } /** * Erases all items in the given range in this vector. * * The items will not be destroyed, and the (now irrelevant) * indices stored inside them will not be modified. * * \pre The given iterators describe a valid range in this vector. * * @param first an iterator pointing to the first element to erase. * @param last an iterator pointing just beyond the last element * to erase. * @return an iterator pointing to the element immediately * after the elements that were erased. */ inline typename std::vector::iterator erase( typename std::vector::iterator first, typename std::vector::iterator last) { for (typename std::vector::iterator it = last; it != end(); ++it) (*it)->marking -= (first - last); return std::vector::erase(first, last); } /** * Swaps the contents of this and the given vector. * * @param other the vector whose contents are to be swapped with this. */ inline void swap(NMarkedVector& other) { std::vector::swap(other); } }; /*@}*/ // Inline functions for NMarkedElement inline long NMarkedElement::markedIndex() const { return marking; } } // namespace regina #endif regina-4.95/engine/utilities/nmatrix2.h000644 000765 000024 00000004673 12234011536 020013 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __LEGACY_NMATRIX2_H #ifndef __DOXYGEN #define __LEGACY_NMATRIX2_H #endif #warning This header is deprecated; please use maths/nmatrix2.h instead. #include "maths/nmatrix2.h" #endif regina-4.95/engine/utilities/nmpi.h000644 000765 000024 00000004663 12234011536 017211 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ #ifndef __LEGACY_NMPI_H #ifndef __DOXYGEN #define __LEGACY_NMPI_H #endif #warning This header is deprecated; please use maths/ninteger.h instead. #include "maths/ninteger.h" #endif regina-4.95/engine/utilities/nproperty.h000644 000765 000024 00000025375 12234011536 020313 0ustar00babstaff000000 000000 /************************************************************************** * * * Regina - A Normal Surface Theory Calculator * * Computational Engine * * * * Copyright (c) 1999-2013, Ben Burton * * For further details contact Ben Burton (bab@debian.org). * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * As an exception, when this program is distributed through (i) the * * App Store by Apple Inc.; (ii) the Mac App Store by Apple Inc.; or * * (iii) Google Play by Google Inc., then that store may impose any * * digital rights management, device limits and/or redistribution * * restrictions that are required by its terms of service. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; if not, write to the Free * * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * * MA 02110-1301, USA. * * * **************************************************************************/ /* end stub */ /*! \file utilities/nproperty.h * \brief Deals with calculable properties of individual objects. */ #ifndef __NPROPERTY_H #ifndef __DOXYGEN #define __NPROPERTY_H #endif #include "regina-core.h" #include "utilities/boostutils.h" namespace regina { /** * \weakgroup utilities * @{ */ /** * An NProperty storage policy indicating that the property should be * held by value. That is, upon assignment or initialisation the * underlying value will be copied into the NProperty wrapper. * * The property type T must have a copy assignment * operator, and it must also have either a copy constructor and/or a * default constructor according to which NProperty constructors are used. * * See the NProperty class notes for details. * * \ifacespython Not present. */ template class StoreValue { public: typedef const T& InitType; /**< The type by which new values for the underlying property are passed. */ typedef const T& QueryType; /**< The type by which the property value is returned to the user. */ protected: T value_; /**< The held property value. */ public: /** * Cleans up any currently held value before the property value is * changed or cleared. * * This implementation does nothing. */ void clear() { } }; /** * An NProperty storage policy indicating that the property should be * held by constant pointer. The property assignment and query routines * will also use constant pointers, and the NProperty wrapper takes no * responsibility for memory management of the held value. * * See the NProperty class notes for details. * * \ifacespython Not present. */ template class StoreConstPtr { public: typedef const T* InitType; /**< The type by which new values for the underlying property are passed. */ typedef const T* QueryType; /**< The type by which the property value is returned to the user. */ protected: const T* value_; /**< The held property value. */ public: /** * Constructor that sets the held pointer to 0. */ StoreConstPtr() : value_(0) { } /** * Cleans up any currently held value before the property value is * changed or cleared. * * This implementation resets the held pointer to 0. */ void clear() { value_ = 0; } }; /** * An NProperty storage policy indicating that the property should be * held by pointer and that the property wrapper will also take * responsibility for memory management. The property assignment and * query routines will also use pointers; in particular the query * routines will return a constant pointer. When the held value is * changed or the NProperty wrapper is destroyed, any currently held * value will be destroyed automatically. * * See the NProperty class notes for details. * * \ifacespython Not present. */ template class StoreManagedPtr { public: typedef T* InitType; /**< The type by which new values for the underlying property are passed. */ typedef const T* QueryType; /**< The type by which the property value is returned to the user. */ protected: T* value_; /**< The held property value. */ public: /** * Constructor that sets the held pointer to 0. */ StoreManagedPtr() : value_(0) { } /** * Cleans up any currently held value before the property value is * changed or cleared. * * This implementation resets the held pointer to 0 and * destroys the previously held value if any exists. */ void clear() { if (value_) { delete value_; value_ = 0; } } protected: /** * Destroys the currently held value if one exists. */ ~StoreManagedPtr() { if (value_) delete value_; } }; /** * A base class that provides routines shared by all properties, * regardless of their individual NProperty template parameters. * * \ifacespython Not present. */ class REGINA_API NPropertyBase { public: /** * Virtual destructor. */ virtual ~NPropertyBase() { } /** * Returns whether or not this property is currently marked as * known. * * @return whether this property is marked as known. */ virtual bool known() const = 0; /** * Marks this property as unknown. */ virtual void clear() = 0; }; /** * Stores a calculable property of an object. The property may be * marked as known or unknown, and its value may be set or retrieved. * * The template parameter \a Storage specifies how the property will be * internally stored. Storage options range from simple storage by value * (see class StoreValue) to more intelligent storage options that include * memory management of pointers (see class StoreManagedPtr). * * \see StoreValue * \see StoreConstPtr * \see StoreManagedPtr * * \ifacespython Not present. */ template class Storage = StoreValue> class NProperty : public NPropertyBase, protected Storage, public regina::boost::noncopyable { public: typedef typename Storage::InitType InitType; /**< The type by which new values for the underlying property are passed. */ typedef typename Storage::QueryType QueryType; /**< The type by which the property value is returned to the user. */ private: bool known_; /**< Whether or not the value of property is currently known. */ public: /** * Constructor. This property is initially marked as unknown. */ NProperty() : Storage(), known_(false) { } /** * Returns the current value of this property. If this property * is marked as unknown then the results are undefined. * * \pre This property is currently marked as known. * * @return the current value of this property. */ QueryType value() const { return Storage::value_; } /** * Assigns a new value to this property. The property will be * marked as known. * * @param newValue the new value to assign to this property. * @return the new value of this property. */ QueryType operator = (InitType newValue) { Storage::clear(); Storage::value_ = newValue; known_ = true; return Storage::value_; } /** * Copies the given property into this property. If the given * property is marked as known, its value will be copied and * this property will also be marked as known. Otherwise this * property will be marked as unknown. * * @param newValue the property to copy into this property. * @return a reference to this property. */ const NProperty& operator = ( const NProperty& newValue) { Storage::clear(); // Use the value() query so that we initialise from // QueryType, not from the held type. This means that if // the value shouldn't be copied directly (e.g., with // StoreManagedPtr) then we'll get a compile error. if (newValue.known_) Storage::value_ = newValue.value(); known_ = newValue.known_; return *this; } // NPropertyBase overrides: bool known() const { return known_; } void clear() { Storage::clear(); known_ = false; } }; /* template