Exemplo n.º 1
0
    )

    fig, ax = plt.subplots()
    so_sys.mag_plot(w, ax=ax)
    rom_bt.mag_plot(w, ax=ax, linestyle='dashed')
    ax.set_title('Magnitude plot of the full and BT reduced model')
    plt.show()

    fig, ax = plt.subplots()
    err_bt.mag_plot(w, ax=ax)
    ax.set_title('Magnitude plot of the BT error system')
    plt.show()

    # Iterative Rational Krylov Algorithm (IRKA)
    r = 5
    irka_reductor = IRKAReductor(so_sys.to_lti())
    rom_irka = irka_reductor.reduce(r)

    fig, ax = plt.subplots()
    ax.semilogy(irka_reductor.conv_crit, '.-')
    ax.set_title('IRKA convergence criterion')
    plt.show()

    poles_rom_irka = rom_irka.poles()
    fig, ax = plt.subplots()
    ax.plot(poles_rom_irka.real, poles_rom_irka.imag, '.')
    ax.set_title("IRKA reduced model's poles")
    plt.show()

    err_irka = so_sys - rom_irka
    print(
Exemplo n.º 2
0
    print(f'BT relative Hankel-error: {err_bt.hankel_norm() / so_sys.hankel_norm():e}')

    fig, ax = plt.subplots()
    so_sys.mag_plot(w, ax=ax)
    rom_bt.mag_plot(w, ax=ax, linestyle='dashed')
    ax.set_title('Bode plot of the full and BT reduced model')
    plt.show()

    fig, ax = plt.subplots()
    err_bt.mag_plot(w, ax=ax)
    ax.set_title('Bode plot of the BT error system')
    plt.show()

    # Iterative Rational Krylov Algorithm (IRKA)
    r = 5
    irka_reductor = IRKAReductor(so_sys.to_lti())
    rom_irka = irka_reductor.reduce(r)

    fig, ax = plt.subplots()
    ax.semilogy(irka_reductor.conv_crit, '.-')
    ax.set_title('IRKA convergence criterion')
    plt.show()

    poles_rom_irka = rom_irka.poles()
    fig, ax = plt.subplots()
    ax.plot(poles_rom_irka.real, poles_rom_irka.imag, '.')
    ax.set_title("IRKA reduced model's poles")
    plt.show()

    err_irka = so_sys - rom_irka
    print(f'IRKA relative H_2-error:    {err_irka.h2_norm() / so_sys.h2_norm():e}')
Exemplo n.º 3
0
    def reduce(self, rom0_params, tol=1e-4, maxit=100, num_prev=1,
               force_sigma_in_rhp=False, projection='orth', conv_crit='sigma',
               compute_errors=False, irka_options=None):
        r"""Reduce using SOR-IRKA.

        It uses IRKA as the intermediate reductor, to reduce from 2r to
        r poles. See Section 5.3.2 in [W12]_.

        Parameters
        ----------
        rom0_params
            Can be:

            - order of the reduced model (a positive integer),
            - dict with `'sigma'`, `'b'`, `'c'` as keys mapping to
              initial interpolation points (a 1D |NumPy array|), right
              tangential directions (|VectorArray| from
              `fom.input_space`), and left tangential directions
              (|VectorArray| from `fom.output_space`), all of the same
              length (the order of the reduced model),
            - initial reduced-order model (|LTIModel|).

            If the order of reduced model is given, initial
            interpolation data is generated randomly.
        tol
            Tolerance for the convergence criterion.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current
            iteration to. Larger number can avoid occasional cyclic
            behavior of IRKA.
        force_sigma_in_rhp
            If `False`, new interpolation are reflections of the current
            reduced order model's poles. Otherwise, only the poles in
            the left half-plane are reflected.
        projection
            Projection method:

            - `'orth'`: projection matrices are orthogonalized with
              respect to the Euclidean inner product
            - `'biorth'`: projection matrices are biorthogolized with
              respect to the E product
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of
              reduced-order models
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of
            intermediate reduced order models be computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive. Use
                this option only if necessary.
        irka_options
            Dict of options for IRKAReductor.reduce.

        Returns
        -------
        rom
            Reduced-order |SecondOrderModel|.
        """
        if not self.fom.cont_time:
            raise NotImplementedError

        self._clear_lists()
        sigma, b, c = self._rom0_params_to_sigma_b_c(rom0_params, force_sigma_in_rhp)
        self._store_sigma_b_c(sigma, b, c)
        self._check_common_args(tol, maxit, num_prev, conv_crit)
        assert projection in ('orth', 'biorth')
        assert irka_options is None or isinstance(irka_options, dict)
        if not irka_options:
            irka_options = {}

        self.logger.info('Starting SOR-IRKA')
        self._conv_data = (num_prev + 1) * [None]
        if conv_crit == 'sigma':
            self._conv_data[0] = sigma
        self._pg_reductor = SOBHIReductor(self.fom, mu=self.mu)
        for it in range(maxit):
            rom = self._pg_reductor.reduce(sigma, b, c, projection=projection)
            with self.logger.block('Intermediate reduction ...'):
                irka_reductor = IRKAReductor(rom.to_lti())
                rom_r = irka_reductor.reduce(rom.order, **irka_options)
            sigma, b, c = self._rom_to_sigma_b_c(rom_r, force_sigma_in_rhp)
            self._store_sigma_b_c(sigma, b, c)
            self._update_conv_data(sigma, rom, conv_crit)
            self._compute_conv_crit(rom, conv_crit, it)
            self._compute_error(rom, it, compute_errors)
            if self.conv_crit[-1] < tol:
                break

        self.V = self._pg_reductor.V
        self.W = self._pg_reductor.W
        return rom
    ax.semilogy(mu_fine, hinf_bt_err_mu, '.-', label=r'$\mathcal{H}_\infty$')
ax.semilogy(mu_fine, hankel_bt_err_mu, '.-', label='Hankel')

ax.set_xlabel(r'$\mu$')
ax.set_title('Balanced truncation errors')
ax.legend()
#plot.sho()

# # Iterative Rational Krylov Algorithm (IRKA)

# In[ ]:

(h2_irka_err_mu, hinf_irka_err_mu, hankel_irka_err_mu) = reduction_errors(
    lti,
    r,
    mu_fine,
    lambda lti, r, mu=mu: IRKAReductor(lti, mu=mu).reduce(r, conv_crit='h2'))

# In[ ]:

fig, ax = plt.subplots()
ax.semilogy(mu_fine, h2_irka_err_mu, '.-', label=r'$\mathcal{H}_2$')
if config.HAVE_SLYCOT:
    ax.semilogy(mu_fine, hinf_irka_err_mu, '.-', label=r'$\mathcal{H}_\infty$')
ax.semilogy(mu_fine, hankel_irka_err_mu, '.-', label='Hankel')

ax.set_xlabel(r'$\mu$')
ax.set_title('IRKA errors')
ax.legend()
#plot.sho()
Exemplo n.º 5
0
def main(
        n: int = Argument(
            101, help='Order of the full second-order model (odd number).'),
        r: int = Argument(5, help='Order of the ROMs.'),
):
    """String equation example."""
    set_log_levels({'pymor.algorithms.gram_schmidt.gram_schmidt': 'ERROR'})

    # Assemble matrices
    assert n % 2 == 1, 'The order has to be an odd integer.'

    n2 = (n + 1) // 2

    d = 10  # damping
    k = 0.01  # stiffness

    M = sps.eye(n, format='csc')
    E = d * sps.eye(n, format='csc')
    K = sps.diags(
        [n * [2 * k * n**2], (n - 1) * [-k * n**2],
         (n - 1) * [-k * n**2]], [0, -1, 1],
        format='csc')
    B = np.zeros((n, 1))
    B[n2 - 1, 0] = n
    Cp = np.zeros((1, n))
    Cp[0, n2 - 1] = 1

    # Second-order system
    so_sys = SecondOrderModel.from_matrices(M, E, K, B, Cp)

    print(f'order of the model = {so_sys.order}')
    print(f'number of inputs   = {so_sys.dim_input}')
    print(f'number of outputs  = {so_sys.dim_output}')

    poles = so_sys.poles()
    fig, ax = plt.subplots()
    ax.plot(poles.real, poles.imag, '.')
    ax.set_title('System poles')
    plt.show()

    w = np.logspace(-4, 2, 200)
    fig, ax = plt.subplots()
    so_sys.mag_plot(w, ax=ax)
    ax.set_title('Magnitude plot of the full model')
    plt.show()

    psv = so_sys.psv()
    vsv = so_sys.vsv()
    pvsv = so_sys.pvsv()
    vpsv = so_sys.vpsv()
    fig, ax = plt.subplots(2, 2, figsize=(12, 8), sharey=True)
    ax[0, 0].semilogy(range(1, len(psv) + 1), psv, '.-')
    ax[0, 0].set_title('Position singular values')
    ax[0, 1].semilogy(range(1, len(vsv) + 1), vsv, '.-')
    ax[0, 1].set_title('Velocity singular values')
    ax[1, 0].semilogy(range(1, len(pvsv) + 1), pvsv, '.-')
    ax[1, 0].set_title('Position-velocity singular values')
    ax[1, 1].semilogy(range(1, len(vpsv) + 1), vpsv, '.-')
    ax[1, 1].set_title('Velocity-position singular values')
    plt.show()

    print(f'FOM H_2-norm:    {so_sys.h2_norm():e}')
    if config.HAVE_SLYCOT:
        print(f'FOM H_inf-norm:  {so_sys.hinf_norm():e}')
    else:
        print('H_inf-norm calculation is skipped due to missing slycot.')
    print(f'FOM Hankel-norm: {so_sys.hankel_norm():e}')

    # Model order reduction
    run_mor_method(so_sys, w, SOBTpReductor(so_sys), 'SOBTp', r)
    run_mor_method(so_sys, w, SOBTvReductor(so_sys), 'SOBTv', r)
    run_mor_method(so_sys, w, SOBTpvReductor(so_sys), 'SOBTpv', r)
    run_mor_method(so_sys, w, SOBTvpReductor(so_sys), 'SOBTvp', r)
    run_mor_method(so_sys, w, SOBTfvReductor(so_sys), 'SOBTfv', r)
    run_mor_method(so_sys, w, SOBTReductor(so_sys), 'SOBT', r)
    run_mor_method(so_sys,
                   w,
                   SORIRKAReductor(so_sys),
                   'SOR-IRKA',
                   r,
                   irka_options={'maxit': 10})
    run_mor_method(so_sys, w, BTReductor(so_sys.to_lti()), 'BT', r)
    run_mor_method(so_sys, w, IRKAReductor(so_sys.to_lti()), 'IRKA', r)
Exemplo n.º 6
0
    def reduce(self, r, sigma=None, b=None, c=None, rom0=None, tol=1e-4, maxit=100, num_prev=1,
               force_sigma_in_rhp=False, projection='orth', conv_crit='sigma', compute_errors=False,
               irka_options=None):
        r"""Reduce using SOR-IRKA.

        It uses IRKA as the intermediate reductor, to reduce from 2r to
        r poles.
        See Section 5.3.2 in [W12]_.

        Parameters
        ----------
        r
            Order of the reduced order model.
        sigma
            Initial interpolation points (closed under conjugation).

            If `None`, interpolation points are log-spaced between 0.1
            and 10. If `sigma` is an `int`, it is used as a seed to
            generate it randomly. Otherwise, it needs to be a
            one-dimensional array-like of length `r`.

            `sigma` and `rom0` cannot both be not `None`.
        b
            Initial right tangential directions.

            If `None`, if is chosen as all ones. If `b` is an `int`, it
            is used as a seed to generate it randomly. Otherwise, it
            needs to be a |VectorArray| of length `r` from `fom.B.source`.

            `b` and `rom0` cannot both be not `None`.
        c
            Initial left tangential directions.

            If `None`, if is chosen as all ones. If `c` is an `int`, it
            is used as a seed to generate it randomly. Otherwise, it
            needs to be a |VectorArray| of length `r` from `fom.Cp.range`.

            `c` and `rom0` cannot both be not `None`.
        rom0
            Initial reduced order model.

            If `None`, then `sigma`, `b`, and `c` are used. Otherwise,
            it needs to be an |LTIModel| of order `r` and it is used to
            construct `sigma`, `b`, and `c`.
        tol
            Tolerance for the convergence criterion.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current
            iteration to. Larger number can avoid occasional cyclic
            behavior of IRKA.
        force_sigma_in_rhp
            If `False`, new interpolation are reflections of the current
            reduced order model's poles. Otherwise, only the poles in
            the left half-plane are reflected.
        projection
            Projection method:

            - `'orth'`: projection matrices are orthogonalized with
              respect to the Euclidean inner product
            - `'biorth'`: projection matrices are biorthogolized with
              respect to the E product
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of
              reduced-order models
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of
            intermediate reduced order models be computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive. Use
                this option only if necessary.
        irka_options
            Dict of options for IRKAReductor.reduce.

        Returns
        -------
        rom
            Reduced-order |SecondOrderModel|.
        """
        fom = self.fom
        if not fom.cont_time:
            raise NotImplementedError
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'biorth')
        assert conv_crit in ('sigma', 'h2')
        assert irka_options is None or isinstance(irka_options, dict)
        if not irka_options:
            irka_options = {}

        # initial interpolation points and tangential directions
        assert sigma is None or isinstance(sigma, int) or len(sigma) == r
        assert b is None or isinstance(b, int) or b in fom.B.source and len(b) == r
        assert c is None or isinstance(c, int) or c in fom.Cp.range and len(c) == r
        assert (rom0 is None
                or isinstance(rom0, SecondOrderModel)
                and rom0.order == r and rom0.B.source == fom.B.source and rom0.Cp.range == fom.Cp.range)
        assert sigma is None or rom0 is None
        assert b is None or rom0 is None
        assert c is None or rom0 is None
        if rom0 is not None:
            with self.logger.block('Intermediate reduction ...'):
                irka_reductor = IRKAReductor(rom0.to_lti())
                rom_r = irka_reductor.reduce(r, **irka_options)
            poles, b, c = _poles_and_tangential_directions(rom_r)
            sigma = np.abs(poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
        else:
            if sigma is None:
                sigma = np.logspace(-1, 1, r)
            elif isinstance(sigma, int):
                np.random.seed(sigma)
                sigma = np.abs(np.random.randn(r))
            if b is None:
                b = fom.B.source.ones(r)
            elif isinstance(b, int):
                b = fom.B.source.random(r, distribution='normal', seed=b)
            if c is None:
                c = fom.Cp.range.ones(r)
            elif isinstance(c, int):
                c = fom.Cp.range.random(r, distribution='normal', seed=c)

        self.logger.info('Starting SOR-IRKA')
        self.conv_crit = []
        self.sigmas = [np.array(sigma)]
        self.R = [b]
        self.L = [c]
        self.errors = [] if compute_errors else None
        self._pg_reductor = SOBHIReductor(fom)
        # main loop
        for it in range(maxit):
            # interpolatory reduced order model
            rom = self._pg_reductor.reduce(sigma, b, c, projection=projection)

            # reduction to a system with r poles
            with self.logger.block('Intermediate reduction ...'):
                irka_reductor = IRKAReductor(rom.to_lti())
                rom_r = irka_reductor.reduce(r, **irka_options)

            # new interpolation points and tangential directions
            poles, b, c = _poles_and_tangential_directions(rom_r)
            sigma = np.abs(poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
            self.sigmas.append(sigma)
            self.R.append(b)
            self.L.append(c)

            # compute convergence criterion
            if conv_crit == 'sigma':
                dist = _convergence_criterion(self.sigmas[:-num_prev-2:-1], conv_crit)
                self.conv_crit.append(dist)
            elif conv_crit == 'h2':
                if it == 0:
                    rom_list = (num_prev + 1) * [None]
                    rom_list[0] = rom
                    self.conv_crit.append(np.inf)
                else:
                    rom_list[1:] = rom_list[:-1]
                    rom_list[0] = rom
                    dist = _convergence_criterion(rom_list, conv_crit)
                    self.conv_crit.append(dist)

            # report convergence
            self.logger.info(f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}')
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # check if convergence criterion is satisfied
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        rom = self._pg_reductor.reduce(sigma, b, c, projection=projection)
        self.V = self._pg_reductor.V
        self.W = self._pg_reductor.W
        return rom
Exemplo n.º 7
0
    def reduce(self,
               r,
               sigma=None,
               b=None,
               c=None,
               rom0=None,
               tol=1e-4,
               maxit=100,
               num_prev=1,
               force_sigma_in_rhp=False,
               projection='orth',
               conv_crit='sigma',
               compute_errors=False,
               irka_options=None):
        r"""Reduce using SOR-IRKA.

        It uses IRKA as the intermediate reductor, to reduce from 2r to
        r poles.
        See Section 5.3.2 in [W12]_.

        Parameters
        ----------
        r
            Order of the reduced order model.
        sigma
            Initial interpolation points (closed under conjugation).

            If `None`, interpolation points are log-spaced between 0.1
            and 10. If `sigma` is an `int`, it is used as a seed to
            generate it randomly. Otherwise, it needs to be a
            one-dimensional array-like of length `r`.

            `sigma` and `rom0` cannot both be not `None`.
        b
            Initial right tangential directions.

            If `None`, if is chosen as all ones. If `b` is an `int`, it
            is used as a seed to generate it randomly. Otherwise, it
            needs to be a |VectorArray| of length `r` from `fom.B.source`.

            `b` and `rom0` cannot both be not `None`.
        c
            Initial left tangential directions.

            If `None`, if is chosen as all ones. If `c` is an `int`, it
            is used as a seed to generate it randomly. Otherwise, it
            needs to be a |VectorArray| of length `r` from `fom.Cp.range`.

            `c` and `rom0` cannot both be not `None`.
        rom0
            Initial reduced order model.

            If `None`, then `sigma`, `b`, and `c` are used. Otherwise,
            it needs to be an |LTIModel| of order `r` and it is used to
            construct `sigma`, `b`, and `c`.
        tol
            Tolerance for the convergence criterion.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current
            iteration to. Larger number can avoid occasional cyclic
            behavior of IRKA.
        force_sigma_in_rhp
            If `False`, new interpolation are reflections of the current
            reduced order model's poles. Otherwise, only the poles in
            the left half-plane are reflected.
        projection
            Projection method:

            - `'orth'`: projection matrices are orthogonalized with
              respect to the Euclidean inner product
            - `'biorth'`: projection matrices are biorthogolized with
              respect to the E product
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of
              reduced-order models
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of
            intermediate reduced order models be computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive. Use
                this option only if necessary.
        irka_options
            Dict of options for IRKAReductor.reduce.

        Returns
        -------
        rom
            Reduced-order |SecondOrderModel|.
        """
        fom = self.fom
        if not fom.cont_time:
            raise NotImplementedError
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'biorth')
        assert conv_crit in ('sigma', 'h2')
        assert irka_options is None or isinstance(irka_options, dict)
        if not irka_options:
            irka_options = {}

        # initial interpolation points and tangential directions
        assert sigma is None or isinstance(sigma, int) or len(sigma) == r
        assert b is None or isinstance(
            b, int) or b in fom.B.source and len(b) == r
        assert c is None or isinstance(
            c, int) or c in fom.Cp.range and len(c) == r
        assert (rom0 is None or isinstance(rom0, SecondOrderModel)
                and rom0.order == r and rom0.B.source == fom.B.source
                and rom0.Cp.range == fom.Cp.range)
        assert sigma is None or rom0 is None
        assert b is None or rom0 is None
        assert c is None or rom0 is None
        if rom0 is not None:
            with self.logger.block('Intermediate reduction ...'):
                irka_reductor = IRKAReductor(rom0.to_lti())
                rom_r = irka_reductor.reduce(r, **irka_options)
            poles, b, c = _poles_and_tangential_directions(rom_r)
            sigma = np.abs(
                poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
        else:
            if sigma is None:
                sigma = np.logspace(-1, 1, r)
            elif isinstance(sigma, int):
                np.random.seed(sigma)
                sigma = np.abs(np.random.randn(r))
            if b is None:
                b = fom.B.source.ones(r)
            elif isinstance(b, int):
                b = fom.B.source.random(r, distribution='normal', seed=b)
            if c is None:
                c = fom.Cp.range.ones(r)
            elif isinstance(c, int):
                c = fom.Cp.range.random(r, distribution='normal', seed=c)

        self.logger.info('Starting SOR-IRKA')
        self.conv_crit = []
        self.sigmas = [np.array(sigma)]
        self.R = [b]
        self.L = [c]
        self.errors = [] if compute_errors else None
        self._pg_reductor = SOBHIReductor(fom)
        # main loop
        for it in range(maxit):
            # interpolatory reduced order model
            rom = self._pg_reductor.reduce(sigma, b, c, projection=projection)

            # reduction to a system with r poles
            with self.logger.block('Intermediate reduction ...'):
                irka_reductor = IRKAReductor(rom.to_lti())
                rom_r = irka_reductor.reduce(r, **irka_options)

            # new interpolation points and tangential directions
            poles, b, c = _poles_and_tangential_directions(rom_r)
            sigma = np.abs(
                poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
            self.sigmas.append(sigma)
            self.R.append(b)
            self.L.append(c)

            # compute convergence criterion
            if conv_crit == 'sigma':
                dist = _convergence_criterion(self.sigmas[:-num_prev - 2:-1],
                                              conv_crit)
                self.conv_crit.append(dist)
            elif conv_crit == 'h2':
                if it == 0:
                    rom_list = (num_prev + 1) * [None]
                    rom_list[0] = rom
                    self.conv_crit.append(np.inf)
                else:
                    rom_list[1:] = rom_list[:-1]
                    rom_list[0] = rom
                    dist = _convergence_criterion(rom_list, conv_crit)
                    self.conv_crit.append(dist)

            # report convergence
            self.logger.info(
                f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}'
            )
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(
                    f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # check if convergence criterion is satisfied
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        rom = self._pg_reductor.reduce(sigma, b, c, projection=projection)
        self.V = self._pg_reductor.V
        self.W = self._pg_reductor.W
        return rom
Exemplo n.º 8
0
def main(
        diameter: float = Argument(
            0.1, help='Diameter option for the domain discretizer.'),
        r: int = Argument(5, help='Order of the ROMs.'),
):
    r"""2D heat equation demo.

    Discretization of the PDE:

    .. math::
        :nowrap:

        \begin{align*}
            \partial_t z(x, y, t) &= \Delta z(x, y, t),      & 0 < x, y < 1,\ t > 0 \\
            -\nabla z(0, y, t) \cdot n &= z(0, y, t) - u(t), & 0 < y < 1, t > 0 \\
            -\nabla z(1, y, t) \cdot n &= z(1, y, t),        & 0 < y < 1, t > 0 \\
            -\nabla z(0, x, t) \cdot n &= z(0, x, t),        & 0 < x < 1, t > 0 \\
            -\nabla z(1, x, t) \cdot n &= z(1, x, t),        & 0 < x < 1, t > 0 \\
            z(x, y, 0) &= 0                                  & 0 < x, y < 1 \\
            y(t) &= \int_0^1 z(1, y, t) dy,                  & t > 0 \\
        \end{align*}

    where :math:`u(t)` is the input and :math:`y(t)` is the output.
    """
    set_log_levels({'pymor.algorithms.gram_schmidt.gram_schmidt': 'WARNING'})

    p = InstationaryProblem(StationaryProblem(
        domain=RectDomain([[0., 0.], [1., 1.]],
                          left='robin',
                          right='robin',
                          top='robin',
                          bottom='robin'),
        diffusion=ConstantFunction(1., 2),
        robin_data=(ConstantFunction(1., 2),
                    ExpressionFunction('(x[...,0] < 1e-10) * 1.', 2)),
        outputs=[('l2_boundary',
                  ExpressionFunction('(x[...,0] > (1 - 1e-10)) * 1.', 2))]),
                            ConstantFunction(0., 2),
                            T=1.)

    fom, _ = discretize_instationary_cg(p, diameter=diameter, nt=100)

    fom.visualize(fom.solve())

    lti = fom.to_lti()

    print(f'order of the model = {lti.order}')
    print(f'number of inputs   = {lti.dim_input}')
    print(f'number of outputs  = {lti.dim_output}')

    # System poles
    poles = lti.poles()
    fig, ax = plt.subplots()
    ax.plot(poles.real, poles.imag, '.')
    ax.set_title('System poles')
    plt.show()

    # Magnitude plot of the full model
    w = np.logspace(-1, 3, 100)
    fig, ax = plt.subplots()
    lti.mag_plot(w, ax=ax)
    ax.set_title('Magnitude plot of the full model')
    plt.show()

    # Hankel singular values
    hsv = lti.hsv()
    fig, ax = plt.subplots()
    ax.semilogy(range(1, len(hsv) + 1), hsv, '.-')
    ax.set_title('Hankel singular values')
    plt.show()

    # Norms of the system
    print(f'FOM H_2-norm:    {lti.h2_norm():e}')
    if config.HAVE_SLYCOT:
        print(f'FOM H_inf-norm:  {lti.hinf_norm():e}')
    else:
        print('Skipped H_inf-norm calculation due to missing slycot.')
    print(f'FOM Hankel-norm: {lti.hankel_norm():e}')

    # Model order reduction
    run_mor_method(lti, w, BTReductor(lti), 'BT', r, tol=1e-5)
    run_mor_method(lti, w, LQGBTReductor(lti), 'LQGBT', r, tol=1e-5)
    run_mor_method(lti, w, BRBTReductor(lti), 'BRBT', r, tol=1e-5)
    run_mor_method(lti, w, IRKAReductor(lti), 'IRKA', r)
    run_mor_method(lti, w, TSIAReductor(lti), 'TSIA', r)
    run_mor_method(lti, w, OneSidedIRKAReductor(lti, 'V'), 'OS-IRKA', r)