#!/usr/bin/python
# -*- coding utf-8 -*-

#
#  Start-Programm von zufall
#
#  Copyright (C) 2019 Holger Böttcher <hbomat@posteo.de>
#

_version_ = '0.1.0'

# -----------------------------------------------------------------------------
# Variable zur Steuerung des Testbetriebes
# -----------------------------------------------------------------------------
#
# für den Programmierer; hier im Quelltext ein-/ausschalten
# _TEST = True:  es werden die vollständigen Python-Fehlermitteilungen angezeigt
# _TEST = False: es werden (teilweise) zufall-eigene Fehlermitteilungen angezeigt

_TEST = True

# -----------------------------------------------------------------------------
# Zurücksetzen
# -----------------------------------------------------------------------------

_ip = get_ipython()
_ip.magic('reset -sf')

# -----------------------------------------------------------------------------
# Importe
# -----------------------------------------------------------------------------

import copy
import importlib

from IPython.core.inputtransformer import InputTransformer

from sympy.core.expr import Expr
from sympy.matrices import Matrix as SympyMatrix

# ausgewählte SymPy-Objekte und -funktionen zur unmittelbaren Verfügung des Nutzers
from sympy.abc import *
from sympy.core.numbers import Integer, Rational, Float, pi, E, I
from sympy.core.symbol import Symbol, symbols
from sympy.core.function import Lambda
from sympy import (solve, solveset, S, expand, collect, factor, simplify,
    nsimplify, N, Not, And, Or, factorial, binomial)

# Initialisierung der Latex-Ausgabe
from sympy.interactive.printing import init_printing
from sympy.printing.latex import LatexPrinter
LatexPrinter._default_settings["mat_delim"] = "("
init_printing(use_latex='mathjax')

# Import ausgewählter zufall-Elemente
from zufall.lib.objekte.umgebung import UMG
from zufall.lib.objekte.ausnahmen import ZufallError
import zufall

from agla.lib.objekte.ausnahmen import AglaError
import agla

# Alle anderen Objekte und Funktionen werden dynamisch nachgeladen (s.u.)

# -----------------------------------------------------------------------------
# Variable zur Steuerung von internen Vereinfachungen
# -----------------------------------------------------------------------------
#
# Systemvariable, zur interaktiven Benutzung durch den Anwender
# UMG.SIMPL = True:  in den Programmen vorgesehene Vereinfachungen werden
#                    automatisch durchgeführt
# UMG.SIMPL = False: es werden keine automatischen Vereinfachungen durch-
#                    geführt

UMG.SIMPL = True

# -----------------------------------------------------------------------------
# Variable zur Steuerung der Berechnung von hyperbolischen Objekten
# -----------------------------------------------------------------------------
#
# Systemvariable, zur interaktiven Benutzung durch den Anwender
# UMG.EXAKT = True:  die Berechnungen werden exakt (mit SymPY / SympyEngine)
#                    durchgeführt
# UMG.EXAKT = False: die Berechnungen werden mit float-Werten durchgeführt

UMG.EXAKT = False

# -----------------------------------------------------------------------------
# Dynamisches Importieren
# -----------------------------------------------------------------------------

_attr = 'zufall.lib.objekte.'
_fkt = 'zufall.lib.funktionen.funktionen'
_agla = 'agla.lib.objekte.'

_to_load = {

    'v'             : _agla + 'vektor',
    'Vektor'        : _agla + 'vektor',
	'Punkt'         : _agla + 'vektor',
	'O'             : _agla + 'vektor',
    'O2'            : _agla + 'vektor',
	'X2'            : _agla + 'vektor',
    '_ZeilenVektor' : _agla + 'vektor',

	'SympyMatrix'   : _attr + 'markoff_kette',
	'Matrix'        : _attr + 'markoff_kette',
    'NullMat'       : _attr + 'markoff_kette',
    'NullMat2'      : _attr + 'markoff_kette',
    'EinhMat'       : _attr + 'markoff_kette',
    'EinhMat2'      : _attr + 'markoff_kette',

    'DatenReihe'                  : _attr + 'datenreihe',
    'DR'                          : _attr + 'datenreihe',
    'ZufallsGroesse'              : _attr + 'zufalls_groesse',
	'ZG'                          : _attr + 'zufalls_groesse',
	'BinomialVerteilung'          : _attr + 'binomial_verteilung',
	'BV'                          : _attr + 'binomial_verteilung',
	'HyperGeometrischeVerteilung' : _attr + 'hyper_geometrische_verteilung',
	'HGV'                         : _attr + 'hyper_geometrische_verteilung',
	'GeometrischeVerteilung'      : _attr + 'geometrische_verteilung',
	'GV'                          : _attr + 'geometrische_verteilung',
	'PoissonVerteilung'           : _attr + 'poisson_verteilung',
	'PV'                          : _attr + 'poisson_verteilung',
	'GleichVerteilung'            : _attr + 'gleich_verteilung',
	'GLV'                         : _attr + 'gleich_verteilung',
	'NormalVerteilung'            : _attr + 'normal_verteilung',
	'NV'                          : _attr + 'normal_verteilung',
	'ExponentialVerteilung'       : _attr + 'exponential_verteilung',
	'EV'                          : _attr + 'exponential_verteilung',
	'BernoulliKette'              : _attr + 'bernoulli_kette',
	'BK'                          : _attr + 'bernoulli_kette',
	'ZufallsExperiment'           : _attr + 'zufalls_experiment',
	'ZE'                          : _attr + 'zufalls_experiment',
	'ZufallsVersuch'              : _attr + 'zufalls_experiment',
	'ZV'                          : _attr + 'zufalls_experiment',
	'Urne'                        : _attr + 'urne',
	'GluecksRad'                  : _attr + 'gluecks_rad',
	'Rad'                         : _attr + 'gluecks_rad',
	'GR'                          : _attr + 'gluecks_rad',
	'Muenze'                      : _attr + 'muenze',
	'Wuerfel'                     : _attr + 'wuerfel',
	'HaeufigkeitsBaum'            : _attr + 'haeufigkeits_baum',
	'HB'                          : _attr + 'haeufigkeits_baum',
	'VierFelderTafel'             : _attr + 'vier_felder_tafel',
	'VT'                          : _attr + 'vier_felder_tafel',
    'KonfidenzIntervall'          : _attr + 'konfidenz_intervall',
	'KI'                          : _attr + 'konfidenz_intervall',
	'EreignisAlgebra'             : _attr + 'ereignis_algebra',
	'EA'                          : _attr + 'ereignis_algebra',
    'AlternativTest'              : _attr + 'alternativ_test',
	'AT'                          : _attr + 'alternativ_test',
	'SignifikanzTestP'            : _attr + 'signifikanz_test_p',
	'STP'                         : _attr + 'signifikanz_test_p',
	'SkatBlatt'                   : _attr + 'skat_blatt',
	'Skat'                        : _attr + 'skat_blatt',
	'Roulette'                    : _attr + 'roulette',
	'Craps'                       : _attr + 'craps',
	'Toto'                        : _attr + 'toto',
	'Lotto'                       : _attr + 'lotto',
	'Chuck'                       : _attr + 'chuck',
	'ChuckALuck'                  : _attr + 'chuck',
	'MarkoffKette'                : _attr + 'markoff_kette',
	'MK'                          : _attr + 'markoff_kette',

    'permutationen'  : _fkt,
    'perm'           : _fkt,
	'kombinationen'  : _fkt,
    'komb'           : _fkt,
    'variationen'    : _fkt,
	'anzahl'         : _fkt,
    'anzahl_treffer' : _fkt,
	'anzahlTreffer'  : _fkt,
	'summe'          : _fkt,
	'zuf_zahl'       : _fkt,
	'zufZahl'        : _fkt,
	'fakultaet'      : _fkt,
	'fak'            : _fkt,
	'binomial'       : _fkt,
	'B'              : _fkt,
	'loese'          : _fkt,
	'Hilfe'          : _fkt,
	'auswahlen'      : _fkt,
	'gesetze'        : _fkt,
	'stochastisch'   : _fkt,
	'einfach'        : _fkt,
	'is_zahl'        : _fkt,
	'isZahl'         : _fkt,
	'mit_param'   	 : _fkt,
	'mitParam'       : _fkt,
    'jaNein'         : _fkt,
    'kurz_form'      : _fkt,
    'kurzForm'       : _fkt,
	'ja_nein'        : _fkt,
	'ja'             : _fkt,
	'Ja'             : _fkt,
	'nein'           : _fkt,
	'Nein'           : _fkt,
	'mit'            : _fkt,
	'Mit'            : _fkt,
	'ohne'           : _fkt,
	'Ohne'           : _fkt,

    'abs'            : _fkt,
	'sqrt'           : _fkt,
	'exp'            : _fkt,
	'ln'             : _fkt,
	'lg'             : _fkt,
	'log'            : _fkt,
    'sin'            : _fkt,
	'cos'            : _fkt,
	'tan'            : _fkt,
	'cot'            : _fkt,
	'sing'           : _fkt,
	'cosg'           : _fkt,
	'tang'           : _fkt,
	'cotg'           : _fkt,
    'arcsin'         : _fkt,
	'arccos'         : _fkt,
	'arctan'         : _fkt,
	'arccot'         : _fkt,
	'asin'           : _fkt,
	'acos'           : _fkt,
	'atan'           : _fkt,
	'acot'           : _fkt,
    'arcsing'        : _fkt,
	'arccosg'        : _fkt,
	'arctang'        : _fkt,
	'arccotg'        : _fkt,
	'asing'          : _fkt,
	'acosg'          : _fkt,
	'atang'          : _fkt,
	'acotg'          : _fkt,
    'sinh'           : _fkt,
	'cosh'           : _fkt,
	'tanh'           : _fkt,
	'arsinh'         : _fkt,
	'arcosh'         : _fkt,
	'artanh'         : _fkt,
	'asinh'          : _fkt,
	'acosh'          : _fkt,
	'atanh'          : _fkt,
    're'             : _fkt,
	'im'             : _fkt,
	'conjugate'      : _fkt,
	'konjugiert'     : _fkt,
	'max'            : _fkt,
	'min'            : _fkt,
	'deg'            : _fkt,
	'grad'           : _fkt,
	'rad'            : _fkt,
	'bog'            : _fkt

	}

# -----------------------------------------------------------------------------
# Geschützte Namen
# -----------------------------------------------------------------------------

zufall_namen = list(_to_load.keys())
del zufall_namen[zufall_namen.index('B')]

_protected_names = zufall_namen + [

        # Python-Namen
        'and', 'as', 'assert', 'break', 'class', 'continue',
        'def', 'del', 'elif', 'else', 'except', 'exec',
        'finally', 'float', 'for', 'from', 'global', 'if', 'import',
        'in', 'int', 'is', 'lambda', 'not', 'or', 'pass', 'print',
		'raise', 'return', 'try', 'while', 'with', 'yield',

        # SymPy-Namen
        'Rational', 'pi', 'Symbol', 'symbols', 'solve', 'solveset',
        'nsolve', 'expand', 'simplify', 'nsimplify', 'collect', 'diff',
        'factor', 're', 'im', 'min', 'max', 'conjugate', 'sympify',

        # griechische Buchstaben
        'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta', 'eta',
		 'theta', 'iota', 'kappa', 'lamda', 'mu', 'nu', 'xi', 'omicron',
		 'pi', 'rho', 'sigma', 'tau', 'upsilon', 'phi', 'chi', 'psi',
		 'omega'
      ]

# -----------------------------------------------------------------------------
# Funktion zum Laden der Moduln
# -----------------------------------------------------------------------------

def _load_modules(line):

    for obj in _to_load:
        if obj in line and _to_load[obj] != False:
            name = _to_load[obj]
            modul = importlib.import_module(name)
            attribut = getattr(modul, obj)
            _ip.push({obj : attribut})
            _to_load[obj] = False

# -----------------------------------------------------------------------------
# Custom InputTransformer
# -----------------------------------------------------------------------------
#
#   Für
#
# - Dynamisches Laden der Moduln
#
# - Ersetzen der deutschen Umlaute und ß
#
#  - Operator '^'   für das Potenzieren (zusätzlich zu '**')
#    Operator '°'   für das Skalarprodukt und die Verknüpfung
#                   von Abbildungen (zusätzlich zu '*')
#    Operator '><'  für das Kreuzprodukt (Vektorprodukt)
#
# - Schreibwarnung für die zufall-, Python- und SymPy-Namen
#
#  (s.a. 'ipython/core/test/test_interactiveshell.py')

class ZufallInputTransformer(InputTransformer):

    def push(self, line):
        """für InputTransformer erforderlich"""

		# Operatoren, Umlaute
        #
        if line.find('^') >= 0:
            line = line.replace('^', '**')
        if line.find('°') >= 0:
            line = line.replace('°', '*')
        if line.find('><') >= 0:
            line = line.replace('><', '&')

        if line.find('ä') >= 0:
            line = line.replace('ä', 'ae')
        if line.find('ö') >= 0:
            line = line.replace('ö', 'oe')
        if line.find('ü') >= 0:
            line = line.replace('ü', 'ue')
        if line.find('ß') >= 0:
            line = line.replace('ß', 'ss')

        # Dynamisches Laden der Moduln
        #
        _load_modules(line)

        # Schreibblockade
        #

        def klammer(line):    # Index der 1. Klammer oder None
            for i, z in enumerate(line):
                if z in '([{':
                    return i
        def gleich(line):   # Indices aller '='-Zeichen vor der 1. Klammer
            line1 = line.replace('==', 'xx')	 # '==' ausschließen
            k = klammer(line1)
            g = []
            for i, z in enumerate(line1):
                if i == k:
                    break
                if z == '=':
                    g.append(i)
            return g

        if '#' in line:
            k = line.find('#')
            kl = False
            for i in range(k):
                if line[i] in '([{)]}':
                    kl = not kl
            if not kl:		# innere Kommentare bleiben erhalten
               line = line[:k]
        anw_liste = line.split(';')  # Behandlung von Mehrfachzuweisungen

        anw_liste_neu = []
        for i, anw in enumerate(anw_liste):
            gl = gleich(anw)
            no1 = anw.find("'")
            no2 = anw.find('"')
            for i, g in enumerate(gl):
                if abs(no1) < gl[i] or abs(no2) < gl[i]:
                    gl = gl[:i]
                    break
            if len(gl) <= 1:
                anw_liste_neu.append(anw)
                continue
            lhs, rhs = anw[:gl[-1]], anw[gl[-1]:]
            ind = ''
            for i in lhs:
                if i == ' ':
                    ind += ' '
                else:
                    break
            lhs = lhs.split('=')
            anw_neu = []
            for i, name in enumerate(lhs):
                if i == 0:
                    anw_liste_neu.append(name + rhs)
                else:
                    anw_liste_neu.append(ind + name[1:] + rhs)
        anw_liste = anw_liste_neu

        aus_liste = []
        for anw in anw_liste:
            anw = anw.rstrip()
            g = anw.find('=')
            if g <= 0:
                aus_liste += [anw]
                continue
            try:
                if anw[g+1] == '=':
                    aus_liste += [anw]
                    continue
            except IndexError:
                pass
            klammer = False
            for i in range(len(anw)):
                if i == g:
                    break
                if anw[i] in '([{)]}':
                    klammer = not klammer
            if klammer:
                aus_liste += [anw]
                continue
            if anw[g-1] in ('<', '>', '!'):
                aus_liste += [anw]
                continue
            bezeichner = anw[:g].strip()
            fehler = False
            if bezeichner in _protected_names:
                print("zufall: der Wert von " + bezeichner + " kann nicht überschrieben werden")
                fehler = True
            if anw[0] == '_':
                print("zufall: ein Bezeichner darf nicht mit einem Unterstrich beginnen")
                fehler = True
            p = anw.find('.')
            if 0 <= p < g and klammer and not 'UMG.' in anw:
                print("zufall: auf der linken Seite einer Zuweisung darf kein '.' auftreten")
                fehler = True
            if not fehler:
                aus_liste += [anw]
                continue
            else:
                return

        return ';'.join(aus_liste)

    def reset(self):
        """für InputTransformer erforderlich"""
        pass

transformer = ZufallInputTransformer()
_ip.input_splitter.python_line_transforms.append(transformer)
_ip.input_transformer_manager.python_line_transforms.append(transformer)

# -----------------------------------------------------------------------------
# Eigene Fehlermeldungen
# -----------------------------------------------------------------------------

# Extra-Behandlung von SyntaxError (hat kein traceback)
#

import linecache
from IPython.core.ultratb import SyntaxTB
from IPython.utils import py3compat
from IPython.utils import ulinecache
def new_structured_traceback(self, etype, value, elist, tb_offset=None,
                             context=5):
    if isinstance(value, SyntaxError) \
            and isinstance(value.filename, py3compat.string_types) \
            and isinstance(value.lineno, int):
        linecache.checkcache(value.filename)
        newtext = ulinecache.getline(value.filename, value.lineno)
        if newtext:
            value.text = newtext
    nr = value.lineno
    print('SyntaxError:  ' + value.text[:-1] + (('  /Zeile ' + str(nr) + '/') \
	      if nr > 1 else ''))
    return

if not _TEST:
    SyntaxTB.structured_traceback = new_structured_traceback

# Custom exc_handler (für ausgewählte Ausnahmen)
#
def exc_handler(shell, etype, value, tb, tb_offset=None):
    stype, sval = str(etype), str(value)
    if 'ZufallError' in stype or 'AglaError' in stype:
        print(sval)
    elif 'NameError' in stype:
        txt = sval[sval.find("'")+1 : sval.rfind("'")]
        print('NameError: ' + txt + ' ist nicht definiert')
    elif 'KeyError' in stype:
        print('KeyError: ' + sval)
    elif 'IndexError' in stype:
        print('IndexError: ' + 'der Index ist außerhalb des Bereiches')
    elif 'AttributeError' in stype:
        no = []
        for i, z in enumerate(sval):
            if z == "'":
                no += [i]
        if len(no) != 4:
            print('AttributeError')
        else:
            obj, eig = sval[no[0]+1:no[1]], sval[no[2]+1:no[3]]
            print('AttributeError:  ' + 'ein ' + obj + '-Objekt ' + \
                 'hat keine Eigenschaft/Methode \nmit dem Namen ' + eig)
    elif 'TypeError' in stype:
        print('TypeError: ' + 'Funktionsaufruf nicht möglich oder anderer Fehler')
    elif 'MemoryError' in stype:
        print('MemoryError: ' + 'die Zahlen sind zu groß')
    elif 'ImportError' in stype:
        i = sval.find("'")
        j = sval[i+1:].find("'")
        txt = sval[i+1:i+j+1]
        print('ImportError: ' + txt +  ' kann nicht importiert werden')
    elif 'ZeroDivisionError' in stype:
        print('ZeroDivisionError: ' +	 'Division durch Null ist nicht erlaubt')
    elif 'IndentationError' in stype:
        print('IndentationError: ' +	 'Syntaxfehler - falsche Einrückung')
    elif 'RuntimeError' in stype:
        print('RuntimeError: ' +	    'Ein Laufzeitfehler ist aufgetreten')
    elif 'ValueError' in stype:
        print('ValueError: ' +	    'Ein ValueError ist aufgetreten')
    return

if not _TEST:
    _ip.set_custom_exc((ZufallError, AglaError, NameError, KeyError, IndexError, \
      AttributeError, TypeError, MemoryError, ImportError, ZeroDivisionError, \
      IndentationError, RuntimeError), exc_handler)

# -----------------------------------------------------------------------------
# Ausführung der (linksseitigen) Multiplikation  Zahl * Vektor
# -----------------------------------------------------------------------------

"""
Die rechtsseitige Multiplikation eines Vektors mit einer Zahl
(Vektor * Zahl) funktioniert für alle infrage kommenden SymPy-Klassen,
das Ergebnis ist ein Vektor-Objekt
Hinsichtlich der linksseitigen Multiplikation (Zahl * Vektor) verhalten
sich die Klassen unterschiedlich. Bei einigen gibt die __mul__-Methode
NotImplemented zurück, sodass automatisch die __rmul__-Methode von Vektor
aufgerufen wird. Für die anderen wird dieses Verhalten mittels Überladen
der __mul__-Methode des Expr-Objektes bzw. ihrer eigenen __mul__-Methode
(Symbol) erreicht
"""

# -----------------------------------------------------------------------------
# Überladen der Expr.__mul__- Methode
# -----------------------------------------------------------------------------

_expr_mul = copy.deepcopy(Expr.__mul__)
def _expr_mul_anpassung(self, other):
    Vektor = importlib.import_module('agla.lib.objekte.vektor').Vektor
    if isinstance(other, (Vektor, SympyMatrix)):
        return NotImplemented
    return _expr_mul(self, other)
Expr.__mul__ = _expr_mul_anpassung

# -----------------------------------------------------------------------------
# Überladen der Symbol.__mul__- Methode
# -----------------------------------------------------------------------------

_symbol_mul = copy.deepcopy(Symbol.__mul__)
def _symbol_mul_anpassung(self, other):
    Vektor = importlib.import_module('agla.lib.objekte.vektor').Vektor
    if isinstance(other, (Vektor, SympyMatrix)):
        return NotImplemented
    return _symbol_mul(self, other)
Symbol.__mul__ = _symbol_mul_anpassung

# -----------------------------------------------------------------------------
# Float.__mul__ - Überladung
# -----------------------------------------------------------------------------

_Float_mul = copy.deepcopy(Float.__mul__)

def _Float_mul_anpassung(self, other):
    if isinstance(other, SympyMatrix):
        ve = [self*v for v in other.vekt]
        return Matrix(*ve)
    return _Float_mul(self, other)

Float.__mul__ = _Float_mul_anpassung


# sage.doctest: optional - sage.graphs
r"""
Base class for polyhedra, part 4

Define methods relying on :mod:`sage.graphs`.
"""

# ****************************************************************************
#       Copyright (C) 2008-2012 Marshall Hampton <hamptonio@gmail.com>
#       Copyright (C) 2011-2015 Volker Braun <vbraun.name@gmail.com>
#       Copyright (C) 2012-2018 Frederic Chapoton
#       Copyright (C) 2013      Andrey Novoseltsev
#       Copyright (C) 2014-2017 Moritz Firsching
#       Copyright (C) 2014-2019 Thierry Monteil
#       Copyright (C) 2015      Nathann Cohen
#       Copyright (C) 2015-2017 Jeroen Demeyer
#       Copyright (C) 2015-2017 Vincent Delecroix
#       Copyright (C) 2015-2018 Dima Pasechnik
#       Copyright (C) 2015-2020 Jean-Philippe Labbe <labbe at math.huji.ac.il>
#       Copyright (C) 2015-2021 Matthias Koeppe
#       Copyright (C) 2016-2019 Daniel Krenn
#       Copyright (C) 2017      Marcelo Forets
#       Copyright (C) 2017-2018 Mark Bell
#       Copyright (C) 2019      Julian Ritter
#       Copyright (C) 2019-2020 Laith Rastanawi
#       Copyright (C) 2019-2020 Sophia Elia
#       Copyright (C) 2019-2021 Jonathan Kliem <jonathan.kliem@fu-berlin.de>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the 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/
# ****************************************************************************

from sage.misc.cachefunc import cached_method
from .base3 import Polyhedron_base3

class Polyhedron_base4(Polyhedron_base3):
    """
    Methods relying on :mod:`sage.graphs`.

    See :class:`sage.geometry.polyhedron.base.Polyhedron_base`.

    TESTS::

        sage: from sage.geometry.polyhedron.base4 import Polyhedron_base4
        sage: P = polytopes.cube()
        sage: Polyhedron_base4.vertex_facet_graph.f(P)
        Digraph on 14 vertices
        sage: Polyhedron_base4.vertex_graph(P)
        Graph on 8 vertices
        sage: Polyhedron_base4.face_lattice(P)
        Finite lattice containing 28 elements
        sage: Polyhedron_base4.flag_f_vector(P, 0, 2)
        24
        sage: Polyhedron_base4.is_self_dual(P)
        False
        sage: Q = polytopes.cube(intervals='zero_one')
        sage: P == Q
        False
        sage: Polyhedron_base4.is_combinatorially_isomorphic(P, Q)
        True
    """

    @cached_method
    def vertex_facet_graph(self, labels=True):
        r"""
        Return the vertex-facet graph.

        This function constructs a directed bipartite graph.
        The nodes of the graph correspond to the vertices of the polyhedron
        and the facets of the polyhedron. There is an directed edge
        from a vertex to a face if and only if the vertex is incident to the face.

        INPUT:

        - ``labels`` -- boolean (default: ``True``); decide how the nodes
          of the graph are labelled. Either with the original vertices/facets
          of the Polyhedron or with integers.

        OUTPUT:

        - a bipartite DiGraph. If ``labels`` is ``True``, then the nodes
          of the graph will actually be the vertices and facets of ``self``,
          otherwise they will be integers.

        .. SEEALSO::

            :meth:`combinatorial_automorphism_group`,
            :meth:`is_combinatorially_isomorphic`.

        EXAMPLES::

            sage: P = polytopes.cube()
            sage: G = P.vertex_facet_graph(); G
            Digraph on 14 vertices
            sage: G.vertices(key = lambda v: str(v))
            [A vertex at (-1, -1, -1),
             A vertex at (-1, -1, 1),
             A vertex at (-1, 1, -1),
             A vertex at (-1, 1, 1),
             A vertex at (1, -1, -1),
             A vertex at (1, -1, 1),
             A vertex at (1, 1, -1),
             A vertex at (1, 1, 1),
             An inequality (-1, 0, 0) x + 1 >= 0,
             An inequality (0, -1, 0) x + 1 >= 0,
             An inequality (0, 0, -1) x + 1 >= 0,
             An inequality (0, 0, 1) x + 1 >= 0,
             An inequality (0, 1, 0) x + 1 >= 0,
             An inequality (1, 0, 0) x + 1 >= 0]
            sage: G.automorphism_group().is_isomorphic(P.hasse_diagram().automorphism_group())
            True
            sage: O = polytopes.octahedron(); O
            A 3-dimensional polyhedron in ZZ^3 defined as the convex hull of 6 vertices
            sage: O.vertex_facet_graph()
            Digraph on 14 vertices
            sage: H = O.vertex_facet_graph()
            sage: G.is_isomorphic(H)
            False
            sage: G2 = copy(G)
            sage: G2.reverse_edges(G2.edges())
            sage: G2.is_isomorphic(H)
            True

        TESTS:

        Check that :trac:`28828` is fixed::

            sage: G._immutable
            True

        Check that :trac:`29188` is fixed::

            sage: P = polytopes.cube()
            sage: P.vertex_facet_graph().is_isomorphic(P.vertex_facet_graph(False))
            True
        """
        return self.combinatorial_polyhedron().vertex_facet_graph(names=labels)

    def vertex_graph(self, **kwds):
        """
        Return a graph in which the vertices correspond to vertices
        of the polyhedron, and edges to edges.

        INPUT:

        - ``names`` -- boolean (default: ``True``); if ``False``,
          then the nodes of the graph are labeld by the
          indices of the Vrepresentation

        - ``algorithm`` -- string (optional);
          specify whether the face generator starts with facets or vertices:
          * ``'primal'`` -- start with the facets
          * ``'dual'`` -- start with the vertices
          * ``None`` -- choose automatically

        ..NOTE::

            The graph of a polyhedron with lines has no vertices,
            as the polyhedron has no vertices (`0`-faces).

            The method :meth:`Polyhedron_base:vertices` returns
            the defining points in this case.

        EXAMPLES::

            sage: g3 = polytopes.hypercube(3).vertex_graph(); g3
            Graph on 8 vertices
            sage: g3.automorphism_group().cardinality()
            48
            sage: s4 = polytopes.simplex(4).vertex_graph(); s4
            Graph on 5 vertices
            sage: s4.is_eulerian()
            True

        The graph of an unbounded polyhedron
        is the graph of the bounded complex::

            sage: open_triangle = Polyhedron(vertices=[[1,0], [0,1]],
            ....:                            rays    =[[1,1]])
            sage: open_triangle.vertex_graph()
            Graph on 2 vertices

        The graph of a polyhedron with lines has no vertices::

            sage: line = Polyhedron(lines=[[0,1]])
            sage: line.vertex_graph()
            Graph on 0 vertices

        TESTS:

        Check for a line segment (:trac:`30545`)::

            sage: polytopes.simplex(1).graph().edges()
            [(A vertex at (0, 1), A vertex at (1, 0), None)]
        """
        return self.combinatorial_polyhedron().vertex_graph(**kwds)

    graph = vertex_graph

    def vertex_digraph(self, f, increasing=True):
        r"""
        Return the directed graph of the polyhedron according to a linear form.

        The underlying undirected graph is the graph of vertices and edges.

        INPUT:

        - ``f`` -- a linear form. The linear form can be provided as:

            - a vector space morphism with one-dimensional codomain, (see
              :meth:`sage.modules.vector_space_morphism.linear_transformation`
              and
              :class:`sage.modules.vector_space_morphism.VectorSpaceMorphism`)
            - a vector ; in this case the linear form is obtained by duality
              using the dot product: ``f(v) = v.dot_product(f)``.

        - ``increasing`` -- boolean (default ``True``) whether to orient
          edges in the increasing or decreasing direction.

        By default, an edge is oriented from `v` to `w` if
        `f(v) \leq f(w)`.

        If `f(v)=f(w)`, then two opposite edges are created.

        EXAMPLES::

            sage: penta = Polyhedron([[0,0],[1,0],[0,1],[1,2],[3,2]])
            sage: G = penta.vertex_digraph(vector([1,1])); G
            Digraph on 5 vertices
            sage: G.sinks()
            [A vertex at (3, 2)]

            sage: A = matrix(ZZ, [[1], [-1]])
            sage: f = linear_transformation(A)
            sage: G = penta.vertex_digraph(f) ; G
            Digraph on 5 vertices
            sage: G.is_directed_acyclic()
            False

        .. SEEALSO::

            :meth:`vertex_graph`
        """
        from sage.modules.vector_space_morphism import VectorSpaceMorphism
        if isinstance(f, VectorSpaceMorphism):
            if f.codomain().dimension() == 1:
                orientation_check = lambda v: f(v) >= 0
            else:
                raise TypeError('the linear map f must have '
                                'one-dimensional codomain')
        else:
            try:
                if f.is_vector():
                    orientation_check = lambda v: v.dot_product(f) >= 0
                else:
                    raise TypeError('f must be a linear map or a vector')
            except AttributeError:
                raise TypeError('f must be a linear map or a vector')
        if not increasing:
            f = -f
        from sage.graphs.digraph import DiGraph
        dg = DiGraph()
        for j in range(self.n_vertices()):
            vj = self.Vrepresentation(j)
            for vi in vj.neighbors():
                if orientation_check(vj.vector() - vi.vector()):
                    dg.add_edge(vi, vj)
        return dg

    def face_lattice(self):
        """
        Return the face-lattice poset.

        OUTPUT:

        A :class:`~sage.combinat.posets.posets.FinitePoset`. Elements
        are given as
        :class:`~sage.geometry.polyhedron.face.PolyhedronFace`.

        In the case of a full-dimensional polytope, the faces are
        pairs (vertices, inequalities) of the spanning vertices and
        corresponding saturated inequalities. In general, a face is
        defined by a pair (V-rep. objects, H-rep. objects). The
        V-representation objects span the face, and the corresponding
        H-representation objects are those inequalities and equations
        that are saturated on the face.

        The bottom-most element of the face lattice is the "empty
        face". It contains no V-representation object. All
        H-representation objects are incident.

        The top-most element is the "full face". It is spanned by all
        V-representation objects. The incident H-representation
        objects are all equations and no inequalities.

        In the case of a full-dimensional polytope, the "empty face"
        and the "full face" are the empty set (no vertices, all
        inequalities) and the full polytope (all vertices, no
        inequalities), respectively.

        ALGORITHM:

        See :mod:`sage.geometry.polyhedron.combinatorial_polyhedron.face_iterator`.

        .. NOTE::

            The face lattice is not cached, as long as this creates a memory leak, see :trac:`28982`.

        EXAMPLES::

            sage: square = polytopes.hypercube(2)
            sage: fl = square.face_lattice();fl
            Finite lattice containing 10 elements
            sage: list(f.ambient_V_indices() for f in fl)
            [(), (0,), (1,), (0, 1), (2,), (1, 2), (3,), (0, 3), (2, 3), (0, 1, 2, 3)]
            sage: poset_element = fl[5]
            sage: a_face = poset_element
            sage: a_face
            A 1-dimensional face of a Polyhedron in ZZ^2 defined as the convex hull of 2 vertices
            sage: a_face.ambient_V_indices()
            (1, 2)
            sage: set(a_face.ambient_Vrepresentation()) == \
            ....: set([square.Vrepresentation(1), square.Vrepresentation(2)])
            True
            sage: a_face.ambient_Vrepresentation()
            (A vertex at (1, 1), A vertex at (-1, 1))
            sage: a_face.ambient_Hrepresentation()
            (An inequality (0, -1) x + 1 >= 0,)

        A more complicated example::

            sage: c5_10 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5] for i in range(1,11)])
            sage: c5_10_fl = c5_10.face_lattice()
            sage: [len(x) for x in c5_10_fl.level_sets()]
            [1, 10, 45, 100, 105, 42, 1]

        Note that if the polyhedron contains lines then there is a
        dimension gap between the empty face and the first non-empty
        face in the face lattice::

            sage: line = Polyhedron(vertices=[(0,)], lines=[(1,)])
            sage: [ fl.dim() for fl in line.face_lattice() ]
            [-1, 1]

        TESTS::

            sage: c5_20 = Polyhedron(vertices = [[i,i^2,i^3,i^4,i^5]
            ....:     for i in range(1,21)])
            sage: c5_20_fl = c5_20.face_lattice() # long time
            sage: [len(x) for x in c5_20_fl.level_sets()] # long time
            [1, 20, 190, 580, 680, 272, 1]
            sage: polytopes.hypercube(2).face_lattice().plot()  # optional - sage.plot
            Graphics object consisting of 27 graphics primitives
            sage: level_sets = polytopes.cross_polytope(2).face_lattice().level_sets()
            sage: level_sets[0][0].ambient_V_indices(), level_sets[-1][0].ambient_V_indices()
            ((), (0, 1, 2, 3))

        Various degenerate polyhedra::

            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[[0,0,0],[1,0,0],[0,1,0]]).face_lattice().level_sets()]
            [[()], [(0,), (1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], rays=[(0,0,1)]).face_lattice().level_sets()]
            [[()], [(1,), (2,)], [(0, 1), (0, 2), (1, 2)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0,0),(0,1,0)], vertices=[(0,0,1)]).face_lattice().level_sets()]
            [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(rays=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()]
            [[()], [(0,)], [(0, 1), (0, 2)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,),(0,)]).face_lattice().level_sets()]
            [[()], [(0,), (1,)], [(0, 1)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(1,0,0),(0,1,0)], lines=[(0,0,1)]).face_lattice().level_sets()]
            [[()], [(0, 1), (0, 2)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0,0)], vertices=[(0,0,1)]).face_lattice().level_sets()]
            [[()], [(0, 1)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0),(0,1)], vertices=[(0,0)]).face_lattice().level_sets()]
            [[()], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], rays=[(0,1)], vertices=[(0,0)]).face_lattice().level_sets()]
            [[()], [(0, 1)], [(0, 1, 2)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(vertices=[(0,)], lines=[(1,)]).face_lattice().level_sets()]
            [[()], [(0, 1)]]
            sage: [[ls.ambient_V_indices() for ls in lss] for lss in Polyhedron(lines=[(1,0)], vertices=[(0,0)]).face_lattice().level_sets()]
            [[()], [(0, 1)]]

        """
        from sage.combinat.posets.lattices import FiniteLatticePoset
        return FiniteLatticePoset(self.hasse_diagram())

    @cached_method
    def hasse_diagram(self):
        r"""
        Return the Hasse diagram of the face lattice of ``self``.

        This is the Hasse diagram of the poset of the faces of ``self``.

        OUTPUT: a directed graph

        EXAMPLES::

            sage: P = polytopes.regular_polygon(4).pyramid()                    # optional - sage.rings.number_field
            sage: D = P.hasse_diagram(); D                                      # optional - sage.rings.number_field
            Digraph on 20 vertices
            sage: D.degree_polynomial()                                         # optional - sage.rings.number_field
            x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3

        Faces of an mutable polyhedron are not hashable. Hence those are not suitable as
        vertices of the hasse diagram. Use the combinatorial polyhedron instead::

            sage: P = polytopes.regular_polygon(4).pyramid()                    # optional - sage.rings.number_field
            sage: parent = P.parent()                                           # optional - sage.rings.number_field
            sage: parent = parent.change_ring(QQ, backend='ppl')                # optional - sage.rings.number_field
            sage: Q = parent._element_constructor_(P, mutable=True)             # optional - sage.rings.number_field
            sage: Q.hasse_diagram()                                             # optional - sage.rings.number_field
            Traceback (most recent call last):
            ...
            TypeError: mutable polyhedra are unhashable
            sage: C = Q.combinatorial_polyhedron()                              # optional - sage.rings.number_field
            sage: D = C.hasse_diagram()                                         # optional - sage.rings.number_field
            sage: set(D.vertices()) == set(range(20))                           # optional - sage.rings.number_field
            True
            sage: def index_to_combinatorial_face(n):
            ....:     return C.face_by_face_lattice_index(n)
            sage: D.relabel(index_to_combinatorial_face, inplace=True)          # optional - sage.rings.number_field
            sage: D.vertices()                                                  # optional - sage.rings.number_field
            [A -1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 0-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 1-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 2-dimensional face of a 3-dimensional combinatorial polyhedron,
             A 3-dimensional face of a 3-dimensional combinatorial polyhedron]
            sage: D.degree_polynomial()                                         # optional - sage.rings.number_field
            x^5 + x^4*y + x*y^4 + y^5 + 4*x^3*y + 8*x^2*y^2 + 4*x*y^3
        """

        from sage.geometry.polyhedron.face import combinatorial_face_to_polyhedral_face
        C = self.combinatorial_polyhedron()
        D = C.hasse_diagram()

        def index_to_polyhedron_face(n):
            return combinatorial_face_to_polyhedral_face(
                    self, C.face_by_face_lattice_index(n))

        return D.relabel(index_to_polyhedron_face, inplace=False, immutable=True)

    def flag_f_vector(self, *args):
        r"""
        Return the flag f-vector.

        For each `-1 < i_0 < \dots < i_n < d` the flag f-vector
        counts the number of flags `F_0 \subset \dots \subset F_n`
        with `F_j` of dimension `i_j` for each `0 \leq j \leq n`,
        where `d` is the dimension of the polyhedron.

        INPUT:

        - ``args`` -- integers (optional); specify an entry of the
          flag-f-vector; must be an increasing sequence of integers

        OUTPUT:

        - a dictionary, if no arguments were given

        - an Integer, if arguments were given

        EXAMPLES:

        Obtain the entire flag-f-vector::

            sage: P = polytopes.twenty_four_cell()
            sage: P.flag_f_vector()
                {(-1,): 1,
                 (0,): 24,
                 (0, 1): 192,
                 (0, 1, 2): 576,
                 (0, 1, 2, 3): 1152,
                 (0, 1, 3): 576,
                 (0, 2): 288,
                 (0, 2, 3): 576,
                 (0, 3): 144,
                 (1,): 96,
                 (1, 2): 288,
                 (1, 2, 3): 576,
                 (1, 3): 288,
                 (2,): 96,
                 (2, 3): 192,
                 (3,): 24,
                 (4,): 1}

        Specify an entry::

            sage: P.flag_f_vector(0,3)
            144
            sage: P.flag_f_vector(2)
            96

        Leading ``-1`` and trailing entry of dimension are allowed::

            sage: P.flag_f_vector(-1,0,3)
            144
            sage: P.flag_f_vector(-1,0,3,4)
            144

        One can get the number of trivial faces::

            sage: P.flag_f_vector(-1)
            1
            sage: P.flag_f_vector(4)
            1

        Polyhedra with lines, have ``0`` entries accordingly::

            sage: P = (Polyhedron(lines=[[1]]) * polytopes.cross_polytope(3))
            sage: P.flag_f_vector()
            {(-1,): 1,
             (0, 1): 0,
             (0, 1, 2): 0,
             (0, 1, 3): 0,
             (0, 2): 0,
             (0, 2, 3): 0,
             (0, 3): 0,
             (0,): 0,
             (1, 2): 24,
             (1, 2, 3): 48,
             (1, 3): 24,
             (1,): 6,
             (2, 3): 24,
             (2,): 12,
             (3,): 8,
             4: 1}

        If the arguments are not stricly increasing or out of range, a key error is raised::

            sage: P.flag_f_vector(-1,0,3,6)
            Traceback (most recent call last):
            ...
            KeyError: (0, 3, 6)
            sage: P.flag_f_vector(-1,3,0)
            Traceback (most recent call last):
            ...
            KeyError: (3, 0)
        """
        flag = self._flag_f_vector()
        if len(args) == 0:
            return flag
        elif len(args) == 1:
            return flag[(args[0],)]
        else:
            dim = self.dimension()
            if args[0] == -1:
                args = args[1:]
            if args[-1] == dim:
                args = args[:-1]
            return flag[tuple(args)]

    @cached_method(do_pickle=True)
    def _flag_f_vector(self):
        r"""
        Return the flag-f-vector.

        See :meth:`flag_f_vector`.

        TESTS::

            sage: polytopes.hypercube(4)._flag_f_vector()
            {(-1,): 1,
            (0,): 16,
            (0, 1): 64,
            (0, 1, 2): 192,
            (0, 1, 2, 3): 384,
            (0, 1, 3): 192,
            (0, 2): 96,
            (0, 2, 3): 192,
            (0, 3): 64,
            (1,): 32,
            (1, 2): 96,
            (1, 2, 3): 192,
            (1, 3): 96,
            (2,): 24,
            (2, 3): 48,
            (3,): 8,
            (4,): 1}
        """
        return self.combinatorial_polyhedron()._flag_f_vector()

    @cached_method
    def combinatorial_automorphism_group(self, vertex_graph_only=False):
        """
        Computes the combinatorial automorphism group.

        If ``vertex_graph_only`` is ``True``,  the automorphism group
        of the vertex-edge graph of the polyhedron is returned. Otherwise
        the automorphism group of the vertex-facet graph, which is
        isomorphic to the automorphism group of the face lattice is returned.

        INPUT:

        - ``vertex_graph_only`` -- boolean (default: ``False``); whether
          to return the automorphism group of the vertex edges graph or
          of the lattice

        OUTPUT:

        A
        :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic_with_category'>`
        that is isomorphic to the combinatorial automorphism group is
        returned.

        - if ``vertex_graph_only`` is ``True``:
          The automorphism group of the vertex-edge graph of the polyhedron

        - if ``vertex_graph_only`` is ``False`` (default):
          The automorphism group of the vertex-facet graph of the polyhedron,
          see :meth:`vertex_facet_graph`. This group is isomorphic to the
          automorphism group of the face lattice of the polyhedron.

        NOTE:

            Depending on ``vertex_graph_only``, this method returns groups
            that are not necessarily isomorphic, see the examples below.

        .. SEEALSO::

            :meth:`is_combinatorially_isomorphic`,
            :meth:`graph`,
            :meth:`vertex_facet_graph`.

        EXAMPLES::

            sage: quadrangle = Polyhedron(vertices=[(0,0),(1,0),(0,1),(2,3)])
            sage: quadrangle.combinatorial_automorphism_group().is_isomorphic(groups.permutation.Dihedral(4))
            True
            sage: quadrangle.restricted_automorphism_group()
            Permutation Group with generators [()]

        Permutations of the vertex graph only exchange vertices with vertices::

            sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)])
            sage: P.combinatorial_automorphism_group(vertex_graph_only=True)
            Permutation Group with generators [(A vertex at (1,0),A vertex at (1,1))]

        This shows an example of two polytopes whose vertex-edge graphs are isomorphic,
        but their face_lattices are not isomorphic::

            sage: Q=Polyhedron([[-123984206864/2768850730773, -101701330976/922950243591, -64154618668/2768850730773, -2748446474675/2768850730773],
            ....: [-11083969050/98314591817, -4717557075/98314591817, -32618537490/98314591817, -91960210208/98314591817],
            ....: [-9690950/554883199, -73651220/554883199, 1823050/554883199, -549885101/554883199], [-5174928/72012097, 5436288/72012097, -37977984/72012097, 60721345/72012097],
            ....: [-19184/902877, 26136/300959, -21472/902877, 899005/902877], [53511524/1167061933, 88410344/1167061933, 621795064/1167061933, 982203941/1167061933],
            ....: [4674489456/83665171433, -4026061312/83665171433, 28596876672/83665171433, -78383796375/83665171433], [857794884940/98972360190089, -10910202223200/98972360190089, 2974263671400/98972360190089, -98320463346111/98972360190089]])
            sage: C = polytopes.cyclic_polytope(4,8)
            sage: C.is_combinatorially_isomorphic(Q)
            False
            sage: C.combinatorial_automorphism_group(vertex_graph_only=True).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=True))
            True
            sage: C.combinatorial_automorphism_group(vertex_graph_only=False).is_isomorphic(Q.combinatorial_automorphism_group(vertex_graph_only=False))
            False

        The automorphism group of the face lattice is isomorphic to the combinatorial automorphism group::

            sage: CG = C.hasse_diagram().automorphism_group()
            sage: C.combinatorial_automorphism_group().is_isomorphic(CG)
            True
            sage: QG = Q.hasse_diagram().automorphism_group()
            sage: Q.combinatorial_automorphism_group().is_isomorphic(QG)
            True

        """
        if vertex_graph_only:
            G = self.graph()
        else:
            G = self.vertex_facet_graph()
        return G.automorphism_group(edge_labels=True)

    @cached_method
    def restricted_automorphism_group(self, output="abstract"):
        r"""
        Return the restricted automorphism group.

        First, let the linear automorphism group be the subgroup of
        the affine group `AGL(d,\RR) = GL(d,\RR) \ltimes \RR^d`
        preserving the `d`-dimensional polyhedron. The affine group
        acts in the usual way `\vec{x}\mapsto A\vec{x}+b` on the
        ambient space.

        The restricted automorphism group is the subgroup of the linear
        automorphism group generated by permutations of the generators
        of the same type. That is, vertices can only be permuted with
        vertices, ray generators with ray generators, and line
        generators with line generators.

        For example, take the first quadrant

        .. MATH::

            Q = \Big\{ (x,y) \Big| x\geq 0,\; y\geq0 \Big\}
            \subset \QQ^2

        Then the linear automorphism group is

        .. MATH::

            \mathrm{Aut}(Q) =
            \left\{
            \begin{pmatrix}
            a & 0 \\ 0 & b
            \end{pmatrix}
            ,~
            \begin{pmatrix}
            0 & c \\ d & 0
            \end{pmatrix}
            :~
            a, b, c, d \in \QQ_{>0}
            \right\}
            \subset
            GL(2,\QQ)
            \subset
            E(d)

        Note that there are no translations that map the quadrant `Q`
        to itself, so the linear automorphism group is contained in
        the general linear group (the subgroup of transformations
        preserving the origin). The restricted automorphism group is

        .. MATH::

            \mathrm{Aut}(Q) =
            \left\{
            \begin{pmatrix}
            1 & 0 \\ 0 & 1
            \end{pmatrix}
            ,~
            \begin{pmatrix}
            0 & 1 \\ 1 & 0
            \end{pmatrix}
            \right\}
            \simeq \ZZ_2

        INPUT:

        - ``output`` -- how the group should be represented:

          - ``"abstract"`` (default) -- return an abstract permutation
            group without further meaning.

          - ``"permutation"`` -- return a permutation group on the
            indices of the polyhedron generators. For example, the
            permutation ``(0,1)`` would correspond to swapping
            ``self.Vrepresentation(0)`` and ``self.Vrepresentation(1)``.

          - ``"matrix"`` -- return a matrix group representing affine
            transformations. When acting on affine vectors, you should
            append a `1` to every vector. If the polyhedron is not full
            dimensional, the returned matrices act as the identity on
            the orthogonal complement of the affine space spanned by
            the polyhedron.

          - ``"matrixlist"`` -- like ``matrix``, but return the list of
            elements of the matrix group. Useful for fields without a
            good implementation of matrix groups or to avoid the
            overhead of creating the group.

        OUTPUT:

        - For ``output="abstract"`` and ``output="permutation"``:
          a :class:`PermutationGroup<sage.groups.perm_gps.permgroup.PermutationGroup_generic>`.

        - For ``output="matrix"``: a :class:`MatrixGroup`.

        - For ``output="matrixlist"``: a list of matrices.

        REFERENCES:

        - [BSS2009]_

        EXAMPLES:

        A cross-polytope example::

            sage: P = polytopes.cross_polytope(3)
            sage: P.restricted_automorphism_group() == PermutationGroup([[(3,4)], [(2,3),(4,5)],[(2,5)],[(1,2),(5,6)],[(1,6)]])
            True
            sage: P.restricted_automorphism_group(output="permutation") == PermutationGroup([[(2,3)],[(1,2),(3,4)],[(1,4)],[(0,1),(4,5)],[(0,5)]])
            True
            sage: mgens = [[[1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1]], [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]], [[0,1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]]]

        We test groups for equality in a fool-proof way; they can have different generators, etc::

            sage: poly_g = P.restricted_automorphism_group(output="matrix")
            sage: matrix_g = MatrixGroup([matrix(QQ,t) for t in mgens])
            sage: all(t.matrix() in poly_g for t in matrix_g.gens())
            True
            sage: all(t.matrix() in matrix_g for t in poly_g.gens())
            True

        24-cell example::

            sage: P24 = polytopes.twenty_four_cell()
            sage: AutP24 = P24.restricted_automorphism_group()
            sage: PermutationGroup([
            ....:     '(1,20,2,24,5,23)(3,18,10,19,4,14)(6,21,11,22,7,15)(8,12,16,17,13,9)',
            ....:     '(1,21,8,24,4,17)(2,11,6,15,9,13)(3,20)(5,22)(10,16,12,23,14,19)'
            ....: ]).is_isomorphic(AutP24)
            True
            sage: AutP24.order()
            1152

        Here is the quadrant example mentioned in the beginning::

            sage: P = Polyhedron(rays=[(1,0),(0,1)])
            sage: P.Vrepresentation()
            (A vertex at (0, 0), A ray in the direction (0, 1), A ray in the direction (1, 0))
            sage: P.restricted_automorphism_group(output="permutation")
            Permutation Group with generators [(1,2)]

        Also, the polyhedron need not be full-dimensional::

            sage: P = Polyhedron(vertices=[(1,2,3,4,5),(7,8,9,10,11)])
            sage: P.restricted_automorphism_group()
            Permutation Group with generators [(1,2)]
            sage: G = P.restricted_automorphism_group(output="matrixlist")
            sage: G
            (
            [1 0 0 0 0 0]  [ -87/55  -82/55    -2/5   38/55   98/55   12/11]
            [0 1 0 0 0 0]  [-142/55  -27/55    -2/5   38/55   98/55   12/11]
            [0 0 1 0 0 0]  [-142/55  -82/55     3/5   38/55   98/55   12/11]
            [0 0 0 1 0 0]  [-142/55  -82/55    -2/5   93/55   98/55   12/11]
            [0 0 0 0 1 0]  [-142/55  -82/55    -2/5   38/55  153/55   12/11]
            [0 0 0 0 0 1], [      0       0       0       0       0       1]
            )
            sage: g = AffineGroup(5, QQ)(G[1])
            sage: g
                  [ -87/55  -82/55    -2/5   38/55   98/55]     [12/11]
                  [-142/55  -27/55    -2/5   38/55   98/55]     [12/11]
            x |-> [-142/55  -82/55     3/5   38/55   98/55] x + [12/11]
                  [-142/55  -82/55    -2/5   93/55   98/55]     [12/11]
                  [-142/55  -82/55    -2/5   38/55  153/55]     [12/11]
            sage: g^2
                  [1 0 0 0 0]     [0]
                  [0 1 0 0 0]     [0]
            x |-> [0 0 1 0 0] x + [0]
                  [0 0 0 1 0]     [0]
                  [0 0 0 0 1]     [0]
            sage: g(list(P.vertices()[0]))
            (7, 8, 9, 10, 11)
            sage: g(list(P.vertices()[1]))
            (1, 2, 3, 4, 5)

        Affine transformations do not change the restricted automorphism
        group. For example, any non-degenerate triangle has the
        dihedral group with 6 elements, `D_6`, as its automorphism
        group::

            sage: initial_points = [vector([1,0]), vector([0,1]), vector([-2,-1])]
            sage: points = initial_points
            sage: Polyhedron(vertices=points).restricted_automorphism_group()
            Permutation Group with generators [(2,3), (1,2)]
            sage: points = [pt - initial_points[0] for pt in initial_points]
            sage: Polyhedron(vertices=points).restricted_automorphism_group()
            Permutation Group with generators [(2,3), (1,2)]
            sage: points = [pt - initial_points[1] for pt in initial_points]
            sage: Polyhedron(vertices=points).restricted_automorphism_group()
            Permutation Group with generators [(2,3), (1,2)]
            sage: points = [pt - 2*initial_points[1] for pt in initial_points]
            sage: Polyhedron(vertices=points).restricted_automorphism_group()
            Permutation Group with generators [(2,3), (1,2)]

        The ``output="matrixlist"`` can be used over fields without a
        complete implementation of matrix groups::

            sage: P = polytopes.dodecahedron(); P
            A 3-dimensional polyhedron in (Number Field in sqrt5 with defining polynomial x^2 - 5 with sqrt5 = 2.236067977499790?)^3 defined as the convex hull of 20 vertices
            sage: G = P.restricted_automorphism_group(output="matrixlist")
            sage: len(G)
            120

        Floating-point computations are supported with a simple fuzzy
        zero implementation::

            sage: P = Polyhedron(vertices=[(1/3,0,0,1),(0,1/4,0,1),(0,0,1/5,1)], base_ring=RDF)
            sage: P.restricted_automorphism_group()
            Permutation Group with generators [(2,3), (1,2)]
            sage: len(P.restricted_automorphism_group(output="matrixlist"))
            6

        TESTS::

            sage: P = Polyhedron(vertices=[(1,0), (1,1)], rays=[(1,0)])
            sage: P.restricted_automorphism_group(output="permutation")
            Permutation Group with generators [(1,2)]
            sage: P.restricted_automorphism_group(output="matrix")
            Matrix group over Rational Field with 1 generators (
            [ 1  0  0]
            [ 0 -1  1]
            [ 0  0  1]
            )
            sage: P.restricted_automorphism_group(output="foobar")
            Traceback (most recent call last):
            ...
            ValueError: unknown output 'foobar', valid values are ('abstract', 'permutation', 'matrix', 'matrixlist')

        Check that :trac:`28828` is fixed::

            sage: P.restricted_automorphism_group(output="matrixlist")[0].is_immutable()
            True
        """
        # The algorithm works as follows:
        #
        # Let V be the matrix where every column is a homogeneous
        # coordinate of a V-representation object (vertex, ray, line).
        # Let us assume that V has full rank, that the polyhedron is
        # full dimensional.
        #
        # Let Q = V Vt and C = Vt Q^-1 V. The rows and columns of C
        # can be thought of as being indexed by the V-rep objects of the
        # polytope.
        #
        # It turns out that we can identify the restricted automorphism
        # group with the automorphism group of the edge-colored graph
        # on the V-rep objects with colors determined by the symmetric
        # matrix C.
        #
        # An automorphism of this graph is equivalent to a permutation
        # matrix P such that C = Pt C P. If we now define
        # A = V P Vt Q^-1, then one can check that V P = A V.
        # In other words: permuting the generators is the same as
        # applying the affine transformation A on the generators.
        #
        # If the given polyhedron is not fully-dimensional,
        # then Q will be not invertible. In this case, we use a
        # pseudoinverse Q+ instead of Q^-1. The formula for A acting on
        # the space spanned by V then simplifies to A = V P V+ where V+
        # denotes the pseudoinverse of V, which also equals V+ = Vt Q+.
        #
        # If we are asked to return the (group of) transformation
        # matrices to the user, we also require that those
        # transformations act as the identity on the orthogonal
        # complement of the space spanned by V. This complement is the
        # space spanned by the columns of W = 1 - V V+. One can check
        # that B = (V P V+) + W is the correct matrix: it acts the same
        # as A on V and it satisfies B W = W.

        outputs = ("abstract", "permutation", "matrix", "matrixlist")
        if output not in outputs:
            raise ValueError("unknown output {!r}, valid values are {}".format(output, outputs))

        # For backwards compatibility, we treat "abstract" as
        # "permutation", but where we add 1 to the indices of the
        # permutations.
        index0 = 0
        if output == "abstract":
            index0 = 1
            output = "permutation"

        if self.base_ring().is_exact():
            def rational_approximation(c):
                return c
        else:
            c_list = []

            def rational_approximation(c):
                # Implementation detail: Return unique integer if two
                # c-values are the same up to machine precision. But
                # you can think of it as a uniquely-chosen rational
                # approximation.
                for i, x in enumerate(c_list):
                    if self._is_zero(x - c):
                        return i
                c_list.append(c)
                return len(c_list) - 1

        if self.is_compact():
            def edge_label(i, j, c_ij):
                return c_ij
        else:
            # In the non-compact case, we also label the edges by the
            # type of the V-representation object. This ensures that
            # vertices, rays, and lines are only permuted amongst
            # themselves.
            def edge_label(i, j, c_ij):
                return (self.Vrepresentation(i).type(), c_ij, self.Vrepresentation(j).type())

        # Homogeneous coordinates for the V-representation objects.
        # Mathematically, V is a matrix. For efficiency however, we
        # represent it as a list of column vectors.
        V = [v.homogeneous_vector() for v in self.Vrepresentation()]

        # Pseudoinverse of V Vt
        Qplus = sum(v.column() * v.row() for v in V).pseudoinverse()

        # Construct the graph.
        from sage.graphs.graph import Graph
        G = Graph()
        for i in range(len(V)):
            for j in range(i+1, len(V)):
                c_ij = rational_approximation(V[i] * Qplus * V[j])
                G.add_edge(index0+i, index0+j, edge_label(i, j, c_ij))

        permgroup = G.automorphism_group(edge_labels=True)
        if output == "permutation":
            return permgroup
        elif output == "matrix":
            permgroup = permgroup.gens()

        # Compute V+ = Vt Q+ as list of row vectors
        from sage.matrix.constructor import matrix
        Vplus = list(matrix(V) * Qplus)  # matrix(V) is Vt

        # Compute W = 1 - V V+
        W = 1 - sum(V[i].column() * Vplus[i].row() for i in range(len(V)))

        # Convert the permutation group to a matrix group.
        # If P is a permutation, then we return the matrix
        # B = (V P V+) + W.
        #
        # If output == "matrix", we loop over the generators of the group.
        # Otherwise, we loop over all elements.
        matrices = []
        for perm in permgroup:
            A = sum(V[perm(i)].column() * Vplus[i].row() for i in range(len(V)))
            matrices.append(A + W)

        for mat in matrices:
            mat.set_immutable()

        if output == "matrixlist":
            return tuple(matrices)
        else:
            from sage.groups.matrix_gps.finitely_generated import MatrixGroup
            return MatrixGroup(matrices)

    def is_combinatorially_isomorphic(self, other, algorithm='bipartite_graph'):
        r"""
        Return whether the polyhedron is combinatorially isomorphic to another polyhedron.

        We only consider bounded polyhedra. By definition, they are
        combinatorially isomorphic if their face lattices are isomorphic.

        INPUT:

        - ``other`` -- a polyhedron object
        - ``algorithm`` (default = ``bipartite_graph``) -- the algorithm to use.
          The other possible value is ``face_lattice``.

        OUTPUT:

        - ``True`` if the two polyhedra are combinatorially isomorphic
        - ``False`` otherwise

        .. SEEALSO::

            :meth:`combinatorial_automorphism_group`,
            :meth:`vertex_facet_graph`.

        REFERENCES:

        For the equivalence of the two algorithms see [KK1995]_, p. 877-878

        EXAMPLES:

        The square is combinatorially isomorphic to the 2-dimensional cube::

            sage: polytopes.hypercube(2).is_combinatorially_isomorphic(polytopes.regular_polygon(4))
            True

        All the faces of the 3-dimensional permutahedron are either
        combinatorially isomorphic to a square or a hexagon::

            sage: H = polytopes.regular_polygon(6)                              # optional - sage.rings.number_field
            sage: S = polytopes.hypercube(2)
            sage: P = polytopes.permutahedron(4)
            sage: all(F.as_polyhedron().is_combinatorially_isomorphic(S)        # optional - sage.rings.number_field
            ....:       or F.as_polyhedron().is_combinatorially_isomorphic(H)
            ....:     for F in P.faces(2))
            True

        Checking that a regular simplex intersected with its reflection
        through the origin is combinatorially isomorphic to the intersection
        of a cube with a hyperplane perpendicular to its long diagonal::

            sage: def simplex_intersection(k):
            ....:   S1 = Polyhedron([vector(v)-vector(polytopes.simplex(k).center()) for v in polytopes.simplex(k).vertices_list()])
            ....:   S2 = Polyhedron([-vector(v) for v in S1.vertices_list()])
            ....:   return S1.intersection(S2)
            sage: def cube_intersection(k):
            ....:    C = polytopes.hypercube(k+1)
            ....:    H = Polyhedron(eqns=[[0]+[1 for i in range(k+1)]])
            ....:    return C.intersection(H)
            sage: [simplex_intersection(k).is_combinatorially_isomorphic(cube_intersection(k)) for k in range(2,5)]
            [True, True, True]
            sage: simplex_intersection(2).is_combinatorially_isomorphic(polytopes.regular_polygon(6))   # optional - sage.rings.number_field
            True
            sage: simplex_intersection(3).is_combinatorially_isomorphic(polytopes.octahedron())
            True

        Two polytopes with the same `f`-vector, but different combinatorial types::

            sage: P = Polyhedron([[-605520/1525633, -605520/1525633, -1261500/1525633, -52200/1525633, 11833/1525633],\
             [-720/1769, -600/1769, 1500/1769, 0, -31/1769], [-216/749, 240/749, -240/749, -432/749, 461/749], \
             [-50/181, 50/181, 60/181, -100/181, -119/181], [-32/51, -16/51, -4/51, 12/17, 1/17],\
             [1, 0, 0, 0, 0], [16/129, 128/129, 0, 0, 1/129], [64/267, -128/267, 24/89, -128/267, 57/89],\
             [1200/3953, -1200/3953, -1440/3953, -360/3953, -3247/3953], [1512/5597, 1512/5597, 588/5597, 4704/5597, 2069/5597]])
            sage: C = polytopes.cyclic_polytope(5,10)
            sage: C.f_vector() == P.f_vector(); C.f_vector()
            True
            (1, 10, 45, 100, 105, 42, 1)
            sage: C.is_combinatorially_isomorphic(P)
            False

            sage: S = polytopes.simplex(3)
            sage: S = S.face_truncation(S.faces(0)[3])
            sage: S = S.face_truncation(S.faces(0)[4])
            sage: S = S.face_truncation(S.faces(0)[5])
            sage: T = polytopes.simplex(3)
            sage: T = T.face_truncation(T.faces(0)[3])
            sage: T = T.face_truncation(T.faces(0)[4])
            sage: T = T.face_truncation(T.faces(0)[4])
            sage: T.is_combinatorially_isomorphic(S)
            False
            sage: T.f_vector(), S.f_vector()
            ((1, 10, 15, 7, 1), (1, 10, 15, 7, 1))

            sage: C = polytopes.hypercube(5)
            sage: C.is_combinatorially_isomorphic(C)
            True
            sage: C.is_combinatorially_isomorphic(C, algorithm='magic')
            Traceback (most recent call last):
            ...
            AssertionError: `algorithm` must be 'bipartite graph' or 'face_lattice'

            sage: G = Graph()
            sage: C.is_combinatorially_isomorphic(G)
            Traceback (most recent call last):
            ...
            AssertionError: input `other` must be a polyhedron

            sage: H = Polyhedron(eqns=[[0,1,1,1,1]]); H
            A 3-dimensional polyhedron in QQ^4 defined as the convex hull of 1 vertex and 3 lines
            sage: C.is_combinatorially_isomorphic(H)
            Traceback (most recent call last):
            ...
            AssertionError: polyhedron `other` must be bounded

        """
        assert isinstance(other, Polyhedron_base4), "input `other` must be a polyhedron"
        assert self.is_compact(), "polyhedron `self` must be bounded"
        assert other.is_compact(), "polyhedron `other` must be bounded"
        assert algorithm in ['bipartite_graph', 'face_lattice'], "`algorithm` must be 'bipartite graph' or 'face_lattice'"

        # For speed, we check if the polyhedra have the same number of facets and vertices.
        # This is faster than building the bipartite graphs first and
        # then check that they won't be isomorphic.
        if self.n_vertices() != other.n_vertices() or self.n_facets() != other.n_facets():
            return False

        if algorithm == 'bipartite_graph':
            G_self = self.vertex_facet_graph(False)
            G_other = other.vertex_facet_graph(False)

            return G_self.is_isomorphic(G_other)
        else:
            return self.face_lattice().is_isomorphic(other.face_lattice())

    def _test_is_combinatorially_isomorphic(self, tester=None, **options):
        """
        Run tests on the method :meth:`.is_combinatorially_isomorphic`.

        TESTS::

            sage: polytopes.cross_polytope(3)._test_is_combinatorially_isomorphic()
        """
        if tester is None:
            tester = self._tester(**options)

        if not self.is_compact():
            with tester.assertRaises(AssertionError):
                self.is_combinatorially_isomorphic(self)
            return

        if self.n_vertices() > 200 or self.n_facets() > 200:
            # Avoid very long doctests.
            return

        try:
            import sage.graphs.graph
        except ImportError:
            return

        from sage.rings.integer_ring import ZZ
        tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self))
        if self.n_vertices():
            tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center()))

        if self.n_vertices() < 20 and self.n_facets() < 20 and self.is_immutable():
            tester.assertTrue(self.is_combinatorially_isomorphic(ZZ(4)*self, algorithm='face_lattice'))
            if self.n_vertices():
                tester.assertTrue(self.is_combinatorially_isomorphic(self + self.center(), algorithm='face_lattice'))

    def is_self_dual(self):
        r"""
        Return whether the polytope is self-dual.

        A polytope is self-dual if its face lattice is isomorphic to the face
        lattice of its dual polytope.

        EXAMPLES::

            sage: polytopes.simplex().is_self_dual()
            True
            sage: polytopes.twenty_four_cell().is_self_dual()
            True
            sage: polytopes.cube().is_self_dual()
            False
            sage: polytopes.hypersimplex(5,2).is_self_dual()
            False
            sage: P = Polyhedron(vertices=[[1/2, 1/3]], rays=[[1, 1]]).is_self_dual()
            Traceback (most recent call last):
            ...
            ValueError: polyhedron has to be compact

        """
        if not self.is_compact():
            raise ValueError("polyhedron has to be compact")

        n = self.n_vertices()
        m = self.n_facets()
        if n != m:
            return False

        G1 = self.vertex_facet_graph()
        G2 = G1.reverse()
        return G1.is_isomorphic(G2)


# %%
"""
.. _04-create-a-2d-dnpdata-object-from-individual-spectra:

=======================================================
04 - Create a 2D dnpdata Object from Individual Spectra
=======================================================

This example demonstrates how to import a list of DNP-NMR spectra and create a 2D dnpdata object.

Depending on how you record a set of DNP-NMR experiments, you will either end up with a single file corresponding to a 2D array of spectra (in which case you can skip to the next example ...) or with a set of individual files. A common example is recording the DNP enhancement as a function of the microwave power. For easy data handling, these individual spectra can be concatanated in a single dnpdata object for easy processing and analyzing of the data.
"""
# %%
# Load NMR Spectra
# ----------------
# For this example a set of 1D NMR spectra is imported. Each spectrum is recorded using a different microwave power. The import function of DNPLab can handle a list of spectra and will automatically create the dnpdata object. To load multiple spectra first create a list of paths to the individual spectra (alternatively, you can loop over the folder index, however, for educational purposes we keep this simple for now).
import dnplab as dnp
import numpy as np

filenames = [
    "../data/prospa/toluene_10mM_Tempone/1/data.1d",
    "../data/prospa/toluene_10mM_Tempone/2/data.1d",
    "../data/prospa/toluene_10mM_Tempone/3/data.1d",
    "../data/prospa/toluene_10mM_Tempone/4/data.1d",
    "../data/prospa/toluene_10mM_Tempone/5/data.1d",
    "../data/prospa/toluene_10mM_Tempone/6/data.1d",
    "../data/prospa/toluene_10mM_Tempone/7/data.1d",
    "../data/prospa/toluene_10mM_Tempone/8/data.1d",
    "../data/prospa/toluene_10mM_Tempone/9/data.1d",
    "../data/prospa/toluene_10mM_Tempone/10/data.1d",
    "../data/prospa/toluene_10mM_Tempone/11/data.1d",
    "../data/prospa/toluene_10mM_Tempone/12/data.1d",
    "../data/prospa/toluene_10mM_Tempone/13/data.1d",
    "../data/prospa/toluene_10mM_Tempone/14/data.1d",
    "../data/prospa/toluene_10mM_Tempone/15/data.1d",
    "../data/prospa/toluene_10mM_Tempone/16/data.1d",
    "../data/prospa/toluene_10mM_Tempone/17/data.1d",
    "../data/prospa/toluene_10mM_Tempone/18/data.1d",
    "../data/prospa/toluene_10mM_Tempone/19/data.1d",
    "../data/prospa/toluene_10mM_Tempone/20/data.1d",
    "../data/prospa/toluene_10mM_Tempone/21/data.1d",
    "../data/prospa/toluene_10mM_Tempone/22/data.1d",
    "../data/prospa/toluene_10mM_Tempone/23/data.1d",
    "../data/prospa/toluene_10mM_Tempone/24/data.1d",
    "../data/prospa/toluene_10mM_Tempone/25/data.1d",
    "../data/prospa/toluene_10mM_Tempone/26/data.1d",
    "../data/prospa/toluene_10mM_Tempone/27/data.1d",
    "../data/prospa/toluene_10mM_Tempone/28/data.1d",
    "../data/prospa/toluene_10mM_Tempone/29/data.1d",
    "../data/prospa/toluene_10mM_Tempone/30/data.1d",
    "../data/prospa/toluene_10mM_Tempone/31/data.1d",
    "../data/prospa/toluene_10mM_Tempone/32/data.1d",
    "../data/prospa/toluene_10mM_Tempone/33/data.1d",
    "../data/prospa/toluene_10mM_Tempone/34/data.1d",
    "../data/prospa/toluene_10mM_Tempone/35/data.1d",
    "../data/prospa/toluene_10mM_Tempone/36/data.1d",
    "../data/prospa/toluene_10mM_Tempone/37/data.1d",
    "../data/prospa/toluene_10mM_Tempone/38/data.1d",
    "../data/prospa/toluene_10mM_Tempone/39/data.1d",
    "../data/prospa/toluene_10mM_Tempone/40/data.1d",
    "../data/prospa/toluene_10mM_Tempone/41/data.1d",
]

# %%
# Create an array with the power levels. The length of this array should match the number of spectra. The Python list "filenames" and the array of power levels will become input arguments to the load function. Here, the dimension is called "Power" and the values stored in "powers" serves as the "coord" input argument. When importing the spectra DNPLab will automatically create a 2D object with a new dimension namend "Power" and the data is concatenated into a single 2D dnpdata object.
powers = np.array([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40])   # Power in dBm

# %%
# Now load the data and assign the power array to coord,
data = dnp.dnpImport.load(filenames, dim = "Power", coord = powers)

# %%
# Finally, we can create the workspace, add the data to the "raw" object, and copy the "raw" data to the processing buffer.
ws = dnp.create_workspace()
ws.add("raw", data)
ws.copy("raw", "proc")

# %%
# Process and Save the NMR Spectra
# --------------------------------
# Once the 2D data set is created, NMR processing is straightforward. Here, we apply a line-broadening of 10 Hz, perform a Fourier Transformation, and zero-filling of the data set to twice the number of points.
dnp.dnpNMR.remove_offset(ws)
dnp.dnpNMR.window(ws, linewidth = 10)
dnp.dnpNMR.fourier_transform(ws, zero_fill_factor = 2)

# %%
# Finally, the 1D spectra are plotted.
dnp.dnpResults.figure()
dnp.dnpResults.plot(ws["proc"].real)
dnp.dnpResults.xlim([30, -30])
dnp.dnpResults.plt.xlabel("Chemical Shift [ppm]")
dnp.dnpResults.plt.ylabel("Signal Amplitude [a.u.]")
dnp.dnpResults.plt.title("DNP Enhancement Power Build-Up, 10 mM TEMPO in Toluene")
dnp.dnpResults.plt.grid(True)
dnp.dnpResults.show()

# %%
# Saving the Processed Data
# -------------------------
# DNPLab has built-in capabilities to save large data sets, so we can save the already concatenated and processed NMR data in a single file and load just this file for further processing.

file_name_path = "../data/h5/PowerBuildUp.h5"
dnp.dnpSave.save(ws,file_name_path, overwrite = True)

# %%
# DNPLab saves the 2D dnpdata object in the hdf5 file format. We will use this data in the next example (:ref:`05-calculate-dnp-enhancements-i`) for further processing.


#!/usr/bin/env python
# coding: utf-8

#
#

# # PHY321: Classical Mechanics 1
# **Homework 3, due February 4**
#
# Date: **Jan 31, 2022**

# ### Practicalities about  homeworks and projects
#
# 1. You can work in groups (optimal groups are often 2-3 people) or by yourself. If you work as a group you can hand in one answer only if you wish. **Remember to write your name(s)**!
#
# 2. Homeworks are available ten days  before the deadline.
#
# 3. How do I(we)  hand in?  You can hand in the paper and pencil exercises as a  scanned document. For this homework this applies to exercises 1-5. Alternatively, you can hand in everything (if you are ok with typing mathematical formulae using say Latex) as a jupyter notebook at D2L. The numerical exercise(s) (exercise 6 here) should always be handed in as a jupyter notebook by the deadline at D2L.

# ### Introduction to homework 3
#
# This week's sets of classical pen and paper and computational
# exercises deal with the motion of different objects under the
# influence of various forces. The relevant reading background is
# 1. chapter 2 of Taylor (there are many good examples there) and
#
# 2. chapters 5-7 of Malthe-Sørenssen.
#
# In both textbooks there are many nice worked out
# examples. Malthe-Sørenssen's text contains also several coding
# examples you may find useful.
#
# There are several pedagogical aims we have in mind with these exercises:
# 1. Get practice in setting up and analyzing a physical problem, finding the forces and the relevant equations to solve;
#
# 2. Analyze the results and ask yourself whether they make sense or not;
#
# 3. Finding analytical solutions to problems if possible and compare these with numerical results. This teaches us also how to understand errors in numerical calculations;
#
# 4. Being able to solve (in mechanics these are the most common types of equations) numerically ordinary differential equations and compare the solutions where possible with analytical solutions;
#
# 5. Getting used to studying physical problems using all possible tools, from paper and pencil to numerical solutions;
#
# 6. Then analyze the results and ask yourself whether they make sense or not.
#
# The above steps outline important elements of our understanding of the
# scientific method. Furthermore, there are also explicit coding skills
# we aim at such as setting up arrays, solving differential equations
# numerically and plotting your results.  Coding practice is also an
# important aspect. The more we practice the better we get (hopefully).
# From a numerical mathematics point of view, we will solve the differential
# equations using Euler's method (forward Euler).
#
# The code we will develop can be reused as a basis for coming homeworks. We can
# also extend the numerical solver we write here to include other methods (later) like
# the modified Euler method (Euler-Cromer, midpoint Euler) and more
# advanced methods like the family of Runge-Kutta methods and the Velocity-Verlet method.
#
# At the end of this course, we will thus have developed a larger code
# (or set of codes) which will allow us to study different numerical
# methods (integration and differential equations) as well as being able
# to study different physical systems. Combined with analytical skills,
# the hope is that this can allow us to explore interesting and
# realistic physics problems. By doing so, the hope is that can lead to
# deeper insights about the laws of motion which govern a system.
#
# And hopefully you can reuse many of the above solvers in other courses (our ideal).

# ### Exercise 1 (20 pt), Electron moving into an electric field
#
# An electron is sent through a varying electrical
# field. Initially, the electron is moving in the $x$-direction with a velocity
# $v_x = 100$ m/s. The electron enters the field when it passes the origin. The field
# varies with time, causing an acceleration of the electron that varies in time

# $$
# \boldsymbol{a}(t)=\left(−20 \mathrm{m/s}^2 −10\mathrm{m/s}^3t\right) \boldsymbol{e}_y
# $$

# * 1a (4pt) Find the velocity as a function of time for the electron.
#
# * 1b (4pt)  Find the position as a function of time for the electron.
#
# The field is only acting inside a box of length $L = 2m$.
# * 1c (4pt)  How long time is the electron inside the field?
#
# * 1d (4pt)  What is the displacement in the $y$-direction when the electron leaves the box. (We call this the deflection of the electron).
#
# * 1e (4pt)  Find the angle the velocity vector forms with the horizontal axis as the electron leaves the box.

# ### Exercise 2 (10 pt), Drag force
#
# Taylor exercise 2.3

# ### Exercise 3 (10 pt), Falling object
#
# Taylor exercise 2.6

# ### Exercise 4 (10 pt), and then a cyclist
#
# Taylor exercise 2.26

# ### Exercise 5 (10 pt), back to a falling ball and preparing for the numerical exercise
#
# **Useful material: Malthe-Sørenssen chapter 7.5 and Taylor chapter 2.4.**
#
# In this example we study the motion of an object subject to a constant force, a velocity dependent
# force. We will  reuse the code we develop here in homework 4 for a position-dependent force.
#
# Here we limit ourselves to a ball that is thrown from a height $h$
# above the ground with an initial velocity
# $\boldsymbol{v}_0$ at time $t=t_0$. We assume the air resistance is proportional  to the square velocity, Together with the gravitational force these are the forces acting on our system.
# **Note that due to the specific velocity dependence, we cannot find an analytical solution for motion in the $x$ and $y$ directions, see the discussion in Taylor after eq. (2.61).**
# In order to find an analytical solution we need to assume that the object is falling in the $y$-direction (negative direction) only.
#
# The position of the ball as function of time is  $\boldsymbol{r}(t)$ where $t$ is time.
#  The position is measured with respect to a coordinate system with origin at the floor.
#
# We assume we have an initial position $\boldsymbol{r}(t_0)=h\boldsymbol{e}_y$ and an initial velocity $\boldsymbol{v}_0=v_{x,0}\boldsymbol{e}_x+v_{y,0}\boldsymbol{e}_y$.
#
# In this exercise we assume the system is influenced by the gravitational force

# $$
# \boldsymbol{G}=-mg\boldsymbol{e}_y
# $$

# and an air resistance given by a square law

# $$
# -Dv\boldsymbol{v}.
# $$

# The analytical expressions for velocity and position as functions of
# time will be used to compare with the numerical results in exercise 6.
#
# * 5a (3pt) Identify the forces acting on the ball and set up a diagram with the forces acting on the ball. Find the acceleration of the falling ball.
#
# * 5b (4pt) Assume now that the object is falling only in the $y$-direction (negative direction). Integrate the acceleration from an initial time $t_0$ to a final time $t$ and find the velocity. In Taylor equations (2.52) to (2.58) you will find a very good discussion of this.
#
# * 5c (4pt) Find thereafter the position as function of time starting with an initial time $t_0$. Find the time it takes to hit the floor.  Here you will find it convenient to set the initial velocity in the $y$-direction to zero. Taylor equations (2.52)-(2.58) should contain all relevant information for solving this part as well.
#
# We will use the above analytical results in our numerical calculations in exercise 6. The analytical solution in the $y$-direction only will serve as a test for our numerical solution.

# ### Exercise 6 (40pt), Numerical elements, solving exercise 5 numerically
#
# **This exercise should be handed in as a jupyter-notebook** at D2L. Remember to write your name(s).
#
# Last week we:
# 1. Gained more practice with plotting in Python
#
# 2. Became familiar with arrays and representing vectors with such objects
#
# This week we will:
# 1. Learn and utilize Euler's Method to find the position and the velocity
#
# 2. Compare analytical and computational solutions
#
# 3. Add additional forces to our model

# In[1]:

get_ipython().run_line_magic('matplotlib', 'inline')

# let's start by importing useful packages we are familiar with
import numpy as np
import matplotlib.pyplot as plt
get_ipython().run_line_magic('matplotlib', 'inline')

# We will choose the following values
# 1. mass $m=0.2$ kg
#
# 2. accelleration (gravity) $g=9.81$ m/s$^{2}$.
#
# 3. initial position is the height $h=2$ m
#
# 4. initial velocities $v_{x,0}=v_{y,0}=10$ m/s
#
# Can you find a reasonable value for the drag coefficient $D$?
# You need also to define an initial time and
# the step size $\Delta t$. We can define the step size $\Delta t$ as the difference between any
# two neighboring values in time (time steps) that we analyze within
# some range. It can be determined by dividing the interval we are
# analyzing, which in our case is time $t_{\mathrm{final}}-t_0$, by the number of steps we
# are taking $(N)$. This gives us a step size $\Delta t = \dfrac{t_{\mathrm{final}}-t_0}{N}$.
#
# With these preliminaries we are now ready to plot our results from exercise 5.
#
# * 6a (10pt) Set up arrays for time, velocity, acceleration and positions for the results from exercise 5. Define an initial and final time. Choose the final time to be the time when the ball hits the ground for the first time. Make a plot of the position and velocity as functions of time.  Here you could set the initial velocity in the $y$-direction to zero and use the result from exercise 5. Else you need to try different initial times using the result from exercise 5 as a starting guess.  It is not critical if you don't reach the ground when the initial velocity in the $y$-direction is not zero.
#
# We move now to the numerical solution of the differential equations as discussed in the [lecture notes](https://mhjensen.github.io/Physics321/doc/pub/motion/html/motion.html) or Malthe-Sørenssen chapter 7.5.
# Let us remind ourselves about  Euler's Method.
#
# Suppose we know $f(t)$ and its derivative $f'(t)$. To find $f(t+\Delta t)$ at the next step, $t+\Delta t$,
# we can consider the Taylor expansion:
#
# $f(t+\Delta t) = f(t) + \dfrac{(\Delta t)f'(t)}{1!} + \dfrac{(\Delta t)^2f''(t)}{2!} + ...$
#
# If we ignore the $f''$ term and higher derivatives, we obtain
#
# $f(t+\Delta t) \approx f(t) + (\Delta t)f'(t)$.
#
# This approximation is the basis of Euler's method, and the Taylor
# expansion suggests that it will have errors of $O(\Delta t^2)$.  Thus, one
# would expect it to work better, the smaller the step size $h$ that you
# use. In our case the step size is $\Delta t$.
#
# In setting up our code we need to
#
# 1. Define and obtain all initial values, constants, and time to be analyzed with step sizes as done above (you can use the same values)
#
# 2. Calculate the velocity using $v_{i+1} = v_{i} + (\Delta t)*a_{i}$
#
# 3. Calculate the position using $pos_{i+1} = r_{i} + (\Delta t)*v_{i}$
#
# 4. Calculate the new acceleration $a_{i+1}$.
#
# 5. Repeat steps 2-4 for all time steps within a loop.
#
# * 6b (20 pt) Write a code which implements Euler's method and compute numerically and plot the position and velocity as functions of time for various values of $\Delta t$. Comment your results.
#
# * 6c (10pt) Compare your numerically obtained positions and velocities with the analytical results from exercise 5. In order to do this, you need to take out the motion in the $x$-direction. Comment again your results.

# ### Classical Mechanics Extra Credit Assignment: Scientific Writing and attending Talks
#
# The following gives you an opportunity to earn **five extra credit
# points** on each of the remaining homeworks and **ten extra credit points**
# on the midterms and finals.  This assignment also covers an aspect of
# the scientific process that is not taught in most undergraduate
# programs: scientific writing.  Writing scientific reports is how
# scientist communicate their results to the rest of the field.  Knowing
# how to assemble a well written scientific report will greatly benefit
# you in you upper level classes, in graduate school, and in the work
# place.
#
# The full information on extra credits is found at <https://github.com/mhjensen/Physics321/blob/master/doc/Homeworks/ExtraCredits/>. There you will also find examples on how to write a scientific article.
# Below you can also find a description on how to gain extra credits by attending scientific talks.
#
# This assignment allows you to gain extra credit points by practicing
# your scientific writing.  For each of the remaining homeworks you can
# submit the specified section of a scientific report (written about the
# numerical aspect of the homework) for five extra credit points on the
# assignment.  For the two midterms and the final, submitting a full
# scientific report covering the numerical analysis problem will be
# worth ten extra points.  For credit the grader must be able to tell
# that you put effort into the assignment (i.e. well written, well
# formatted, etc.).  If you are unfamiliar with writing scientific
# reports, [see the information here](https://github.com/mhjensen/Physics321/blob/master/doc/Homeworks/ExtraCredits/IntroductionScientificWriting.md)
#
# The following table explains what aspect of a scientific report is due
# with which homework.  You can submit the assignment in any format you
# like, in the same document as your homework, or in a different one.
# Remember to cite any external references you use and include a
# reference list.  There are no length requirements, but make sure what
# you turn in is complete and through.  If you have any questions,
# please contact Julie Butler at butler@frib.msu.edu.
#
#

#

#
  HW/Project 	 Due Date	 Extra Credit Assignment

#

#

#
   HW 3             	    2-4         	    Abstract

#
   HW 4             	    2-11        	    Introduction

#
   HW 5             	    2-18        	    Methods

#
   HW 6             	    3-18        	    Results and Discussion

#
   **Midterm 1**    	    **3-4**     	    *Full Written Report*

#
   HW 7             	    3-25        	    Abstract

#
   HW 8             	    4-15        	    Introduction

#
   HW 9             	    4-22        	    Results and Discussion

#
   **Midterm 2      	    ** _4-8_    	    *Full Written Report*

#
   HW 10            	    4-29        	    Abstract

#
   **Final**        	    **5-6**     	    *Full Written Report*

#

#

#
# You can also gain extra credits if you attend scientific talks.
# This is described here.

# ### Integrating Classwork With Research
#
# This opportunity will allow you to earn up to 5 extra credit points on a Homework per week. These points can push you above 100% or help make up for missed exercises.
# In order to earn all points you must:
#
# 1. Attend an MSU research talk (recommended research oriented Clubs is  provided below)
#
# 2. Summarize the talk using at least 150 words
#
# 3. Turn in the summary along with your Homework.
#
# Approved talks:
# Talks given by researchers through the following clubs:
# * Research and Idea Sharing Enterprise (RAISE): Meets Wednesday Nights Society for Physics Students (SPS): Meets Monday Nights
#
# * Astronomy Club: Meets Monday Nights
#
# * Facility For Rare Isotope Beam (FRIB) Seminars: Occur multiple times a week
#
# If you have any questions please consult Julie or Morten
#
# All the material on extra credits is at <https://github.com/mhjensen/Physics321/blob/master/doc/Homeworks/ExtraCredits/>.