Source code for sharpy.rom.balanced

"""Balancing Methods

The following classes are available to reduce a linear system employing balancing methods.

The main class is :class:`.Balanced` and the other available classes:

* :class:`.Direct`

* :class:`.Iterative`

* :class:`.FrequencyLimited`

correspond to the reduction algorithm.

"""
import sharpy.utils.settings as settings
import numpy as np
from abc import ABCMeta
import sharpy.utils.cout_utils as cout
import sharpy.utils.rom_interface as rom_interface
import sharpy.rom.utils.librom as librom
import sharpy.linear.src.libss as libss
import time
from sharpy.linear.utils.ss_interface import LinearVector, StateVariable

dict_of_balancing_roms = dict()

def bal_rom(arg):
    global dict_of_balancing_roms
    try:
        arg._bal_rom_id
    except AttributeError:
        raise AttributeError('Class defined as balanced rom has no _bal_rom_id attribute')
    dict_of_balancing_roms[arg._bal_rom_id] = arg
    return arg


class BaseBalancedRom(metaclass=ABCMeta):

    print_info = False

    def initialise(self, in_settings=None):
        pass

    def run(self, ss):
        pass


[docs]@bal_rom class Direct(BaseBalancedRom): __doc__ = librom.balreal_direct_py.__doc__ _bal_rom_id = 'Direct' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['tune'] = 'bool' settings_default['tune'] = True settings_description['tune'] = 'Tune ROM to specified tolerance' settings_types['use_schur'] = 'bool' settings_default['use_schur'] = False settings_description['use_schur'] = 'Use Schur decomposition during build' settings_types['rom_tolerance'] = 'float' settings_default['rom_tolerance'] = 1e-2 settings_description['rom_tolerance'] = 'Absolute accuracy with respect to full order frequency response' settings_types['rom_tune_freq_range'] = 'list(float)' settings_default['rom_tune_freq_range'] = [0, 1] settings_description['rom_tune_freq_range'] = 'Beginning and end of frequency range where to tune ROM' settings_types['convergence'] = 'str' settings_default['convergence'] = 'min' settings_description['convergence'] = 'ROM tuning convergence. If ``min`` attempts to find minimal number of states.' \ 'If ``all`` it starts from larger size ROM until convergence to ' \ 'specified tolerance is found.' settings_types['reduction_method'] = 'str' settings_default['reduction_method'] = 'realisation' settings_description['reduction_method'] = 'Desired reduction method' settings_options['reduction_method'] = ['realisation', 'truncation'] settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) def run(self, ss): if self.print_info: cout.cout_wrap('Reducing system using a Direct balancing method...') t0 = time.time() A, B, C, D = ss.get_mats() try: if ss.dt is not None: dtsystem = True else: dtsystem = False except AttributeError: dtsystem = False S, T, Tinv = librom.balreal_direct_py(A, B, C, DLTI=dtsystem, Schur=self.settings['use_schur']) Ar = T.dot(A.dot(Tinv)) Br = T.dot(B) Cr = C.dot(Tinv) if dtsystem: ss_bal = libss.StateSpace(Ar, Br, Cr, D, dt=ss.dt) else: ss_bal = libss.StateSpace(Ar, Br, Cr, D) t1 = time.time() if self.print_info: cout.cout_wrap('\t...completed balancing in %.2fs' % (t1-t0), 1) if self.settings['tune']: cout.cout_wrap('Tuning ROM to specified tolerance...', 1) kv = np.linspace(self.settings['rom_tune_freq_range'][0], self.settings['rom_tune_freq_range'][1]) ssrom = librom.tune_rom(ss_bal, kv=kv, tol=self.settings['rom_tolerance'], gv=S, convergence=self.settings['convergence'], method=self.settings['reduction_method']) if librom.check_stability(ssrom.A, dt=True): if self.print_info: cout.cout_wrap('ROM by direct balancing is stable') t2 = time.time() cout.cout_wrap('\t...completed reduction in %.2fs' % (t2-t0), 1) return ssrom else: return ss_bal
[docs]@bal_rom class FrequencyLimited(BaseBalancedRom): __doc__ = librom.balfreq.__doc__ _bal_rom_id = 'FrequencyLimited' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['frequency'] = 'float' settings_default['frequency'] = 1. settings_description['frequency'] = 'defines limit frequencies for balancing. The balanced model will be accurate ' \ 'in the range ``[0,F]``, where ``F`` is the value of this key. Note that ``F`` ' \ 'units must be consistent with the units specified in the in ' \ 'the ``self.ScalingFacts`` dictionary.' settings_types['method_low'] = 'str' settings_default['method_low'] = 'trapz' settings_description['method_low'] = 'Specifies whether to use gauss quadrature or ' \ 'trapezoidal rule in the low-frequency range ``[0,F]``' settings_options['method_low'] = ['gauss', 'trapz'] settings_types['options_low'] = 'dict' settings_default['options_low'] = dict() settings_description['options_low'] = 'Settings for the low frequency integration. See Notes.' settings_types['method_high'] = 'str' settings_default['method_high'] = 'trapz' settings_description['method_high'] = 'Specifies whether to use gauss quadrature or ' \ 'trapezoidal rule in the high-frequency range ``[F,FN]``' settings_options['method_high'] = ['gauss', 'trapz'] settings_types['options_high'] = 'dict' settings_default['options_high'] = dict() settings_description['options_high'] = 'Settings for the high frequency integration. See Notes.' settings_types['check_stability'] = 'bool' settings_default['check_stability'] = True settings_description['check_stability'] = 'if True, the balanced model is truncated to eliminate ' \ 'unstable modes - if any is found. Note that very accurate ' \ 'balanced model can still be obtained, even if high order ' \ 'modes are unstable.' settings_types['get_frequency_response'] = 'bool' settings_default['get_frequency_response'] = False settings_description['get_frequency_response'] = 'if True, the function also returns the frequency ' \ 'response evaluated at the low-frequency range integration' \ ' points. If True, this option also allows to automatically' \ ' tune the balanced model.' # Integrator options settings_options_types = dict() settings_options_default = dict() settings_options_description = dict() settings_options_types['points'] = 'int' settings_options_default['points'] = 12 settings_options_description['points'] = 'Trapezoidal points of integration' settings_options_types['partitions'] = 'int' settings_options_default['partitions'] = 2 settings_options_description['partitions'] = 'Number of Gauss-Lobotto quadratures' settings_options_types['order'] = 'int' settings_options_default['order'] = 2 settings_options_description['order'] = 'Order of Gauss-Lobotto quadratures' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) options_table = settings.SettingsTable() __doc__ += options_table.generate(settings_options_types, settings_options_default, settings_options_description, header_line='The parameters of integration take the following options:\n') def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options, no_ctype=True) settings.to_custom_types(self.settings['options_low'], self.settings_options_types, self.settings_options_default, no_ctype=True) settings.to_custom_types(self.settings['options_high'], self.settings_options_types, self.settings_options_default, no_ctype=True) def run(self, ss): output_results = librom.balfreq(ss, self.settings) return output_results[0]
[docs]@bal_rom class Iterative(BaseBalancedRom): __doc__ = librom.balreal_iter.__doc__ _bal_rom_id = 'Iterative' settings_types = dict() settings_default = dict() settings_description = dict() settings_types['lowrank'] = 'bool' settings_default['lowrank'] = True settings_description['lowrank'] = 'Use low rank methods' settings_types['smith_tol'] = 'float' settings_default['smith_tol'] = 1e-10 settings_description['smith_tol'] = 'Smith tolerance' settings_types['tolSVD'] = 'float' settings_default['tolSVD'] = 1e-6 settings_description['tolSVD'] = 'SVD threshold' settings_types['tolSVD'] = 'float' settings_default['tolSVD'] = 1e-6 settings_description['tolSVD'] = 'SVD threshold' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description) def __init__(self): self.settings = dict() def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, no_ctype=True) def run(self, ss): A, B, C, D = ss.get_mats() s, T, Tinv, rcmax, romax = librom.balreal_iter(A, B, C, lowrank=self.settings['lowrank'], tolSmith=self.settings['smith_tol'], tolSVD=self.settings['tolSVD']) Ar = Tinv.dot(A.dot(T)) Br = Tinv.dot(B) Cr = C.dot(T) ssrom = libss.StateSpace(Ar, Br, Cr, D, dt=ss.dt) return ssrom
[docs]@rom_interface.rom class Balanced(rom_interface.BaseRom): """Balancing ROM methods Main class to load a balancing ROM. See below for the appropriate settings to be parsed in the ``algorithm_settings`` based on your selection. Supported algorithms: * Direct balancing :class:`.Direct` * Iterative balancing :class:`.Iterative` * Frequency limited balancing :class:`.FrequencyLimited` """ rom_id = 'Balanced' settings_types = dict() settings_default = dict() settings_description = dict() settings_options = dict() settings_types['print_info'] = 'bool' settings_default['print_info'] = True settings_description['print_info'] = 'Write output to screen' settings_types['algorithm'] = 'str' settings_default['algorithm'] = '' settings_description['algorithm'] = 'Balanced realisation method' settings_options['algorithm'] = ['Direct', 'Iterative', 'FrequencyLimited'] settings_types['algorithm_settings'] = 'dict' settings_default['algorithm_settings'] = dict() settings_description['algorithm_settings'] = 'Settings for the desired algorithm' settings_table = settings.SettingsTable() __doc__ += settings_table.generate(settings_types, settings_default, settings_description, settings_options) def __init__(self): self.settings = dict() self.algorithm = None self.ssrom = None self.ss = None self.dtsystem = None def initialise(self, in_settings=None): if in_settings is not None: self.settings = in_settings settings.to_custom_types(self.settings, self.settings_types, self.settings_default, self.settings_options) if not (self.settings['algorithm'] in dict_of_balancing_roms): raise AttributeError('Balancing algorithm %s is not yet implemented' % self.settings['algorithm']) self.algorithm = dict_of_balancing_roms[self.settings['algorithm']]() self.algorithm.initialise(self.settings['algorithm_settings']) self.algorithm.print_info = self.settings['print_info'] def run(self, ss): self.ss = ss A, B, C, D = self.ss.get_mats() if self.ss.dt: self.dtsystem = True else: self.dtsystem = False out = self.algorithm.run(ss) if type(out) == libss.StateSpace: self.ssrom = out else: Ar, Br, Cr = out if self.dtsystem: self.ssrom = libss.StateSpace(Ar, Br, Cr, D, dt=self.ss.dt) else: self.ssrom = libss.StateSpace(Ar, Br, Cr, D) try: self.ssrom.input_variables = self.ss.input_variables.copy() self.ssrom.output_variables = self.ss.output_variables.copy() self.ssrom.state_variables = LinearVector( [StateVariable('balanced_{:s}'.format(self.settings['algorithm'].lower()), size=self.ssrom.states, index=0)]) except AttributeError: pass return self.ssrom