diff --git a/README.md b/README.md
index af91374fe19..eb35f5a8186 100644
--- a/README.md
+++ b/README.md
@@ -14,408 +14,52 @@
Sage is open source mathematical software released under the GNU General Public
Licence GPLv2+, and includes packages that have [compatible software licenses](./COPYING.txt).
[People all around the globe](https://www.sagemath.org/development-map.html) have contributed to the
-development of Sage. [Full documentation](https://doc.sagemath.org/html/en/index.html) is available online.
+development of Sage.
-Table of Contents
+Flag algebras
-----------------
-* [Getting Started](#getting-started)
-* [Supported Platforms](#supported-platforms)
-* [\[Windows\] Preparing the Platform](#windows-preparing-the-platform)
-* [\[macOS\] Preparing the Platform](#macos-preparing-the-platform)
-* [Instructions to Build from Source](#instructions-to-build-from-source)
-* [SageMath Docker Images](#sagemath-docker-images)
-* [Troubleshooting](#troubleshooting)
-* [Contributing to Sage](#contributing-to-sage)
-* [Directory Layout](#directory-layout)
-* [Build System](#build-system)
-* [Relocation](#relocation)
-* [Redistribution](#redistribution)
-* [Build System](#build-system)
-* [Changes to Included Software](#changes-to-included-software)
-
-Getting Started
----------------
-
-Those who are impatient may use prebuilt Sage available online from any of
-
-[](https://mybinder.org/v2/gh/sagemath/sage-binder-env/master
-) [](https://gitpod.io/#https://github.com/sagemath/sage/tree/master
-) [](https://codespaces.new/sagemath/sage/tree/master)
-
-without local installation. Otherwise read on.
-
-The [Sage Installation Guide](https://doc.sagemath.org/html/en/installation/index.html)
-provides a decision tree that guides you to the type of installation
-that will work best for you. This includes building from source,
-obtaining Sage from a package manager, using a container image, or using
-Sage in the cloud.
-
-**This README contains self-contained instructions for building Sage from source.**
-This requires you to clone the git repository (as described in this README) or download the
-[sources](https://www.sagemath.org/download-source.html) in the form
-of a tarball.
-
-If you have questions or encounter problems, please do not hesitate
-to email the [sage-support mailing list](https://groups.google.com/group/sage-support)
-or ask on the [Ask Sage questions and answers site](https://ask.sagemath.org).
-
-Supported Platforms
--------------------
-
-Sage attempts to support all major Linux distributions, recent versions of
-macOS, and Windows (using Windows Subsystem for Linux or
-virtualization).
-
-Detailed information on supported platforms for a specific version of Sage
-can be found in the section _Availability and installation help_ of the
-[release tour for this version](https://github.com/sagemath/sage/releases).
-
-We highly appreciate contributions to Sage that fix portability bugs
-and help port Sage to new platforms; let us know at the [sage-devel
-mailing list](https://groups.google.com/group/sage-devel).
-
-[Windows] Preparing the Platform
---------------------------------
-
-The preferred way to run Sage on Windows is using Windows Subsystem for
-Linux (WSL). Follow the
-[official WSL setup guide](https://docs.microsoft.com/en-us/windows/wsl/faq)
-to install Ubuntu (or another Linux distribution).
-Make sure you allocate WSL sufficient RAM; 5GB is known to work, while
-2GB might be not enough for building Sage from source.
-Then all instructions for installation in Linux apply.
-
-As an alternative, you can also run Linux on Windows using Docker ([see
-below](#sagemath-docker-images)) or other virtualization solutions.
-
-[macOS] Preparing the Platform
-------------------------------
-
-- If your Mac uses the Apple Silicon (M1, M2, M3, M4; arm64) architecture and
- you set up your Mac by transferring files from an older Mac, make sure
- that the directory ``/usr/local`` does not contain an old copy of Homebrew
- (or other software) for the x86_64 architecture that you may have copied
- over. Note that Homebrew for the M1 is installed in ``/opt/homebrew``, not
- ``/usr/local``.
-
-- If you wish to use conda, please see the [section on
- conda](https://doc.sagemath.org/html/en/installation/conda.html) in the Sage
- Installation Manual for guidance.
-
-- Otherwise, we strongly recommend to use Homebrew ("the missing package
- manager for macOS") from https://brew.sh/, which provides the ``gfortran``
- compiler and many libraries.
-
-- Otherwise, if you do not wish to install Homebrew, you will need to install
- the latest version of Xcode Command Line Tools. Open a terminal window and
- run `xcode-select --install`; then click "Install" in the pop-up window. If
- the Xcode Command Line Tools are already installed, you may want to check if
- they need to be updated by typing `softwareupdate -l`.
+This repository is a copy of the official SageMath project with additional functionality to handle
+flag algebraic calculations. Explore the capabilities and functionalities of the package related to flag algebras by visiting the [tutorial notebook](https://github.com/bodnalev/sage/blob/flag-algebras/flag_tutorial.ipynb).
Instructions to Build from Source
---------------------------------
-Like many other software packages, Sage is built from source using
-`./configure`, followed by `make`. However, we strongly recommend to
-read the following step-by-step instructions for building Sage.
-
-The instructions cover all of Linux, macOS, and WSL.
-
-More details, providing a background for these instructions, can be found
-in the section [Install from Source Code](https://doc.sagemath.org/html/en/installation/source.html)
-in the Installation Guide.
-
-
-1. Decide on the source/build directory (`SAGE_ROOT`):
-
- - On personal computers, any subdirectory of your :envvar:`HOME`
- directory should do.
-
- - For example, you could use `SAGE_ROOT=~/sage/sage`, which we
- will use as the running example below.
-
- - You need at least 10 GB of free disk space.
-
- - The full path to the source directory must contain **no spaces**.
+Sage attempts to support all major Linux distributions, recent versions of
+macOS, and Windows (using Windows Subsystem for Linux or
+virtualization). The additional software and packages needed for flag algebraic calculations
+are only tested on a few Debian based distributions and on Mac. This guide is only guaranteed to work on Debian based distributions.
+1. Prepare the environment:
+ - At least 10 GB of free space is required.
+ - Pick a path for the folder that will contain the source files. Note it **can not contain spaces**.
- After starting the build, you cannot move the source/build
directory without breaking things.
-
- - You may want to avoid slow filesystems such as
- [network file systems (NFS)](https://en.wikipedia.org/wiki/Network_File_System)
- and the like.
-
- - [macOS] macOS allows changing directories without using exact capitalization.
- Beware of this convenience when compiling for macOS. Ignoring exact
- capitalization when changing into :envvar:`SAGE_ROOT` can lead to build
- errors for dependencies requiring exact capitalization in path names.
-
-2. Clone the sources with `git`:
-
- - To check that `git` is available, open a terminal and enter
- the following command at the shell prompt (`$`):
-
- $ git --version
- git version 2.42.0
-
- The exact version does not matter, but if this command gives an error,
- install `git` using your package manager, using one of these commands:
-
- $ sudo pacman -S git # on Arch Linux
- $ sudo apt-get update && apt-get install git # on Debian/Ubuntu
- $ sudo yum install git # on Fedora/Redhat/CentOS
- $ sudo zypper install git # on openSUSE
- $ sudo xbps-install git # on Void Linux
-
- - Create the directory where `SAGE_ROOT` should be established:
-
- $ mkdir -p ~/sage
- $ cd ~/sage
-
- - Clone the Sage git repository:
-
- $ git clone -c core.symlinks=true --filter blob:none \
- --origin upstream --branch develop --tags \
- https://github.com/sagemath/sage.git
-
- This command obtains the most recent development release.
- Replace `--branch develop` by `--branch master` to select
- the most recent stable release instead.
-
- This will create the subdirectory `~/sage/sage`. (See the section
- [Setting up git](https://doc.sagemath.org/html/en/developer/git_setup.html)
- and the following sections in the Sage Developer's Guide
- for more information.)
-
- - Change into the created subdirectory:
-
- $ cd sage
-
- - [Windows] The Sage source tree contains symbolic links, and the
- build will not work if Windows line endings rather than UNIX
- line endings are used.
-
- Therefore it is recommended (but not necessary) to use the
- WSL version of `git`.
-
-3. Install system packages.
-
- Either refer for this to the [section on installation from
- source](https://doc.sagemath.org/html/en/installation/source.html) in the
- Sage Installation Manual for compilations of system packages
- that you can install. When done, skip to step 7 (bootstrapping).
-
- Alternatively, follow the more fine-grained approach below.
-
-4. [Linux, WSL] Install the required minimal build prerequisites:
-
- - Compilers: `gcc`, `gfortran`, `g++` (GCC versions from 8.4.0 to 13.x
- and recent versions of Clang (LLVM) are supported).
- See [build/pkgs/gcc/SPKG.rst](build/pkgs/gcc/SPKG.rst) and
- [build/pkgs/gfortran/SPKG.rst](build/pkgs/gfortran/SPKG.rst)
- for a discussion of suitable compilers.
-
- - Build tools: GNU `make`, GNU `m4`, `perl` (including
- `ExtUtils::MakeMaker`), `ranlib`, `git`, `tar`, `bc`.
- See [build/pkgs/_prereq/SPKG.rst](build/pkgs/_prereq/SPKG.rst) for
- more details.
-
- - Python 3.4 or later, or Python 2.7, a full installation including
- `urllib`; but ideally version 3.9.x, 3.10.x, 3.11.x, 3.12.x, which
- will avoid having to build Sage's own copy of Python 3.
- See [build/pkgs/python3/SPKG.rst](build/pkgs/python3/SPKG.rst)
- for more details.
-
- We have collected lists of system packages that provide these build
- prerequisites. See, in the folder
- [build/pkgs/_prereq/distros](build/pkgs/_prereq/distros),
- the files
- [arch.txt](build/pkgs/_prereq/distros/arch.txt),
- [debian.txt](build/pkgs/_prereq/distros/debian.txt)
- (also for Ubuntu, Linux Mint, etc.),
- [fedora.txt](build/pkgs/_prereq/distros/fedora.txt)
- (also for Red Hat, CentOS),
- [opensuse.txt](build/pkgs/_prereq/distros/opensuse.txt),
- [slackware.txt](build/pkgs/_prereq/distros/slackware.txt), and
- [void.txt](build/pkgs/_prereq/distros/void.txt), or visit
- https://doc.sagemath.org/html/en/reference/spkg/_prereq.html#spkg-prereq
-
-5. Optional: It is recommended that you have both LaTeX and
- the ImageMagick tools (e.g. the "convert" command) installed
- since some plotting functionality benefits from them.
-
-6. [Development] If you plan to do Sage development or otherwise work with
- ticket branches and not only releases, install the bootstrapping
- prerequisites. See the files in the folder
- [build/pkgs/_bootstrap/distros](build/pkgs/_bootstrap/distros), or
- visit
- https://doc.sagemath.org/html/en/reference/spkg/_bootstrap.html#spkg-bootstrap
-
-7. Bootstrap the source tree using the following command:
-
- $ make configure
-
- (If the bootstrapping prerequisites are not installed, this command
- will download a package providing pre-built bootstrap output instead.)
-
-8. Sanitize the build environment. Use the command
-
- $ env
-
- to inspect the current environment variables, in particular `PATH`,
- `PKG_CONFIG_PATH`, `LD_LIBRARY_PATH`, `CFLAGS`, `CPPFLAGS`, `CXXFLAGS`,
- and `LDFLAGS` (if set).
-
- Remove items from these (colon-separated) environment variables
- that Sage should not use for its own build. In particular, remove
- items if they refer to a previous Sage installation.
-
- - [WSL] In particular, WSL imports many items from the Windows
- `PATH` variable into the Linux environment, which can lead to
- confusing build errors. These items typically start with `/mnt/c`.
- It is best to remove all of them from the environment variables.
- For example, you can set `PATH` using the command:
-
- $ export PATH=/usr/sbin/:/sbin/:/bin/:/usr/lib/wsl/lib/
-
- - [macOS with homebrew] Set required environment variables for the build:
-
- $ source ./.homebrew-build-env
-
- This is to make some of Homebrew's packages (so-called keg-only
- packages) available for the build. Run it once to apply the
- suggestions for the current terminal session. You may need to
- repeat this command before you rebuild Sage from a new terminal
- session, or after installing additional homebrew packages. (You
- can also add it to your shell profile so that it gets run
- automatically in all future sessions.)
-
-9. Optionally, decide on the installation prefix (`SAGE_LOCAL`):
-
- - Traditionally, and by default, Sage is installed into the
- subdirectory hierarchy rooted at `SAGE_ROOT/local/`.
-
- - This can be changed using `./configure --prefix=SAGE_LOCAL`,
- where `SAGE_LOCAL` is the desired installation prefix, which
- must be writable by the user.
-
- If you use this option in combination with `--disable-editable`,
- you can delete the entire Sage source tree after completing
- the build process. What is installed in `SAGE_LOCAL` will be
- a self-contained installation of Sage.
-
- - Note that in Sage's build process, `make` builds **and**
- installs (`make install` is a no-op). Therefore the
- installation hierarchy must be writable by the user.
-
- - See the Sage Installation Manual for options if you want to
- install into shared locations such as `/usr/local/`.
- Do not attempt to build Sage as `root`.
-
-10. Optionally, review the configuration options, which includes
- many optional packages:
-
- $ ./configure --help
-
- Notable options for Sage developers are the following:
-
- - Use the option `--config-cache` to have `configure`
- keep a disk cache of configuration values. This gives a nice speedup
- when trying out ticket branches that make package upgrades, which
- involves automatic re-runs of the configuration step.
-
- - Use the option `--enable-ccache` to have Sage install and use the
- optional package `ccache`, which is preconfigured to keep a
- disk cache of object files created from source files. This can give
- a great speedup when switching between different branches, at the
- expense of disk space use.
-
-11. Optional, but highly recommended: Set some environment variables to
+ - For a minimal installation, it is enough to have the following:
+ - Compilers: `gcc`, `gfortran`, `g++`
+ - Build tools: GNU `make`, GNU `m4`, `perl` (including `ExtUtils::MakeMaker`), `ranlib`, `git`, `tar`, `bc`.
+ - For a complete installation (recommended), see the linux system packages [in this guide](https://doc.sagemath.org/html/en/installation/source.html)
+2. Download Sage:
+ - Open a terminal at the target folder and clone the Sage files there: `git clone https://github.com/bodnalev/sage.git`
+ - Move inside the sage source files with the command `cd sage`
+3. Optional, but highly recommended: Set some environment variables to
customize the build.
-
+
For example, the `MAKE` environment variable controls whether to
run several jobs in parallel. On a machine with 4 processors, say,
typing `export MAKE="make -j4"` will configure the build script to
perform a parallel compilation of Sage using 4 jobs. On some
powerful machines, you might even consider `-j16`, as building with
more jobs than CPU cores can speed things up further.
-
+
To reduce the terminal output during the build, type `export V=0`.
(`V` stands for "verbosity".)
-
- Some environment variables deserve a special mention: `CC`,
- `CXX` and `FC`. These variables defining your compilers
- can be set at configuration time and their values will be recorded for
- further use at build time and runtime.
-
- For an in-depth discussion of more environment variables for
- building Sage, see [the installation
- guide](https://doc.sagemath.org/html/en/installation/source.html#environment-variables).
-
-12. Type `./configure`, followed by any options that you wish to use.
- For example, to build Sage with `gf2x` package supplied by Sage,
- use `./configure --with-system-gf2x=no`.
-
- At the end of a successful `./configure` run, you may see messages
- recommending to install extra system packages using your package
- manager.
-
- For a large [list of Sage
- packages](https://github.com/sagemath/sage/issues/27330), Sage is able to
- detect whether an installed system package is suitable for use with
- Sage; in that case, Sage will not build another copy from source.
-
- Sometimes, the messages will recommend to install packages that are
- already installed on your system. See the earlier configure
- messages or the file `config.log` for explanation. Also, the
- messages may recommend to install packages that are actually not
- available; only the most recent releases of your distribution will
- have all of these recommended packages.
-
-13. Optional: If you choose to install the additional system packages,
- a re-run of `./configure` will test whether the versions installed
- are usable for Sage; if they are, this will reduce the compilation
- time and disk space needed by Sage. The usage of packages may be
- adjusted by `./configure` parameters (check again the output of
- `./configure --help`).
-
-14. Type `make`. That's it! Everything is automatic and
- non-interactive.
-
- If you followed the above instructions, in particular regarding the
- installation of system packages recommended by the output of
- `./configure` (step 11), and regarding the parallel build (step 10),
- building Sage takes less than one hour on a modern computer.
- (Otherwise, it can take much longer.)
-
- The build should work fine on all fully supported platforms. If it
- does not, we want to know!
-
-15. Type `./sage` to try it out. In Sage, try for example `2 + 2`,
+4. Configure the build process with the command `make configure`. Then type `make build`.
+5. Type `./sage` to try it out. In Sage, try for example `2 + 2`,
`plot(x^2)`, `plot3d(lambda x, y: x*y, (-1, 1), (-1, 1))`
to test a simple computation and plotting in 2D and 3D.
Type Ctrl+D or `quit` to quit Sage.
-
-16. Optional: Type `make ptestlong` to test all examples in the documentation
- (over 200,000 lines of input!) -- this takes from 10 minutes to
- several hours. Don't get too disturbed if there are 2 to 3 failures,
- but always feel free to email the section of `logs/ptestlong.log` that
- contains errors to the [sage-support mailing list](https://groups.google.com/group/sage-support).
- If there are numerous failures, there was a serious problem with your build.
-
-17. The HTML version of the [documentation](https://doc.sagemath.org/html/en/index.html)
- is built during the compilation process of Sage and resides in the directory
- `local/share/doc/sage/html/`. You may want to bookmark it in your browser.
-
-18. Optional: If you want to build the PDF version of the documentation,
- run `make doc-pdf` (this requires LaTeX to be installed).
-
-19. Optional: Install optional packages of interest to you:
- get a list by typing `./sage --optional` or by visiting the
- [packages documentation page](https://doc.sagemath.org/html/en/reference/spkg/).
-
-20. Optional: Create a symlink to the installed `sage` script in a
+6. Optional: Create a symlink to the installed `sage` script in a
directory in your `PATH`, for example `/usr/local`. This will
allow you to start Sage by typing `sage` from anywhere rather than
having to either type the full path or navigate to the Sage
@@ -423,248 +67,6 @@ in the Installation Guide.
$ sudo ln -s $(./sage -sh -c 'ls $SAGE_ROOT/venv/bin/sage') /usr/local/bin
-21. Optional: Set up SageMath as a Jupyter kernel in an existing Jupyter notebook
- or JupyterLab installation, as described in the section
- [Launching SageMath](https://doc.sagemath.org/html/en/installation/launching.html)
- in the Sage Installation Guide.
-
-Alternative Installation using PyPI
----------------
-
-For installing Sage in a Python environment from PyPI, Sage provides the
-`pip`-installable package [sagemath-standard](https://pypi.org/project/sagemath-standard/).
-
-Unless you need to install Sage into a specific existing environment, we recommend
-to create and activate a fresh virtual environment, for example `~/sage-venv/`:
-
- $ python3 -m venv ~/sage-venv
- $ source ~/sage-venv/bin/activate
-
-As the first installation step, install [sage_conf](https://pypi.org/project/sage-conf/),
-which builds various prerequisite packages in a subdirectory of `~/.sage/`:
-
- (sage-venv) $ python3 -m pip install -v sage_conf
-
-After a successful installation, a wheelhouse provides various Python packages.
-You can list the wheels using the command:
-
- (sage-venv) $ ls $(sage-config SAGE_SPKG_WHEELS)
-
-If this gives an error saying that `sage-config` is not found, check any messages
-that the `pip install` command may have printed. You may need to adjust your `PATH`,
-for example by:
-
- $ export PATH="$(python3 -c 'import sysconfig; print(sysconfig.get_path("scripts", "posix_user"))'):$PATH"
-
-Now install the packages from the wheelhouse and the [sage_setup](https://pypi.org/project/sage-conf/)
-package, and finally install the Sage library:
-
- (sage-venv) $ python3 -m pip install $(sage-config SAGE_SPKG_WHEELS)/*.whl sage_setup
- (sage-venv) $ python3 -m pip install --no-build-isolation -v sagemath-standard
-
-The above instructions install the latest stable release of Sage.
-To install the latest development version instead, add the switch `--pre` to all invocations of
-`python3 -m pip install`.
-
-**NOTE:** PyPI has various other `pip`-installable packages with the word "sage" in their names.
-Some of them are maintained by the SageMath project, some are provided by SageMath users for
-various purposes, and others are entirely unrelated to SageMath. Do not use the packages
-`sage` and `sagemath`. For a curated list of packages, see the chapter
-[Packages and Features](https://doc.sagemath.org/html/en/reference/spkg/index.html) of the
-Sage Reference Manual.
-
-SageMath Docker images
-----------------------
-
-[](https://hub.docker.com/r/sagemath/sagemath)
-
-SageMath is available on Docker Hub and can be downloaded by:
-``` bash
-docker pull sagemath/sagemath
-```
-
-Currently, only stable versions are kept up to date.
-
-Troubleshooting
----------------
-
-If you have problems building Sage, check the Sage Installation Guide,
-as well as the version-specific installation help in the [release
-tour](https://github.com/sagemath/sage/releases) corresponding to the
-version that you are installing.
-
-Please do not hesitate to ask for help in the [SageMath forum
-](https://ask.sagemath.org/questions/) or the [sage-support mailing
-list](https://groups.google.com/forum/#!forum/sage-support). The
-[Troubleshooting section in the Sage Installation Guide
-](https://doc.sagemath.org/html/en/installation/troubles.html)
-provides instructions on what information to provide so that we can provide
-help more effectively.
-
-Contributing to Sage
---------------------
-
-If you'd like to contribute to Sage, we strongly recommend that you read the
-[Developer's Guide](https://doc.sagemath.org/html/en/developer/index.html).
-
-Sage has significant components written in the following languages:
-C/C++, Python, Cython, Common Lisp, Fortran, and a bit of Perl.
-
-Directory Layout
-----------------
-
-Simplified directory layout (only essential files/directories):
-```
-SAGE_ROOT Root directory (create by git clone)
-├── build
-│ └── pkgs Every package is a subdirectory here
-│ ├── 4ti2/
-│ …
-│ └── zlib/
-├── configure Top-level configure script
-├── COPYING.txt Copyright information
-├── pkgs Source trees of Python distribution packages
-│ ├── sage-conf
-│ │ ├── sage_conf.py
-│ │ └── setup.py
-│ ├── sage-docbuild
-│ │ ├── sage_docbuild/
-│ │ └── setup.py
-│ ├── sage-setup
-│ │ ├── sage_setup/
-│ │ └── setup.py
-│ ├── sage-sws2rst
-│ │ ├── sage_sws2rst/
-│ │ └── setup.py
-│ └── sagemath-standard
-│ ├── bin/
-│ ├── sage -> ../../src/sage
-│ └── setup.py
-├── local (SAGE_LOCAL) Installation hierarchy for non-Python packages
-│ ├── bin Executables
-│ ├── include C/C++ headers
-│ ├── lib Shared libraries, architecture-dependent data
-│ ├── share Databases, architecture-independent data, docs
-│ │ └── doc Viewable docs of Sage and of some components
-│ └── var
-│ ├── lib/sage
-│ │ ├── installed/
-│ │ │ Records of installed non-Python packages
-│ │ ├── scripts/ Scripts for uninstalling installed packages
-│ │ └── venv-python3.9 (SAGE_VENV)
-│ │ │ Installation hierarchy (virtual environment)
-│ │ │ for Python packages
-│ │ ├── bin/ Executables and installed scripts
-│ │ ├── lib/python3.9/site-packages/
-│ │ │ Python modules/packages are installed here
-│ │ └── var/lib/sage/
-│ │ └── wheels/
-│ │ Python wheels for all installed Python packages
-│ │
-│ └── tmp/sage/ Temporary files when building Sage
-├── logs
-│ ├── install.log Full install log
-│ └── pkgs Build logs of individual packages
-│ ├── alabaster-0.7.12.log
-│ …
-│ └── zlib-1.2.11.log
-├── m4 M4 macros for generating the configure script
-│ └── *.m4
-├── Makefile Running "make" uses this file
-├── prefix -> SAGE_LOCAL Convenience symlink to the installation tree
-├── README.md This file
-├── sage Script to start Sage
-├── src Monolithic Sage library source tree
-│ ├── bin/ Scripts that Sage uses internally
-│ ├── doc/ Sage documentation sources
-│ └── sage/ The Sage library source code
-├── upstream Source tarballs of packages
-│ ├── Babel-2.9.1.tar.gz
-│ …
-│ └── zlib-1.2.11.tar.gz
-├── venv -> SAGE_VENV Convenience symlink to the virtual environment
-└── VERSION.txt
-```
-For more details see [our Developer's Guide](https://doc.sagemath.org/html/en/developer/coding_basics.html#files-and-directory-structure).
-
-Build System
-------------
-
-This is a brief summary of the Sage software distribution's build system.
-There are two components to the full Sage system--the Sage Python library
-and its associated user interfaces, and the larger software distribution of
-Sage's main dependencies (for those dependencies not supplied by the user's
-system).
-
-Sage's Python library is built and installed using a `setup.py` script as is
-standard for Python packages (Sage's `setup.py` is non-trivial, but not
-unusual).
-
-Most of the rest of the build system is concerned with building all of Sage's
-dependencies in the correct order in relation to each other. The dependencies
-included by Sage are referred to as SPKGs (i.e. "Sage Packages") and are listed
-under `build/pkgs`.
-
-The main entrypoint to Sage's build system is the top-level `Makefile` at the
-root of the source tree. Unlike most normal projects that use autoconf (Sage
-does as well, as described below), this `Makefile` is not generated. Instead,
-it contains a few high-level targets and targets related to bootstrapping the
-system. Nonetheless, we still run `make ` from the root of the source
-tree--targets not explicitly defined in the top-level `Makefile` are passed
-through to another Makefile under `build/make/Makefile`.
-
-The latter `build/make/Makefile` *is* generated by an autoconf-generated
-`configure` script, using the template in `build/make/Makefile.in`. This
-includes rules for building the Sage library itself (`make sagelib`), and for
-building and installing each of Sage's dependencies (e.g. `make gf2x`).
-
-The `configure` script itself, if it is not already built, can be generated by
-running the `bootstrap` script (the latter requires _GNU autotools_ being installed).
-The top-level `Makefile` also takes care of this automatically.
-
-To summarize, running a command like `make python3` at the top-level of the
-source tree goes something like this:
-
-1. `make python3`
-2. run `./bootstrap` if `configure` needs updating
-3. run `./configure` with any previously configured options if `build/make/Makefile`
- needs updating
-4. change directory into `build/make` and run the `install` script--this is
- little more than a front-end to running `make -f build/make/Makefile python3`,
- which sets some necessary environment variables and logs some information
-5. `build/make/Makefile` contains the actual rule for building `python3`; this
- includes building all of `python3`'s dependencies first (and their
- dependencies, recursively); the actual package installation is performed
- with the `sage-spkg` program
-
-Relocation
-----------
-
-It is not supported to move the `SAGE_ROOT` or `SAGE_LOCAL` directory
-after building Sage. If you do move the directories, you will have to
-run ``make distclean`` and build Sage again from scratch.
-
-For a system-wide installation, you have to build Sage as a "normal" user
-and then as root you can change permissions. See the [Installation Guide](https://doc.sagemath.org/html/en/installation/source.html#installation-in-a-multiuser-environment)
-for further information.
-
-Redistribution
---------------
-
-Your local Sage install is almost exactly the same as any "developer"
-install. You can make changes to documentation, source, etc., and very
-easily package the complete results up for redistribution just like we
-do.
-
-1. To make a binary distribution with your currently installed packages,
- visit [sagemath/binary-pkg](https://github.com/sagemath/binary-pkg).
-
-2. To make your own source tarball of Sage, type:
-
- $ make dist
-
- The result is placed in the directory `dist/`.
-
Changes to Included Software
----------------------------
diff --git a/build/pkgs/blisspy/SPKG.rst b/build/pkgs/blisspy/SPKG.rst
new file mode 100644
index 00000000000..98a2e9f1360
--- /dev/null
+++ b/build/pkgs/blisspy/SPKG.rst
@@ -0,0 +1,24 @@
+blisspy: Python Wrapper for Bliss Graph Library
+===============================================
+
+Description
+-----------
+`blisspy` is a Python wrapper for the Bliss graph library, providing functionalities to compute canonical forms and automorphism groups of graphs.
+
+License
+-------
+GNU General Public License v3.0
+
+Upstream Contact
+----------------
+[GitHub repository](https://github.com/bodnalev/blisspy)
+
+Dependencies
+------------
+- Cython >=0.29
+- cysignals
+
+Special Update/Build Instructions
+----------------------------------
+None.
+
diff --git a/build/pkgs/blisspy/checksums.ini b/build/pkgs/blisspy/checksums.ini
new file mode 100644
index 00000000000..482def9ee8d
--- /dev/null
+++ b/build/pkgs/blisspy/checksums.ini
@@ -0,0 +1,4 @@
+tarball=blisspy-VERSION.tar.gz
+sha1=467b066ff1e189aeae7921df83ec3c1ad33b17ee
+sha256=3e5e26c04b6fd41f08cb50e187265b9de1d15cfa695a4fe69a4623cc1ce8a967
+upstream_url=https://github.com/bodnalev/blisspy/raw/refs/heads/master/dist/blisspy-VERSION.tar.gz
diff --git a/build/pkgs/blisspy/dependencies b/build/pkgs/blisspy/dependencies
new file mode 100644
index 00000000000..acf79d4997e
--- /dev/null
+++ b/build/pkgs/blisspy/dependencies
@@ -0,0 +1 @@
+cysignals | $(PYTHON_TOOLCHAIN) cython $(PYTHON)
diff --git a/build/pkgs/blisspy/package-version.txt b/build/pkgs/blisspy/package-version.txt
new file mode 100644
index 00000000000..49d59571fbf
--- /dev/null
+++ b/build/pkgs/blisspy/package-version.txt
@@ -0,0 +1 @@
+0.1
diff --git a/build/pkgs/blisspy/spkg-configure.m4 b/build/pkgs/blisspy/spkg-configure.m4
new file mode 100644
index 00000000000..219b3f2d818
--- /dev/null
+++ b/build/pkgs/blisspy/spkg-configure.m4
@@ -0,0 +1 @@
+SAGE_SPKG_CONFIGURE([blisspy], [SAGE_PYTHON_PACKAGE_CHECK([blisspy])])
diff --git a/build/pkgs/blisspy/spkg-install.in b/build/pkgs/blisspy/spkg-install.in
new file mode 100644
index 00000000000..37ac1a53437
--- /dev/null
+++ b/build/pkgs/blisspy/spkg-install.in
@@ -0,0 +1,2 @@
+cd src
+sdh_pip_install .
diff --git a/build/pkgs/blisspy/type b/build/pkgs/blisspy/type
new file mode 100644
index 00000000000..a6a7b9cd726
--- /dev/null
+++ b/build/pkgs/blisspy/type
@@ -0,0 +1 @@
+standard
diff --git a/build/pkgs/blisspy/version_requirements.txt b/build/pkgs/blisspy/version_requirements.txt
new file mode 100644
index 00000000000..6e6c2ffc8af
--- /dev/null
+++ b/build/pkgs/blisspy/version_requirements.txt
@@ -0,0 +1,2 @@
+Cython >=0.29
+cysignals
diff --git a/build/pkgs/csdpy/SPKG.rst b/build/pkgs/csdpy/SPKG.rst
new file mode 100644
index 00000000000..608a5580bd7
--- /dev/null
+++ b/build/pkgs/csdpy/SPKG.rst
@@ -0,0 +1,24 @@
+csdpy: Python Wrapper for the Coin-Or project's CSDP solver
+===============================================
+
+Description
+-----------
+`csdpy` is a Python wrapper for the CSDP library, providing a fast algorithm to solve SDP problems.
+
+License
+-------
+GNU General Public License v3.0
+
+Upstream Contact
+----------------
+[GitHub repository](https://github.com/bodnalev/csdpy)
+
+Dependencies
+------------
+- Cython >=0.29
+- cysignals
+
+Special Update/Build Instructions
+----------------------------------
+None.
+
diff --git a/build/pkgs/csdpy/checksums.ini b/build/pkgs/csdpy/checksums.ini
new file mode 100644
index 00000000000..5d4eca933f0
--- /dev/null
+++ b/build/pkgs/csdpy/checksums.ini
@@ -0,0 +1,4 @@
+tarball=csdpy-VERSION.tar.gz
+sha1=1c1b0f0d5c3f67dd28f7f16cba933ed1357266af
+sha256=e84c13ba5994c3fbfd1fef36a60ed05a3a382b7b4402f2968c77afcc276779f2
+upstream_url=https://github.com/bodnalev/csdpy/raw/refs/heads/master/dist/csdpy-VERSION.tar.gz
diff --git a/build/pkgs/csdpy/dependencies b/build/pkgs/csdpy/dependencies
new file mode 100644
index 00000000000..1690f4be775
--- /dev/null
+++ b/build/pkgs/csdpy/dependencies
@@ -0,0 +1 @@
+$(PYTHON_TOOLCHAIN) cython $(PYTHON)
diff --git a/build/pkgs/csdpy/package-version.txt b/build/pkgs/csdpy/package-version.txt
new file mode 100644
index 00000000000..49d59571fbf
--- /dev/null
+++ b/build/pkgs/csdpy/package-version.txt
@@ -0,0 +1 @@
+0.1
diff --git a/build/pkgs/csdpy/spkg-configure.m4 b/build/pkgs/csdpy/spkg-configure.m4
new file mode 100644
index 00000000000..150f72277d5
--- /dev/null
+++ b/build/pkgs/csdpy/spkg-configure.m4
@@ -0,0 +1 @@
+SAGE_SPKG_CONFIGURE([csdpy], [SAGE_PYTHON_PACKAGE_CHECK([csdpy])])
diff --git a/build/pkgs/csdpy/spkg-install b/build/pkgs/csdpy/spkg-install
new file mode 100644
index 00000000000..980c8796fed
--- /dev/null
+++ b/build/pkgs/csdpy/spkg-install
@@ -0,0 +1,20 @@
+#!/bin/sh
+if [ "$SAGE_LOCAL" = "" ]; then
+ echo "SAGE_LOCAL is not set. Exiting."
+ exit 1
+fi
+GITHUB_FILES="https://github.com/bodnalev/csdpy/archive/refs/heads/master.zip"
+TMP_DIR=$(mktemp -d)
+wget -O "$TMP_DIR/csdpy.zip" "$GITHUB_FILES" || exit 1
+unzip "$TMP_DIR/csdpy.zip" -d "$TMP_DIR" || exit 1
+cd "$TMP_DIR/csdpy-master" || exit 1
+OS_TYPE=$(uname)
+if [ "$OS_TYPE" = "Linux" ]; then
+ "$SAGE_LOCAL/bin/python3" setup_linux.py install || exit 1
+elif [ "$OS_TYPE" = "Darwin" ]; then
+ "$SAGE_LOCAL/bin/python3" setup_mac.py install || exit 1
+else
+ echo "Unsupported OS: $OS_TYPE. Exiting."
+ exit 1
+fi
+rm -rf "$TMP_DIR"
\ No newline at end of file
diff --git a/build/pkgs/csdpy/spkg-install.in b/build/pkgs/csdpy/spkg-install.in
new file mode 100644
index 00000000000..37ac1a53437
--- /dev/null
+++ b/build/pkgs/csdpy/spkg-install.in
@@ -0,0 +1,2 @@
+cd src
+sdh_pip_install .
diff --git a/build/pkgs/csdpy/type b/build/pkgs/csdpy/type
new file mode 100644
index 00000000000..a6a7b9cd726
--- /dev/null
+++ b/build/pkgs/csdpy/type
@@ -0,0 +1 @@
+standard
diff --git a/build/pkgs/csdpy/version_requirements.txt b/build/pkgs/csdpy/version_requirements.txt
new file mode 100644
index 00000000000..6e6c2ffc8af
--- /dev/null
+++ b/build/pkgs/csdpy/version_requirements.txt
@@ -0,0 +1,2 @@
+Cython >=0.29
+cysignals
diff --git a/build/pkgs/sagelib/dependencies b/build/pkgs/sagelib/dependencies
index b1ebfd0825e..b5a9961ed00 100644
--- a/build/pkgs/sagelib/dependencies
+++ b/build/pkgs/sagelib/dependencies
@@ -1,4 +1,4 @@
-FORCE $(SCRIPTS) boost_cropped $(BLAS) brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml importlib_metadata importlib_resources jupyter_core lcalc lrcalc_python libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount primecountpy $(PYTHON) requests rw sage_conf singular symmetrica typing_extensions $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup $(PYTHON) pythran
+FORCE $(SCRIPTS) boost_cropped $(BLAS) blisspy brial cliquer cypari cysignals cython ecl eclib ecm flint libgd gap giac givaro glpk gmpy2 gsl iml importlib_metadata importlib_resources jupyter_core lcalc lrcalc_python libbraiding libhomfly libpng linbox m4ri m4rie memory_allocator mpc mpfi mpfr $(MP_LIBRARY) ntl numpy pari pip pkgconfig planarity ppl pplpy primesieve primecount primecountpy $(PYTHON) requests rw sage_conf singular symmetrica typing_extensions $(PCFILES) | $(PYTHON_TOOLCHAIN) sage_setup $(PYTHON) pythran
----------
All lines of this file are ignored except the first.
diff --git a/build/pkgs/tqdm/SPKG.rst b/build/pkgs/tqdm/SPKG.rst
new file mode 100644
index 00000000000..c0770977d53
--- /dev/null
+++ b/build/pkgs/tqdm/SPKG.rst
@@ -0,0 +1,24 @@
+tqdm: Fast, Extensible Progress Meter
+===============================================
+
+Description
+-----------
+tqdm derives from the Arabic word taqaddum (تقدّم) which can mean “progress,” and is an abbreviation for “I love you so much” in Spanish (te quiero demasiado).
+
+Instantly make your loops show a smart progress meter - just wrap any iterable with tqdm(iterable), and you’re done!
+
+License
+-------
+MPL 2.0 and MIT
+
+Upstream Contact
+----------------
+[Official PyPi page](https://pypi.org/project/tqdm/)
+
+Dependencies
+------------
+
+Special Update/Build Instructions
+----------------------------------
+None.
+
diff --git a/build/pkgs/tqdm/checksums.ini b/build/pkgs/tqdm/checksums.ini
new file mode 100644
index 00000000000..632dfa5c814
--- /dev/null
+++ b/build/pkgs/tqdm/checksums.ini
@@ -0,0 +1,4 @@
+tarball=tqdm-VERSION.tar.gz
+sha1=e70c395f3cd6ad596a4d6d27b1284a7d377d5048
+sha256=f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2
+upstream_url=https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-VERSION.tar.gz
diff --git a/build/pkgs/tqdm/dependencies b/build/pkgs/tqdm/dependencies
new file mode 100644
index 00000000000..ca33204bd52
--- /dev/null
+++ b/build/pkgs/tqdm/dependencies
@@ -0,0 +1 @@
+ | $(PYTHON_TOOLCHAIN) $(PYTHON)
diff --git a/build/pkgs/tqdm/package-version.txt b/build/pkgs/tqdm/package-version.txt
new file mode 100644
index 00000000000..f538bef708d
--- /dev/null
+++ b/build/pkgs/tqdm/package-version.txt
@@ -0,0 +1 @@
+4.67.1
diff --git a/build/pkgs/tqdm/spkg-configure.m4 b/build/pkgs/tqdm/spkg-configure.m4
new file mode 100644
index 00000000000..ff66a23eadf
--- /dev/null
+++ b/build/pkgs/tqdm/spkg-configure.m4
@@ -0,0 +1 @@
+SAGE_SPKG_CONFIGURE([tqdm], [SAGE_PYTHON_PACKAGE_CHECK([tqdm])])
diff --git a/build/pkgs/tqdm/spkg-install b/build/pkgs/tqdm/spkg-install
new file mode 100755
index 00000000000..23dda9803ec
--- /dev/null
+++ b/build/pkgs/tqdm/spkg-install
@@ -0,0 +1,8 @@
+#!/bin/sh
+if [ "$SAGE_LOCAL" = "" ]; then
+ echo "SAGE_LOCAL is not set. Exiting."
+ exit 1
+fi
+
+# Install the latest version of tqdm from PyPI
+"$SAGE_LOCAL/bin/pip" install tqdm
diff --git a/build/pkgs/tqdm/spkg-install.in b/build/pkgs/tqdm/spkg-install.in
new file mode 100644
index 00000000000..37ac1a53437
--- /dev/null
+++ b/build/pkgs/tqdm/spkg-install.in
@@ -0,0 +1,2 @@
+cd src
+sdh_pip_install .
diff --git a/build/pkgs/tqdm/type b/build/pkgs/tqdm/type
new file mode 100644
index 00000000000..a6a7b9cd726
--- /dev/null
+++ b/build/pkgs/tqdm/type
@@ -0,0 +1 @@
+standard
diff --git a/flag_tutorial.ipynb b/flag_tutorial.ipynb
new file mode 100644
index 00000000000..c3cf6eae617
--- /dev/null
+++ b/flag_tutorial.ipynb
@@ -0,0 +1,3295 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "dbd830bd-b428-4786-887c-da759b5217d4",
+ "metadata": {},
+ "source": [
+ "\n",
+ "Short guide to the flag algebra package\n",
+ "=======================================\n",
+ "\n",
+ "Contents:\n",
+ "1. Quick start\n",
+ "2. Flags\n",
+ " 1. Creating flags\n",
+ " 2. Everything is induced\n",
+ " 3. Operations over flags\n",
+ " 4. Types\n",
+ " 5. Patterns\n",
+ "3. Theories\n",
+ " 1. Already present theories\n",
+ " 4. Creating new theories\n",
+ " 2. Excluding things\n",
+ " 3. Generating things\n",
+ " 5. Combining theories\n",
+ "4. Optimizing\n",
+ " 1. Assumptions\n",
+ " 2. Rounding\n",
+ " 3. Construction\n",
+ " 4. Certificates\n",
+ " 6. Exporting the SDP problem\n",
+ " 8. Rounding over field extensions\n",
+ "5. Miscallenous"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "499b2984-9356-461b-9299-c55cc50d41e1",
+ "metadata": {},
+ "source": [
+ "Quick start\n",
+ "===========\n",
+ "\n",
+ "Here Mantel's theorem will be demonstrated"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "0a2fb8ed-2cf2-4703-865a-81355945e997",
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 560.44it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [2, -3, -2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5751371e+01 Ad: 7.93e-01 Dobj: -1.8596991e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3829292e+01 Ad: 9.44e-01 Dobj: -2.5639377e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -3.6548137e+00 Ad: 9.22e-01 Dobj: -2.8097573e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -6.7094195e-01 Ad: 8.42e-01 Dobj: -3.1080901e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -5.8576089e-01 Ad: 8.47e-01 Dobj: -4.8072710e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.0662032e-01 Ad: 8.78e-01 Dobj: -4.9275677e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.0069959e-01 Ad: 9.33e-01 Dobj: -4.9911657e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0005821e-01 Ad: 1.00e+00 Dobj: -4.9996202e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.0000265e-01 Ad: 1.00e+00 Dobj: -4.9999892e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000020e-01 Ad: 1.00e+00 Dobj: -4.9999999e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -5.0000001e-01 Ad: 9.90e-01 Dobj: -5.0000000e-01 \n",
+ "Iter: 12 Ap: 9.57e-01 Pobj: -5.0000000e-01 Ad: 9.69e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000000e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 6.40e-16 \n",
+ "Relative dual infeasibility: 1.18e-10 \n",
+ "Real Relative Gap: 2.59e-10 \n",
+ "XZ Relative Gap: 4.63e-10 \n",
+ "DIMACS error measures: 6.70e-16 0.00e+00 2.46e-10 0.00e+00 2.59e-10 4.63e-10\n",
+ "The initial run gave an accurate looking construction\n",
+ "Rounded construction vector is: \n",
+ "Flag Algebra Element over Rational Field\n",
+ "1/4 - Flag on 3 points, ftype from () with edges=()\n",
+ "0 - Flag on 3 points, ftype from () with edges=(01)\n",
+ "3/4 - Flag on 3 points, ftype from () with edges=(01 02)\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [1, -3, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.2107440e+01 Ad: 8.40e-01 Dobj: 6.1913653e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.0543280e+01 Ad: 9.38e-01 Dobj: -1.3876525e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -2.4595774e+00 Ad: 9.42e-01 Dobj: -1.9392703e-01 \n",
+ "Iter: 4 Ap: 9.51e-01 Pobj: -6.1484578e-01 Ad: 8.50e-01 Dobj: -2.3934806e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -6.4529870e-01 Ad: 7.42e-01 Dobj: -4.8596320e-01 \n",
+ "Iter: 6 Ap: 9.78e-01 Pobj: -5.0782840e-01 Ad: 8.89e-01 Dobj: -4.9454657e-01 \n",
+ "Iter: 7 Ap: 9.90e-01 Pobj: -5.0036064e-01 Ad: 9.65e-01 Dobj: -4.9953666e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0002547e-01 Ad: 1.00e+00 Dobj: -4.9997505e-01 \n",
+ "Iter: 9 Ap: 9.90e-01 Pobj: -5.0000127e-01 Ad: 1.00e+00 Dobj: -4.9999949e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000006e-01 Ad: 1.00e+00 Dobj: -4.9999997e-01 \n",
+ "Iter: 11 Ap: 9.70e-01 Pobj: -5.0000000e-01 Ad: 9.70e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000000e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 1.06e-15 \n",
+ "Relative dual infeasibility: 1.57e-09 \n",
+ "Real Relative Gap: 1.08e-09 \n",
+ "XZ Relative Gap: 3.47e-09 \n",
+ "DIMACS error measures: 1.11e-15 0.00e+00 3.20e-09 0.00e+00 1.08e-09 3.47e-09\n",
+ "Starting the rounding of the result\n",
+ "Rounding X matrices\n",
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 1/1 [00:00<00:00, 377.15it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Final rounded bound is 1/2\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1/2"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Check Mantel's theorem\n",
+ "\n",
+ "# Define a triangle\n",
+ "triangle = GraphTheory(3, edges=[[0, 1], [0, 2], [1, 2]])\n",
+ "\n",
+ "# Define an edge\n",
+ "edge = GraphTheory(2, edges=[[0, 1]])\n",
+ "\n",
+ "# Exclude triangles\n",
+ "GraphTheory.exclude(triangle)\n",
+ "# Maximize edges, calculate up to size 3, make the result exact\n",
+ "GraphTheory.optimize(edge, 3, maximize=True, exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "6926a408-3bbe-486e-b90b-bcfa1512bd1a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Python syntax\n",
+ "# For short, use G for GraphTheory\n",
+ "G = GraphTheory"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "120b1a0d-f003-495f-8de6-cfd0aefdf725",
+ "metadata": {},
+ "source": [
+ "Flags\n",
+ "=====\n",
+ "\n",
+ "1. Creating flags\n",
+ "2. Everything is induced\n",
+ "3. Operations with flags\n",
+ "4. Types\n",
+ "5. Patterns"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "3ae807f1-369f-495c-bcbe-bb7453cea306",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Is cherry the same as other cherry? True\n",
+ "Triangle flag is: Flag on 3 points, ftype from () with edges=(01 02 12)\n",
+ "The test flag is Flag on 3 points, ftype from () with edges=()\n"
+ ]
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Creating flags\n",
+ "###\n",
+ "\n",
+ "# To create a flag, write TheoryName(size, relation_name=...)\n",
+ "\n",
+ "# Example for graphs\n",
+ "triangle = G(3, edges=[[0, 1], [0, 2], [1, 2]])\n",
+ "cherry = G(3, edges=[[0, 1], [0, 2]])\n",
+ "# Example for three graphs\n",
+ "k4m = ThreeGraphTheory(4, edges=[[0, 1, 2], [0, 1, 3], [1, 2, 3]])\n",
+ "\n",
+ "# Note automorphism doesn't matter\n",
+ "other_cherry = G(3, edges=[[0, 1], [2, 1]])\n",
+ "print(\"Is cherry the same as other cherry? \", cherry==other_cherry)\n",
+ "\n",
+ "# Flags can be printed\n",
+ "print(\"Triangle flag is: \", triangle)\n",
+ "\n",
+ "# If a relation is not included, it is assumed to be empty\n",
+ "test = G(3)\n",
+ "print(\"The test flag is \", test)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "a19c1502-cbef-4d35-9611-466fb98d1465",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 325.92it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [2, -3, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5751371e+01 Ad: 7.93e-01 Dobj: -1.8596991e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3829292e+01 Ad: 9.44e-01 Dobj: -2.5639371e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -3.6548572e+00 Ad: 9.22e-01 Dobj: -2.8097530e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -6.7102979e-01 Ad: 8.42e-01 Dobj: -3.1080769e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -5.8576247e-01 Ad: 8.44e-01 Dobj: -4.8002387e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.0647721e-01 Ad: 8.85e-01 Dobj: -4.9296240e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.0060022e-01 Ad: 9.41e-01 Dobj: -4.9923793e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0005080e-01 Ad: 1.00e+00 Dobj: -4.9996691e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.0000234e-01 Ad: 1.00e+00 Dobj: -4.9999913e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000016e-01 Ad: 1.00e+00 Dobj: -5.0000000e-01 \n",
+ "Iter: 11 Ap: 9.58e-01 Pobj: -5.0000001e-01 Ad: 9.70e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000001e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 4.18e-16 \n",
+ "Relative dual infeasibility: 3.98e-09 \n",
+ "Real Relative Gap: 2.49e-09 \n",
+ "XZ Relative Gap: 9.08e-09 \n",
+ "DIMACS error measures: 4.38e-16 0.00e+00 8.28e-09 0.00e+00 2.49e-09 9.08e-09\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.5000000069458577"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Everything is induced\n",
+ "###\n",
+ "\n",
+ "# Note that everything is induced. Mantel's theorem can be checked with the complements\n",
+ "\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "G.optimize(G(2), 3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "9893cc17-27d3-49ee-b729-118f6cc83354",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag Algebra Element over Rational Field\n",
+ " 1 - Flag on 3 points, ftype from () with edges=()\n",
+ " 0 - Flag on 3 points, ftype from () with edges=(01)\n",
+ " 1 - Flag on 3 points, ftype from () with edges=(01 02)\n",
+ " 0 - Flag on 3 points, ftype from () with edges=(01 02 12),\n",
+ " Flag Algebra Element over Rational Field\n",
+ " 1 - Flag on 4 points, ftype from () with edges=()\n",
+ " 2/3 - Flag on 4 points, ftype from () with edges=(01)\n",
+ " 1/3 - Flag on 4 points, ftype from () with edges=(01 03)\n",
+ " 2/3 - Flag on 4 points, ftype from () with edges=(02 13)\n",
+ " 1/3 - Flag on 4 points, ftype from () with edges=(01 02 13)\n",
+ " 1/3 - Flag on 4 points, ftype from () with edges=(02 03 12 13))"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Operations with flags\n",
+ "###\n",
+ "\n",
+ "\n",
+ "G.reset()\n",
+ "empty3 = G(3)\n",
+ "# Addition, Multiplication\n",
+ "empty3 + cherry, G(2)*G(2)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "0f4b29d7-6e5a-486c-b87b-cbf4f9dcd636",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Flag Algebra Element over Rational Field\n",
+ "1/6 - Flag on 4 points, ftype from () with edges=(01)\n",
+ "1/3 - Flag on 4 points, ftype from () with edges=(01 03)\n",
+ "1/3 - Flag on 4 points, ftype from () with edges=(02 13)\n",
+ "1/2 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "1/2 - Flag on 4 points, ftype from () with edges=(01 03 13)\n",
+ "1/2 - Flag on 4 points, ftype from () with edges=(01 02 13)\n",
+ "2/3 - Flag on 4 points, ftype from () with edges=(01 02 03 13)\n",
+ "2/3 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "5/6 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13)\n",
+ "1 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23)"
+ ]
+ },
+ "execution_count": 45,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Chain rule, increase size by 1\n",
+ "k2 = G(2, edges=[[0, 1]])\n",
+ "# Increase size with << operator\n",
+ "k2 << 2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "c37ef1f5-4be3-43e0-96d5-706c00833f77",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Flag on 2 points, ftype from (0,) with edges=(01)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Types\n",
+ "###\n",
+ "\n",
+ "# Add ftype=[] list of points to define the marked vertices, when creating a flag\n",
+ "pointed_edge = G(2, edges=[[0, 1]], ftype=[0])\n",
+ "pointed_edge"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "id": "35d47c34-9758-4fb7-9296-673703ca56f8",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(False, False, False)"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Types must map to types (with the same order)\n",
+ "\n",
+ "fl0 = G(3, edges=[[0, 1]], ftype=[0, 2])\n",
+ "fl1 = G(3, edges=[[0, 1]], ftype=[2, 0])\n",
+ "fl2 = G(3, edges=[[0, 1]], ftype=[2])\n",
+ "fl0==fl1, fl0==fl2, fl1==fl2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "cd8afb21-1df5-4482-8be6-059df12cba6e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Flag Algebra Element over Rational Field\n",
+ "0 - Flag on 3 points, ftype from (0,) with edges=()\n",
+ "1/2 - Flag on 3 points, ftype from (0,) with edges=(01)\n",
+ "0 - Flag on 3 points, ftype from (2,) with edges=(01)\n",
+ "2 - Flag on 3 points, ftype from (0,) with edges=(01 02)\n",
+ "1/2 - Flag on 3 points, ftype from (1,) with edges=(01 02)\n",
+ "1 - Flag on 3 points, ftype from (0,) with edges=(01 02 12)\n",
+ "Flag Algebra Element over Rational Field\n",
+ "0 - Flag on 3 points, ftype from (0,) with edges=()\n",
+ "0 - Flag on 3 points, ftype from (0,) with edges=(01)\n",
+ "0 - Flag on 3 points, ftype from (2,) with edges=(01)\n",
+ "1 - Flag on 3 points, ftype from (0,) with edges=(01 02)\n",
+ "0 - Flag on 3 points, ftype from (1,) with edges=(01 02)\n",
+ "1 - Flag on 3 points, ftype from (0,) with edges=(01 02 12)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Operations still work\n",
+ "pointed_cherry = G(3, edges=[[0, 1], [1, 2]], ftype=[1])\n",
+ "print(pointed_edge + pointed_cherry)\n",
+ "print(pointed_edge*pointed_edge)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "d3b0ed9c-69c0-46e9-965e-b5f6831af09a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Flag Algebra Element over Rational Field\n",
+ "0 - Flag on 3 points, ftype from () with edges=()\n",
+ "0 - Flag on 3 points, ftype from () with edges=(01)\n",
+ "1/3 - Flag on 3 points, ftype from () with edges=(01 02)\n",
+ "0 - Flag on 3 points, ftype from () with edges=(01 02 12)"
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# The averaging operator, called project here\n",
+ "pointed_cherry.project()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 49,
+ "id": "c9ae6813-815b-416d-a11d-0c99cba78393",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Ftype on 2 points with edges=(01)"
+ ]
+ },
+ "execution_count": 49,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# To create a type, simply create a flag with all vertices marked as type\n",
+ "edgetype = G(2, edges=[[0, 1]], ftype=[0, 1])\n",
+ "edgetype"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "f2f4795c-c0ea-4467-a4d4-f103f9a4292c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[Flag on 3 points, ftype from () with edges=(01),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12)]"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Patterns\n",
+ "###\n",
+ "\n",
+ "# A pattern is a non-induced flag. Every relation is optional, unless stated otherwise.\n",
+ "\n",
+ "# This induced graph has 3 points and an edge.\n",
+ "edge3 = G(3, edges=[[0, 1]])\n",
+ "# This pattern is the same, but non-induced.\n",
+ "pat3 = G.pattern(3, edges=[[0, 1]])\n",
+ "\n",
+ "# To see the list of induced flags compatible with this patter, call\n",
+ "pat3.compatible_flags()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "a3f69265-c186-4d4d-9e02-b723a4ffc2d2",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[Flag on 3 points, ftype from () with edges=(01),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02)]"
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# A pattern can have strictly missing relations too. We can specify by listing them with the _missing or _m suffix\n",
+ "\n",
+ "# This pattern on 3 points must have one edge present and one edge missing\n",
+ "mpat3 = G.pattern(3, edges=[[0, 1]], edges_missing=[[1, 2]])\n",
+ "mpat3.compatible_flags()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "dcc4c945-9709-44c6-a7db-1274f1b6d0fe",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Flag Algebra Element over Rational Field\n",
+ "0 - Flag on 3 points, ftype from () with edges=()\n",
+ "4/3 - Flag on 3 points, ftype from () with edges=(01)\n",
+ "5/3 - Flag on 3 points, ftype from () with edges=(01 02)\n",
+ "1 - Flag on 3 points, ftype from () with edges=(01 02 12)"
+ ]
+ },
+ "execution_count": 14,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# When a pattern is used in an expression, it is just converted to the sum of it's elements\n",
+ "\n",
+ "edge + mpat3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 58,
+ "id": "98438562-8de7-4cc5-b67f-94496d9a1a27",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[Flag on 5 points, ftype from () with edges=(01 04 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 03 04 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 02 03 04 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 03 04 12 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 02 04 12 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 02 03 04 12 14),\n",
+ " Flag on 5 points, ftype from () with edges=(01 02 03 04 12 13 14)]"
+ ]
+ },
+ "execution_count": 58,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Patterns can be created with the shorted .p\n",
+ "# Also instead of adding _missing, it is enough to add _m\n",
+ "G.reset()\n",
+ "# 5 points, has a triangle and a missing triangle.\n",
+ "test = G.p(5, edges=[[0, 1], [1, 2], [0, 2]], edges_m=[[2, 3], [3, 4], [2, 4]])\n",
+ "test.compatible_flags()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1857a500-de28-4618-aac7-0299cada29b5",
+ "metadata": {},
+ "source": [
+ "Theories\n",
+ "========\n",
+ "\n",
+ "1. Already present theories\n",
+ "4. Creating new theories\n",
+ "2. Excluding things\n",
+ "3. Generating things\n",
+ "5. Combining theories"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "208e31ba-c06d-46c0-9a9c-b963a4f0bc2c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "###\n",
+ "### Already present theories\n",
+ "###\n",
+ "\n",
+ "# The following theories are already created, the names are self explanatory\n",
+ "\n",
+ "GraphTheory = Theory(\"Graph\")\n",
+ "DiGraphTheory = Theory(\"DiGraph\", arity=2, is_ordered=True)\n",
+ "ThreeGraphTheory = Theory(\"ThreeGraph\", arity=3)\n",
+ "DiThreeGraphTheory = Theory(\"DiThreeGraph\", arity=3, is_ordered=True)\n",
+ "FourGraphTheory = Theory(\"FourGraph\", arity=4)\n",
+ "Color0 = Theory(\"Color0\", relation_name=\"C0\", arity=1)\n",
+ "Color1 = Theory(\"Color1\", relation_name=\"C1\", arity=1)\n",
+ "Color2 = Theory(\"Color2\", relation_name=\"C2\", arity=1)\n",
+ "Color3 = Theory(\"Color3\", relation_name=\"C3\", arity=1)\n",
+ "Color4 = Theory(\"Color4\", relation_name=\"C4\", arity=1)\n",
+ "Color5 = Theory(\"Color5\", relation_name=\"C5\", arity=1)\n",
+ "Color6 = Theory(\"Color6\", relation_name=\"C6\", arity=1)\n",
+ "Color7 = Theory(\"Color7\", relation_name=\"C7\", arity=1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 61,
+ "id": "2835afb0-55c7-44d6-9078-cd337c2ae629",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "DiGraphTheory.exclude(DiGraphTheory(2, edges=[[0, 1], [1, 0]]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 62,
+ "id": "4d2d8a1d-3292-43f0-b76b-5e7ea30ac9de",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "(Flag on 3 points, ftype from () with edges=(), Flag on 3 points, ftype from () with edges=(012))\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "Flag on 4 points, ftype from () with edges=(012 023 123)"
+ ]
+ },
+ "execution_count": 62,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Creating new theories\n",
+ "###\n",
+ "\n",
+ "# One can create a new theory using the command:\n",
+ "# Theory([\"name_for_the_theory\"], relation_name=[\"name_for_the_relation\"], arity=[arity_of_relation], is_ordered=[is_relation_ordered])\n",
+ "\n",
+ "# The default values are: relation_name=\"edges\", arity=2, is_orderd=False\n",
+ "# Here are a few examples\n",
+ "\n",
+ "OtherDiGraphTheory = Theory(\"OtherDiGraph\", arity=2, is_ordered=True, relation_name=\"diedge\")\n",
+ "OtherThreeGraphTheory = Theory(\"OtherThreeGraph\", arity=3, relation_name=\"edges3\")\n",
+ "\n",
+ "# The elements in the new theory has relations named accordingly\n",
+ "print(OtherThreeGraphTheory.generate(3))\n",
+ "\n",
+ "# And creating elements must also follow the convention\n",
+ "k4m = OtherThreeGraphTheory(4, edges3=[[0, 1, 2], [1, 2, 3], [0, 2, 3]])\n",
+ "k4m"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "8849dbbf-161a-42c2-9095-76c019e1141f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "###\n",
+ "### Excluding things\n",
+ "###\n",
+ "\n",
+ "# To reset a theory, so nothing is excluded, call\n",
+ "G.reset()\n",
+ "\n",
+ "# A list of flags can be excluded\n",
+ "structure_1 = G(4, edges=[[0, 1], [0, 2], [0, 3], [1, 2]])\n",
+ "structure_2 = G(3, edges=[[0, 1], [0, 2]])\n",
+ "G.exclude([structure_1, structure_2])\n",
+ "\n",
+ "# Exclude works incrementally, the following works too\n",
+ "G.reset()\n",
+ "G.exclude(structure_1)\n",
+ "G.exclude(structure_2)\n",
+ "\n",
+ "# Excluding also allows patterns, \n",
+ "G.reset()\n",
+ "G.exclude(G.p(4, edges=[[0, 1], [1, 2], [2, 3], [0, 2]]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "384b5ad6-e856-46b3-a6f4-0d5308f62eb4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 4 points, ftype from () with edges=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01),\n",
+ " Flag on 4 points, ftype from () with edges=(01 03),\n",
+ " Flag on 4 points, ftype from () with edges=(02 13),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03),\n",
+ " Flag on 4 points, ftype from () with edges=(01 03 13),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 13),\n",
+ " Flag on 4 points, ftype from () with edges=(02 03 12 13))"
+ ]
+ },
+ "execution_count": 19,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Generating flags\n",
+ "###\n",
+ "\n",
+ "# To generate all flags (respecting the excluded structures, see the above cell)\n",
+ "G.generate(4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 63,
+ "id": "4ca23a2c-7578-458d-bdb6-ccfbfe875d8d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 4 points, ftype from (0, 1) with edges=(01),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 03),\n",
+ " Flag on 4 points, ftype from (1, 0) with edges=(01 03),\n",
+ " Flag on 4 points, ftype from (0, 2) with edges=(02 13),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 02 03),\n",
+ " Flag on 4 points, ftype from (1, 0) with edges=(01 02 03),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 03 13),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 02 13),\n",
+ " Flag on 4 points, ftype from (0, 2) with edges=(01 02 13),\n",
+ " Flag on 4 points, ftype from (2, 0) with edges=(01 02 13),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 02 03 13),\n",
+ " Flag on 4 points, ftype from (0, 2) with edges=(01 02 03 13),\n",
+ " Flag on 4 points, ftype from (1, 0) with edges=(01 02 03 13),\n",
+ " Flag on 4 points, ftype from (1, 3) with edges=(01 02 03 13),\n",
+ " Flag on 4 points, ftype from (2, 0) with edges=(01 02 03 13),\n",
+ " Flag on 4 points, ftype from (0, 2) with edges=(02 03 12 13),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 02 03 12 13),\n",
+ " Flag on 4 points, ftype from (0, 2) with edges=(01 02 03 12 13),\n",
+ " Flag on 4 points, ftype from (2, 0) with edges=(01 02 03 12 13),\n",
+ " Flag on 4 points, ftype from (0, 1) with edges=(01 02 03 12 13 23))"
+ ]
+ },
+ "execution_count": 63,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# It is also possible to generate all flags with a given type\n",
+ "edgetype = G(2, edges=[[0, 1]], ftype=[0, 1])\n",
+ "G.reset()\n",
+ "G.generate(4, edgetype)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 65,
+ "id": "7a766765-ab70-49a0-b466-84e595c35d10",
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "ValueError",
+ "evalue": "The relation names must be different!",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
+ "Cell \u001b[0;32mIn[65], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcombine\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mCombinedThing\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mGraphTheory\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mThreeGraphTheory\u001b[49m\u001b[43m)\u001b[49m\n",
+ "File \u001b[0;32m~/sage/src/sage/algebras/combinatorial_theory.py:194\u001b[0m, in \u001b[0;36mcombine\u001b[0;34m(name, symmetries, *theories)\u001b[0m\n\u001b[1;32m 192\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m kk \u001b[38;5;129;01min\u001b[39;00m theory\u001b[38;5;241m.\u001b[39m_signature:\n\u001b[1;32m 193\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m kk \u001b[38;5;129;01min\u001b[39;00m result_signature:\n\u001b[0;32m--> 194\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mThe relation names must be different!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 195\u001b[0m tkk \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(theory\u001b[38;5;241m.\u001b[39m_signature[kk])\n\u001b[1;32m 196\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m can_symmetry:\n",
+ "\u001b[0;31mValueError\u001b[0m: The relation names must be different!"
+ ]
+ }
+ ],
+ "source": [
+ "combine(\"CombinedThing\", GraphTheory, ThreeGraphTheory)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 71,
+ "id": "011bc895-a8e7-4be4-a792-914e91752de9",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 3 points, ftype from () with edges=(01), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01), edges3=(012),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), edges3=(012),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), edges3=(012))"
+ ]
+ },
+ "execution_count": 71,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# To create complex theories, it is possible to combine them.\n",
+ "\n",
+ "# For this to work, the theories must have different relation_name-s\n",
+ "\n",
+ "# We can't combine GraphTheory and ThreeGraphTheory since they both use the \"edges\" relation\n",
+ "# So let's create an alternative ThreeGraphTheory using edges3 relation\n",
+ "\n",
+ "TG = Theory(\"OtherThreeGraph\", arity=3, relation_name=\"edges3\")\n",
+ "\n",
+ "# Then we can combine the theories\n",
+ "# First a name is needed, then the list of theories\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "CombinedTheory = combine(\"TwoThreeGraph\", G, TG)\n",
+ "CombinedTheory.reset()\n",
+ "CombinedTheory.generate(3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 72,
+ "id": "6063870c-f6f1-4ed0-b74d-531ff06ee65d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 3 points, ftype from () with edges=(), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(), edges3=(012),\n",
+ " Flag on 3 points, ftype from () with edges=(01), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01), edges3=(012),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), edges3=(012),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), edges3=(),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), edges3=(012))"
+ ]
+ },
+ "execution_count": 72,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "G.reset()\n",
+ "CombinedTheory.generate(3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "dac44a0a-d8e9-4547-ba7e-04cd4acf647e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 4 points, ftype from () with edges=(), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(), edges3=(012 013),\n",
+ " Flag on 4 points, ftype from () with edges=(), edges3=(012 013 023),\n",
+ " Flag on 4 points, ftype from () with edges=(), edges3=(012 013 023 123),\n",
+ " Flag on 4 points, ftype from () with edges=(02), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(23), edges3=(012 013),\n",
+ " Flag on 4 points, ftype from () with edges=(23), edges3=(012 013 023),\n",
+ " Flag on 4 points, ftype from () with edges=(02), edges3=(012 013 023 123),\n",
+ " Flag on 4 points, ftype from () with edges=(02 03), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(13 23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(13 23), edges3=(012 013 023),\n",
+ " Flag on 4 points, ftype from () with edges=(01 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 23), edges3=(012 013 023 123),\n",
+ " Flag on 4 points, ftype from () with edges=(03 13 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(03 13 23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(02 03 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 12), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(12 13 23), edges3=(012 013 023),\n",
+ " Flag on 4 points, ftype from () with edges=(02 03 23), edges3=(012 013 023 123),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 12 23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(01 03 12 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13), edges3=(012 013),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23), edges3=(),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23), edges3=(012),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23), edges3=(012 013),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23), edges3=(012 013 023),\n",
+ " Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23), edges3=(012 013 023 123))"
+ ]
+ },
+ "execution_count": 22,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# For combined theories, we can create flags by specifying the relations separately\n",
+ "test_flag = CombinedTheory(3, edges=[[0, 1], [0, 2]], edges3=[[0, 1, 2]])\n",
+ "\n",
+ "# Patterns work too\n",
+ "test_pattern = CombinedTheory.p(4, edges=[[0, 1]], edges_m=[[1, 2]], edges3=[[0, 1, 2]], edges3_m=[[1, 2, 3]])\n",
+ "\n",
+ "# See what happens if we exclude these\n",
+ "CombinedTheory.exclude([test_flag, test_pattern])\n",
+ "CombinedTheory.generate(4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 73,
+ "id": "ad5027ed-df99-4f6e-a035-04b49f54b727",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Without symmetry, we have the structures: \n",
+ "Flag on 2 points, ftype from () with edges=(), oedges=()\n",
+ "Flag on 2 points, ftype from () with edges=(), oedges=(01)\n",
+ "Flag on 2 points, ftype from () with edges=(01), oedges=()\n",
+ "Flag on 2 points, ftype from () with edges=(01), oedges=(01)\n",
+ "\n",
+ "\n",
+ "With symmetry, we have the structures: \n",
+ "Flag on 2 points, ftype from () with edges=(), oedges=()\n",
+ "Flag on 2 points, ftype from () with edges=(), oedges=(01)\n",
+ "Flag on 2 points, ftype from () with edges=(01), oedges=(01)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# If the relations combined have the same arity, all ordered or all unordered\n",
+ "# Then a symmetry can be specified\n",
+ "\n",
+ "Gp = Theory(\"OtherGraph\", relation_name=\"oedges\")\n",
+ "\n",
+ "# By default there is no symmetry. If we omit symmetries, then no symmetry is assumed\n",
+ "Test1 = combine(\"DoubleEdgeGraph\", G, Gp, symmetries=NoSymmetry)\n",
+ "Test2 = combine(\"SymmetricDoubleEdgeGraph\", G, Gp, symmetries=FullSymmetry)\n",
+ "\n",
+ "\n",
+ "print(\"Without symmetry, we have the structures: \")\n",
+ "print(\"\\n\".join(map(str, Test1.generate(2))))\n",
+ "print(\"\\n\\nWith symmetry, we have the structures: \")\n",
+ "print(\"\\n\".join(map(str, Test2.generate(2))))\n",
+ "# Note that as the edges and other edges are identical in the second case, \n",
+ "# the flag with only one of these present is included only once"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 79,
+ "id": "fc7d27e3-35e5-446b-957f-cba155707eb2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# For colors, there are pre-defined theories with different names.\n",
+ "# There are also pre-defined symmetry groups.\n",
+ "\n",
+ "Cyclic5Colors = combine(\"Cyclic5Colors\", Color0, Color1, Color2, Color3, Color4, symmetries=CyclicSymmetry(5))\n",
+ "Cyclic5Colors.exclude([\n",
+ " Cyclic5Colors(1), \n",
+ " Cyclic5Colors.p(1, C0=[0], C1=[0]),\n",
+ " Cyclic5Colors.p(1, C0=[0], C2=[0])\n",
+ "])\n",
+ "\n",
+ "\n",
+ "# There is also a symmetry for the k4m colors\n",
+ "NoK4mColors = combine(\"NoK4mColors\", Color0, Color1, Color2, Color3, Color4, Color5, symmetries=K4mSymmetry)\n",
+ "\n",
+ "# Then, these can be combined with other theories.\n",
+ "TT = combine(\"Cyclic5ColoredGraphs\", G, Cyclic5Colors)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 80,
+ "id": "4c801149-4402-42c7-8c55-7e4fcf51d073",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 3 points, ftype from () with edges=(), C0=(), C1=(), C2=(), C3=(), C4=(0 1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(), C2=(), C3=(), C4=(0 1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(02), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(02), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(12), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(), C2=(), C3=(), C4=(0 1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(02 12), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 12), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(02 12), C0=(), C1=(0), C2=(), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(), C2=(), C3=(), C4=(0 1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(), C2=(), C3=(0), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(), C2=(0), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(0), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(0), C1=(), C2=(), C3=(), C4=(1 2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(), C2=(0), C3=(1), C4=(2),\n",
+ " Flag on 3 points, ftype from () with edges=(01 02 12), C0=(), C1=(0), C2=(), C3=(1), C4=(2))"
+ ]
+ },
+ "execution_count": 80,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "TT.generate(3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 81,
+ "id": "1d6374cf-638a-470a-8f7f-77531ae48fdb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "(Flag on 3 points, ftype from () with C0=(), C1=(), C2=(0 1 2), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(), C2=(0 1 2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(), C2=(0 1 2), edges=(01 02 12),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(0), C2=(1 2), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(2), C2=(0 1), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(0), C2=(1 2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(1), C2=(0 2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(), C1=(0), C2=(1 2), edges=(01 02 12),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(), C2=(1 2), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(2), C1=(), C2=(0 1), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(), C2=(1 2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(1), C1=(), C2=(0 2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(), C2=(1 2), edges=(01 02 12),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(1), C2=(2), edges=(01),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(1), C2=(2), edges=(01 02),\n",
+ " Flag on 3 points, ftype from () with C0=(0), C1=(1), C2=(2), edges=(01 02 12))"
+ ]
+ },
+ "execution_count": 81,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Altough the exclusion can happen in the combined theories, \n",
+ "# it is also possible to exclude before combining. \n",
+ "# In that case, the exclusions at the moment they are combined are used.\n",
+ "\n",
+ "# Here is an example where Colors are combined, but they are forced to be disjoint first\n",
+ "Cyclic3Colors = combine(\"Cyclic3Colors\", Color0, Color1, Color2, symmetries=CyclicSymmetry(3))\n",
+ "# This guarantees the colors are disjoint (due to the symmetry)\n",
+ "Cyclic3Colors.exclude([\n",
+ " Cyclic3Colors(1), \n",
+ " Cyclic3Colors.p(1, C0=[0], C1=[0])\n",
+ "])\n",
+ "\n",
+ "# Make graphs without empty triples\n",
+ "G.exclude(G(3))\n",
+ "\n",
+ "Cyclic3ColGraph = combine(\"Cyclic3Graph\", Cyclic3Colors, G)\n",
+ "# So the resulting theory uses disjoint colors and has no empty graphs\n",
+ "Cyclic3ColGraph.generate(3)\n",
+ "\n",
+ "# Note the changing the excluded structures in a component will change the\n",
+ "# excluded structures in the result too, even after the combination.\n",
+ "\n",
+ "# Running the below would give a different result as above.\n",
+ "# G.reset()\n",
+ "# Cyclic3ColGraph.generate(3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 86,
+ "id": "098b34ff-6b53-44a9-b57f-3bbd86d8ced0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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",
+ "text/plain": [
+ "Graphics object consisting of 47 graphics primitives"
+ ]
+ },
+ "execution_count": 86,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Graph(K4mSymmetry).plot()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 82,
+ "id": "ccd5c66e-aedf-4433-882b-38978fa2cd35",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Theory for Cyclic3ColorsOther"
+ ]
+ },
+ "execution_count": 82,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# To specify any symmetry, we can provide a symmetry graph\n",
+ "\n",
+ "# For this example, I will just recreate the cyclic 3 symmetry.\n",
+ "# To provide cyclic 3 symmetry, we need a graph, where supposing that the first 3 vertices\n",
+ "# are mapped to the first 3 vertices, the automorphism group of those 3 vertices is C3\n",
+ "\n",
+ "# The graph with the following edge set works:\n",
+ "Cyclic3 = [[0, 1], [0, 3],[0, 6],\n",
+ " [3, 7], [0, 7], [1, 2],\n",
+ " [1, 4], [1, 7], [4, 8],\n",
+ " [1, 8], [2, 0], [2, 5],\n",
+ " [2, 8], [5, 6], [2, 6]]\n",
+ "\n",
+ "# Therefore it is possible to use it in later inputs\n",
+ "combine(\"Cyclic3ColorsOther\", Color0, Color1, Color2, symmetries=Cyclic3)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b1176d4e-4a1a-4510-a2a1-03957264571a",
+ "metadata": {},
+ "source": [
+ "Optimizing\n",
+ "==========\n",
+ "1. Assumptions\n",
+ "2. Rounding\n",
+ "3. Construction\n",
+ "4. Certificates\n",
+ "6. Exporting the SDP problem\n",
+ "8. Rounding over field extensions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 27,
+ "id": "cb3115c7-16f4-45af-8f1b-a9e7a3b2884d",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 121.86it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [2, -3, -2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5751371e+01 Ad: 7.93e-01 Dobj: -1.8596991e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3829292e+01 Ad: 9.44e-01 Dobj: -2.5639377e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -3.6548137e+00 Ad: 9.22e-01 Dobj: -2.8097573e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -6.7094195e-01 Ad: 8.42e-01 Dobj: -3.1080901e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -5.8576089e-01 Ad: 8.47e-01 Dobj: -4.8072710e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.0662032e-01 Ad: 8.78e-01 Dobj: -4.9275677e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.0069959e-01 Ad: 9.33e-01 Dobj: -4.9911657e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0005821e-01 Ad: 1.00e+00 Dobj: -4.9996202e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.0000265e-01 Ad: 1.00e+00 Dobj: -4.9999892e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000020e-01 Ad: 1.00e+00 Dobj: -4.9999999e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -5.0000001e-01 Ad: 9.90e-01 Dobj: -5.0000000e-01 \n",
+ "Iter: 12 Ap: 9.57e-01 Pobj: -5.0000000e-01 Ad: 9.69e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000000e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 6.40e-16 \n",
+ "Relative dual infeasibility: 1.18e-10 \n",
+ "Real Relative Gap: 2.59e-10 \n",
+ "XZ Relative Gap: 4.63e-10 \n",
+ "DIMACS error measures: 6.70e-16 0.00e+00 2.46e-10 0.00e+00 2.59e-10 4.63e-10\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.5000000004275386"
+ ]
+ },
+ "execution_count": 27,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# To run the optimizer, we can call the .optimize function on a theory\n",
+ "\n",
+ "G.reset()\n",
+ "G.exclude(triangle)\n",
+ "\n",
+ "# The basic requirement is to have a target we optimize for, and a target size\n",
+ "# By default it maximizes, here edges and target size is 3\n",
+ "G.optimize(edge, 3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 28,
+ "id": "a59aae0d-79c6-48df-a23d-480f82efea2f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 7\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 3]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 14.23it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 2, 1, -7, -2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.8806565e+01 Ad: 7.54e-01 Dobj: -6.9981849e-02 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.8408806e+01 Ad: 9.44e-01 Dobj: -5.3423421e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -8.9956342e+00 Ad: 8.73e-01 Dobj: -5.3133258e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.7394355e+00 Ad: 7.87e-01 Dobj: -5.3695578e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -9.4703414e-01 Ad: 8.73e-01 Dobj: -5.5895310e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -7.4536200e-01 Ad: 8.81e-01 Dobj: -6.3958028e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -6.7883373e-01 Ad: 8.58e-01 Dobj: -6.5988123e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -6.6776858e-01 Ad: 9.34e-01 Dobj: -6.6583853e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -6.6674852e-01 Ad: 9.94e-01 Dobj: -6.6663187e-01 \n",
+ "Iter: 10 Ap: 9.81e-01 Pobj: -6.6667197e-01 Ad: 1.00e+00 Dobj: -6.6666778e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -6.6666696e-01 Ad: 1.00e+00 Dobj: -6.6666691e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -6.6666668e-01 Ad: 9.79e-01 Dobj: -6.6666667e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -6.6666667e-01 Ad: 9.59e-01 Dobj: -6.6666667e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -6.6666667e-01 \n",
+ "Dual objective value: -6.6666667e-01 \n",
+ "Relative primal infeasibility: 1.34e-13 \n",
+ "Relative dual infeasibility: 1.52e-10 \n",
+ "Real Relative Gap: 1.08e-10 \n",
+ "XZ Relative Gap: 5.12e-10 \n",
+ "DIMACS error measures: 1.80e-13 0.00e+00 4.23e-10 0.00e+00 1.08e-10 5.12e-10\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.6666666672726631"
+ ]
+ },
+ "execution_count": 28,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# The target function can be any liner combination of flags\n",
+ "G.optimize(edge + G(3, edges=[[0, 1]]), 4)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 29,
+ "id": "90f162ca-a0f0-4d5e-91e5-af5ff1bb00d4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 22.42it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|████| 1/1 [00:00<00:00, 24.02it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0362520e+01 Ad: 7.29e-01 Dobj: -6.0813905e-02 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0324044e+01 Ad: 9.49e-01 Dobj: -1.4090950e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2792797e+01 Ad: 8.87e-01 Dobj: -1.4525769e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.6397236e+00 Ad: 8.16e-01 Dobj: -1.4716863e-01 \n",
+ "Iter: 5 Ap: 9.70e-01 Pobj: -5.7645242e-01 Ad: 8.89e-01 Dobj: -1.6649882e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -4.7413525e-01 Ad: 8.51e-01 Dobj: -2.9000569e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.7538701e-01 Ad: 7.49e-01 Dobj: -3.2184161e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -3.5709889e-01 Ad: 8.67e-01 Dobj: -3.4598987e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -3.5385657e-01 Ad: 1.00e+00 Dobj: -3.5309874e-01 \n",
+ "Iter: 10 Ap: 9.99e-01 Pobj: -3.5356572e-01 Ad: 1.00e+00 Dobj: -3.5353550e-01 \n",
+ "Iter: 11 Ap: 9.94e-01 Pobj: -3.5355424e-01 Ad: 1.00e+00 Dobj: -3.5355362e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -3.5355343e-01 Ad: 9.87e-01 Dobj: -3.5355340e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -3.5355339e-01 Ad: 9.57e-01 Dobj: -3.5355339e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -3.5355339e-01 \n",
+ "Dual objective value: -3.5355339e-01 \n",
+ "Relative primal infeasibility: 3.01e-15 \n",
+ "Relative dual infeasibility: 1.84e-09 \n",
+ "Real Relative Gap: 3.80e-10 \n",
+ "XZ Relative Gap: 3.51e-09 \n",
+ "DIMACS error measures: 3.27e-15 0.00e+00 4.99e-09 0.00e+00 3.80e-10 3.51e-09\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.35355339235478905"
+ ]
+ },
+ "execution_count": 29,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Assumptions\n",
+ "###\n",
+ "\n",
+ "# It is possible to add assumptions to the optimizer. \n",
+ "# Any linear combination of flags included will be assumed to be non-negative\n",
+ "\n",
+ "G.reset()\n",
+ "# Here positives=[ 1/2 - edge] asserts that 1/2-edge >= 0, so the density of edges is at most 1/2\n",
+ "# We maximize the number of triangles under this assumption\n",
+ "G.optimize(triangle, 4, positives=[ 1/2 - edge ])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "id": "41b4b0a6-e34f-4472-bde4-b6d389582559",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 204.10it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|████| 1/1 [00:00<00:00, 7.49it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -8]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0351247e+01 Ad: 7.28e-01 Dobj: 1.9241068e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0298857e+01 Ad: 9.49e-01 Dobj: -1.1331216e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2166750e+01 Ad: 8.89e-01 Dobj: -1.1297865e-01 \n",
+ "Iter: 4 Ap: 9.44e-01 Pobj: -3.5639778e+00 Ad: 7.81e-01 Dobj: -8.8235703e-02 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -1.0447222e+00 Ad: 8.33e-01 Dobj: -8.4380440e-02 \n",
+ "Iter: 6 Ap: 9.75e-01 Pobj: -3.1581467e-01 Ad: 8.64e-01 Dobj: -1.0555082e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.0596168e-01 Ad: 7.93e-01 Dobj: -2.0824259e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -2.5581926e-01 Ad: 8.13e-01 Dobj: -2.3309729e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -2.5041006e-01 Ad: 9.80e-01 Dobj: -2.4855614e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -2.5001867e-01 Ad: 1.00e+00 Dobj: -2.4993040e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -2.5000179e-01 Ad: 9.88e-01 Dobj: -2.4999529e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -2.5000054e-01 Ad: 1.00e+00 Dobj: -2.4999886e-01 \n",
+ "Iter: 13 Ap: 1.00e+00 Pobj: -2.5000007e-01 Ad: 1.00e+00 Dobj: -2.4999989e-01 \n",
+ "Iter: 14 Ap: 1.00e+00 Pobj: -2.5000000e-01 Ad: 1.00e+00 Dobj: -2.4999999e-01 \n",
+ "Iter: 15 Ap: 9.60e-01 Pobj: -2.5000000e-01 Ad: 9.54e-01 Dobj: -2.5000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -2.5000000e-01 \n",
+ "Dual objective value: -2.5000000e-01 \n",
+ "Relative primal infeasibility: 1.37e-14 \n",
+ "Relative dual infeasibility: 1.46e-10 \n",
+ "Real Relative Gap: 3.78e-10 \n",
+ "XZ Relative Gap: 7.90e-10 \n",
+ "DIMACS error measures: 1.49e-14 0.00e+00 3.95e-10 0.00e+00 3.78e-10 7.90e-10\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.2500000001939485"
+ ]
+ },
+ "execution_count": 30,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Can also add positive constraints involving types. Then this is assumed to hold for all type\n",
+ "\n",
+ "# The same optimization, but now we assume every vertex has relative degree at most 1/2\n",
+ "G.optimize_problem(G(3), 4, positives=[ 1/2 - G(2, ftype=[0]) ])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "id": "78d5c2bf-cf3d-4264-a155-6a797eeb45bc",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 303.06it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 1: 100%|████| 2/2 [00:00<00:00, 25.30it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.8017644e+01 Ad: 7.48e-01 Dobj: 3.1540360e+00 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.7888131e+01 Ad: 9.47e-01 Dobj: 6.8888413e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.0366074e+01 Ad: 8.82e-01 Dobj: 6.3185654e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.2050229e+00 Ad: 7.85e-01 Dobj: 6.4393155e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: 2.5269727e-01 Ad: 8.86e-01 Dobj: 6.3929369e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: 4.8301428e-01 Ad: 8.90e-01 Dobj: 6.1016973e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: 5.2082258e-01 Ad: 7.25e-01 Dobj: 5.7817993e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: 5.5020117e-01 Ad: 8.34e-01 Dobj: 5.6249091e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: 5.5504209e-01 Ad: 1.00e+00 Dobj: 5.5604574e-01 \n",
+ "Iter: 10 Ap: 9.94e-01 Pobj: 5.5553248e-01 Ad: 1.00e+00 Dobj: 5.5557653e-01 \n",
+ "Iter: 11 Ap: 9.91e-01 Pobj: 5.5555401e-01 Ad: 1.00e+00 Dobj: 5.5555607e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: 5.5555548e-01 Ad: 9.93e-01 Dobj: 5.5555558e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: 5.5555555e-01 Ad: 9.58e-01 Dobj: 5.5555556e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: 5.5555555e-01 \n",
+ "Dual objective value: 5.5555556e-01 \n",
+ "Relative primal infeasibility: 1.90e-14 \n",
+ "Relative dual infeasibility: 1.97e-09 \n",
+ "Real Relative Gap: 1.62e-09 \n",
+ "XZ Relative Gap: 3.64e-09 \n",
+ "DIMACS error measures: 2.75e-14 0.00e+00 5.35e-09 0.00e+00 1.62e-09 3.64e-09\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.5555555525754818"
+ ]
+ },
+ "execution_count": 31,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Listing multiple positivity assumptions works too.\n",
+ "# Also we can specify maximize=False, for a minimization problem\n",
+ "\n",
+ "# Minimize induced no edges, such that induced empty triple is at least 1/3 and \n",
+ "# induced triple with one edge is at least 1/3\n",
+ "G.optimize(G(2), 4, positives=[ G(3) - 1/3, G(3, edges=[[0, 1]]) - 1/3 ], maximize=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "id": "97b56bd7-71a8-4ab4-8613-d960ea14171f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 159.19it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [2, -3, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5751371e+01 Ad: 7.93e-01 Dobj: -1.8596991e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3829292e+01 Ad: 9.44e-01 Dobj: -2.5639371e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -3.6548572e+00 Ad: 9.22e-01 Dobj: -2.8097530e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -6.7102979e-01 Ad: 8.42e-01 Dobj: -3.1080769e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -5.8576247e-01 Ad: 8.44e-01 Dobj: -4.8002387e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.0647721e-01 Ad: 8.85e-01 Dobj: -4.9296240e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.0060022e-01 Ad: 9.41e-01 Dobj: -4.9923793e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0005080e-01 Ad: 1.00e+00 Dobj: -4.9996691e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.0000234e-01 Ad: 1.00e+00 Dobj: -4.9999913e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000016e-01 Ad: 1.00e+00 Dobj: -5.0000000e-01 \n",
+ "Iter: 11 Ap: 9.58e-01 Pobj: -5.0000001e-01 Ad: 9.70e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000001e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 4.18e-16 \n",
+ "Relative dual infeasibility: 3.98e-09 \n",
+ "Real Relative Gap: 2.49e-09 \n",
+ "XZ Relative Gap: 9.08e-09 \n",
+ "DIMACS error measures: 4.38e-16 0.00e+00 8.28e-09 0.00e+00 2.49e-09 9.08e-09\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The initial run gave an accurate looking construction\n",
+ "Rounded construction vector is: \n",
+ "Flag Algebra Element over Rational Field\n",
+ "3/4 - Flag on 3 points, ftype from () with edges=(01)\n",
+ "0 - Flag on 3 points, ftype from () with edges=(01 02)\n",
+ "1/4 - Flag on 3 points, ftype from () with edges=(01 02 12)\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [1, -3, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.2107440e+01 Ad: 8.40e-01 Dobj: 6.1913653e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.0543280e+01 Ad: 9.38e-01 Dobj: -1.3876525e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -2.4595774e+00 Ad: 9.42e-01 Dobj: -1.9392703e-01 \n",
+ "Iter: 4 Ap: 9.51e-01 Pobj: -6.1484578e-01 Ad: 8.50e-01 Dobj: -2.3934806e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -6.4529870e-01 Ad: 7.42e-01 Dobj: -4.8596320e-01 \n",
+ "Iter: 6 Ap: 9.78e-01 Pobj: -5.0782840e-01 Ad: 8.89e-01 Dobj: -4.9454657e-01 \n",
+ "Iter: 7 Ap: 9.90e-01 Pobj: -5.0036064e-01 Ad: 9.65e-01 Dobj: -4.9953666e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0002547e-01 Ad: 1.00e+00 Dobj: -4.9997505e-01 \n",
+ "Iter: 9 Ap: 9.90e-01 Pobj: -5.0000127e-01 Ad: 1.00e+00 Dobj: -4.9999949e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000006e-01 Ad: 1.00e+00 Dobj: -4.9999997e-01 \n",
+ "Iter: 11 Ap: 9.70e-01 Pobj: -5.0000000e-01 Ad: 9.70e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000000e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 4.18e-16 \n",
+ "Relative dual infeasibility: 1.57e-09 \n",
+ "Real Relative Gap: 1.08e-09 \n",
+ "XZ Relative Gap: 3.47e-09 \n",
+ "DIMACS error measures: 4.38e-16 0.00e+00 3.20e-09 0.00e+00 1.08e-09 3.47e-09\n",
+ "Starting the rounding of the result\n",
+ "Rounding X matrices\n",
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 1/1 [00:00<00:00, 150.35it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Final rounded bound is 1/2\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1/2"
+ ]
+ },
+ "execution_count": 32,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Rounding\n",
+ "###\n",
+ "\n",
+ "# Specifying the optimizer `exact=True` tries to round the result\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "# Get exactly 1/2 for Mantel's theorem\n",
+ "G.optimize(G(2), 3, exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "id": "83876f79-a88e-4602-9b9c-5772997a1ce7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 255.93it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 196.38it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -8]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0351247e+01 Ad: 7.28e-01 Dobj: 1.9241068e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0298857e+01 Ad: 9.49e-01 Dobj: -1.1331216e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2166750e+01 Ad: 8.89e-01 Dobj: -1.1297865e-01 \n",
+ "Iter: 4 Ap: 9.44e-01 Pobj: -3.5639778e+00 Ad: 7.81e-01 Dobj: -8.8235703e-02 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -1.0447222e+00 Ad: 8.33e-01 Dobj: -8.4380440e-02 \n",
+ "Iter: 6 Ap: 9.75e-01 Pobj: -3.1581467e-01 Ad: 8.64e-01 Dobj: -1.0555082e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.0596168e-01 Ad: 7.93e-01 Dobj: -2.0824259e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -2.5581926e-01 Ad: 8.13e-01 Dobj: -2.3309729e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -2.5041006e-01 Ad: 9.80e-01 Dobj: -2.4855614e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -2.5001867e-01 Ad: 1.00e+00 Dobj: -2.4993040e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -2.5000179e-01 Ad: 9.88e-01 Dobj: -2.4999529e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -2.5000054e-01 Ad: 1.00e+00 Dobj: -2.4999886e-01 \n",
+ "Iter: 13 Ap: 1.00e+00 Pobj: -2.5000007e-01 Ad: 1.00e+00 Dobj: -2.4999989e-01 \n",
+ "Iter: 14 Ap: 1.00e+00 Pobj: -2.5000000e-01 Ad: 1.00e+00 Dobj: -2.4999999e-01 \n",
+ "Iter: 15 Ap: 9.60e-01 Pobj: -2.5000000e-01 Ad: 9.54e-01 Dobj: -2.5000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -2.5000000e-01 \n",
+ "Dual objective value: -2.5000000e-01 \n",
+ "Relative primal infeasibility: 1.37e-14 \n",
+ "Relative dual infeasibility: 1.46e-10 \n",
+ "Real Relative Gap: 3.78e-10 \n",
+ "XZ Relative Gap: 7.90e-10 \n",
+ "DIMACS error measures: 1.49e-14 0.00e+00 3.95e-10 0.00e+00 3.78e-10 7.90e-10\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The initial run gave an accurate looking construction\n",
+ "Rounded construction vector is: \n",
+ "Flag Algebra Element over Rational Field\n",
+ "1/8 - Flag on 4 points, ftype from () with edges=()\n",
+ "1/2 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "3/8 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [2, 1, 2, 1, -11, -8]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.6899685e+01 Ad: 7.65e-01 Dobj: 7.9442214e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.6923744e+01 Ad: 9.47e-01 Dobj: -7.7908927e-02 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.0216264e+01 Ad: 8.89e-01 Dobj: -8.5334539e-02 \n",
+ "Iter: 4 Ap: 9.71e-01 Pobj: -2.9740218e+00 Ad: 7.74e-01 Dobj: -6.3244542e-02 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -7.4813836e-01 Ad: 8.49e-01 Dobj: -6.3640609e-02 \n",
+ "Iter: 6 Ap: 9.71e-01 Pobj: -3.0977453e-01 Ad: 8.43e-01 Dobj: -9.2208413e-02 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.1279644e-01 Ad: 7.37e-01 Dobj: -1.9386842e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -2.5718711e-01 Ad: 8.00e-01 Dobj: -2.2633313e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -2.5046552e-01 Ad: 9.64e-01 Dobj: -2.4756687e-01 \n",
+ "Iter: 10 Ap: 9.98e-01 Pobj: -2.5002103e-01 Ad: 1.00e+00 Dobj: -2.4987493e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -2.5000133e-01 Ad: 9.97e-01 Dobj: -2.4999411e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -2.5000035e-01 Ad: 1.00e+00 Dobj: -2.4999906e-01 \n",
+ "Iter: 13 Ap: 1.00e+00 Pobj: -2.5000004e-01 Ad: 1.00e+00 Dobj: -2.4999988e-01 \n",
+ "Iter: 14 Ap: 1.00e+00 Pobj: -2.5000000e-01 Ad: 9.99e-01 Dobj: -2.4999999e-01 \n",
+ "Iter: 15 Ap: 9.60e-01 Pobj: -2.5000000e-01 Ad: 9.55e-01 Dobj: -2.5000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -2.5000000e-01 \n",
+ "Dual objective value: -2.5000000e-01 \n",
+ "Relative primal infeasibility: 1.35e-14 \n",
+ "Relative dual infeasibility: 1.40e-10 \n",
+ "Real Relative Gap: 3.45e-10 \n",
+ "XZ Relative Gap: 7.40e-10 \n",
+ "DIMACS error measures: 1.47e-14 0.00e+00 3.70e-10 0.00e+00 3.45e-10 7.40e-10\n",
+ "Starting the rounding of the result\n",
+ "Rounding X matrices\n",
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 2/2 [00:00<00:00, 139.46it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Final rounded bound is 1/4\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1/4"
+ ]
+ },
+ "execution_count": 33,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# If the optimal construction is well-behaved, this works well.\n",
+ "# From an earlier cell\n",
+ "G.reset()\n",
+ "G.optimize_problem(G(3), 4, positives=[ 1/2 - G(2, ftype=[0]) ], exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "id": "e3d108b6-62c2-46d6-a1e6-326143e95562",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 164.44it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 269.64it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0362520e+01 Ad: 7.29e-01 Dobj: -6.0813905e-02 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0324044e+01 Ad: 9.49e-01 Dobj: -1.4090950e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2792797e+01 Ad: 8.87e-01 Dobj: -1.4525769e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.6397236e+00 Ad: 8.16e-01 Dobj: -1.4716863e-01 \n",
+ "Iter: 5 Ap: 9.70e-01 Pobj: -5.7645242e-01 Ad: 8.89e-01 Dobj: -1.6649882e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -4.7413525e-01 Ad: 8.51e-01 Dobj: -2.9000569e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.7538701e-01 Ad: 7.49e-01 Dobj: -3.2184161e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -3.5709889e-01 Ad: 8.67e-01 Dobj: -3.4598987e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -3.5385657e-01 Ad: 1.00e+00 Dobj: -3.5309874e-01 \n",
+ "Iter: 10 Ap: 9.99e-01 Pobj: -3.5356572e-01 Ad: 1.00e+00 Dobj: -3.5353550e-01 \n",
+ "Iter: 11 Ap: 9.94e-01 Pobj: -3.5355424e-01 Ad: 1.00e+00 Dobj: -3.5355362e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -3.5355343e-01 Ad: 9.87e-01 Dobj: -3.5355340e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -3.5355339e-01 Ad: 9.57e-01 Dobj: -3.5355339e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -3.5355339e-01 \n",
+ "Dual objective value: -3.5355339e-01 \n",
+ "Relative primal infeasibility: 3.01e-15 \n",
+ "Relative dual infeasibility: 1.84e-09 \n",
+ "Real Relative Gap: 3.80e-10 \n",
+ "XZ Relative Gap: 3.51e-09 \n",
+ "DIMACS error measures: 3.27e-15 0.00e+00 4.99e-09 0.00e+00 3.80e-10 3.51e-09\n",
+ "The initial run didn't provide an accurate construction\n",
+ "Rounding X matrices\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 4/4 [00:00<00:00, 481.05it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 2/2 [00:00<00:00, 322.99it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The rounded result is -363/1024\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "363/1024"
+ ]
+ },
+ "execution_count": 34,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# If the solution does not look simple, then an approximate result is returned (usually)\n",
+ "G.reset()\n",
+ "G.optimize(triangle, 4, positives=[ 1/2 - edge ], exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "id": "a1d43d8f-7880-403a-b81b-dfcfe01cfee1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 194.11it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 184.11it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -4]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0362520e+01 Ad: 7.29e-01 Dobj: -6.0813905e-02 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0324044e+01 Ad: 9.49e-01 Dobj: -1.4090950e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2792797e+01 Ad: 8.87e-01 Dobj: -1.4525769e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.6397236e+00 Ad: 8.16e-01 Dobj: -1.4716863e-01 \n",
+ "Iter: 5 Ap: 9.70e-01 Pobj: -5.7645242e-01 Ad: 8.89e-01 Dobj: -1.6649882e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -4.7413525e-01 Ad: 8.51e-01 Dobj: -2.9000569e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.7538701e-01 Ad: 7.49e-01 Dobj: -3.2184161e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -3.5709889e-01 Ad: 8.67e-01 Dobj: -3.4598987e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -3.5385657e-01 Ad: 1.00e+00 Dobj: -3.5309874e-01 \n",
+ "Iter: 10 Ap: 9.99e-01 Pobj: -3.5356572e-01 Ad: 1.00e+00 Dobj: -3.5353550e-01 \n",
+ "Iter: 11 Ap: 9.94e-01 Pobj: -3.5355424e-01 Ad: 1.00e+00 Dobj: -3.5355362e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -3.5355343e-01 Ad: 9.87e-01 Dobj: -3.5355340e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -3.5355339e-01 Ad: 9.57e-01 Dobj: -3.5355339e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -3.5355339e-01 \n",
+ "Dual objective value: -3.5355339e-01 \n",
+ "Relative primal infeasibility: 3.01e-15 \n",
+ "Relative dual infeasibility: 1.84e-09 \n",
+ "Real Relative Gap: 3.80e-10 \n",
+ "XZ Relative Gap: 3.51e-09 \n",
+ "DIMACS error measures: 3.27e-15 0.00e+00 4.99e-09 0.00e+00 3.80e-10 3.51e-09\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The initial run didn't provide an accurate construction\n",
+ "Rounding X matrices\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|█████████████████████████████████████| 4/4 [00:00<00:00, 1033.78it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 2/2 [00:00<00:00, 247.22it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The rounded result is -741457/2097152\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "741457/2097152"
+ ]
+ },
+ "execution_count": 35,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# This can be refined, to use higher denominator\n",
+ "G.reset()\n",
+ "G.optimize(triangle, 4, positives=[ 1/2 - edge ], exact=True, denom=1024*1024)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "id": "bb54c471-cc89-4e1b-a3ff-c2e96291b828",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|████████████████████████████████████| 15/15 [00:00<00:00, 130.38it/s]\n",
+ "100%|███████████████████████████████████| 15/15 [00:00<00:00, 1287.68it/s]\n",
+ "100%|████████████████████████████████████| 15/15 [00:00<00:00, 195.67it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 10\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 21.87it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [2, 1, 2, 1, -10, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5796684e+01 Ad: 7.85e-01 Dobj: 6.8056615e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.5707513e+01 Ad: 9.45e-01 Dobj: -3.2226608e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -9.0891397e+00 Ad: 8.85e-01 Dobj: -3.7904920e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.2516642e+00 Ad: 7.96e-01 Dobj: -3.9403156e-01 \n",
+ "Iter: 5 Ap: 9.85e-01 Pobj: -7.9859298e-01 Ad: 8.64e-01 Dobj: -4.2692015e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -7.9752891e-01 Ad: 8.05e-01 Dobj: -5.9421260e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -6.7904726e-01 Ad: 8.09e-01 Dobj: -6.3478376e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -6.6818656e-01 Ad: 8.96e-01 Dobj: -6.6029120e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -6.6678401e-01 Ad: 1.00e+00 Dobj: -6.6632552e-01 \n",
+ "Iter: 10 Ap: 9.99e-01 Pobj: -6.6667180e-01 Ad: 1.00e+00 Dobj: -6.6665332e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -6.6666722e-01 Ad: 9.98e-01 Dobj: -6.6666650e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -6.6666670e-01 Ad: 9.85e-01 Dobj: -6.6666661e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -6.6666667e-01 Ad: 9.56e-01 Dobj: -6.6666667e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -6.6666667e-01 \n",
+ "Dual objective value: -6.6666667e-01 \n",
+ "Relative primal infeasibility: 4.28e-15 \n",
+ "Relative dual infeasibility: 1.57e-09 \n",
+ "Real Relative Gap: 1.31e-09 \n",
+ "XZ Relative Gap: 3.42e-09 \n",
+ "DIMACS error measures: 6.09e-15 0.00e+00 4.20e-09 0.00e+00 1.31e-09 3.42e-09\n",
+ "Starting the rounding of the result\n",
+ "Rounding X matrices\n",
+ "Linear coefficient is negative: -329/512\n",
+ "The result will be worse than expected.\n",
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 2/2 [00:00<00:00, 105.27it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Final rounded bound is 2/3\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "2/3"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Construction\n",
+ "###\n",
+ "\n",
+ "# When the optimizer fails to give a correct looking construction,\n",
+ "# It is possible to provide it directly\n",
+ "\n",
+ "# Theory.blowup_construction(target_size, construction_size, **relations)\n",
+ "# provides a way to specify blowups of simple patterns\n",
+ "\n",
+ "G.reset()\n",
+ "\n",
+ "# Here is a tripartite graph, with target size 4\n",
+ "trip_constr = G.blowup_construction(4, 3, edges=[[0, 1], [0, 2], [1, 2]])\n",
+ "\n",
+ "#Here is the complement of that graph. So only edges inside the parts\n",
+ "trip_constr_compl = G.blowup_construction(4, 3, edges=[[0, 0], [1, 1], [2, 2]])\n",
+ "\n",
+ "G.reset()\n",
+ "G.exclude(G(4))\n",
+ "# Note the constructions above were for Graphs with perhaps a different excluded structure.\n",
+ "# We need to construct it again for K4-free graphs\n",
+ "trip_constr_compl = G.blowup_construction(4, 3, edges=[[0, 0], [1, 1], [2, 2]])\n",
+ "G.optimize(G(2), 4, construction=trip_constr_compl, exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "id": "0ca869fb-903b-40ed-92c4-fd9de7341bb1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|████████████████████████████████████| 15/15 [00:00<00:00, 218.50it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Symbolic construction: \n",
+ " Flag Algebra Element over Multivariate Polynomial Ring in X0, X1, X2 over Rational Field\n",
+ "X0^4 + X1^4 + X2^4 - Flag on 4 points, ftype from () with edges=()\n",
+ "4*X0^3*X1 + 4*X0*X1^3 + 4*X0^3*X2 + 4*X1^3*X2 + 4*X0*X2^3 + 4*X1*X2^3 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "6*X0^2*X1^2 + 6*X0^2*X2^2 + 6*X1^2*X2^2 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "12*X0^2*X1*X2 + 12*X0*X1^2*X2 + 12*X0*X1*X2^2 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13)\n",
+ "\n",
+ "\n",
+ "Construction at another point: \n",
+ " Flag Algebra Element over Rational Field\n",
+ "49/648 - Flag on 4 points, ftype from () with edges=()\n",
+ "59/162 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "49/216 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "1/3 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13)\n",
+ "\n",
+ "\n",
+ "Construction after sum set and diff: \n",
+ " Flag Algebra Element over Multivariate Polynomial Ring in X0, X1, X2 over Rational Field\n",
+ "8*X0^3 + 12*X0^2*X1 + 12*X0*X1^2 + 4*X1^3 - 12*X0^2 - 24*X0*X1 - 12*X1^2 + 12*X0 + 12*X1 - 4 - Flag on 4 points, ftype from () with edges=()\n",
+ "-32*X0^3 - 48*X0^2*X1 - 48*X0*X1^2 - 16*X1^3 + 48*X0^2 + 72*X0*X1 + 36*X1^2 - 24*X0 - 24*X1 + 4 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "24*X0^3 + 36*X0^2*X1 + 36*X0*X1^2 + 12*X1^3 - 36*X0^2 - 24*X0*X1 - 12*X1^2 + 12*X0 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "-24*X0*X1 - 12*X1^2 + 12*X1 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13) \n",
+ "\n",
+ "\n",
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 691.84it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 270.46it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [1, 1, 1, -11, -3]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.3408760e+01 Ad: 8.19e-01 Dobj: -6.3955233e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3318852e+01 Ad: 9.28e-01 Dobj: -4.5726524e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -8.1795916e+00 Ad: 7.94e-01 Dobj: -4.2808208e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -1.9104284e+00 Ad: 8.04e-01 Dobj: -4.1569778e-01 \n",
+ "Iter: 5 Ap: 9.55e-01 Pobj: -6.9836150e-01 Ad: 8.94e-01 Dobj: -4.4765946e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.9772430e-01 Ad: 9.53e-01 Dobj: -5.4377973e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.7362396e-01 Ad: 9.01e-01 Dobj: -5.6678083e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.7155774e-01 Ad: 9.90e-01 Dobj: -5.7120497e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.7143532e-01 Ad: 1.00e+00 Dobj: -5.7141901e-01 \n",
+ "Iter: 10 Ap: 9.84e-01 Pobj: -5.7142917e-01 Ad: 1.00e+00 Dobj: -5.7142841e-01 \n",
+ "Iter: 11 Ap: 1.00e+00 Pobj: -5.7142860e-01 Ad: 9.74e-01 Dobj: -5.7142855e-01 \n",
+ "Iter: 12 Ap: 9.59e-01 Pobj: -5.7142857e-01 Ad: 9.59e-01 Dobj: -5.7142857e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.7142857e-01 \n",
+ "Dual objective value: -5.7142857e-01 \n",
+ "Relative primal infeasibility: 1.31e-15 \n",
+ "Relative dual infeasibility: 1.59e-09 \n",
+ "Real Relative Gap: 7.87e-10 \n",
+ "XZ Relative Gap: 2.83e-09 \n",
+ "DIMACS error measures: 1.89e-15 0.00e+00 3.74e-09 0.00e+00 7.87e-10 2.83e-09\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.5714285725860463"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# It is possible to create symbolic constructions\n",
+ "G.reset()\n",
+ "trip_constr = G.blowup_construction(4, 3, edges=[[0, 1], [0, 2], [1, 2]], symbolic=True)\n",
+ "\n",
+ "# Here the construction is a flag over the rationals extended with 3 variables X0, X1, X2 (one for each part)\n",
+ "print(\"Symbolic construction: \\n\", trip_constr)\n",
+ "\n",
+ "# To evaluate it at a given point, we can call\n",
+ "eval_constr = trip_constr.subs([1/2, 1/3, 1/6])\n",
+ "print(\"\\n\\nConstruction at another point: \\n\", eval_constr)\n",
+ "\n",
+ "# It is also possible to force the sum to be 1\n",
+ "sumset_constr = trip_constr.set_sum()\n",
+ "\n",
+ "# We can also differentiate each variable a given number of times\n",
+ "der_sumset_constr = sumset_constr.derivative([1, 0, 0])\n",
+ "print(\"\\n\\nConstruction after sum set and diff: \\n\", der_sumset_constr, \"\\n\\n\")\n",
+ "\n",
+ "# Probably the most useful function is to differentiate in all possible way and\n",
+ "# Substitute at a given point\n",
+ "# This returns a list of constructions (with some scaling)\n",
+ "constrs = trip_constr.derivatives([1/2, 1/3, 1/6])\n",
+ "# It is possible to pass multiple constructions to the optimizer\n",
+ "G.optimize(G(2), 4, construction=constrs, positives=[-G(3)])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "id": "74d34638-ba0e-44ce-8386-d307aebcb8cf",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 505.22it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [2, -3, -2]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5751371e+01 Ad: 7.93e-01 Dobj: -1.8596991e-01 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.3829292e+01 Ad: 9.44e-01 Dobj: -2.5639371e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -3.6548572e+00 Ad: 9.22e-01 Dobj: -2.8097530e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -6.7102979e-01 Ad: 8.42e-01 Dobj: -3.1080769e-01 \n",
+ "Iter: 5 Ap: 1.00e+00 Pobj: -5.8576247e-01 Ad: 8.44e-01 Dobj: -4.8002387e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -5.0647721e-01 Ad: 8.85e-01 Dobj: -4.9296240e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -5.0060022e-01 Ad: 9.41e-01 Dobj: -4.9923793e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -5.0005080e-01 Ad: 1.00e+00 Dobj: -4.9996691e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -5.0000234e-01 Ad: 1.00e+00 Dobj: -4.9999913e-01 \n",
+ "Iter: 10 Ap: 1.00e+00 Pobj: -5.0000016e-01 Ad: 1.00e+00 Dobj: -5.0000000e-01 \n",
+ "Iter: 11 Ap: 9.58e-01 Pobj: -5.0000001e-01 Ad: 9.70e-01 Dobj: -5.0000000e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -5.0000001e-01 \n",
+ "Dual objective value: -5.0000000e-01 \n",
+ "Relative primal infeasibility: 4.18e-16 \n",
+ "Relative dual infeasibility: 3.98e-09 \n",
+ "Real Relative Gap: 2.49e-09 \n",
+ "XZ Relative Gap: 9.08e-09 \n",
+ "DIMACS error measures: 4.38e-16 0.00e+00 8.28e-09 0.00e+00 2.49e-09 9.08e-09\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "0.5000000069458577"
+ ]
+ },
+ "execution_count": 38,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Certificates\n",
+ "###\n",
+ "\n",
+ "# The optimizer can save the certificates to a provided file\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "# The format could be either an exact rational solution, or an approximate like this\n",
+ "G.optimize(G(2), 3, file=\"test\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 39,
+ "id": "a348d7a4-ef0b-49e4-9a30-296380046620",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking X matrices\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "1it [00:00, 93.17it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Solution matrices are all positive semidefinite, linear coefficients are all non-negative\n",
+ "Calculating multiplication tables\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "1it [00:00, 826.63it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Done calculating linear constraints\n",
+ "Calculating the bound provided by the certificate\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "1it [00:00, 772.86it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The solution is valid, it proves the bound 29780781672501568/59561562885324537\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "29780781672501568/59561562885324537"
+ ]
+ },
+ "execution_count": 39,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# A given certificate can be verified\n",
+ "\n",
+ "# Note that the problem we verify the certificate for has to be the same\n",
+ "# This includes target value, target size, positives, excluded structures.\n",
+ "# The construction used for rounding is not needed here\n",
+ "\n",
+ "# For now, this transforms everything to a rational\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "G.verify(\"test\", G(2), 3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 40,
+ "id": "dd48bad4-194a-45a3-b484-e814236b29ae",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 3\n",
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [2]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 1 points with edges=(): : 1it [00:00, 206.43it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n",
+ "Relevant ftypes up to size 4\n",
+ " [(3, Ftype on 2 points with edges=(), 4), (3, Ftype on 2 points with edges=(01), 4)]\n",
+ "Base flags generated, their number is 7\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The relevant ftypes are constructed, their number is 1\n",
+ "Block sizes before symmetric/asymmetric change is applied: [3]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(): : 1it [00:00, 22.33it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n",
+ "Constraints finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Exporting the SDP problem\n",
+ "###\n",
+ "\n",
+ "# It is possible to create an SDP problem instance and solve that on a different machine\n",
+ "G.reset()\n",
+ "G.exclude(G(3))\n",
+ "# The parameters are the same as for the regular optimize, \n",
+ "# except it can't do an exact solution and requires a file name to write the problem to.\n",
+ "G.external_optimize(G(2), 3, file=\"problem\")\n",
+ "\n",
+ "# If the problem is too large, it is possible to export with only a few types.\n",
+ "# The following command lists the relevant ftypes appearing in an optimization with target size 4\n",
+ "print(\"Relevant ftypes up to size 4\\n\", G._get_relevant_ftypes(4))\n",
+ "\n",
+ "# Choosing a subset of this can be passed to the external optimize like this\n",
+ "\n",
+ "types_relevant = [G._get_relevant_ftypes(4)[0]]\n",
+ "G.external_optimize(G(2), 4, file=\"problem_2\", specific_ftype=types_relevant)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 41,
+ "id": "814fef6b-9040-4446-907f-da2d2242e7f4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 197.44it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 324.29it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Running sdp without construction. Used block sizes are [3, 1, 3, 1, -11, -4]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Iter: 1 Ap: 1.00e+00 Pobj: -2.0362520e+01 Ad: 7.29e-01 Dobj: -6.0813905e-02 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -2.0324044e+01 Ad: 9.49e-01 Dobj: -1.4090943e-01 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -1.2792868e+01 Ad: 8.87e-01 Dobj: -1.4525769e-01 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -2.6397598e+00 Ad: 8.16e-01 Dobj: -1.4716860e-01 \n",
+ "Iter: 5 Ap: 9.72e-01 Pobj: -5.7583916e-01 Ad: 8.88e-01 Dobj: -1.6648294e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -4.7426918e-01 Ad: 8.51e-01 Dobj: -2.9014271e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.7535668e-01 Ad: 7.50e-01 Dobj: -3.2194950e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -3.5708921e-01 Ad: 8.67e-01 Dobj: -3.4601216e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -3.5385242e-01 Ad: 1.00e+00 Dobj: -3.5310489e-01 \n",
+ "Iter: 10 Ap: 9.99e-01 Pobj: -3.5356555e-01 Ad: 1.00e+00 Dobj: -3.5353581e-01 \n",
+ "Iter: 11 Ap: 9.94e-01 Pobj: -3.5355424e-01 Ad: 1.00e+00 Dobj: -3.5355363e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -3.5355343e-01 Ad: 9.87e-01 Dobj: -3.5355340e-01 \n",
+ "Iter: 13 Ap: 9.59e-01 Pobj: -3.5355339e-01 Ad: 9.57e-01 Dobj: -3.5355339e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -3.5355339e-01 \n",
+ "Dual objective value: -3.5355339e-01 \n",
+ "Relative primal infeasibility: 1.49e-15 \n",
+ "Relative dual infeasibility: 1.83e-09 \n",
+ "Real Relative Gap: 3.80e-10 \n",
+ "XZ Relative Gap: 3.49e-09 \n",
+ "DIMACS error measures: 1.61e-15 0.00e+00 4.96e-09 0.00e+00 3.80e-10 3.49e-09\n",
+ "The initial run didn't provide an accurate construction\n",
+ "Rounding X matrices\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 4/4 [00:00<00:00, 942.70it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|██████████████████████████████████████| 2/2 [00:00<00:00, 238.29it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "The rounded result is -363/1024\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "363/1024"
+ ]
+ },
+ "execution_count": 41,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "###\n",
+ "### Rounding over larger fields\n",
+ "###\n",
+ "\n",
+ "# It is possible to specify a field extension of the rational\n",
+ "# And perform the rounding there\n",
+ "\n",
+ "# Recall the problem\n",
+ "G.reset()\n",
+ "G.optimize(G(3), 4, positives=[ 1/2 - G(2) ], exact=True)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 42,
+ "id": "ed323bdd-46c1-461a-a7f3-fae76c1c050c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "16it [00:00, 1226.27it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Base flags generated, their number is 11\n",
+ "The relevant ftypes are constructed, their number is 2\n",
+ "Block sizes before symmetric/asymmetric change is applied: [4, 4]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with mult table for Ftype on 2 points with edges=(01): : 2it [00:00, 840.79it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tables finished\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Done with positivity constraint 0: 100%|███| 1/1 [00:00<00:00, 301.23it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Constraints finished\n",
+ "Adjusting table with kernels from construction\n",
+ "Running SDP after kernel correction. Used block sizes are [2, 1, 1, -11, -4]\n",
+ "CSDP 6.2.0\n",
+ "Iter: 0 Ap: 0.00e+00 Pobj: 0.0000000e+00 Ad: 0.00e+00 Dobj: 0.0000000e+00 \n",
+ "Iter: 1 Ap: 1.00e+00 Pobj: -1.5710306e+01 Ad: 7.86e-01 Dobj: 1.5423449e+00 \n",
+ "Iter: 2 Ap: 1.00e+00 Pobj: -1.5648648e+01 Ad: 9.46e-01 Dobj: -2.6486804e-02 \n",
+ "Iter: 3 Ap: 1.00e+00 Pobj: -9.4555323e+00 Ad: 8.85e-01 Dobj: -8.6856597e-02 \n",
+ "Iter: 4 Ap: 1.00e+00 Pobj: -1.5831052e+00 Ad: 8.29e-01 Dobj: -9.5169021e-02 \n",
+ "Iter: 5 Ap: 9.58e-01 Pobj: -5.1183501e-01 Ad: 8.57e-01 Dobj: -1.2754711e-01 \n",
+ "Iter: 6 Ap: 1.00e+00 Pobj: -4.6540901e-01 Ad: 7.67e-01 Dobj: -2.6233982e-01 \n",
+ "Iter: 7 Ap: 1.00e+00 Pobj: -3.8486853e-01 Ad: 7.35e-01 Dobj: -3.0767273e-01 \n",
+ "Iter: 8 Ap: 1.00e+00 Pobj: -3.5735741e-01 Ad: 8.69e-01 Dobj: -3.4221005e-01 \n",
+ "Iter: 9 Ap: 1.00e+00 Pobj: -3.5381039e-01 Ad: 1.00e+00 Dobj: -3.5279892e-01 \n",
+ "Iter: 10 Ap: 9.95e-01 Pobj: -3.5356425e-01 Ad: 1.00e+00 Dobj: -3.5352377e-01 \n",
+ "Iter: 11 Ap: 9.92e-01 Pobj: -3.5355404e-01 Ad: 1.00e+00 Dobj: -3.5355307e-01 \n",
+ "Iter: 12 Ap: 1.00e+00 Pobj: -3.5355342e-01 Ad: 9.97e-01 Dobj: -3.5355338e-01 \n",
+ "Iter: 13 Ap: 9.60e-01 Pobj: -3.5355339e-01 Ad: 9.57e-01 Dobj: -3.5355339e-01 \n",
+ "Success: SDP solved\n",
+ "Primal objective value: -3.5355339e-01 \n",
+ "Dual objective value: -3.5355339e-01 \n",
+ "Relative primal infeasibility: 2.19e-15 \n",
+ "Relative dual infeasibility: 2.14e-09 \n",
+ "Real Relative Gap: 3.25e-10 \n",
+ "XZ Relative Gap: 4.23e-09 \n",
+ "DIMACS error measures: 2.37e-15 0.00e+00 5.18e-09 0.00e+00 3.25e-10 4.23e-09\n",
+ "Starting the rounding of the result\n",
+ "Rounding X matrices\n",
+ "Calculating resulting bound\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|███████████████████████████████████████| 2/2 [00:00<00:00, 98.88it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Final rounded bound is 1/4*sqrt2\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "1/4*sqrt2"
+ ]
+ },
+ "execution_count": 42,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# This, even when ran with exact=True fails to give a correct value, \n",
+ "# as the precise number is not rational\n",
+ "# to perform the same calculation in a larger field, we can specify it\n",
+ "\n",
+ "# Rationals extended with sqrt(2)\n",
+ "TargetRing = QQ[sqrt(2)]\n",
+ "\n",
+ "# Symbolic optimal construction \n",
+ "constr_symbolic = G.blowup_construction(4, 2, True, False, False, edges=[[0, 1], [1, 1]])\n",
+ "# Optimal construction in the extended field\n",
+ "constr = constr_symbolic.subs([1/sqrt(2), 1-(1/sqrt(2))], ring=TargetRing)\n",
+ "# Optimizer also in the extended field\n",
+ "G.optimize(G(3), 4, positives=[1/2 - G(2)], construction=constr, exact=True, ring=TargetRing)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "49e99676-293b-4d42-bfa7-3a70cb51d4dc",
+ "metadata": {},
+ "source": [
+ "Miscallenous\n",
+ "============"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 43,
+ "id": "8eec3474-134b-42e2-9dcb-eb03a2746059",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "###\n",
+ "### Useful functions \n",
+ "###\n",
+ "\n",
+ "\n",
+ "# for theories\n",
+ "G.empty() # provides the empty element of the theory\n",
+ "G.generate(3, G(2, ftype=[0, 1])) # generates flags with given size and type\n",
+ "G.exclude([G(3), G(2)]) # sets the excluded structures\n",
+ "G.reset() # resets the excluded structures\n",
+ "#G.clear() # clears all cached calculations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 44,
+ "id": "b346fa75-41b4-4c29-9885-164a35c3a057",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "True\n",
+ "True\n"
+ ]
+ }
+ ],
+ "source": [
+ "# For flags\n",
+ "\n",
+ "e2 = G(2, edges=[], ftype=[0])\n",
+ "\n",
+ "e2 + e2\n",
+ "e2 * e2\n",
+ "test = e2 << 2\n",
+ "test.size()\n",
+ "e2.blocks()\n",
+ "\n",
+ "pe2 = G(2, ftype=[])\n",
+ "print(pe2.afae() == pe2.project())\n",
+ "print(pe2.mul_project(pe2) == (pe2*pe2).project())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 45,
+ "id": "a01e66c4-3918-450b-b498-cae7e7f11538",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Flag Algebra with Ftype on 0 points with edges=() over Rational Field \n",
+ " Flag Algebra with Ftype on 1 points with edges=() over Rational Field \n",
+ " Flag Algebra with Ftype on 0 points with edges=() over Real Field with 53 bits of precision \n",
+ " Flag Algebra with Ftype on 0 points with edges=() over Univariate Polynomial Ring in x over Rational Field\n"
+ ]
+ }
+ ],
+ "source": [
+ "# The FlagAlgebra objects\n",
+ "\n",
+ "G = GraphTheory\n",
+ "alg = FlagAlgebra(G, QQ)\n",
+ "\n",
+ "point = G(1, ftype=[0])\n",
+ "alg_pointed = FlagAlgebra(G, QQ, point)\n",
+ "\n",
+ "alg_real = FlagAlgebra(G, RR)\n",
+ "\n",
+ "alg_poly = FlagAlgebra(G, QQ[\"x\"])\n",
+ "\n",
+ "print(alg, \"\\n\", alg_pointed, \"\\n\", alg_real, \"\\n\", alg_poly)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 46,
+ "id": "4f5b451e-f39b-47c8-8d94-6ef23a75a1c0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "Flag Algebra Element over Univariate Polynomial Ring in x over Rational Field\n",
+ "x^2 + 3/2*x + 1/2 - Flag on 4 points, ftype from () with edges=()\n",
+ "x^2 + 7/6*x + 1/4 - Flag on 4 points, ftype from () with edges=(01)\n",
+ "x^2 + 5/6*x - Flag on 4 points, ftype from () with edges=(01 03)\n",
+ "x^2 + 5/6*x + 1/3 - Flag on 4 points, ftype from () with edges=(02 13)\n",
+ "x^2 + 1/2*x - 1/4 - Flag on 4 points, ftype from () with edges=(01 02 03)\n",
+ "x^2 + 1/2*x - 1/4 - Flag on 4 points, ftype from () with edges=(01 03 13)\n",
+ "x^2 + 1/2*x + 1/12 - Flag on 4 points, ftype from () with edges=(01 02 13)\n",
+ "x^2 + 1/6*x - 1/6 - Flag on 4 points, ftype from () with edges=(01 02 03 13)\n",
+ "x^2 + 1/6*x + 1/6 - Flag on 4 points, ftype from () with edges=(02 03 12 13)\n",
+ "x^2 - 1/6*x - 1/12 - Flag on 4 points, ftype from () with edges=(01 02 03 12 13)\n",
+ "x^2 - 1/2*x - Flag on 4 points, ftype from () with edges=(01 02 03 12 13 23)"
+ ]
+ },
+ "execution_count": 46,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Calculations over polynomial rings\n",
+ "\n",
+ "x = alg_poly(x)\n",
+ "(x + G(2)) * (G(2, edge=[[0, 1]]) - 1/2 + x)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "SageMath 10.5.beta7",
+ "language": "sage",
+ "name": "sagemath"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.10.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/src/doc/en/reference/algebras/index.rst b/src/doc/en/reference/algebras/index.rst
index 621767627b5..a35ea119c90 100644
--- a/src/doc/en/reference/algebras/index.rst
+++ b/src/doc/en/reference/algebras/index.rst
@@ -71,6 +71,8 @@ Named associative algebras
sage/algebras/steenrod/steenrod_algebra_mult
sage/algebras/weyl_algebra
sage/algebras/yangian
+ sage/algebras/flag_algebras
+ sage/algebras/flag
Hecke algebras
--------------
diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst
index 6bc9946a442..80795883d25 100644
--- a/src/doc/en/reference/references/index.rst
+++ b/src/doc/en/reference/references/index.rst
@@ -1056,6 +1056,9 @@ REFERENCES:
Canadian Mathematical Society Proceedings, 2, Part 1.
Providence 1982. ISBN 978-0-8218-6003-8.
+.. [Bod2023] Levente Bodnár, *Generalized Tur\'an problem for Complete
+ Hypergraphs*, Preprint, :arxiv:`2302.07571`, (2023)
+
.. [Bond2007] P. Bonderson, Nonabelian anyons and interferometry,
Dissertation (2007). https://thesis.library.caltech.edu/2447/
@@ -1611,6 +1614,10 @@ REFERENCES:
.. [Conr] Keith Conrad, "Artin-Hasse-Type Series and Roots of Unity",
http://www.math.uconn.edu/~kconrad/blurbs/gradnumthy/AHrootofunity.pdf
+.. [CoRa2015] Leonardo Nagami Coregliano, Alexander A. Razborov, *On the
+ Density of Transitive Tournaments*, Preprint,
+ :arxiv:`1501.04074` (2015)
+
.. [Coron2023] Basile Coron *Supersolvability of built lattices and Koszulness
of generalized Chow rings*. Preprint, :arxiv:`2302.13072` (2023).
@@ -4379,6 +4386,11 @@ REFERENCES:
.. [Lin1999] \J. van Lint, Introduction to coding theory, 3rd ed.,
Springer-Verlag GTM, 86, 1999.
+.. [LiPf2021] Bernard Lidický, Florian Pfender, *Semidefinite
+ Programming and Ramsey Numbers*, SIAM Journal on
+ Discrete Mathematics, volume 35, 2021, pp. 2328--2344,
+ http://dx.doi.org/10.1137/18M1169473
+
.. [Liv1993] Charles Livingston, *Knot Theory*, Carus Mathematical
Monographs, number 24.
@@ -5562,6 +5574,10 @@ REFERENCES:
**75** (1997). 99-133. :arxiv:`math/9511223v1`.
http://www.ms.unimelb.edu.au/~ram/Publications/1997PLMSv75p99.pdf
+.. [Raz2007] Alexander \A. Razborov, *Flag algebras*, Journal of Symbolic Logic
+ Volume 72, 2007, pp. 1239--1282,
+ https://people.cs.uchicago.edu/~razborov/files/flag.pdf
+
.. [RCES1994] Ruskey, R. Cohen, P. Eades, A. Scott.
*Alley CATs in search of good homes.*
Congressus numerantium, 1994.
diff --git a/src/sage/algebras/all.py b/src/sage/algebras/all.py
index 0e995a677ec..c1c2191e7c2 100644
--- a/src/sage/algebras/all.py
+++ b/src/sage/algebras/all.py
@@ -29,6 +29,9 @@
from sage.algebras.quantum_groups.all import *
from sage.algebras.lie_conformal_algebras.all import *
+from sage.algebras.flag_algebras import *
+from sage.algebras.combinatorial_theory import *
+
# Algebra base classes
from sage.algebras.free_algebra import FreeAlgebra
from sage.algebras.free_algebra_quotient import FreeAlgebraQuotient
diff --git a/src/sage/algebras/combinatorial_theory.py b/src/sage/algebras/combinatorial_theory.py
new file mode 100644
index 00000000000..4c9871a1327
--- /dev/null
+++ b/src/sage/algebras/combinatorial_theory.py
@@ -0,0 +1,4021 @@
+r"""
+Implementation of flag algebras, with a class for combinatorial theories
+
+
+A combinatorial theory is any theory with universal axioms only,
+(therefore the elements satisfy a heredetary property). This
+implementation allows the construction of any such theory, and
+can perform flag algebraic computations on them. The theory of
+flag algebras is from [Raz2007]_
+
+To find out more about flags, how to create and manipulate them,
+see :mod:`sage.algebras.flag`. This docstring is for combinatorial
+theories and combinatorial optimization problems using flag algebras.
+
+The rest of this docstring will use `GraphTheory` since the number
+of structures is realtively small there. `Flag` docstring shows
+ways to create and calculate with flags. To create a flag we need to
+provide the vertex size and a list of elements for each signature.
+To create an edge flag for graphs, use ::
+
+ sage: e = GraphTheory(2, edges=[[0, 1]])
+
+To create a triangle `K_3` we can write ::
+
+ sage: k3 = GraphTheory(3, edges=[[0, 1], [0, 2], [1, 2]])
+
+If we have a theory, say GraphTheory, we can simply exclude structures
+with :func:`exclude`. This takes a list of flags or a single flag ::
+
+ sage: GraphTheory.exclude(k3)
+
+Excluding structures overwrites the theory, and in the future will only
+consider members without the excluded structures. So with the above,
+excluding a triangle will make GraphTheory not generate `k3`, or any larger
+flag with induced `k3` in it. We can check this by generating flags of size
+`4` ::
+
+ sage: GraphTheory.generate(4)
+ (Flag on 4 points, ftype from () with edges=(),
+ Flag on 4 points, ftype from () with edges=(01),
+ Flag on 4 points, ftype from () with edges=(01 03),
+ Flag on 4 points, ftype from () with edges=(02 13),
+ Flag on 4 points, ftype from () with edges=(01 02 03),
+ Flag on 4 points, ftype from () with edges=(01 02 13),
+ Flag on 4 points, ftype from () with edges=(02 03 12 13))
+
+Calling :func:`exclude` adds new structures to the list of flags we don't want
+to generate, and we don't want to appear in larger strctures as an induced
+subflag. To reset the theory and don't exclude anything, call :func:`reset`.
+
+To optimize the density of a flag (or linear combination of flags) in a theory,
+we can call :func:`optimize`. To try to find the maximum number of
+edges `e` in `k3` free graphs we can write ::
+
+ sage: x = GraphTheory.optimize(e, 3)
+ ...
+ Success: SDP solved
+ ...
+ sage: abs(x-0.5)<1e-6
+ True
+
+The second parameter, `optimize_problem(e, 3)` indicates the maximum size the
+program expands the flags. We can reset the excluded graphs, and try to minimize
+the density of triangles and empty triples ::
+
+ sage: GraphTheory.reset()
+ sage: e3 = GraphTheory(3)
+ sage: x = GraphTheory.optimize(e3+k3, 3, maximize=False)
+ ...
+ sage: abs(x-0.25)<1e-6
+ True
+
+The :func:`optimize` function requires csdpy, an sdp solver. But it is possible
+to get these relations directly, by expressing the inequalities
+as a sum of squares ::
+
+ sage: GraphTheory.reset()
+ sage: GraphTheory.exclude(k3)
+ sage: pe = GraphTheory(2, ftype=[0]) - 1/2
+ sage: 1/2 >= pe.mul_project(pe) * 2 + e
+ True
+
+:func:`mul_project` is short for multiplication and projection, the
+non-negativity of self multiplication is preserved after projection so
+`pe.mul_project(pe)` is non-negative on all large enough structures
+giving that `1/2` is larger than `e` in all large enough structures.
+
+The following longer example shows that the density of K^3_4 is always
+less than 3/8 in K^3_5-free hypergraphs. It uses the ThreeGraphTheory
+object to deal with 3-uniform hypergraphs and hand-picked squares.
+These values come from [Bod2023]_::
+
+ sage: em5 = ThreeGraphTheory(5, edges=[])
+ sage: ThreeGraphTheory.exclude(em5)
+
+ sage: em4 = ThreeGraphTheory(4, edges=[])
+
+ sage: em3p1 = ThreeGraphTheory(3, ftype_points=[0], edges=[])
+ sage: sq1 = (em3p1 - 3/4).mul_project(em3p1 - 3/4) # todo: not implemented
+
+ sage: la4p2 = ThreeGraphTheory(4, ftype_points=[0, 1], edges=[[0, 2, 3]])
+ sage: lb4p2 = ThreeGraphTheory(4, ftype_points=[0, 1], edges=[[1, 2, 3]])
+ sage: sq2 = (la4p2 - lb4p2).mul_project(la4p2 - lb4p2) # todo: not implemented
+
+ sage: ma4p3 = ThreeGraphTheory(4, ftype_points=[0, 1, 2], edges=[[0, 1, 3]])
+ sage: mb4p3 = ThreeGraphTheory(4, ftype_points=[0, 1, 2], edges=[[0, 2, 3]])
+ sage: mc4p3 = ThreeGraphTheory(4, ftype_points=[0, 1, 2], edges=[[1, 2, 3]])
+ sage: sq3 = (ma4p3 + mb4p3 + mc4p3 - 1/2).mul_project(ma4p3 + mb4p3 + mc4p3 - 1/2) # todo: not implemented
+
+ sage: em4p3 = ThreeGraphTheory(4, ftype_points=[0, 1, 2])
+ sage: sq4 = (em4p3 - 1/2).mul_project(em4p3 - 1/2) # todo: not implemented
+
+ sage: n5q4 = ThreeGraphTheory(5, ftype_points=[0, 1, 2, 3], edges=[[0, 1, 2]])
+ sage: sq5 = (n5q4 - 1/2).mul_project(n5q4 - 1/2) # todo: not implemented
+
+ sage: oa5t4 = ThreeGraphTheory(5, ftype_points=[0, 1, 2, 3], edges=[[0, 1, 4]])
+ sage: ob5t4 = ThreeGraphTheory(5, ftype_points=[0, 1, 2, 3], edges=[[2, 3, 4]])
+ sage: sq6 = (oa5t4 - ob5t4).mul_project(oa5t4 - ob5t4) # todo: not implemented
+
+ sage: sos = 2/3 * sq1 + 1/6 * sq2 + 13/12 * sq3 + 11/12 * sq4 + 2 * sq5 + 1/2 * sq6 # todo: not implemented
+ sage: 3/8 >= sos + em4 # todo: not implemented
+ True
+
+
+.. SEEALSO::
+ :func:`CombinatorialTheory.__init__`
+ :func:`CombinatorialTheory.exclude`
+ :func:`CombinatorialTheory.optimize_problem`
+ :func:`CombinatorialTheory.generate_flags`
+
+AUTHORS:
+
+- Levente Bodnar (2023-2025): Main development
+
+"""
+
+# ****************************************************************************
+# Copyright (C) 2023 LEVENTE BODNAR
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+# https://www.gnu.org/licenses/
+# ****************************************************************************
+
+import itertools
+
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.structure.parent import Parent
+from sage.all import QQ, NN, Integer, ZZ, infinity, RR
+from sage.algebras.flag import BuiltFlag, ExoticFlag, Pattern, inductive_generator, overlap_generator
+from sage.algebras.flag_algebras import FlagAlgebra, FlagAlgebraElement
+
+from sage.categories.sets_cat import Sets
+from sage.all import vector, matrix, diagonal_matrix
+
+from sage.misc.prandom import randint
+from sage.arith.misc import falling_factorial, binomial, factorial
+from sage.misc.functional import round
+from functools import lru_cache
+
+import hashlib
+import pickle
+import os
+from tqdm import tqdm
+
+# This should really be in the doctests
+def test_generate():
+ def test_theory(TT, nstart, nend, vals):
+ print("\nTesting {}".format(str(TT)))
+ for ii,jj in enumerate(range(nstart, nend+1)):
+ print("Size {}, the number is {} (should be {})".format(
+ jj,
+ len(TT.generate(jj)),
+ vals[ii]
+ ))
+
+ CG = combine("CGraph", Color0, GraphTheory)
+ Cs = combine("Cs", Color0, Color1, symmetries=True)
+ CG.clear()
+ Cs.clear()
+ test_theory(Color0, 5, 10, [6, 7, 8, 9, 10, 11])
+ test_theory(Cs, 3, 8, [13, 22, 34, 50, 70, 95])
+ test_theory(GraphTheory, 3, 7, [4, 11, 34, 156, 1044])
+ test_theory(ThreeGraphTheory, 3, 6, [2, 5, 34, 2136])
+ test_theory(DiGraphTheory, 2, 5, [3, 16, 218, 9608])
+ test_theory(DiThreeGraphTheory, 3, 3, [16])
+ test_theory(CG, 2, 6, [6, 20, 90, 544, 5096])
+ Cs.exclude([Cs(1), Cs(1, C0=[[0]], C1=[[0]])])
+ GraphTheory.exclude(GraphTheory(3))
+ CGp = combine("CGsym", GraphTheory, Cs)
+ CGp.clear()
+ test_theory(Cs, 4, 8, [3, 3, 4, 4, 5])
+ test_theory(GraphTheory, 3, 8, [3, 7, 14, 38, 107, 410])
+ test_theory(CGp, 2, 5, [4, 8, 32, 106])
+ Css = combine("Colors3Sym", Color0, Color1, Color2, symmetries=True)
+ Css.clear()
+ pe = Css(1)
+ p0 = Css.p(1, C2=[0], C1=[0])
+ Css.exclude([pe, p0])
+ test_theory(Css, 3, 8, [3, 4, 5, 7, 8, 10])
+ Cyc4 = combine("Cyclic4", Color0, Color1, Color2, Color3,
+ symmetries=CyclicSymmetry(4))
+ Cyc4.clear()
+ Cyc4.exclude([
+ Cyc4(1),
+ Cyc4.p(1, C0=[0], C1=[0]),
+ Cyc4.p(1, C0=[0], C2=[0])
+ ])
+ GraphTheory.reset()
+ T4 = combine("Cyclic4Graph", Cyc4, GraphTheory)
+ Cyc6 = combine("Cyclic6", Color0, Color1, Color2, Color3, Color4, Color5,
+ symmetries=CyclicSymmetry(6))
+ Cyc6.clear()
+ Cyc6.exclude([
+ Cyc6(1),
+ Cyc6.p(1, C0=[0], C1=[0]),
+ Cyc6.p(1, C0=[0], C2=[0]),
+ Cyc6.p(1, C0=[0], C3=[0])
+ ])
+ T6 = combine("Cyclic6Graph", Cyc6, GraphTheory)
+ T4.clear()
+ T6.clear()
+ test_theory(Cyc4, 4, 9, [10, 14, 22, 30, 43, 55])
+ test_theory(Cyc6, 3, 7, [10, 22, 42, 80, 132])
+ test_theory(T4, 2, 5, [6, 30, 260, 3052])
+ test_theory(T6, 2, 4, [8, 62, 754])
+
+def clear_all_calculations(theory_name=None):
+ calcs_dir = os.path.join(os.getenv('HOME'), '.sage', 'calcs')
+ if not os.path.exists(calcs_dir):
+ return
+ for xx in os.listdir(calcs_dir):
+ if theory_name==None or str(xx).startswith(theory_name):
+ file_path = os.path.join(calcs_dir, xx)
+ os.remove(file_path)
+
+def show_all_calculations(theory_name=None):
+ calcs_dir = os.path.join(os.getenv('HOME'), '.sage', 'calcs')
+ if not os.path.exists(calcs_dir):
+ return
+ for xx in os.listdir(calcs_dir):
+ file_path = os.path.join(calcs_dir, xx)
+ file_theory = str(xx).split(".")[0]
+ if theory_name==None:
+ with open(file_path , "rb") as file:
+ data = pickle.load(file)
+ if data != None:
+ print(file_theory + ": " + str(data["key"][:2]))
+ elif str(xx).startswith(theory_name):
+ with open(file_path , "rb") as file:
+ data = pickle.load(file)
+ if data != None:
+ print(data["key"][:2])
+
+# Primitive rounding methods
+def _flatten_matrix(mat, doubled=False):
+ r"""
+ Flatten a symmetric matrix, optionally double non-diagonal elements
+ """
+ res = []
+ factor = 2 if doubled else 1
+ try:
+ for ii in range(len(mat)):
+ res.append(mat[ii][ii])
+ res += [factor*mat[ii][jj] for jj in range(ii+1, len(mat))]
+ except:
+ for ii in range(len(mat)):
+ res.append(mat[ii])
+ res += [0 for jj in range(ii+1, len(mat))]
+ return res
+
+def _unflatten_matrix(ls, dim=-1, doubled=False, upper=False):
+ r"""
+ Unflatten a symmetric matrix, optionally correct for the doubled
+ non-diagonal elements
+ """
+ if dim==-1:
+ dim = Integer(round((1/2) * ((8*len(ls)+1)**(1/2) - 1) ))
+ mat = [[0]*dim for ii in range(dim)]
+ factor = 2 if doubled else 1
+ index = 0
+ for ii in range(dim):
+ # Fill the diagonal element
+ mat[ii][ii] = ls[index]
+ index += 1
+ # Fill the off-diagonal elements
+ for jj in range(ii + 1, dim):
+ mat[ii][jj] = ls[index] / factor
+ if not upper:
+ mat[jj][ii] = ls[index] / factor
+ index += 1
+ return matrix(mat), ls[index:]
+
+def _round(value, method=1, quotient_bound=7, denom_bound=9,
+ denom=1024):
+ r"""
+ Helper function, to round a number using either
+ method=0 - simple fixed denominator
+ method=1 - continued fractions
+ """
+ if method==0:
+ return QQ(round(value*denom)/denom)
+ else:
+ from sage.rings.continued_fraction import continued_fraction
+ cf = continued_fraction(value)
+ for ii, xx in enumerate(cf.quotients()):
+ if xx>=2**quotient_bound or cf.denominator(ii)>2**(denom_bound):
+ if ii>0:
+ return cf.convergent(ii-1)
+ return 0
+ return cf.value()
+
+def _round_list(ls, force_pos=False, method=1, quotient_bound=7,
+ denom_bound=9, denom=1024):
+ r"""
+ Helper function, to round a list
+ """
+ if force_pos:
+ return [max(
+ _round(xx, method, quotient_bound, denom_bound, denom), 0
+ ) for xx in ls]
+ else:
+ return [_round(xx, method, quotient_bound,
+ denom_bound, denom) for xx in ls]
+
+def _round_matrix(mat, method=1, quotient_bound=7, denom_bound=9,
+ denom=1024):
+ r"""
+ Helper function, to round a matrix
+ """
+ try:
+ return matrix(QQ, [_round_list(xx, False,
+ method, quotient_bound,
+ denom_bound, denom
+ ) for xx in mat])
+ except:
+ #This happens when a semidef constraint turns out to be just linear
+ return diagonal_matrix(QQ, _round_list(mat, True,
+ method, quotient_bound,
+ denom_bound, denom))
+
+def _round_adaptive(ls, onevec, denom=1024):
+ r"""
+ Adaptive rounding based on continued fraction and preserving
+ an inner product with `onevec`
+
+ If the continued fraction rounding fails fall back to a simple
+ denominator method
+ """
+ best_vec = None
+ best_error = 1000
+ best_lcm = 1000000000
+
+ orig = vector(ls)
+ for resol1 in range(5, 20):
+ resol2 = round(resol1*1.5)
+ rls = vector([_round(xx, quotient_bound=resol1, denom_bound=resol2) \
+ for xx in orig])
+ ip = rls*onevec
+ if ip != 0 and abs(ip - 1) best_lcm**1.5 and ip != 1:
+ continue
+ best_vec = rls/ip
+ best_error = abs(ip - 1)
+ best_lcm = ip.as_integer_ratio()[1]
+ if best_vec==None:
+ rvec = vector(QQ, _round_list(ls, True, method=0, denom=denom))
+ best_vec = rvec/(rvec*onevec)
+ return best_vec, ((best_vec-orig)/(len(orig)**0.5)).norm()
+
+def _remove_kernel(mat, factor=1024, threshold=1e-4):
+ d = mat.nrows()
+ M_scaled = matrix(ZZ, [[round(xx*factor) for xx in vv] for vv in mat])
+ M_augmented = matrix.identity(ZZ, d).augment(M_scaled)
+ LLL_reduced = M_augmented.LLL()
+ LLL_coeffs = LLL_reduced[:, :d]
+ norm_test = LLL_coeffs * mat
+ #print("norms are: ", " ".join([str(int(log(rr.norm(1)/d, 10))) for rr in norm_test]))
+ kernel_base = [LLL_coeffs[ii] for ii,rr in enumerate(norm_test) if rr.norm(1)/d < threshold]
+ if len(kernel_base)==0:
+ return mat, matrix.identity(d, sparse=True)
+ K = matrix(ZZ, kernel_base).stack(matrix.identity(d))
+ image_space = matrix(K.gram_schmidt()[0][len(kernel_base):, :], sparse=True)
+ kernel_removed_mat = image_space * mat * image_space.T
+ norm_factor = image_space * image_space.T
+ mat_recover = image_space.T * norm_factor.inverse()
+ return kernel_removed_mat, mat_recover
+
+def custom_psd_test(mat):
+ dim = mat.nrows()
+ for ii in range(dim):
+ pmin = mat[:ii+1, :ii+1]
+ if pmin.det()<0:
+ return False
+ return True
+
+class _CombinatorialTheory(Parent, UniqueRepresentation):
+ def __init__(self, name):
+ self._name = name
+ self._excluded = tuple()
+ self._printlevel = 1
+ Parent.__init__(self, category=(Sets(), ))
+ self._populate_coercion_lists_()
+
+ def _repr_(self):
+ r"""
+ Give a nice string representation of the theory object
+
+ OUTPUT: The representation as a string
+
+ EXAMPLES::
+
+ sage: print(GraphTheory)
+ Theory for Graph
+ """
+ return 'Theory for {}'.format(self._name)
+
+ def signature(self):
+ r"""
+ Returns the signature data for this theory
+
+ OUTPUT: A dictionary containing the signature data
+
+ EXAMPLES::
+
+ sage: GraphTheory.signature()
+ {'edges': {'arity': 2, 'group': 0, 'ordered': False}}
+ """
+ return self._signature
+
+ def symmetries(self):
+ r"""
+ Returns the symmetry data for this theory
+ """
+ return self._symmetries
+
+ def sizes(self):
+ return NN
+
+ # Persistend data management
+ def _calcs_dir(self):
+ r"""
+ Returns the path where the calculations are stored.
+
+ EXAMPLES::
+
+ sage: GraphTheory._calcs_dir()
+ '/home/bodnalev/.sage/calcs'
+ """
+ calcs_dir = os.path.join(os.getenv('HOME'), '.sage', 'calcs')
+ if not os.path.exists(calcs_dir):
+ os.makedirs(calcs_dir)
+ return calcs_dir
+
+ def _save(self, data, key=None, path=None, name=None):
+ r"""
+ Saves data to persistent storage.
+
+ The file name is determined based on the provided arguments. If ``name`` is not
+ given, a hashed key is generated from ``(self, key)`` and appended to ``self._name``.
+ The file is saved in the directory given by ``path`` if provided, or in the directory
+ returned by ``self._calcs_dir()`` if ``path`` is None. If ``path`` is an empty string,
+ the file is saved in the current working directory.
+
+ INPUT:
+
+ - ``data`` -- any serializable object; the data to be saved.
+ - ``key`` -- optional; used to generate the file name if ``name`` is not provided.
+ - ``path`` -- optional; the directory path where the file will be saved.
+ - ``name`` -- optional; explicit file name. Overrides key-based file naming if provided.
+
+ OUTPUT:
+ None
+
+ EXAMPLES:
+
+ sage: obj = SomeClass() # Assume SomeClass defines _name and _calcs_dir appropriately.
+ sage: obj._save("example data", key="sample")
+ sage: file_name = obj._name + "." + hashlib.sha256(pickle.dumps((obj, "sample"))).hexdigest()
+ sage: file_path = os.path.join(obj._calcs_dir(), file_name)
+ sage: os.path.exists(file_path)
+ True
+
+ TESTS:
+
+ sage: obj._save("data", name="test_file.pkl")
+ sage: os.path.exists("test_file.pkl")
+ True
+
+ sage: obj._save("data") # Neither key nor name provided.
+ Traceback (most recent call last):
+ ...
+ ValueError: Either the key or the name must be provided!
+ """
+ if name == None:
+ if key == None:
+ raise ValueError("Either the key or the name must be provided!")
+ serialized_key = pickle.dumps((self, key))
+ hashed_key = hashlib.sha256(serialized_key).hexdigest()
+ file_name = self._name + "." + hashed_key
+ else:
+ file_name = name
+
+ if path == None:
+ file_path = os.path.join(self._calcs_dir(), file_name)
+ elif path == "":
+ file_path = file_name
+ else:
+ if not os.path.exists(path):
+ os.makedirs(path)
+ file_path = os.path.join(path, file_name)
+ save_object = {'key': key, 'data': data}
+ with open(file_path, "wb") as file:
+ pickle.dump(save_object, file)
+
+ def _load(self, key=None, path=None, name=None):
+ r"""
+ Loads data from persistent storage.
+
+ The file name is determined by the provided ``key`` or explicitly by ``name``.
+ If ``name`` is not provided, a hash is computed from ``(self, key)`` and appended to
+ ``self._name``. The file is then sought in the directory specified by ``path`` (if given)
+ or in the directory returned by ``self._calcs_dir()`` (if ``path`` is None). If the file
+ does not exist, the function returns ``None``. Additionally, if a key is provided and the
+ loaded data's key does not match the provided key, a warning is issued and ``None`` is returned.
+
+ INPUT:
+
+ - ``key`` -- optional; used to generate the file name if ``name`` is not provided.
+ - ``path`` -- optional; directory path where the file is expected to be.
+ - ``name`` -- optional; explicit file name. Overrides key-based file naming if provided.
+
+ OUTPUT:
+ The data stored in the file if found and valid; otherwise, ``None``.
+
+ EXAMPLES:
+
+ sage: obj = SomeClass() # Assume SomeClass defines _name and _calcs_dir appropriately.
+ sage: # Attempt to load data using a key.
+ sage: data = obj._load(key="sample_key")
+ sage: data # Should return the saved data or None if not found.
+
+ TESTS:
+
+ sage: # Test: loading a non-existent file returns None.
+ sage: obj._load(name="nonexistent_file.pkl")
+ None
+
+ sage: # Test: key mismatch triggers a warning and returns None.
+ sage: obj._load(key="incorrect_key", name="existing_file.pkl")
+ Traceback (most recent call last):
+ ...
+ Warning: Hash collision or corrupted data!
+ """
+ if key != None:
+ serialized_key = pickle.dumps((self, key))
+ hashed_key = hashlib.sha256(serialized_key).hexdigest()
+ file_name = self._name + "." + hashed_key
+ if name != None:
+ file_name = name
+
+ if path == None:
+ file_path = os.path.join(self._calcs_dir(), file_name)
+ else:
+ if not os.path.exists(path):
+ os.makedirs(path)
+ file_path = os.path.join(path, file_name)
+
+ if not os.path.exists(file_path):
+ return None
+
+ with open(file_path, "rb") as file:
+ save_object = pickle.load(file)
+
+ if key != None and save_object != None and save_object['key'] != key:
+ import warnings
+ warnings.warn("Hash collision or corrupted data!")
+ return None
+
+ return save_object['data']
+
+ def show_files(self):
+ r"""
+ Shows the persistent files saved from this theory.
+ """
+ for xx in os.listdir(self._calcs_dir()):
+ if xx.startswith(self._name + "."):
+ file_path = os.path.join(self._calcs_dir(), xx)
+ with open(file_path , "rb") as file:
+ data = pickle.load(file)
+ if data != None:
+ print(data["key"][:2])
+
+ def clear(self):
+ r"""
+ Clears all calculation from the persistent memory.
+ """
+ for xx in os.listdir(self._calcs_dir()):
+ if xx.startswith(self._name + "."):
+ file_path = os.path.join(self._calcs_dir(), xx)
+ os.remove(file_path)
+
+ def _serialize(self, excluded=None):
+ r"""
+ Serializes this theory. Note this contains information about
+ the structures excluded from this theory.
+
+ EXAMPLES::
+
+ sage: GraphTheory._serialize()
+ {'excluded': ((3, (), ((0, 1), (0, 2), (1, 2))),),
+ 'name': 'Graph',
+ 'signature': {'edges': {'arity': 2, 'group': 0, 'ordered': False}},
+ 'sources': None,
+ 'symmetries': ((1, 1, ()),)}
+ """
+ if excluded==None:
+ excluded = self.get_total_excluded(100000)
+ else:
+ excluded = tuple(excluded)
+ sourceser = None
+ if self._sources != None:
+ sourceser = (
+ self._sources[0]._serialize(),
+ self._sources[1]._serialize()
+ )
+ return {
+ "name": self._name,
+ "signature": self._signature,
+ "symmetries": self._symmetries,
+ "sources": sourceser,
+ "excluded": tuple([xx._serialize() for xx in excluded])
+ }
+
+ # Optimizing and rounding
+
+ def blowup_construction(self, target_size, pattern_size,
+ symbolic_parts=False, symbolic=False, printlevel=None,
+ **kwargs):
+
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
+ if symbolic:
+ symbolic_parts = True
+ import warnings
+ warnings.warn("The parameter symbolic will be replaced with symbolic_parts.", DeprecationWarning)
+ RX = PolynomialRing(QQ, pattern_size, "X")
+ Xs = RX.gens()
+
+ flat_edges = []
+ for kk in kwargs:
+ if kk not in self.signature():
+ continue
+ for ee in kwargs[kk]:
+ flat_edges.append((kk, ee))
+
+ res = 0
+ terms = ((sum(Xs))**target_size).dict()
+ if printlevel!=None:
+ self._printlevel = printlevel
+ if self._printlevel>0:
+ iterator = tqdm(terms)
+ else:
+ iterator = terms
+ for exps in iterator:
+ verts = []
+ for ind, exp in enumerate(exps):
+ verts += [ind]*exp
+ coeff = terms[exps]/(pattern_size**target_size)
+ if symbolic_parts:
+ coeff = terms[exps]
+ for ind, exp in enumerate(exps):
+ coeff *= Xs[ind]**exp
+ blocks = {}
+ for rel in kwargs:
+ if rel not in self.signature():
+ continue
+ reledges = kwargs[rel]
+ bladd = []
+ for edge in reledges:
+ clusters = [
+ [ii for ii in range(target_size) if verts[ii]==ee] \
+ for ee in edge
+ ]
+ bladd += list( \
+ set([tuple(sorted(xx)) \
+ for xx in itertools.product(*clusters) \
+ if len(set(xx))==len(edge)]) \
+ )
+ blocks[rel] = bladd
+ try:
+ res += self(target_size, **blocks).afae()*coeff
+ except:
+ raise ValueError(
+ "The construction contains excluded structures: ",
+ self(target_size, **blocks)
+ )
+ return res
+
+ def get_Z_matrices(self, phi_vector, table_constructor, test=True):
+ Zs = []
+ for param in table_constructor.keys():
+ ns, ftype, target_size = param
+ table = self.mul_project_table(ns, ns, ftype, ftype_inj=[],
+ target_size=target_size)
+ Zm = [None for _ in range(len(table_constructor[param]))]
+ for gg, morig in enumerate(table):
+ if phi_vector[gg]==0:
+ continue
+ for ii, base in enumerate(table_constructor[param]):
+ mat = base * morig * base.T
+ if Zm[ii]==None:
+ Zm[ii] = mat*phi_vector[gg]
+ else:
+ Zm[ii] += mat*phi_vector[gg]
+ Zs.append(Zm)
+ for Zii in Zm:
+ if test and (not Zii.is_positive_semidefinite()):
+ self.fprint("Construction based Z matrix for " +
+ "{} is not semidef: {}".format(
+ ftype, min(Zii.eigenvalues())
+ ))
+ return Zs
+
+ def _adjust_table_phi(self, table_constructor, phi_vectors_exact,
+ test=False):
+ r"""
+ Helper to modify a table constructor, incorporating extra data from
+ constructions (phi_vectors_exact)
+ """
+ if len(phi_vectors_exact)==0:
+ return table_constructor
+
+ to_pop = []
+ for param in table_constructor.keys():
+ ns, ftype, target_size = param
+ table = self.mul_project_table(ns, ns, ftype, ftype_inj=[],
+ target_size=target_size)
+ Zs = [
+ [None for _ in range(len(phi_vectors_exact))] \
+ for _ in range(len(table_constructor[param]))
+ ]
+ for gg, morig in enumerate(table):
+ for ii, base in enumerate(table_constructor[param]):
+ mat = base * morig * base.T
+ for phind, phi_vector_exact in enumerate(phi_vectors_exact):
+ if Zs[ii][phind]==None:
+ Zs[ii][phind] = mat*phi_vector_exact[gg]
+ else:
+ Zs[ii][phind] += mat*phi_vector_exact[gg]
+
+ new_bases = []
+ for ii, Zgroup in enumerate(Zs):
+ Z = None
+ for Zjj in Zgroup:
+ if test and (not Zjj.is_positive_semidefinite()):
+ self.fprint("Construction based Z matrix for " +
+ "{} is not semidef: {}".format(
+ ftype, min(Zjj.eigenvalues())
+ ))
+ if Z==None:
+ Z = Zjj
+ else:
+ Z.augment(Zjj)
+ Zk = Z.kernel()
+ Zkern = Zk.basis_matrix()
+ if Zkern.nrows()>0:
+ new_bases.append(
+ matrix(Zkern * table_constructor[param][ii],
+ sparse=True)
+ )
+ if len(new_bases)!=0:
+ table_constructor[param] = new_bases
+ else:
+ to_pop.append(param)
+
+ for param in to_pop:
+ table_constructor.pop(param, None)
+ return table_constructor
+
+ def _tables_to_sdp_data(self, table_constructor, prev_data=None):
+ r"""
+ Helper to transform the data from the multiplication
+ tables to an SDP input
+ """
+ if prev_data==None:
+ block_sizes = []
+ target = []
+ mat_inds = []
+ mat_vals = []
+ else:
+ block_sizes, target, mat_inds, mat_vals = prev_data
+ block_index = len(block_sizes) + 1
+ for params in table_constructor.keys():
+ ns, ftype, target_size = params
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ block_sizes += [base.nrows() for base in table_constructor[params]]
+
+ #only loop through the table once
+ for gg, morig in enumerate(table):
+ #for each base change create the entries
+ for plus_index, base in enumerate(table_constructor[params]):
+ mm = base * morig * base.T
+ dd = mm._dict()
+ if len(dd)>0:
+ inds, values = zip(*mm._dict().items())
+ iinds, jinds = zip(*inds)
+ for cc in range(len(iinds)):
+ if iinds[cc]>=jinds[cc]:
+ mat_inds.extend(
+ [gg+1, block_index + plus_index,
+ iinds[cc]+1, jinds[cc]+1]
+ )
+ mat_vals.append(values[cc])
+ block_index += len(table_constructor[params])
+ return block_sizes, target, mat_inds, mat_vals
+
+ def _constraints_to_sdp_data(self, constraints_data, prev_data=None):
+ r"""
+ Helper to transform the data from the constraints to an SDP input data
+ """
+ if prev_data==None:
+ block_sizes = []
+ target = []
+ mat_inds = []
+ mat_vals = []
+ else:
+ block_sizes, target, mat_inds, mat_vals = prev_data
+ flag_num, constraints_vals, constraints_flags_vec, one_vector = \
+ constraints_data
+ block_index = len(block_sizes) + 1
+ constr_num = len(constraints_vals)
+ for ii in range(constr_num):
+ mat_inds.extend([0, block_index+1, 1+ii, 1+ii])
+ mat_vals.append(constraints_vals[ii])
+
+ for gg in range(flag_num):
+ mat_inds.extend([gg+1, block_index, gg+1, gg+1])
+ mat_vals.append(1)
+ for ii in range(constr_num):
+ mat_inds.extend([gg+1, block_index+1, ii+1, ii+1])
+ mat_vals.append(constraints_flags_vec[ii][gg])
+ block_sizes += [-flag_num, -constr_num]
+ return block_sizes, target, mat_inds, mat_vals
+
+ def _target_to_sdp_data(self, target, prev_data=None):
+ r"""
+ Helper to transform the target to an SDP input data
+ """
+ if prev_data==None:
+ return [], list(target), [], []
+ prev_data[1] = list(target)
+ return prev_data
+
+ def _get_relevant_ftypes(self, target_size):
+ r"""
+ Returns the ftypes useful for optimizing up to `target_size`
+ """
+ plausible_sizes = list(range(1, target_size))
+ ftype_pairs = []
+ for fs, ns in itertools.combinations(plausible_sizes, r=int(2)):
+ if ns+ns-fs <= target_size:
+ kk = ns-fs
+ found = False
+ for ii, (bfs, bns) in enumerate(ftype_pairs):
+ if bns-bfs==kk:
+ found = True
+ if ns>bns:
+ ftype_pairs[ii]=(fs, ns)
+ break
+ if not found:
+ ftype_pairs.append((fs, ns))
+
+ ftype_data = []
+ for fs, ns in ftype_pairs:
+ ftype_flags = self.generate_flags(fs)
+ ftypes = [flag.subflag([], ftype_points=list(range(fs))) \
+ for flag in ftype_flags]
+ for xx in ftypes:
+ ftype_data.append((ns, xx, target_size))
+ ftype_data.sort()
+ return ftype_data
+
+ def _create_table_constructor(self, ftype_data, target_size):
+ r"""
+ Table constructor is a dictionary that holds the data to construct
+ all the multiplication tables.
+
+ For each ftype and base change it provides the data to create
+ the multiplication table. Also pre-computes the multiplication
+ tables if they are not calculated yet.
+ """
+
+ sym_asym_mats = [
+ self.sym_asym_bases(dat[0], dat[1]) for dat in ftype_data
+ ]
+
+ table_constructor = {}
+ if self._printlevel>0:
+ iterator = tqdm(enumerate(ftype_data))
+ pbar = iterator
+ else:
+ iterator = enumerate(ftype_data)
+ pbar = None
+ for ii, dat in iterator:
+ ns, ftype, target_size = dat
+ #pre-calculate the table here
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ if table==None:
+ pbar.set_description(
+ "{} ({}) had singular table!".format(ftype, ns)
+ )
+ continue
+ sym_base, asym_base = sym_asym_mats[ii]
+ bases = []
+ if sym_base.nrows()!=0:
+ bases.append(sym_base)
+ if asym_base.nrows()!=0:
+ bases.append(asym_base)
+ table_constructor[dat] = bases
+ if not (pbar is None):
+ pbar.set_description("Done with mult table for {}".format(ftype))
+ return table_constructor
+
+ def _create_constraints_data(self, positives, target_element, target_size):
+ r"""
+ Creates the data that holds the linear constraints
+ """
+
+ base_flags = self.generate_flags(target_size)
+
+ if positives == None:
+ positives_list_exact = []
+ constraints_vals = []
+ else:
+ positives_list_exact = []
+ if self._printlevel>0:
+ iterator = tqdm(range(len(positives)))
+ pbar = iterator
+ else:
+ iterator = range(len(positives))
+ pbar = None
+ for ii in iterator:
+ fv = positives[ii]
+ if isinstance(fv, ExoticFlag) or isinstance(fv, BuiltFlag):
+ continue
+ kf = fv.ftype().size()
+ nf = fv.size()
+ df = target_size - nf + kf
+ mult_table = self.mul_project_table(
+ nf, df, fv.ftype(), ftype_inj=[], target_size=target_size
+ )
+ fvvals = fv.values()
+ m = matrix(QQ, [vector(fvvals*mat) for mat in mult_table])
+ positives_list_exact += list(m.T)
+ if not (pbar is None):
+ pbar.set_description(
+ "Done with positivity constraint {}".format(ii)
+ )
+ constraints_vals = [0]*len(positives_list_exact)
+
+ # The one vector is also calculated here and is a linear constraint
+ if target_element.ftype().size()==0:
+ one_vector = vector([1]*len(base_flags))
+ else:
+ one_vector = (target_element.ftype().project()<<(
+ target_size - target_element.ftype().size()
+ )).values()
+ positives_list_exact.extend([one_vector, one_vector*(-1)])
+ constraints_vals.extend([1, -1])
+
+ return len(base_flags), constraints_vals, \
+ positives_list_exact, one_vector
+
+ def _round_sdp_solution_no_phi(self, sdp_result, sdp_data,
+ table_constructor, constraints_data,
+ **params):
+ r"""
+ Rounds the SDP solution without adjusting the phi vector.
+
+ This function processes an SDP solution by rounding its matrices and slack variables.
+ It performs the following steps:
+
+ - Rounds each matrix in the SDP result using a specified denominator (default: 1024),
+ correcting for any negative eigenvalues.
+ - Flattens the rounded matrices and computes slack variables based on the provided
+ table constructor and constraints data.
+ - Determines the final result by scaling the slack variables with a one-vector extracted
+ from the constraints.
+
+ INPUT:
+
+ - ``sdp_result`` -- dictionary;
+ - ``sdp_data`` -- tuple;
+ - ``table_constructor`` -- dictionary;
+ - ``constraints_data`` -- tuple;
+ - ``**kwargs`` -- keyword arguments for rounding parameters:
+ * ``denom`` (integer, default: 1024): the denominator used for rounding.
+
+ EXAMPLES:
+
+ sage:
+
+ TESTS:
+
+ sage:
+ """
+
+ import numpy as np
+ from numpy import linalg as LA
+ from sage.functions.other import ceil
+
+ # set up parameters
+ denom = params.get("denom", 1024)
+
+ #unpack variables
+
+ block_sizes, target_list_exact, mat_inds, mat_vals = sdp_data
+ target_vector_exact = vector(target_list_exact)
+ flags_num, constraints_vals, positives_list_exact, one_vector = \
+ constraints_data
+ positives_matrix_exact = matrix(
+ QQ, len(positives_list_exact), flags_num, positives_list_exact
+ )
+
+ # find the one_vector from the equality constraint
+ one_vector_exact = positives_matrix_exact.rows()[-2]
+ # remove the equality constraints
+ positives_matrix_exact = positives_matrix_exact[:-2, :]
+
+ flags_num = -block_sizes[-2] # same as |F_n|
+
+ X_matrices_approx = sdp_result['X'][:-2]
+ X_matrices_rounded = []
+ self.fprint("Rounding X matrices")
+ if self._printlevel>0:
+ iterator = tqdm(X_matrices_approx)
+ else:
+ iterator = X_matrices_approx
+ for X in iterator:
+ Xr = _round_matrix(X, method=0, denom=denom)
+ Xnp = np.array(Xr)
+ eigenvalues, eigenvectors = LA.eig(Xnp)
+ emin = min(eigenvalues)
+ if emin<0:
+ eminr = ceil(-emin*denom)/denom
+ Xr = matrix(QQ, Xr) + \
+ diagonal_matrix(QQ, [eminr]*len(X), sparse=True)
+ X_matrices_rounded.append(Xr)
+ X_matrices_flat = [
+ vector(_flatten_matrix(X.rows(), doubled=False)) \
+ for X in (X_matrices_rounded)
+ ]
+
+ e_vector_approx = sdp_result['X'][-1][:-2]
+ e_vector_rounded = vector(QQ,
+ _round_list(e_vector_approx, force_pos=True, method=0, denom=denom)
+ )
+
+ phi_vector_approx = sdp_result['y']
+ phi_vector_rounded = vector(QQ,
+ _round_list(phi_vector_approx, force_pos=True, method=0, denom=denom)
+ )
+
+ slacks = target_vector_exact - positives_matrix_exact.T*e_vector_rounded
+ block_index = 0
+ self.fprint("Calculating resulting bound")
+ if self._printlevel > 0:
+ iterator = tqdm(table_constructor.keys())
+ else:
+ iterator = table_constructor.keys()
+ for params in iterator:
+ ns, ftype, target_size = params
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ for gg, morig in enumerate(table):
+ for plus_index, base in enumerate(table_constructor[params]):
+ block_dim = block_sizes[block_index + plus_index]
+ X_flat = X_matrices_flat[block_index + plus_index]
+ M = base * morig * base.T
+ M_flat_vector_exact = vector(QQ,
+ _flatten_matrix(M.rows(), doubled=True)
+ )
+ slacks[gg] -= M_flat_vector_exact*X_flat
+ block_index += len(table_constructor[params])
+ # scale back slacks with the one vector, the minimum is the final result
+ result = min(
+ [slacks[ii]/oveii for ii, oveii in \
+ enumerate(one_vector_exact) if oveii!=0]
+ )
+ # pad the slacks, so it is all positive where it counts
+ slacks -= result*one_vector_exact
+
+ self.fprint("The rounded result is {}".format(result))
+
+ return result, X_matrices_rounded, e_vector_rounded, \
+ slacks, [phi_vector_rounded]
+
+ def _round_sdp_solution_no_phi_alter(self, sdp_result, sdp_data,
+ table_constructor, constraints_data,
+ **params):
+ r"""
+ Round the SDP results output to get something exact.
+ """
+ import gc
+ import time
+
+ # set up parameters
+ denom = params.get("denom", 1024)
+ ring = params.get("ring", QQ)
+ slack_threshold = params.get("slack_threshold", 1e-9)
+ linear_threshold = params.get("linear_threshold", 1e-6)
+ kernel_threshold = params.get("kernel_threshold", 1e-4)
+ kernel_denom = params.get("kernel_denom", 1024)
+
+ # unpack variables
+ block_sizes, target_list_exact, _, __ = sdp_data
+ target_vector_exact = vector(ring, target_list_exact)
+ flags_num, _, positives_list_exact, __ = constraints_data
+ _ = None; __ = None; gc.collect()
+ positives_matrix_exact = matrix(
+ ring, len(positives_list_exact), flags_num, positives_list_exact
+ )
+
+ # find the one_vector from the equality constraint
+ one_vector_exact = positives_matrix_exact.rows()[-2]
+ # remove the equality constraints
+ positives_matrix_exact = positives_matrix_exact[:-2, :]
+
+ # dim: |F_n|, c vector, primal slack for flags
+ c_vector_approx = vector(sdp_result['X'][-2])
+
+ c_zero_inds = [
+ FF for FF, xx in enumerate(c_vector_approx) if
+ (xx<=slack_threshold)
+ ]
+
+ # same as m, number of positive constraints (-2 for the equality)
+ positives_num = -block_sizes[-1] - 2
+ # dim: m, the e vector, primal slack for positivitives
+ e_vector_approx = vector(sdp_result['X'][-1][:-2])
+ # as above but rounded
+ e_vector_rounded = vector(QQ,
+ _round_list(e_vector_approx, method=0, denom=denom)
+ )
+
+ # The f (ff) positivity constraints where the e vector is zero/nonzero
+ e_zero_inds = [
+ ff for ff, xx in enumerate(e_vector_approx) if \
+ (xx0 and min(e_nonzero_vector_corr)<0:
+ self.fprint("Linear coefficient is negative: {}".format(
+ min(e_nonzero_vector_corr)
+ ))
+ e_nonzero_vector_corr = [max(xx, 0) for xx in e_nonzero_vector_corr]
+ e_vector_dict = dict(zip(e_nonzero_inds, e_nonzero_vector_corr))
+ e_vector_corr = vector(ring, positives_num, e_vector_dict)
+ self.fprint("This took {}s".format(time.time() - start_time))
+ start_time = time.time()
+
+ X_final = []
+ slacks = target_vector_exact - positives_matrix_exact.T*e_vector_corr
+ block_index = 0
+ self.fprint("Calculating resulting bound")
+ if self._printlevel > 0:
+ iterator = tqdm(table_constructor.keys())
+ else:
+ iterator = table_constructor.keys()
+ for params in iterator:
+ ns, ftype, target_size = params
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ for plus_index, base in enumerate(table_constructor[params]):
+ block_dim = X_sizes_corrected[block_index + plus_index]
+ X_ii_raw, x_vector_corr = _unflatten_matrix(
+ x_vector_corr, block_dim
+ )
+ X_ii_raw = matrix(ring, X_ii_raw)
+ recover_base = X_recover_bases[block_index + plus_index]
+ X_ii_small = recover_base * X_ii_raw * recover_base.T
+
+ # verify semidefiniteness
+ if not X_ii_small.is_positive_semidefinite():
+ self.fprint("Rounded X matrix "+
+ "{} is not semidefinite: {}".format(
+ block_index+plus_index,
+ min(X_ii_small.eigenvalues())
+ ))
+ return None
+
+ # update slacks
+ for gg, morig in enumerate(table):
+ M = base * morig * base.T
+ M_flat_vector_exact = vector(
+ _flatten_matrix(M.rows(), doubled=True)
+ )
+ slacks[gg] -= M_flat_vector_exact*vector(
+ _flatten_matrix(X_ii_small.rows(), doubled=False)
+ )
+
+ X_final.append(X_ii_small)
+ block_index += len(table_constructor[params])
+
+ # scale back slacks with the one vector, the minimum is the final result
+ result = min([slacks[ii]/oveii \
+ for ii, oveii in enumerate(one_vector_exact) if \
+ oveii!=0])
+ # pad the slacks, so it is all positive where it counts
+ slacks -= result*one_vector_exact
+ self.fprint("This took {}s".format(time.time() - start_time))
+ start_time = time.time()
+
+ return result, X_final, e_vector_corr, slacks, []
+
+ def _round_sdp_solution_phi(self, sdp_result, sdp_data,
+ table_constructor, constraints_data,
+ phi_vectors_exact, **params):
+ r"""
+ Round the SDP results output to get something exact.
+ """
+ import gc
+ import time
+
+ # set up parameters
+ denom = params.get("denom", 1024)
+ ring = params.get("ring", QQ)
+ slack_threshold = params.get("slack_threshold", 1e-9)
+ linear_threshold = params.get("linear_threshold", 1e-6)
+ kernel_threshold = params.get("kernel_threshold", 1e-4)
+ kernel_denom = params.get("kernel_denom", 1024)
+
+ # unpack variables
+ block_sizes, target_list_exact, _, __ = sdp_data
+ target_vector_exact = vector(ring, target_list_exact)
+ flags_num, _, positives_list_exact, __ = constraints_data
+ _ = None; __ = None; gc.collect()
+ positives_matrix_exact = matrix(
+ ring, len(positives_list_exact), flags_num, positives_list_exact
+ )
+
+ no_constr = len(phi_vectors_exact)==0
+ phi_vector_exact = vector(
+ ring,
+ [0]*positives_matrix_exact.ncols()
+ ) if no_constr else phi_vectors_exact[0]
+
+ # find the one_vector from the equality constraint
+ one_vector_exact = positives_matrix_exact.rows()[-2]
+ # remove the equality constraints
+ positives_matrix_exact = positives_matrix_exact[:-2, :]
+
+ # dim: |F_n|, c vector, primal slack for flags
+ c_vector_approx = vector(sdp_result['X'][-2])
+
+ c_zero_inds = [
+ FF for FF, xx in enumerate(c_vector_approx) if
+ (abs(xx)<=slack_threshold or phi_vector_exact[FF]!=0)
+ ]
+
+ # same as m, number of positive constraints (-2 for the equality)
+ positives_num = -block_sizes[-1] - 2
+
+ # dim: m, witness that phi is positive
+ phi_pos_vector_exact = positives_matrix_exact*phi_vector_exact
+
+ # dim: m, the e vector, primal slack for positivitives
+ e_vector_approx = vector(sdp_result['X'][-1][:-2])
+ # as above but rounded
+ e_vector_rounded = vector(QQ,
+ _round_list(e_vector_approx, method=0, denom=denom)
+ )
+
+ # The f (ff) positivity constraints where the e vector is zero/nonzero
+ e_zero_inds = [
+ ff for ff, xx in enumerate(e_vector_approx) if \
+ (abs(xx)0 and min(e_nonzero_vector_corr)<0:
+ self.fprint("Linear coefficient is negative: {}".format(
+ min(e_nonzero_vector_corr)
+ ))
+ e_nonzero_vector_corr = [max(xx, 0) for xx in e_nonzero_vector_corr]
+ e_vector_dict = dict(zip(e_nonzero_inds, e_nonzero_vector_corr))
+ e_vector_corr = vector(ring, positives_num, e_vector_dict)
+ self.fprint("This took {}s".format(time.time() - start_time))
+ start_time = time.time()
+
+ X_final = []
+ slacks = target_vector_exact - positives_matrix_exact.T*e_vector_corr
+ block_index = 0
+ self.fprint("Calculating resulting bound")
+ if self._printlevel > 0:
+ iterator = tqdm(table_constructor.keys())
+ else:
+ iterator = table_constructor.keys()
+ for params in iterator:
+ ns, ftype, target_size = params
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ for plus_index, base in enumerate(table_constructor[params]):
+ block_dim = X_sizes_corrected[block_index + plus_index]
+ X_ii_raw, x_vector_corr = _unflatten_matrix(
+ x_vector_corr, block_dim
+ )
+ X_ii_raw = matrix(ring, X_ii_raw)
+ recover_base = X_recover_bases[block_index + plus_index]
+ X_ii_small = recover_base * X_ii_raw * recover_base.T
+
+ # verify semidefiniteness
+ invalid = False
+ try:
+ if not X_ii_small.is_positive_semidefinite():
+ self.fprint("Rounded X matrix "+
+ "{} is not semidefinite: {}".format(
+ block_index+plus_index,
+ min(X_ii_small.eigenvalues())
+ ))
+ invalid = True
+ except:
+ if not custom_psd_test(X_ii_small):
+ self.fprint("Rounded X matrix "+
+ "{} is not semidefinite: {}".format(
+ block_index+plus_index,
+ min(X_ii_small.eigenvalues())
+ ))
+ invalid = True
+ if invalid:
+ return None
+
+ # update slacks
+ for gg, morig in enumerate(table):
+ M = base * morig * base.T
+ M_flat_vector_exact = vector(
+ _flatten_matrix(M.rows(), doubled=True)
+ )
+ slacks[gg] -= M_flat_vector_exact*vector(
+ _flatten_matrix(X_ii_small.rows(), doubled=False)
+ )
+
+ X_final.append(X_ii_small)
+ block_index += len(table_constructor[params])
+
+ # scale back slacks with the one vector, the minimum is the final result
+ result = min([slacks[ii]/oveii \
+ for ii, oveii in enumerate(one_vector_exact) if \
+ oveii!=0])
+ # pad the slacks, so it is all positive where it counts
+ slacks -= result*one_vector_exact
+ self.fprint("This took {}s".format(time.time() - start_time))
+ start_time = time.time()
+
+ return result, X_final, e_vector_corr, slacks, phi_vectors_exact
+
+ def _fix_X_bases(self, table_constructor, X_original):
+ r"""
+ Transforms the X matrices to a base that agrees with the original
+ list of flags
+
+ Basically undoes the sym/asym changes and the reductions by the
+ constructions' kernels.
+ """
+ if X_original==None:
+ return None
+ X_flats = []
+ block_index = 0
+ for params in table_constructor.keys():
+ ns, ftype, target_size = params
+ X_ii = None
+ for plus_index, base in enumerate(table_constructor[params]):
+ base_scale = base * base.T
+ if X_ii == None:
+ X_ii = base.T * X_original[block_index + plus_index] * base
+ else:
+ X_ii += base.T * X_original[block_index + plus_index] * base
+ block_index += len(table_constructor[params])
+ X_flats.append(vector(_flatten_matrix(X_ii.rows())))
+ return X_flats
+
+ def _format_optimizer_output(self, table_constructor, mult=1,
+ sdp_output=None, rounding_output=None,
+ file=None):
+ r"""
+ Formats the outputs to a nice certificate
+
+ The result contains: the final bound, the X matrices, the linear
+ coefficients, the slacks, a guess or exact construction, the
+ list of base flags, the list of used (typed) flags
+ """
+
+ target_size = 0
+ typed_flags = {}
+ for params in table_constructor.keys():
+ ns, ftype, target_size = params
+ typed_flags[(ns, ftype)] = self.generate_flags(ns, ftype)
+
+ base_flags = self.generate_flags(target_size)
+
+ result = None
+ X_original = None
+ e_vector = None
+ slacks = None
+ phi_vecs = None
+
+ if sdp_output!=None:
+ result = sdp_output['primal']
+ X_original = [matrix(dat) for dat in sdp_output['X'][:-2]]
+ e_vector = vector(sdp_output['X'][-1])
+ slacks = vector(sdp_output['X'][-2])
+ phi_vecs = [vector(sdp_output['y'])]
+ elif rounding_output!=None:
+ result, X_original, e_vector, slacks, phi_vecs = rounding_output
+
+ result *= mult
+ #Ps, Ls, Ds = self._fix_X_bases_pld(table_constructor, X_original)
+ Xs = self._fix_X_bases(table_constructor, X_original)
+
+ cert_dict = {"result": result,
+ "X matrices": Xs,
+ "e vector": e_vector,
+ "slack vector": slacks,
+ "phi vectors": phi_vecs,
+ "base flags": base_flags,
+ "typed flags": typed_flags
+ }
+
+ if file!=None and file!="" and file!="notebook":
+ if not file.endswith(".pickle"):
+ file += ".pickle"
+ with open(file, "wb") as file_handle:
+ pickle.dump(cert_dict, file_handle)
+ if file=="notebook":
+ return cert_dict
+ return result
+
+ def _handle_sdp_params(self, **params):
+ sdp_params=["axtol", "atytol", "objtol", "pinftol", "dinftol", "maxiter",
+ "minstepfrac", "maxstepfrac", "minstepp", "minstepd", "usexzgap",
+ "tweakgap", "affine", "printlevel", "perturbobj", "fastmode"]
+
+ if "precision" in params and params["precision"]!=None:
+ precision = params["precision"]
+ if "axtol" not in params:
+ params["axtol"] = precision
+ if "atytol" not in params:
+ params["atytol"] = precision
+ if "objtol" not in params:
+ params["objtol"] = precision
+ if "pinftol" not in params:
+ params["pinftol"] = 1/precision
+ if "dinftol" not in params:
+ params["dinftol"] = 1/precision
+ if "minstepp" not in params:
+ params["minstepp"] = precision
+ if "minstepd" not in params:
+ params["minstepd"] = precision
+
+ if self._printlevel != 1:
+ if params=={}:
+ params = {"printlevel": self._printlevel}
+ else:
+ if "printlevel" not in params:
+ params["printlevel"] = self._printlevel
+ if params!={}:
+ with open("param.csdp", "w") as paramsfile:
+ for key, value in params.items():
+ if str(key) not in sdp_params:
+ continue
+ paramsfile.write(f"{key}={value}\n")
+ if "printlevel" in params:
+ self._printlevel = params["printlevel"]
+ else:
+ self._printlevel = 1
+
+ def solve_sdp(self, target_element, target_size, construction,
+ maximize=True, positives=None, file=None,
+ specific_ftype=None, **params):
+ r"""
+ TODO Docstring
+ """
+ from csdpy import solve_sdp
+ import time
+
+ #
+ # Initial setup
+ #
+
+ self._handle_sdp_params(**params)
+ base_flags = self.generate_flags(target_size)
+ self.fprint("Base flags generated, their number is {}".format(
+ len(base_flags)
+ ))
+ mult = -1 if maximize else 1
+ target_vector_exact = (
+ target_element.project()*(mult) << \
+ (target_size - target_element.size())
+ ).values()
+ sdp_data = self._target_to_sdp_data(target_vector_exact)
+
+ #
+ # Create the relevant ftypes
+ #
+
+ if specific_ftype==None:
+ ftype_data = self._get_relevant_ftypes(target_size)
+ else:
+ ftype_data = specific_ftype
+ self.fprint("The relevant ftypes are constructed, their " +
+ "number is {}".format(len(ftype_data)))
+ flags = [self.generate_flags(dat[0], dat[1]) for dat in ftype_data]
+ flag_sizes = [len(xx) for xx in flags]
+ self.fprint("Block sizes before symmetric/asymmetric change is" +
+ " applied: {}".format(flag_sizes))
+
+ #
+ # Create the table constructor and adjust it based on construction
+ #
+
+ table_constructor = self._create_table_constructor(
+ ftype_data, target_size
+ )
+ if isinstance(construction, FlagAlgebraElement):
+ phi_vectors_exact = [construction.values()]
+ else:
+ phi_vectors_exact = [xx.values() for xx in construction]
+ self.fprint("Adjusting table with kernels from construction")
+ table_constructor = self._adjust_table_phi(
+ table_constructor, phi_vectors_exact
+ )
+ sdp_data = self._tables_to_sdp_data(
+ table_constructor, prev_data=sdp_data
+ )
+ self.fprint("Tables finished")
+
+ #
+ # Add constraints data and add it to sdp_data
+ #
+
+ constraints_data = self._create_constraints_data(
+ positives, target_element, target_size
+ )
+ sdp_data = self._constraints_to_sdp_data(
+ constraints_data, prev_data=sdp_data
+ )
+ self.fprint("Constraints finished")
+
+ #
+ # Then run the optimizer
+ #
+
+ self.fprint("Running SDP. Used block sizes are {}".format(sdp_data[0]))
+ time.sleep(float(0.1))
+ final_sol = solve_sdp(*sdp_data)
+ time.sleep(float(0.1))
+
+ if file!=None:
+ if not file.endswith(".pickle"):
+ file += ".pickle"
+ with open(file, "wb") as file_handle:
+ save_data = (
+ final_sol,
+ (sdp_data[0], sdp_data[1], None, None),
+ table_constructor,
+ (constraints_data[0], None, constraints_data[2], None),
+ phi_vectors_exact,
+ mult)
+ pickle.dump(save_data, file_handle)
+
+ return final_sol["primal"]*mult
+
+ def round_solution(self, sdp_output_file, certificate_file=None, **params):
+ r"""
+ TODO Docstring
+ """
+ if not sdp_output_file.endswith(".pickle"):
+ sdp_output_file += ".pickle"
+ with open(sdp_output_file, "rb") as file_handle:
+ save_data = pickle.load(file_handle)
+ #
+ # Unpack the data
+ #
+
+ sdp_result, sdp_data, table_constructor, \
+ constraints_data, phi_vectors_exact, \
+ mult = save_data
+
+
+ #
+ # Perform the rounding
+ #
+
+ self.fprint("Starting the rounding of the result")
+ rounding_output = self._round_sdp_solution_phi( \
+ sdp_result,
+ sdp_data,
+ table_constructor,
+ constraints_data,
+ phi_vectors_exact,
+ **params
+ )
+ if rounding_output==None:
+ print("Rounding was unsuccessful!")
+ return
+ value = rounding_output[0]*mult
+ self.fprint("Final rounded bound is {}".format(value))
+
+ if certificate_file==None:
+ return value
+ return self._format_optimizer_output(
+ table_constructor,
+ mult=mult,
+ rounding_output=rounding_output,
+ file=certificate_file
+ )
+
+ def optimize_problem(self, target_element, target_size, maximize=True,
+ positives=None, construction=None, file=None,
+ exact=False, specific_ftype=None,
+ **params):
+ r"""
+ Try to maximize or minimize the value of `target_element`
+
+ The algorithm calculates the multiplication tables and
+ sends the SDP problem to CSDPY.
+
+ INPUT:
+
+ - ``target_element`` -- Flag or FlagAlgebraElement;
+ the target whose density this function tries to
+ maximize or minimize in large structures.
+ - ``target_size`` -- integer; The program evaluates
+ flags and the relations between them up to this
+ size.
+ - ``maximize`` -- boolean (default: `True`);
+ - ``file`` -- file to save the certificate
+ (default: `None`); Use None to not create
+ certificate
+ - ``positives`` -- list of flag algebra elements,
+ optimizer will assume those are positive, can
+ have different types
+ - ``construction`` -- a list or a single element of
+ `FlagAlgebraElement`s; to consider in the kernel
+ `None` by default, in this case the construction
+ is guessed from a preliminary run.
+ - ``exact`` -- boolean; to round the result or not
+
+ OUTPUT: A bound for the optimization problem. If
+ certificate is requested then returns the entire
+ output of the solver as the second argument.
+ """
+ from csdpy import solve_sdp
+ import time
+
+ #
+ # Initial setup
+ #
+
+ self._handle_sdp_params(**params)
+
+ constr_print_limit = params.get("constr_print_limit", 1000)
+ constr_error_threshold = params.get("constr_error_threshold", 1e-6)
+
+ if target_size not in self.sizes():
+ raise ValueError("For theory {}, size {} is not allowed.".format(self._name, target_size))
+
+ base_flags = self.generate_flags(target_size)
+ self.fprint("Base flags generated, their number is {}".format(
+ len(base_flags)
+ ))
+ mult = -1 if maximize else 1
+ target_vector_exact = (
+ target_element.project()*(mult) << \
+ (target_size - target_element.size())
+ ).values()
+ sdp_data = self._target_to_sdp_data(target_vector_exact)
+
+ #
+ # Create the relevant ftypes
+ #
+
+ if specific_ftype==None:
+ ftype_data = self._get_relevant_ftypes(target_size)
+ else:
+ ftype_data = specific_ftype
+ self.fprint("The relevant ftypes are constructed, their " +
+ "number is {}".format(len(ftype_data)))
+ flags = [self.generate_flags(dat[0], dat[1]) for dat in ftype_data]
+ flag_sizes = [len(xx) for xx in flags]
+ self.fprint("Block sizes before symmetric/asymmetric change is" +
+ " applied: {}".format(flag_sizes))
+
+ #
+ # Create the table constructor and add it to sdp_data
+ #
+
+ table_constructor = self._create_table_constructor(
+ ftype_data, target_size
+ )
+ sdp_data = self._tables_to_sdp_data(
+ table_constructor, prev_data=sdp_data
+ )
+ self.fprint("Tables finished")
+
+ #
+ # Add constraints data and add it to sdp_data
+ #
+
+ constraints_data = self._create_constraints_data(
+ positives, target_element, target_size
+ )
+ sdp_data = self._constraints_to_sdp_data(
+ constraints_data, prev_data=sdp_data
+ )
+ self.fprint("Constraints finished")
+
+ #
+ # Helper for returning the data, writing to file, and some cleanup
+ #
+
+ def help_return(value, sdpo=None, roundo=None):
+ try:
+ os.remove("param.csdp")
+ except OSError:
+ pass
+
+ if file==None:
+ return value
+ return self._format_optimizer_output(
+ table_constructor,
+ mult=mult,
+ sdp_output=sdpo,
+ rounding_output=roundo,
+ file=file
+ )
+
+ #
+ # If construction is None or [] then run the optimizer
+ # without any construction
+ #
+
+ if construction==None or construction==[] or construction is False:
+ self.fprint("Running sdp without construction. " +
+ "Used block sizes are {}".format(sdp_data[0]))
+
+ time.sleep(float(0.1))
+ initial_sol = solve_sdp(*sdp_data)
+ time.sleep(float(0.1))
+
+ # Format the result and return it if floating point values are fine
+ if (not exact):
+ return help_return(initial_sol['primal'] * mult,
+ sdpo=initial_sol)
+
+ # Guess the construction in this case
+ if construction==None:
+ one_vector = constraints_data[-1]
+ phi_vector_rounded, error_coeff = _round_adaptive(
+ initial_sol['y'], one_vector
+ )
+ if error_coeff0 and min(e_values)<0:
+ print("Solution is not valid!")
+ self.fprint("Linear constraint's coefficient is negative ",
+ min(e_values))
+ return -1
+
+ X_flats = certificate["X matrices"]
+ # Ps = certificate["P dictionaries"]
+ # Ds = certificate["D vectors"]
+ # Ls = certificate["L matrices"]
+ self.fprint("Checking X matrices")
+ if self._printlevel > 0:
+ iterator = tqdm(enumerate(X_flats))
+ else:
+ iterator = enumerate(X_flats)
+ for ii, Xf in iterator:
+ X = matrix(QQ, _unflatten_matrix(Xf)[0])
+ if not (X.is_positive_semidefinite()):
+ print("Solution is not valid!")
+ self.fprint("Matrix {} is not semidefinite".format(ii))
+ return -1
+ # P = matrix(QQ, len(Ddiag), len(Ddiag), Ps[ii], sparse=True)
+ # D = diagonal_matrix(QQ, Ddiag, sparse=True)
+ # Larr, _ = _unflatten_matrix(
+ # Ls[ii], dim=len(Ddiag),
+ # doubled=False, upper=True
+ # )
+ # L = matrix(QQ, Larr).T
+ # PL = P*L
+ # X = PL * D * PL.T
+ # X_flats.append(vector(QQ, _flatten_matrix(X.rows())))
+
+ self.fprint("Solution matrices are all positive semidefinite, " +
+ "linear coefficients are all non-negative")
+
+ #
+ # Initial setup
+ #
+
+ mult = -1 if maximize else 1
+ base_flags = certificate["base flags"]
+ target_vector_exact = (
+ target_element.project()*(mult)<<\
+ (target_size - target_element.size())
+ ).values()
+ if target_element.ftype().size()==0:
+ one_vector = vector([1]*len(base_flags))
+ else:
+ one_vector = (
+ target_element.ftype().project()<<\
+ (target_size - target_element.ftype().size())
+ ).values()
+
+ ftype_data = list(certificate["typed flags"].keys())
+
+ #
+ # Create the semidefinite matrix data
+ #
+
+ table_list = []
+ self.fprint("Calculating multiplication tables")
+ if self._printlevel > 0:
+ iterator = tqdm(enumerate(ftype_data))
+ else:
+ iterator = enumerate(ftype_data)
+ for ii, dat in iterator:
+ ns, ftype = dat
+ #calculate the table
+ table = self.mul_project_table(
+ ns, ns, ftype, ftype_inj=[], target_size=target_size
+ )
+ if table!=None:
+ table_list.append(table)
+
+ #
+ # Create the data from linear constraints
+ #
+
+ positives_list_exact = []
+ if positives != None:
+ for ii, fv in enumerate(positives):
+ if isinstance(fv, BuiltFlag) or isinstance(fv, ExoticFlag):
+ continue
+ nf = fv.size()
+ df = target_size + fv.ftype().size() - nf
+ mult_table = self.mul_project_table(
+ nf, df, fv.ftype(), ftype_inj=[], target_size=target_size
+ )
+ fvvals = fv.values()
+ m = matrix(QQ, [vector(fvvals*mat) for mat in mult_table])
+ positives_list_exact += list(m.T)
+ self.fprint("Done with positivity constraint {}".format(ii))
+ else:
+ e_values = vector(QQ, 0)
+ positives_matrix_exact = matrix(
+ QQ,
+ len(positives_list_exact), len(base_flags),
+ positives_list_exact
+ )
+
+ self.fprint("Done calculating linear constraints")
+
+ #
+ # Calculate the bound the solution provides
+ #
+
+ self.fprint("Calculating the bound provided by the certificate")
+
+ slacks = target_vector_exact - positives_matrix_exact.T*e_values
+ if self._printlevel > 0:
+ iterator = tqdm(enumerate(table_list))
+ else:
+ iterator = enumerate(table_list)
+ for ii, table in iterator:
+ for gg, mat_gg in enumerate(table):
+ mat_flat = vector(
+ _flatten_matrix(mat_gg.rows(), doubled=True)
+ )
+ slacks[gg] -= mat_flat * X_flats[ii]
+ result = min(
+ [slacks[ii]/oveii for ii, oveii in enumerate(one_vector) \
+ if oveii!=0]
+ )
+ result *= mult
+ print("The solution is valid, it proves "+
+ "the bound {}".format(result))
+
+ return result
+
+ verify = verify_certificate
+
+
+ # Generating flags
+
+ def _find_ftypes(self, empstrs, ftype):
+ import multiprocessing as mp
+ pool = mp.Pool(mp.cpu_count()-1)
+ pares = pool.map(ftype.ftypes_inside, empstrs)
+ pool.close(); pool.join()
+ return tuple(itertools.chain.from_iterable(pares))
+
+ # Generating tables
+
+ def sym_asym_bases(self, n, ftype=None):
+ r"""
+ Generate the change of base matrices for the symmetric
+ and the asymmetric subspaces
+ """
+
+ flags = self.generate_flags(n, ftype)
+ uniques = []
+ sym_base = []
+ asym_base = []
+ sym_base_lasts = []
+ for xx in flags:
+ xxid = xx.unique(weak=True)[0]
+ if xxid not in uniques:
+ uniques.append(xxid)
+ sym_base.append(xx.afae())
+ sym_base_lasts.append(xx.afae())
+ else:
+ sym_ind = uniques.index(xxid)
+ asym_base.append(sym_base_lasts[sym_ind] - xx)
+ sym_base[sym_ind] += xx
+ sym_base_lasts[sym_ind] = xx
+ m_sym = matrix(
+ len(sym_base), len(flags),
+ [xx.values() for xx in sym_base], sparse=True
+ )
+ m_asym = matrix(
+ len(asym_base), len(flags),
+ [xx.values() for xx in asym_base], sparse=True
+ )
+ return m_sym, m_asym
+
+ def _density_wrapper(self, ar):
+ r"""
+ Helper function used in the parallelization of calculating densities
+ """
+ return ar[0].densities(*ar[1:])
+
+class BuiltTheory(_CombinatorialTheory):
+
+ Element = BuiltFlag
+
+ def __init__(self, name, relation_name="edges", arity=2,
+ is_ordered=False, _from_data=None):
+ r"""
+ Initialize a Combinatorial Theory
+
+ A combinatorial theory is any theory with universal axioms only,
+ (therefore the elements satisfy a heredetary property).
+ See the file docstring for more information.
+
+ INPUT:
+
+ - ``name`` -- string; name of the Theory
+ - ``relation_name`` -- string; name of the relation
+ - ``arity`` -- integer; arity of the relation
+ - ``is_ordered`` -- boolean; if the values are ordered
+ - ``_from_data`` -- list; only used internally
+
+ OUTPUT: A CombinatorialTheory object
+ """
+
+ if _from_data != None:
+ self._sources = _from_data[0]
+ _from_data = _from_data[1]
+
+ sered_signature = _from_data[0]
+ self._signature = {}
+ max_group = -1
+ for ll in sered_signature:
+ key = ll[0]
+ val = {
+ "arity": ll[1][0],
+ "ordered": ll[1][1],
+ "group": ll[1][2]
+ }
+ max_group = max(max_group, val["group"])
+ self._signature[key] = val
+ self._symmetries = _from_data[1]
+ if len(self._symmetries) != max_group+1:
+ print(self._symmetries)
+ print(self._signature)
+ raise ValueError("Provided data has different symmetry " +
+ "set size than group number")
+ else:
+ if arity < 1 or (arity not in NN):
+ raise ValueError("Arity must be nonzero positive integer!")
+ self._signature = {relation_name: {
+ "arity": arity,
+ "ordered": is_ordered,
+ "group": 0
+ }}
+ self._sources = None
+ self._symmetries = ((1, 1, tuple()), )
+ self._no_question = True
+ _CombinatorialTheory.__init__(self, name)
+
+ # Parent methods
+ def _element_constructor_(self, n, **kwds):
+ r"""
+ Construct elements of this theory
+
+ INPUT:
+
+ - ``n`` -- the size of the flag
+ - ``**kwds`` -- can contain ftype_points, listing
+ the points that will form part of the ftype;
+ and can contain the blocks for each signature.
+ If they are not included, they are assumed to
+ be empty lists.
+
+ OUTPUT: A Flag with the given parameters
+
+ EXAMPLES::
+
+ Create an empty graph on 3 vertices ::
+
+ sage: GraphTheory(3)
+ Flag on 3 points, ftype from () with edges=()
+
+ Create an edge with one point marked as an ftype ::
+
+ sage: GraphTheory(2, ftype_points=[0], edges=[[0, 1]])
+ Flag on 2 points, ftype from (0,) with edges=(01)
+
+ .. NOTE::
+
+ Different input parameters can result in equal objects, for
+ example the following two graphs are automorphic::
+ sage: b1 = [[0, 1], [0, 2], [0, 4], [1, 3], [2, 4]]
+ sage: b2 = [[0, 4], [1, 2], [1, 3], [2, 3], [3, 4]]
+ sage: g1 = GraphTheory(5, edges=b1)
+ sage: g2 = GraphTheory(5, edges=b2)
+ sage: g1==g2
+ True
+
+ .. SEEALSO::
+
+ :func:`__init__` of :class:`Flag`
+ """
+
+ if isinstance(n, BuiltFlag) or isinstance(n, Pattern):
+ if n.parent()==self:
+ return n
+ n = n.as_pattern()
+ return self.pattern(n.size(), ftype=n.ftype_points(),
+ **n.as_pattern().blocks())
+
+ ftype_points = tuple()
+ if 'ftype_points' in kwds:
+ try:
+ ftype_points = tuple(kwds['ftype_points'])
+ except:
+ raise ValueError("The provided ftype_points must be iterable")
+ elif 'ftype' in kwds:
+ try:
+ ftype_points = tuple(kwds['ftype'])
+ except:
+ raise ValueError("The provided ftype must be iterable")
+
+ blocks = {}
+ for xx in self._signature.keys():
+ blocks[xx] = tuple()
+ unary = (self._signature[xx]["arity"]==1)
+ if xx in kwds:
+ try:
+ blocks[xx] = tuple(kwds[xx])
+ except:
+ raise ValueError("The provided {} must be iterable".format(xx))
+ if unary:
+ if len(blocks[xx])>0:
+ try:
+ tuple(blocks[xx][0])
+ except:
+ blocks[xx] = tuple([[aa] for aa in blocks[xx]])
+
+ return self.element_class(self, n, ftype_points, **blocks)
+
+ def empty_element(self):
+ r"""
+ Returns the empty element, ``n``=0 and no blocks
+
+ OUTPUT: The empty element of the CombinatorialTheory
+
+ EXAMPLES::
+
+ sage: GraphTheory.empty_element()
+ Ftype on 0 points with edges=()
+
+ .. NOTE::
+ This has an alias called :func:`empty`
+ Since the underlying vertex set (empty set)
+ is the same as the ftype point set, this is
+ an ftype
+
+ .. SEEALSO::
+
+ :func:`empty`
+ """
+ blocks = {}
+ for xx in self._signature:
+ blocks[xx] = tuple()
+ return self.element_class(self, 0, tuple(), **blocks)
+
+ empty = empty_element
+
+ def pattern(self, n, **kwds):
+ r"""
+ Construct patterns for this theory
+
+ INPUT:
+
+ - ``n`` -- the size of the flag
+ - ``**kwds`` -- can contain ftype_points, listing
+ the points that will form part of the ftype;
+ and can contain the blocks for each signature.
+ If they are not included, they are assumed to
+ be empty lists. Can also contain missing relations
+ for each signature entry.
+
+ OUTPUT: A Pattern with the given parameters
+
+ EXAMPLES::
+
+ Create a pattern on 3 vertices with one edge required
+ and one edge missing ::
+
+ sage: GraphTheory(3, edges=[[0, 1]], edges_m=[[1, 2]])
+ Flag on 3 points, ftype from () with edges=(01)
+
+ .. NOTE::
+ Also has alias :func:`P`, :func:`p`, :func:`Pattern`
+
+ .. SEEALSO::
+
+ :func:`__init__` of :class:`Pattern`
+ """
+ ftype_points = tuple()
+ if 'ftype_points' in kwds:
+ try:
+ ftype_points = tuple(kwds['ftype_points'])
+ except:
+ raise ValueError("The provided ftype_points must be iterable")
+ elif 'ftype' in kwds:
+ try:
+ ftype_points = tuple(kwds['ftype'])
+ except:
+ raise ValueError("The provided ftype must be iterable")
+ if len(ftype_points)==n:
+ return self._element_constructor_(n, **kwds)
+
+ blocks = {}
+ for xx in self._signature.keys():
+ blocks[xx] = tuple()
+ blocks[xx+"_m"] = tuple()
+ unary = self._signature[xx]["arity"]==1
+
+
+ if xx in kwds:
+ try:
+ blocks[xx] = tuple(kwds[xx])
+ except:
+ raise ValueError(
+ "The provided {} must be iterable".format(xx)
+ )
+ if unary:
+ if len(blocks[xx])>0:
+ try:
+ tuple(blocks[xx][0])
+ except:
+ blocks[xx] = tuple([[aa] for aa in blocks[xx]])
+
+ for xx_missing in [xx+"_m", xx+"_missing", xx+"_miss"]:
+ if xx_missing in kwds:
+ blocks[xx+"_m"] = kwds[xx_missing]
+
+
+ try:
+ blocks[xx+"_m"] = tuple(kwds[xx_missing])
+ except:
+ raise ValueError(
+ "The provided {} must be iterable".format(xx_missing)
+ )
+ if unary:
+ if len(blocks[xx])>0:
+ try:
+ tuple(blocks[xx][0])
+ except:
+ blocks[xx+"_m"] = tuple(
+ [[aa] for aa in blocks[xx+"_m"]]
+ )
+ return Pattern(self, n, ftype_points, **blocks)
+
+ p = pattern
+ P = pattern
+ Pattern = pattern
+
+ def _an_element_(self, n=0, ftype=None):
+ r"""
+ Returns a random element
+
+ INPUT:
+
+ - ``n`` -- integer (default: `0`); size of the element
+ - ``ftype`` -- Flag (default: `None`); ftype of the element
+ if not provided then returns an element with empty ftype
+
+ OUTPUT: A Flag with matching parameters
+
+ EXAMPLES::
+
+ sage: GraphTheory._an_element_()
+ Ftype on 0 points with edges=()
+ """
+ if ftype==None:
+ ftype = self.empty_element()
+ if n==None or n==ftype.size():
+ return ftype
+ ls = self.generate_flags(n, ftype)
+ return ls[randint(0, len(ls)-1)]
+
+ def some_elements(self):
+ r"""
+ Returns a list of elements
+
+ EXAMPLES::
+
+ sage: GraphTheory.some_elements()
+ [Ftype on 0 points with edges=()]
+ """
+ res = [self._an_element_()]
+ return res
+
+ # Optimizing and rounding
+
+ def _get_relevant_ftypes(self, target_size):
+ r"""
+ Returns the relevant ftypes for optimizing up to ``target_size``.
+
+ INPUT:
+
+ - ``target_size`` -- integer; the target size for which the ftypes are generated.
+
+ OUTPUT:
+ list; a list of tuples where each tuple is of the form (ns, ftype, target_size).
+
+ EXAMPLES:
+
+ sage: GraphTheory._get_relevant_ftypes(3)
+ asd
+
+ TESTS:
+
+ sage: GraphTheory._get_relevant_ftypes(-1)
+ asd
+ """
+ if target_size < 0:
+ raise ValueError("Target size should be non-negative!")
+
+ plausible_sizes = list(range(1, target_size))
+ ftype_pairs = []
+ for fs, ns in itertools.combinations(plausible_sizes, r=int(2)):
+ if ns + ns - fs <= target_size:
+ kk = ns - fs
+ found = False
+ for ii, (bfs, bns) in enumerate(ftype_pairs):
+ if bns - bfs == kk:
+ found = True
+ if ns > bns:
+ ftype_pairs[ii] = (fs, ns)
+ break
+ if not found:
+ ftype_pairs.append((fs, ns))
+
+ ftype_data = []
+ for fs, ns in ftype_pairs:
+ ftype_flags = self.generate_flags(fs)
+ ftypes = [flag.subflag([], ftype_points=list(range(fs)))
+ for flag in ftype_flags]
+ for xx in ftypes:
+ ftype_data.append((ns, xx, target_size))
+ ftype_data.sort()
+ return ftype_data
+
+ # Generating flags
+
+ def _guess_number(self, n):
+ if n==0:
+ return 1
+ excluded = self.get_total_excluded(n)
+ key = ("generate", n,
+ self._serialize(excluded), self.empty()._serialize())
+ loaded = self._load(key=key)
+ if loaded != None:
+ return len(loaded)
+ if self._sources==None:
+ max_arity = -1
+ for xx in self._signature:
+ max_arity = max(max_arity, self._signature[xx]["arity"])
+ if max_arity==1 or n=target_size:
+ break
+ if fs==0:
+ continue
+ plausible_sizes.append(fs)
+ ftype_pairs = []
+ for fs, ns in itertools.combinations(plausible_sizes, r=int(2)):
+ if self.size_combine(fs, ns, ns) <= target_size:
+ kk = ns-fs
+ found = False
+ for ii, (bfs, bns) in enumerate(ftype_pairs):
+ if bns-bfs==kk:
+ found = True
+ if ns>bns:
+ ftype_pairs[ii]=(fs, ns)
+ break
+ if not found:
+ ftype_pairs.append((fs, ns))
+
+ ftype_data = []
+ for fs, ns in ftype_pairs:
+ ftype_flags = self.generate_flags(fs)
+ ftypes = [flag.subflag([], ftype_points=list(range(fs))) for flag in ftype_flags]
+ for xx in ftypes:
+ ftype_data.append((ns, xx, target_size))
+ ftype_data.sort()
+ return ftype_data
+
+ # Generating flags
+
+ def sizes(self):
+ return self._sizes
+
+ def size_combine(self, k, n1, n2):
+ if k<0 or n1<0 or n2<0:
+ raise ValueError("Can't have negative size.")
+ if n10 and n2>0:
+ return None
+ n1 = max(n1, 1)
+ n2 = max(n2, 1)
+ k = max(k, 1)
+ if log(n1, 2) not in NN or log(n2, 2) not in NN or log(k, 2) not in NN:
+ return None
+ return QQ(n1*n2/k)
+
+PermutationTheory = ExoticTheory('Permutation',
+ _generator_permutation,
+ _identify_permutation,
+ edges=2)
+
+OEGraphTheory = ExoticTheory('OEdgeGraph',
+ _generator_oe_graph,
+ _identify_oe_graph,
+ edges=2)
+
+OVGraphTheory = ExoticTheory('OVertexGraph',
+ _generator_ov_graph,
+ _identify_ov_graph,
+ edges=2)
+
+# should only be used up to size 8.
+HypercubeGraphTheory = ExoticTheory('HypercubeGraph',
+ _generator_cube_graphs,
+ _identify_cube_graphs,
+ size_combine=_cube_size_combine,
+ edges=2)
+
+# should only be used up to size 16.
+HypercubeVertexTheory = ExoticTheory('HypercubeVertex',
+ _generator_cube_points,
+ _identify_cube_points,
+ size_combine=_cube_size_combine,
+ edges=2, points=1)
+
+def Theory(name, *args, **kwdargs):
+ if name=="Permutation":
+ return PermutationTheory
+ if name=="OEdgeGraph":
+ return OEGraphTheory
+ if name=="OVertexGraph":
+ return OVGraphTheory
+ if name=="HypercubeGraph":
+ return HypercubeGraphTheory
+ if name=="HypercubeVertex":
+ return HypercubeVertexTheory
+ return BuiltTheory(name, *args, **kwdargs)
\ No newline at end of file
diff --git a/src/sage/algebras/flag.pyx b/src/sage/algebras/flag.pyx
new file mode 100644
index 00000000000..c00b449503a
--- /dev/null
+++ b/src/sage/algebras/flag.pyx
@@ -0,0 +1,1975 @@
+r"""
+Implementation of Flag, elements of :class:`CombinatorialTheory`
+
+AUTHORS:
+
+- Levente Bodnar (2023-2025): Main development
+
+"""
+
+# ****************************************************************************
+# Copyright (C) 2023 LEVENTE BODNAR
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+# https://www.gnu.org/licenses/
+# ****************************************************************************
+
+import itertools
+from sage.all import QQ
+from cysignals.signals cimport sig_check
+from sage.structure.element cimport Element
+from sage.structure.coerce cimport coercion_model
+from blisspy cimport canonical_form_from_edge_list, automorphism_group_gens_from_edge_list
+from tqdm import tqdm
+
+# Elementary block operations
+cdef tuple _subblock_helper(tuple points, tuple block, bint inverse = False):
+ if len(block)==0:
+ return tuple()
+ cdef set points_set = set(points)
+ cdef dict points_index = {p: i for i, p in enumerate(points)}
+ if inverse:
+ points_index = {i: p for i, p in enumerate(points)}
+ cdef list ret = []
+ cdef bint gd
+ cdef int yy
+ for xx in block:
+ gd = True
+ for yy in xx:
+ if yy not in points_set:
+ gd = False
+ break
+ if gd:
+ ret.append(tuple([points_index[yy] for yy in xx]))
+ return tuple(ret)
+
+cdef dict _merge_blocks(dict block0, dict block1, tuple only_include):
+ cdef dict merged = {}
+ cdef str key
+ cdef tuple tp
+ cdef int xx
+ for key in block0:
+ merged[key] = tuple(list(block0[key]) + [
+ tp for tp in block1[key] if all([(xx in tp) for xx in only_include])
+ ])
+ return merged
+
+cdef dict _perm_blocks(dict blocks, tuple perm, bint inverse = False):
+ cdef dict ret
+ cdef str xx
+ ret = {
+ xx: _subblock_helper(perm, blocks[xx], inverse)
+ for xx in blocks.keys()
+ }
+ return ret
+
+cdef dict _standardize_blocks(dict blocks, dict signature, bint pattern):
+ cdef dict ret = {}
+ cdef str xx, kk
+ for xx in blocks:
+ if not pattern:
+ if signature[xx]["ordered"]:
+ ret[xx] = tuple(sorted([tuple(yy) for yy in blocks[xx]]))
+ else:
+ ret[xx] = tuple(sorted([tuple(sorted(yy)) for yy in blocks[xx]]))
+ else:
+ kk = xx
+ if xx not in signature:
+ kk = xx[:-2]
+ if signature[kk]["ordered"]:
+ ret[xx] = tuple(sorted([tuple(yy) for yy in blocks[xx]]))
+ else:
+ ret[xx] = tuple(sorted([tuple(sorted(yy)) for yy in blocks[xx]]))
+ return ret
+
+cdef dict _perm_signature(dict blocks, tuple perm):
+ cdef dict ret = {}
+ cdef int ii
+ cdef str xx
+ for ii, xx in enumerate(blocks.keys()):
+ ret[xx] = blocks[perm[ii]]
+ return ret
+
+cdef dict _perm_pattern_signature(dict blocks, tuple perm, dict orig_sign):
+ cdef dict ret = {}
+ cdef int ii
+ cdef str xx
+ for ii, xx in enumerate(orig_sign.keys()):
+ ret[xx] = blocks[perm[ii]]
+ ret[xx+"_m"] = blocks[perm[ii]+"_m"]
+ return ret
+
+# Group operations
+
+cdef set _generate_group(tuple generators, int n):
+ cdef set group = set()
+ cdef list to_check = [tuple(range(n))]
+ cdef tuple perm, gen, new_perm
+ cdef int i
+ while to_check:
+ perm = to_check.pop()
+ if perm in group:
+ continue
+ group.add(perm)
+ for gen in generators:
+ new_perm = tuple(gen[perm[i]] for i in range(n))
+ if new_perm not in group:
+ to_check.append(new_perm)
+ return group
+
+cdef list _compute_coset_reps(set G, set H, int n):
+ cdef list coset_reps = []
+ cdef tuple g, c, h_tuple
+ cdef int i
+ cdef bint in_existing_coset
+ cdef list h
+ for g in G:
+ in_existing_coset = False
+ for c in coset_reps:
+ h = [0] * n
+ for i in range(n):
+ h[c[i]] = g[i]
+ h_tuple = tuple(h)
+ if h_tuple in H:
+ in_existing_coset = True
+ break
+ if not in_existing_coset:
+ coset_reps.append(g)
+ return coset_reps
+
+cdef inline tuple _compose2(tuple a, tuple b, tuple c, int n):
+ cdef int ii
+ cdef list out_list = [0]*n
+ for ii in range(n):
+ out_list[ii] = a[b[c[ii]]]
+ return tuple(out_list)
+
+cdef inline tuple _compose(tuple a, tuple b, int n):
+ cdef int ii
+ cdef list out_list = [0]*n
+ for ii in range(n):
+ out_list[ii] = a[b[ii]]
+ return tuple(out_list)
+
+cdef tuple _invert_c(tuple p, int n):
+ cdef int ii
+ cdef list inv_list = [0]*n
+ for ii in range(n):
+ inv_list[p[ii]] = ii
+ return tuple(inv_list)
+
+cdef set _generate_coset(set G, tuple sigma, int n):
+ cdef set coset = set()
+ cdef tuple g
+ for g in G:
+ coset.add(_compose(g, sigma, n))
+ return coset
+
+cdef dict _left_coset_representative_map(set G0, int n):
+ cdef dict rep_map = {}
+ cdef set visited = set()
+ cdef tuple sigma
+ cdef set coset
+ for sigma in itertools.permutations(range(n)):
+ if sigma in visited:
+ continue
+ rep_map[sigma] = sigma
+ coset = _generate_coset(G0, sigma, n)
+ for c in coset:
+ visited.add(c)
+ rep_map[c] = sigma
+ return rep_map
+
+cdef set _find_double_coset_reps(set G0, set G1, dict rep_map, int n):
+ cdef set T = set(rep_map.values())
+ cdef set double_coset_reps = set()
+ cdef tuple t, r, g1
+ cdef tuple g1_inv, r_inv, candidate_g0
+ cdef bint is_new
+
+ for t in T:
+ is_new = True
+ for r in double_coset_reps:
+ for g1 in G1:
+ g1_inv = _invert_c(g1, n)
+ r_inv = _invert_c(r, n)
+ candidate_g0 = _compose2(t, g1_inv, r_inv, n)
+ if candidate_g0 in G0:
+ is_new = False
+ break
+ if not is_new:
+ break
+ if is_new:
+ double_coset_reps.add(t)
+ return double_coset_reps
+
+# Helpers for the generators
+
+cdef int _get_max_arity(dict signature, int n=1000):
+ cdef int max_arity = 0
+ cdef int curr_arity
+ cdef str xx
+ for xx in signature:
+ curr_arity = signature[xx]['arity']
+ if curr_arity>n:
+ continue
+ max_arity = max(max_arity, curr_arity)
+ return max_arity
+
+cpdef tuple _get_single_extensions(dict relation, int n, int fix):
+ cdef int arity = relation['arity']
+ if narity or n=other.size():
+ return False
+ if self.ftype() != other.ftype():
+ return False
+ for subp in itertools.combinations(other.not_ftype_points(), self.size()-self.ftype().size()):
+ sig_check()
+ osub = other.subflag(subp)
+ if self==osub:
+ return True
+ return False
+
+ def __le__(self, other):
+ r"""
+ Compare two flags for <= (induced inclusion)
+
+ Returns true if self appears as an induced structure inside
+ other.
+
+ EXAMPLES::
+
+ Edge appears in a 4 star ::
+
+
+ sage: star = GraphTheory(4, edges=[[0, 1], [0, 2], [0, 3]])
+ sage: edge = GraphTheory(2, edges=[[0, 1]])
+ sage: edge <= star
+ True
+
+ The ftypes must agree ::
+
+ sage: p_edge = GraphTheory(2, edges=[[0, 1]], ftype_points=[0])
+ sage: p_edge <= star
+ False
+
+ But when ftypes agree, the inclusion must respect it ::
+
+ sage: pstar = star.subflag(ftype_points=[0])
+ sage: sub1 = GraphTheory(3, ftype=[0], edges=[[0, 1], [0, 2]])
+ sage: sub1 <= pstar
+ True
+ sage: sub2 = GraphTheory(3, ftype=[1], edges=[[0, 1], [0, 2]])
+ sage: sub2 <= pstar
+ False
+
+ .. SEEALSO::
+
+ :func:`__lt__`
+ :func:`__eq__`
+ :func:`unique`
+ """
+ return self==other or self
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+# https://www.gnu.org/licenses/
+# ****************************************************************************
+
+import itertools
+
+from sage.structure.richcmp import richcmp
+from sage.structure.unique_representation import UniqueRepresentation
+from sage.structure.parent import Parent
+from sage.structure.element import Element, get_coercion_model
+from sage.all import QQ, Integer
+from sage.algebras.flag import _Flag, Pattern
+
+from sage.all import vector
+
+
+class FlagAlgebraElement(Element):
+ def __init__(self, parent, n, values):
+ r"""
+ Initialize a Flag Algebra Element
+
+ INPUT:
+
+ - ``parent`` -- FlagAlgebra; The parent :class:`FlagAlgebra`
+ - ``n`` -- integer; the size of the flags
+ - ``values`` -- vector; the values representing the
+ linear combination of :class:`Flag` elements
+
+ OUTPUT: A FlagAlgebraElement object with the given initial parameters
+
+ .. NOTE::
+
+ It is recommended to use the FlagAlgebra element constructor
+
+ .. SEEALSO::
+
+ :func:`FlagAlgebra._element_constructor_`
+ """
+ if len(values)!=parent.get_size(n):
+ raise ValueError("The coefficients must have the same length " +
+ "as the number of flags")
+ self._n = n
+ base = parent.base()
+ self._values = vector(base, values, sparse=True)
+ Element.__init__(self, parent)
+
+ def ftype(self):
+ r"""
+ Return the ftype of the parent FlagAlgebra
+
+ The algebra is defined over elements of a CombinatorialTheory, all
+ having the same ftype.
+
+ OUTPUT: The ftype of the parent FlagAlgebra. A :class:`Flag` element
+
+ EXAMPLES::
+
+ The ftype of a :class:`Flag` is the same as the ftype of the
+ :class:`FlagAlgebraElement` we can construct from it ::
+
+
+ sage: g = GraphTheory(3)
+ sage: g.ftype()
+ Ftype on 0 points with edges=()
+ sage: g.ftype()==g.afae().ftype()
+ True
+
+ .. SEEALSO::
+
+ :func:`ftype` in :class:`FlagAlgebra`
+ :func:`ftype` in :class:`Flag`
+ """
+ return self.parent().ftype()
+
+ def size(self):
+ r"""
+ Return the size of the vertex set of flags in this element
+
+ OUTPUT: The size of each flag is :func:`flags`.
+
+ TESTS::
+
+
+ sage: FG = FlagAlgebra(GraphTheory, QQ)
+ sage: FGElem = FG._an_element_()
+ sage: FGElem.size() == FGElem.flags()[0].size()
+ True
+ """
+ return self._n
+
+ vertex_number = size
+
+ def flags(self):
+ r"""
+ Returns the list of flags, corresponding to each base element
+
+ The flags returned are the list of flags with size equal to
+ `self.size()` and ftype to `self.ftype()`. Their number is
+ the same as the length of `self.values()`.
+
+ OUTPUT: The list of flags
+
+ EXAMPLES::
+
+ 3 vertex graphs with empty ftype ::
+
+
+ sage: g = GraphTheory(3)
+ sage: g.afae()
+ Flag Algebra Element over Rational Field
+ 1 - Flag on 3 points, ftype from () with edges=()
+ 0 - Flag on 3 points, ftype from () with edges=(01)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02 12)
+ sage: g.afae().flags()
+ (Flag on 3 points, ftype from () with edges=(),
+ Flag on 3 points, ftype from () with edges=(01),
+ Flag on 3 points, ftype from () with edges=(01 02),
+ Flag on 3 points, ftype from () with edges=(01 02 12))
+
+ .. NOTE::
+
+ This is the same as
+ `self.theory().generate_flags(self.size(), self.ftype())`
+
+ .. SEEALSO::
+
+ :func:`CombinatorialTheory.generate_flags`
+ :func:`size`
+ :func:`ftype`
+ :func:`values`
+ :func:`Flag.afae`
+
+ TESTS::
+
+
+ sage: g.afae().flags() == g.theory().generate_flags(g.size(), g.ftype())
+ True
+ """
+ return self.parent().generate_flags(self._n)
+
+ def values(self):
+ r"""
+ Returns the vector of values, corresponding to each element
+ in :func:`flags`
+
+ OUTPUT: A vector
+
+ EXAMPLES::
+
+ A flag transformed to a flag algebra element has
+ all zeroes except one entry, itself ::
+
+
+ sage: g = GraphTheory(3)
+ sage: g.afae().values()
+ (1, 0, 0, 0)
+
+ .. SEEALSO::
+
+ :func:`flags`
+ :func:`_vector_`
+ :func:`Flag.afae`
+ """
+ return self._values
+
+ def _vector_(self, R):
+ r"""
+ Returns the vector of values, corresponding to each element
+ in :func:`flags` in a given base.
+
+ OUTPUT: A vector
+
+ EXAMPLES::
+
+ A flag transformed to a flag algebra element has
+ all zeroes except one entry, itself ::
+
+
+ sage: g = GraphTheory(3)
+ sage: g.afae()._vector_(QQ['x'])
+ (1, 0, 0, 0)
+
+ .. SEEALSO::
+
+ :func:`values()`
+ :func:`Flag.afae`
+ """
+ return vector(R, self._values, sparse=True)
+
+ def __len__(self):
+ r"""
+ Returns the length, the number of elements
+ in :func:`flags` or :func:`values` (which is the same)
+
+ EXAMPLES::
+
+ sage: g = GraphTheory(3)
+ sage: len(g.afae())
+ 4
+
+ .. SEEALSO::
+
+ :func:`flags`
+ :func:`values`
+ :func:`Flag.afae`
+ :func:`__iter__`
+ """
+ return len(self._values)
+
+ def __iter__(self):
+ r"""
+ Iterates over the elements of self
+
+ It yields (number, flag) tuples, indicating the
+ coefficient of the flag.
+
+ EXAMPLES::
+
+ sage: g = GraphTheory(3)
+ sage: for x in g.afae():
+ ....: print(x)
+ (1, Flag on 3 points, ftype from () with edges=())
+ (0, Flag on 3 points, ftype from () with edges=(01))
+ (0, Flag on 3 points, ftype from () with edges=(01 02))
+ (0, Flag on 3 points, ftype from () with edges=(01 02 12))
+
+
+ .. SEEALSO::
+
+ :func:`flags`
+ :func:`values`
+ :func:`Flag.afae`
+ :func:`__len__`
+ """
+ for ii, fl in enumerate(self.parent().generate_flags(self.size())):
+ yield (self._values[ii], fl)
+
+ def _repr_(self):
+ r"""
+ Give a string representation
+
+ Lists the flags and the corresponding coefficients,
+ each on a separate line. If the list is too long
+ then only shows nonzero entries.
+
+
+ EXAMPLES::
+
+ Short list, so display all ::
+
+
+ sage: gf = GraphTheory(3).afae()
+ sage: gf
+ Flag Algebra Element over Rational Field
+ 1 - Flag on 3 points, ftype from () with edges=()
+ 0 - Flag on 3 points, ftype from () with edges=(01)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02 12)
+
+ Long list, only the nonzero entries are displayed::
+
+ sage: g1 = GraphTheory(5)
+ sage: g2 = GraphTheory(5, edges=[[0, 1], [3, 4]])
+ sage: g1+g2
+ Flag Algebra Element over Rational Field
+ 1 - Flag on 5 points, ftype from () with edges=()
+ 1 - Flag on 5 points, ftype from () with edges=(02 14)
+
+ .. SEEALSO::
+
+ :func:`Flag._repr_`
+ """
+ sttrl = ['Flag Algebra Element over {}'.format(
+ self.parent().base()
+ )]
+ strs = [str(xx) for xx in self.values()]
+ maxstrlen = max([len(xx) for xx in strs])
+ for ii, fl in enumerate(self.parent().generate(self.size())):
+ if len(self)<10:
+ sttrl.append(('{:<'+str(maxstrlen)+'} - {}').format(
+ strs[ii], str(fl)
+ ))
+ else:
+ include = True
+ try:
+ include = abs(float(self.values()[ii]))>=1e-8
+ except:
+ include = self.values()[ii]!=0
+ if include:
+ sttrl.append(('{:<'+str(maxstrlen)+'} - {}').format(
+ strs[ii], str(fl)
+ ))
+ return "\n".join(sttrl)
+
+ def base(self):
+ return self.parent().base()
+
+ def theory(self):
+ return self.parent().theory()
+
+ def custom_coerce(self, other):
+ if isinstance(other, _Flag):
+ if self.ftype()!=other.ftype():
+ raise ValueError("The ftypes must agree.")
+ alg = self.parent()
+ return (self, alg(other))
+ elif isinstance(other, FlagAlgebraElement):
+ if self.ftype()!=other.ftype():
+ raise ValueError("The ftypes must agree.")
+ sbase = self.base()
+ obase = other.base()
+ base = get_coercion_model().common_parent(sbase, obase)
+ alg = FlagAlgebra(self.theory(), base, self.ftype())
+ return (alg(self), alg(other))
+ else:
+ base = get_coercion_model().common_parent(
+ self.base(), other.parent()
+ )
+ alg = FlagAlgebra(self.theory(), base, self.ftype())
+ return (alg(self), alg(base(other)))
+
+ def as_flag_algebra_element(self):
+ r"""
+ Returns self.
+
+ Only here to allow calling this function on
+ both flags and flag algebra elements
+
+ .. SEEALSO::
+
+ :func:`Flag.afae`
+ """
+ return self
+
+ afae = as_flag_algebra_element
+
+ def _add_(self, other):
+ r"""
+ Adds to FlagAlgebraElements together
+
+ OUTPUT: The sum
+
+ EXAMPLES::
+
+ The smaller size is shifted to match the larger ::
+
+
+ sage: g = GraphTheory(3).afae()
+ sage: e = GraphTheory(2).afae()
+ sage: e+g
+ Flag Algebra Element over Rational Field
+ 2 - Flag on 3 points, ftype from () with edges=()
+ 2/3 - Flag on 3 points, ftype from () with edges=(01)
+ 1/3 - Flag on 3 points, ftype from () with edges=(01 02)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02 12)
+
+ .. NOTE::
+
+ The result's size will match the size of the larger component
+
+ .. SEEALSO::
+
+ :func:`Flag._add_`
+ :func:`__lshift__`
+ :func:`_sub_`
+ """
+ nm = max(self.size(), other.size())
+ vals = (self<<(nm-self.size())).values() + \
+ (other<<(nm-other.size())).values()
+ return self.__class__(self.parent(), nm, vals)
+
+ def _sub_(self, other):
+ r"""
+ Subtract a FlagAlgebraElement from this
+
+ EXAMPLES::
+
+ This also shifts the smaller flag to match the larger ::
+
+
+ sage: g = GraphTheory(3).afae()
+ sage: e = GraphTheory(2).afae()
+ sage: e-g
+ Flag Algebra Element over Rational Field
+ 0 - Flag on 3 points, ftype from () with edges=()
+ 2/3 - Flag on 3 points, ftype from () with edges=(01)
+ 1/3 - Flag on 3 points, ftype from () with edges=(01 02)
+ 0 - Flag on 3 points, ftype from () with edges=(01 02 12)
+
+ .. SEEALSO::
+
+ :func:`Flag._sub_`
+ :func:`__lshift__`
+ :func:`_add_`
+ """
+ nm = max(self.size(), other.size())
+ vals = (self<<(nm-self.size())).values() - \
+ (other<<(nm-other.size())).values()
+ return self.__class__(self.parent(), nm, vals)
+
+ def _neg_(self):
+ return self.__class__(
+ self.parent(),
+ self.size(),
+ self.values()*(-1))
+
+ def _mul_(self, other):
+ r"""
+ Multiplies two elements together
+
+ The result will have size
+ `self.size() + other.size() - self.ftype().size()`
+
+ EXAMPLES::
+
+ Two empty edges multiplied together has size 4 ::
+
+
+ sage: e = GraphTheory(2).afae()
+ sage: (e*e).size()
+ 4
+
+ But if pointed (size of ftype is 1), then the size is 3 ::
+
+ sage: pe = GraphTheory(2, ftype=[0])
+ sage: pe*pe
+ Flag Algebra Element over Rational Field
+ 1 - Flag on 3 points, ftype from (0,) with edges=()
+ 0 - Flag on 3 points, ftype from (0,) with edges=(01)
+ 1 - Flag on 3 points, ftype from (2,) with edges=(01)
+ 0 - Flag on 3 points, ftype from (0,) with edges=(01 02)
+ 0 - Flag on 3 points, ftype from (1,) with edges=(01 02)
+ 0 - Flag on 3 points, ftype from (0,) with edges=(01 02 12)
+
+ Can also multiply with constants:
+
+ sage: pe*3
+ Flag Algebra Element over Rational Field
+ 3 - Flag on 2 points, ftype from (0,) with edges=()
+ 0 - Flag on 2 points, ftype from (0,) with edges=(01)
+
+ .. NOTE::
+
+ Multiplying and then projecting to a smaller ftype
+ can be performed more efficiently with :func:`mul_project`
+
+ .. SEEALSO::
+
+ :func:`Flag._mul_`
+ :func:`mul_project`
+ """
+ if self.size()==self.ftype().size():
+ vals = other.values() * self.values()[0]
+ return self.__class__(self.parent(), other.size(), vals)
+ if other.size()==self.ftype().size():
+ vals = self.values() * other.values()[0]
+ return self.__class__(self.parent(), self.size(), vals)
+ table = self.parent().mpt(self.size(), other.size())
+ N = -self.ftype().size() + self.size() + other.size()
+ vals = [self.values() * mat * other.values() for mat in table]
+ return self.__class__(self.parent(), N, vals)
+
+ def __truediv__(self, other):
+ r"""
+ Divide by a scalar
+
+ INPUT:
+
+ - ``other`` -- number; any number such that `1` can be divided with that
+
+ OUTPUT: The `FlagAlgebraElement` resulting from the division
+
+ EXAMPLES::
+
+ If 1 can be divided by that, then the division is allowed ::
+
+
+ sage: var('x')
+ x
+ sage: g = GraphTheory(2)
+ sage: g.afae()/x
+ Flag Algebra Element over Symbolic Ring
+ 1/x - Flag on 2 points, ftype from () with edges=()
+ 0 - Flag on 2 points, ftype from () with edges=(01)
+
+ .. NOTE::
+
+ This is the linear extension of :func:`Flag.__truediv__`
+
+ .. SEEALSO::
+
+ :func:`Flag.afae`
+ :func:`Flag.__truediv__`
+ """
+ return self * (1/other)
+
+ def __lshift__(self, amount):
+ r"""
+ `FlagAlgebraElement`, equal to this, with size is
+ shifted by the amount
+
+ The result will have size equal to
+ `self.size() + amount`, but the elements will be equal
+
+ EXAMPLES::
+
+ Edge shifted to size `3` ::
+
+
+ sage: edge = GraphTheory(2, edges=[[0, 1]])
+ sage: (edge.afae()<<1).values()
+ (0, 1/3, 2/3, 1)
+
+ .. NOTE::
+
+ This is the linear extension of :func:`Flag.__lshift__`
+
+ .. SEEALSO::
+
+ :func:`Flag.__lshift__`
+ :func:`Flag.afae()`
+ """
+ if amount<0:
+ raise ValueError('Can not have negative shifts')
+ if amount==0:
+ return self
+ ressize = amount + self.size()
+ table = self.parent().mpt(self.size(), self.ftype().size(),
+ target_size=ressize)
+ vals = [sum(self.values() * mat) for mat in table]
+ return self.__class__(self.parent(), ressize, vals)
+
+ def __getitem__(self, flag):
+ if isinstance(flag, _Flag) and not isinstance(flag, Pattern):
+ ind = self.parent().get_index(flag)
+ elif isinstance(flag, Integer) and \
+ 0 <= flag and \
+ flag < self.parent().get_size(self.size()):
+ ind = flag
+ if ind == -1:
+ raise TypeError("Indecies must be Flags with matching " +
+ "ftype and size, or integers. " +
+ "Not {}".format(str(type(flag))))
+ return self._values[ind]
+
+ def __setitem__(self, flag, value):
+ if isinstance(flag, _Flag) and not isinstance(flag, Pattern):
+ ind = self.parent().get_index(flag)
+ elif isinstance(flag, Integer) and \
+ 0 <= flag and \
+ flag < self.parent().get_size(self.size()):
+ ind = flag
+ if ind == -1:
+ raise TypeError("Indecies must be Flags with matching " +
+ "ftype and size, or integers. " +
+ "Not {}".format(str(type(flag))))
+ self.values()[self.flags().index(flag)] = value
+
+ def project(self, ftype_inj=tuple()):
+ r"""
+ Project this to a smaller ftype
+
+
+ INPUT:
+
+ - ``ftype_inj`` -- tuple (default: (, )); the injection of the
+ projected ftype inside the larger ftype
+
+ OUTPUT: the `FlagAlgebraElement` resulting from the projection
+
+ EXAMPLES::
+
+ If the center of a cherry is flagged, then the projection has
+ coefficient 1/3 ::
+
+
+ sage: p_cherry = GraphTheory(3, edges=[[0, 1], [0, 2]], ftype_points=[0])
+ sage: p_cherry.afae().project().values()
+ (0, 0, 1/3, 0)
+
+ .. NOTE::
+
+ This is the linear extension of :func:`Flag.project`
+
+ If `ftype_inj==tuple(range(self.ftype().size()))` then this
+ does nothing.
+
+ .. SEEALSO::
+
+ :func:`Flag.project`
+ """
+ return self.mul_project(1, ftype_inj)
+
+ def mul_project(self, other, ftype_inj=tuple(), target_size=None):
+ r"""
+ Multiply self with other, and the project the result.
+
+ INPUT:
+
+ - ``ftype_inj`` -- tuple (default: (, )); the injection of the
+ projected ftype inside the larger ftype
+
+ OUTPUT: the `FlagAlgebraElement` resulting from the multiplication
+ and projection
+
+ EXAMPLES::
+
+ Pointed edge multiplied with itself and projected ::
+
+
+ sage: felem = GraphTheory(2, edges=[[0, 1]], ftype_points=[0]).afae()
+ sage: felem.mul_project(felem).values()
+ (0, 0, 1/3, 1)
+
+ .. NOTE::
+
+ This is the bilinear extension of :func:`Flag.mul_project`
+
+ If `ftype_inj==tuple(range(self.ftype().size()))` then this
+ is the same as usual multiplication.
+
+ .. SEEALSO::
+
+ :func:`_mul_`
+ :func:`project`
+ :func:`Flag.mul_project`
+ """
+ other = self.parent(other)
+ ftype_inj = tuple(ftype_inj)
+ new_ftype = self.ftype().subflag([], ftype_points=ftype_inj)
+ if new_ftype==None or new_ftype.unique()==None:
+ raise ValueError("The ftype injection maps to an invalid ftype.")
+ N = -self.ftype().size() + self.size() + other.size()
+ if target_size!=None:
+ if target_size= GraphTheory(2)`, so K_3 free graphs can not
+ contain more than 1/2 density of K_2 ::
+
+ sage: GraphTheory.exclude(GraphTheory(3))
+ sage: f = GraphTheory(2, ftype=[0]) - 1/2
+ sage: 1/2 >= f.mul_project(f)*2 + GraphTheory(2)
+ True
+
+ .. NOTE::
+
+ When comparing Flags with FlagAlgebraElements, it
+ will return False, unless the Flag is transformed
+ to a FlagAlgebraElement.
+
+ .. SEEALSO::
+
+ :func:`mul_project`
+ """
+ nm = max(self.size(), other.size())
+ v1 = (self<<(nm-self.size())).values()
+ v2 = (other<<(nm-other.size())).values()
+ return all([richcmp(v1[ii], v2[ii], op) for ii in range(len(v1))])
+
+class FlagAlgebra(Parent, UniqueRepresentation):
+
+ def __init__(self, theory, base=QQ, ftype=None):
+ r"""
+ Initialize a FlagAlgebra
+
+ INPUT:
+
+ - ``base`` -- Ring; The base ring, this FlagAlgebra is constructed over
+ This must contain the rationals `QQ`
+ - ``theory`` -- CombinatorialTheory; The combinatorial theory
+ this flag algebra is based on
+ - ``ftype`` -- Flag (default=`None`); The ftype of the elements
+ in this FlagAlgebra. The default `None` gives the empty type
+
+ OUTPUT: The resulting FlagAlgebra
+
+ EXAMPLES::
+
+ Create the FlagAlgebra for GraphTheory (without any ftype) ::
+
+ sage: GraphFlagAlgebra = FlagAlgebra(GraphTheory, QQ)
+ sage: GraphFlagAlgebra
+ Flag Algebra with Ftype on 0 points with edges=() over Rational Field
+ """
+ if ftype==None:
+ ftype = theory.empty_element()
+ else:
+ if not ftype.is_ftype():
+ raise ValueError('{} is not an Ftype'.format(ftype))
+ if ftype.theory() != theory:
+ raise ValueError('{} is not a part of {}'.format(ftype, theory))
+ if not base.has_coerce_map_from(QQ):
+ raise ValueError('The base must contain the rationals')
+ self._theory = theory
+ self._ftype = ftype
+ self._index_set = {}
+ self._size_set = {}
+ self._curr_excluded = theory._excluded
+ Parent.__init__(self, base)
+
+ Element = FlagAlgebraElement
+
+ def get_index_set(self, n):
+ if self._theory._excluded != self._curr_excluded:
+ self._curr_excluded = self._theory._excluded
+ self._size_set = {}
+ self._index_set = {}
+ if n not in self._index_set:
+ fls = self.generate_flags(n)
+ fldict = dict(zip(fls, range(len(fls))))
+ self._index_set[n] = fldict
+ return self._index_set[n]
+
+ def get_index(self, flag):
+ if not isinstance(flag, _Flag):
+ return -1
+ if flag.ftype()!=self.ftype():
+ return -1
+ indn = self.get_index_set(flag.size())
+ if flag not in indn:
+ return -1
+ return indn[flag]
+
+ def get_size(self, n):
+ if self._theory._excluded != self._curr_excluded:
+ self._curr_excluded = self._theory._excluded
+ self._size_set = {}
+ self._index_set = {}
+ if n not in self._size_set:
+ self._size_set[n] = len(self.generate_flags(n))
+ return self._size_set[n]
+
+ def _element_constructor_(self, *args, **kwds):
+ r"""
+ Constructs a FlagAlgebraElement with the given parameters
+
+ If a single value is provided it constructs the constant in the Algebra
+ If a Flag is provided it constructs the element whose only `1` value is
+ that Flag.
+ If a different FlagAlgebraElement is provided, then checks if the
+ `theory` and the `ftype` agrees, then tries to coerce the values to the
+ base of self.
+ Otherwise uses the constructor of FlagAlgebraElement, which accepts a
+ size value and a list of coefficients, whose length must be precisely the
+ number of flags.
+
+ EXAMPLES::
+
+ Construct from a constant ::
+
+
+ sage: FA = FlagAlgebra(GraphTheory, QQ)
+ sage: FA(3)
+ Flag Algebra Element over Rational Field
+ 3 - Ftype on 0 points with edges=()
+
+ Construct from a flag ::
+
+ sage: g = GraphTheory(2)
+ sage: el = FA(g)
+ sage: el
+ Flag Algebra Element over Rational Field
+ 1 - Flag on 2 points, ftype from () with edges=()
+ 0 - Flag on 2 points, ftype from () with edges=(01)
+
+ Construct from a FlagAlgebraElement with smaller base ::
+
+ sage: FAX = FlagAlgebra(GraphTheory, QQ['x'])
+ sage: FAX(el)
+ Flag Algebra Element over Univariate Polynomial Ring in x over Rational Field
+ 1 - Flag on 2 points, ftype from () with edges=()
+ 0 - Flag on 2 points, ftype from () with edges=(01)
+
+ Constructing the element directly from coefficients ::
+
+ sage: FA(2, [3, 4])
+ Flag Algebra Element over Rational Field
+ 3 - Flag on 2 points, ftype from () with edges=()
+ 4 - Flag on 2 points, ftype from () with edges=(01)
+
+ .. SEEALSO::
+
+ :func:`FlagAlgebraElement.__init__`
+ """
+ if len(args)==1:
+ v = args[0]
+ base = self.base()
+ if isinstance(v, _Flag) and not isinstance(v, Pattern):
+ ind = self.get_index(v)
+ size = self.get_size(v.size())
+ if ind!=-1:
+ return self.element_class(
+ self, v.size(), vector(self.base(), size, {ind:1})
+ )
+ elif isinstance(v, Pattern):
+ if self.ftype() == v.ftype():
+ dvec = {self.get_index(xx):1 for xx in v.compatible_flags()}
+ size = self.get_size(v.size())
+ return self.element_class(
+ self, v.size(), vector(self.base(), size, dvec)
+ )
+ elif isinstance(v, FlagAlgebraElement):
+ if v.ftype()==self.ftype():
+ if self.base()==v.parent().base():
+ return v
+ elif self.base().has_coerce_map_from(v.parent().base()):
+ vals = vector(self.base(), v.values(), sparse=True)
+ return self.element_class(self, v.size(), vals)
+ elif v in base:
+ return self.element_class(self, self.ftype().size(), vector(base, [v], sparse=True))
+ raise ValueError('Can\'t construct an element from {} for the theory\n{}'.format(v, self))
+ return self.element_class(self, *args, **kwds)
+
+ def _coerce_map_from_(self, S):
+ r"""
+ Checks if it can be coerced from S
+ """
+ if self.base().has_coerce_map_from(S):
+ return True
+ if S==self.theory():
+ return True
+ if isinstance(S, FlagAlgebra):
+ if S.ftype()==self.ftype() and self.base().has_coerce_map_from(S.base()):
+ return True
+ return False
+
+ def _pushout_(self, S):
+ r"""
+ Constructs the pushout FlagAlgebra
+ """
+ if S.has_coerce_map_from(self.base()):
+ return FlagAlgebra(self.theory(), S, self.ftype())
+ return None
+
+ def _repr_(self):
+ r"""
+ Returns a short text representation
+
+ EXAMPLES::
+
+
+ sage: FlagAlgebra(GraphTheory, QQ)
+ Flag Algebra with Ftype on 0 points with edges=() over Rational Field
+
+ .. SEEALSO::
+
+ :func:`Flag._repr_`
+ """
+ return 'Flag Algebra with {} over {}'.format(self.ftype(), self.base())
+
+ def ftype(self):
+ r"""
+ Returns the ftype of this FlagAlgebra.
+
+ EXAMPLES::
+
+ Without specifying anything in the constructor, the ftype
+ is empty ::
+
+
+ sage: FA = FlagAlgebra(GraphTheory, QQ)
+ sage: FA.ftype()
+ Ftype on 0 points with edges=()
+
+ .. NOTE::
+
+ This is the same ftype as the ftype of the elements,
+ and the ftype of the flags in those elements.
+
+ .. SEEALSO::
+
+ :func:`Flag.ftype`
+ :func:`FlagAlgebraElement.ftype`
+ """
+ return self._ftype
+
+ def combinatorial_theory(self):
+ r"""
+ Returns the :class:`CombinatorialTheory` object, whose
+ flags form the basis of this FlagAlgebra
+
+ EXAMPLES::
+
+ This is the same as provided in the constructor ::
+
+
+ sage: FA = FlagAlgebra(GraphTheory, QQ)
+ sage: FA.theory()
+ Theory for Graph
+
+ .. SEEALSO::
+
+ :func:`__init__`
+ :class:`CombinatorialTheory`
+ """
+ return self._theory
+
+ theory = combinatorial_theory
+
+ def base_ring(self):
+ r"""
+ Returns the base_ring
+
+ Same as `base_ring` of the `base` provided in the constructor
+
+ EXAMPLES::
+
+
+ sage: FA = FlagAlgebra(GraphTheory, QQ)
+ sage: FA.base_ring()
+ Rational Field
+
+ .. SEEALSO::
+
+ :func:`base`
+ """
+ return self.base().base_ring()
+
+ def characteristic(self):
+ r"""
+ Returns the characteristic
+
+ Same as `characteristic` of the `base` provided in the constructor
+
+ EXAMPLES::
+
+
+ sage: FA = FlagAlgebra(GraphTheory, QQ)
+ sage: FA.characteristic()
+ 0
+
+ .. SEEALSO::
+
+ :func:`base`
+ """
+ return self.base().characteristic()
+
+ def generate_flags(self, n):
+ r"""
+ Generates flags of a given size, and `ftype` of `self`
+
+ .. NOTE::
+
+ Same as `CombinatorialTheory.generate_flags` with `ftype`
+ from `self`
+
+ .. SEEALSO::
+
+ :func:`CombinatorialTheory.generate_flags`
+ """
+ return self.theory().generate_flags(n, self.ftype())
+
+ generate = generate_flags
+
+ def _an_element_(self):
+ r"""
+ Returns an element
+ """
+ a = self.base().an_element()
+ f = self.combinatorial_theory()._an_element_(n=self.ftype().size(), ftype=self.ftype())
+ return self(f)*a
+
+ def some_elements(self):
+ r"""
+ Returns a small list of elements
+ """
+ return [self.an_element(),self(self.base().an_element())]
+
+ def mul_project_table(self, n1, n2, ftype_inj=None, target_size=None):
+ r"""
+ Returns the multiplication projection table
+
+ This is the same as :func:`CombinatorialTheory.mul_project_table`
+ with self.ftype() substituted in.
+
+ .. SEEALSO::
+
+ :func:`CombinatorialTheory.mul_project_table`
+ """
+ return self.theory().mul_project_table(n1, n2, self.ftype(), ftype_inj, target_size)
+
+ mpt = mul_project_table
diff --git a/src/sage/structure/coerce.pyx b/src/sage/structure/coerce.pyx
index cc15eff82e9..d2b4d28a5df 100644
--- a/src/sage/structure/coerce.pyx
+++ b/src/sage/structure/coerce.pyx
@@ -1276,7 +1276,7 @@ cdef class CoercionModel:
res = mul_method(x)
if res is not None and res is not NotImplemented:
return res
-
+
# We should really include the underlying error.
# This causes so much headache.
raise bin_op_exception(op, x, y)
@@ -1412,6 +1412,17 @@ cdef class CoercionModel:
else:
return self.canonical_coercion(x, y)
+ try:
+ return x.custom_coerce(y)
+ except AttributeError:
+ self._record_exception()
+
+ try:
+ ym, xm = y.custom_coerce(x)
+ return (xm, ym)
+ except AttributeError:
+ self._record_exception()
+
# Allow coercion of 0 even if no coercion from Z
if (x_numeric or is_Integer(x)) and not x and type(yp) is not type:
try: