GitHub

romtools.vector_space.utils.shifter


Notes

The vector defining the affine offset for a linear subspace is viewed as a matrix of shape $$\mathbf{u}_{\mathrm{shift}} \in \mathbb{R}^{N_{\mathrm{vars}} \times N_{\mathrm{x}} }$$


Theory

What is a shift vector, and why would I use it? In ROMs, we restrict a state to belong to a low-dimensional affine vector space, $$\mathbf{u} \approx \tilde{\mathbf{u}} \in \mathcal{V} + \mathbf{u}_{\mathrm{shift}}$$ where $\mathcal{V} \equiv \mathrm{range}(\boldsymbol \Phi) $. Here $\mathbf{u}_{\mathrm{shift}}$ defines an affine offset. Affine offsets can be useful for a variety of reasons, including satisfying boundary conditions, and satisfying initial conditions.

The _Shifter class encapsulates the affine offset.


API

  1#
  2# ************************************************************************
  3#
  4#                         ROM Tools and Workflows
  5# Copyright 2019 National Technology & Engineering Solutions of Sandia,LLC
  6#                              (NTESS)
  7#
  8# Under the terms of Contract DE-NA0003525 with NTESS, the
  9# U.S. Government retains certain rights in this software.
 10#
 11# ROM Tools and Workflows is licensed under BSD-3-Clause terms of use:
 12#
 13# Redistribution and use in source and binary forms, with or without
 14# modification, are permitted provided that the following conditions
 15# are met:
 16#
 17# 1. Redistributions of source code must retain the above copyright
 18# notice, this list of conditions and the following disclaimer.
 19#
 20# 2. Redistributions in binary form must reproduce the above copyright
 21# notice, this list of conditions and the following disclaimer in the
 22# documentation and/or other materials provided with the distribution.
 23#
 24# 3. Neither the name of the copyright holder nor the names of its
 25# contributors may be used to endorse or promote products derived
 26# from this software without specific prior written permission.
 27#
 28# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 29# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 30# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 31# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 32# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 33# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 34# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 35# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 36# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 37# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 38# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 39# POSSIBILITY OF SUCH DAMAGE.
 40#
 41# Questions? Contact Eric Parish (ejparis@sandia.gov)
 42#
 43# ************************************************************************
 44#
 45
 46'''
 47___
 48##**Notes**
 49The vector defining the affine offset for a linear subspace is viewed as a matrix of shape
 50$$\\mathbf{u}_{\\mathrm{shift}} \in \\mathbb{R}^{N_{\\mathrm{vars}} \\times N_{\mathrm{x}} }$$
 51
 52___
 53##**Theory**
 54
 55*What is a shift vector, and why would I use it?* In ROMs, we restrict a state to belong to a low-dimensional affine
 56vector space,
 57$$\\mathbf{u} \\approx \\tilde{\\mathbf{u}} \\in \\mathcal{V} + \\mathbf{u}_{\\mathrm{shift}}$$
 58where
 59$\\mathcal{V} \\equiv \\mathrm{range}(\\boldsymbol \\Phi) $. Here $\\mathbf{u}_{\\mathrm{shift}}$ defines an affine offset.
 60Affine offsets can be useful for a variety of reasons, including satisfying boundary conditions, and satisfying initial
 61conditions.
 62
 63The _Shifter class encapsulates the affine offset.
 64
 65___
 66##**API**
 67'''
 68
 69import sys
 70from numbers import Number
 71from typing import Protocol
 72import numpy as np
 73import romtools.linalg.linalg as la
 74
 75
 76class Shifter(Protocol):
 77    '''Interface for the Shifter class.'''
 78    def apply_shift(self, my_array: np.ndarray) -> None:
 79        '''Shifts the snapshot matrix by subtracting a vector generated by the public-facing free functions.'''
 80        ...
 81
 82    def apply_inverse_shift(self, my_array: np.ndarray) -> None:
 83        '''Shifts the snapshot matrix by adding a vector generated by the public-facing free functions.'''
 84        ...
 85
 86    def get_shift_vector(self) -> np.ndarray:
 87        '''Returns the vector used to shift the data.'''
 88        ...
 89
 90
 91class _Shifter():
 92    '''
 93    Shifts the data by a vector generated by the public-facing free functions.
 94
 95    This class conforms to `Shifter` protocol.
 96    '''
 97    def __init__(self, shift_vector: np.ndarray) -> None:
 98        '''
 99        Constructor
100
101        Args:
102            shift_vector (np.ndarray): The vector to shift the data by.
103        '''
104        self.__shift_vector = shift_vector.copy()
105
106    def apply_shift(self, my_array: np.ndarray) -> None:
107        '''Shifts the input array in place by subtracting the provided shift vector.'''
108        my_array -= self.__shift_vector[..., None]
109
110    def apply_inverse_shift(self, my_array: np.ndarray) -> None:
111        '''Shifts the input array in place by adding the provided shift vector.'''
112        my_array += self.__shift_vector[..., None]
113
114    def get_shift_vector(self) -> np.ndarray:
115        '''Returns the shift vector.'''
116        return self.__shift_vector
117
118
119def create_noop_shifter(my_array: np.ndarray) -> Shifter:
120    '''No op implementation.'''
121    shift_vector = np.zeros((my_array.shape[0], my_array.shape[1]))
122    shifter = _Shifter(shift_vector)
123    return shifter
124
125
126def create_constant_shifter(shift_value, my_array: np.ndarray) -> Shifter:
127    '''Shifts the data by a constant value.'''
128    if isinstance(shift_value, np.ndarray):
129        shift_vector = np.empty((my_array.shape[0], my_array.shape[1],))
130        assert my_array.shape[0] == shift_value.size
131        for i in range(0, my_array.shape[0]):
132            shift_vector[i] = shift_value[i]
133    elif isinstance(shift_value, Number):
134        shift_vector = np.full((my_array.shape[0], my_array.shape[1],), shift_value)
135    else:
136        sys.exit("Error: shift_value must be either a number or np.ndarray.")
137    shifter = _Shifter(shift_vector)
138    return shifter
139
140
141def create_vector_shifter(shift_vector: np.ndarray) -> Shifter:
142    '''Shifts the data by a user-input vector.'''
143    shifter = _Shifter(shift_vector)
144    return shifter
145
146
147def create_average_shifter(my_array: np.ndarray) -> Shifter:
148    '''Shifts the data by the average of a data matrix.'''
149    shift_vector = la.mean(my_array, axis=2)
150    return _Shifter(shift_vector)
151
152
153def create_firstvec_shifter(my_array: np.ndarray) -> Shifter:
154    '''Shifts the data by the first vector of a data matrix.'''
155    shift_vector = my_array[:, :, 0]
156    shifter = _Shifter(shift_vector)
157    return shifter
class Shifter(typing.Protocol):
77class Shifter(Protocol):
78    '''Interface for the Shifter class.'''
79    def apply_shift(self, my_array: np.ndarray) -> None:
80        '''Shifts the snapshot matrix by subtracting a vector generated by the public-facing free functions.'''
81        ...
82
83    def apply_inverse_shift(self, my_array: np.ndarray) -> None:
84        '''Shifts the snapshot matrix by adding a vector generated by the public-facing free functions.'''
85        ...
86
87    def get_shift_vector(self) -> np.ndarray:
88        '''Returns the vector used to shift the data.'''
89        ...

Interface for the Shifter class.

Shifter(*args, **kwargs)
1771def _no_init_or_replace_init(self, *args, **kwargs):
1772    cls = type(self)
1773
1774    if cls._is_protocol:
1775        raise TypeError('Protocols cannot be instantiated')
1776
1777    # Already using a custom `__init__`. No need to calculate correct
1778    # `__init__` to call. This can lead to RecursionError. See bpo-45121.
1779    if cls.__init__ is not _no_init_or_replace_init:
1780        return
1781
1782    # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
1783    # The first instantiation of the subclass will call `_no_init_or_replace_init` which
1784    # searches for a proper new `__init__` in the MRO. The new `__init__`
1785    # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
1786    # instantiation of the protocol subclass will thus use the new
1787    # `__init__` and no longer call `_no_init_or_replace_init`.
1788    for base in cls.__mro__:
1789        init = base.__dict__.get('__init__', _no_init_or_replace_init)
1790        if init is not _no_init_or_replace_init:
1791            cls.__init__ = init
1792            break
1793    else:
1794        # should not happen
1795        cls.__init__ = object.__init__
1796
1797    cls.__init__(self, *args, **kwargs)
def apply_shift(self, my_array: numpy.ndarray) -> None:
79    def apply_shift(self, my_array: np.ndarray) -> None:
80        '''Shifts the snapshot matrix by subtracting a vector generated by the public-facing free functions.'''
81        ...

Shifts the snapshot matrix by subtracting a vector generated by the public-facing free functions.

def apply_inverse_shift(self, my_array: numpy.ndarray) -> None:
83    def apply_inverse_shift(self, my_array: np.ndarray) -> None:
84        '''Shifts the snapshot matrix by adding a vector generated by the public-facing free functions.'''
85        ...

Shifts the snapshot matrix by adding a vector generated by the public-facing free functions.

def get_shift_vector(self) -> numpy.ndarray:
87    def get_shift_vector(self) -> np.ndarray:
88        '''Returns the vector used to shift the data.'''
89        ...

Returns the vector used to shift the data.

def create_noop_shifter(my_array: numpy.ndarray) -> Shifter:
120def create_noop_shifter(my_array: np.ndarray) -> Shifter:
121    '''No op implementation.'''
122    shift_vector = np.zeros((my_array.shape[0], my_array.shape[1]))
123    shifter = _Shifter(shift_vector)
124    return shifter

No op implementation.

def create_constant_shifter( shift_value, my_array: numpy.ndarray) -> Shifter:
127def create_constant_shifter(shift_value, my_array: np.ndarray) -> Shifter:
128    '''Shifts the data by a constant value.'''
129    if isinstance(shift_value, np.ndarray):
130        shift_vector = np.empty((my_array.shape[0], my_array.shape[1],))
131        assert my_array.shape[0] == shift_value.size
132        for i in range(0, my_array.shape[0]):
133            shift_vector[i] = shift_value[i]
134    elif isinstance(shift_value, Number):
135        shift_vector = np.full((my_array.shape[0], my_array.shape[1],), shift_value)
136    else:
137        sys.exit("Error: shift_value must be either a number or np.ndarray.")
138    shifter = _Shifter(shift_vector)
139    return shifter

Shifts the data by a constant value.

def create_vector_shifter( shift_vector: numpy.ndarray) -> Shifter:
142def create_vector_shifter(shift_vector: np.ndarray) -> Shifter:
143    '''Shifts the data by a user-input vector.'''
144    shifter = _Shifter(shift_vector)
145    return shifter

Shifts the data by a user-input vector.

def create_average_shifter(my_array: numpy.ndarray) -> Shifter:
148def create_average_shifter(my_array: np.ndarray) -> Shifter:
149    '''Shifts the data by the average of a data matrix.'''
150    shift_vector = la.mean(my_array, axis=2)
151    return _Shifter(shift_vector)

Shifts the data by the average of a data matrix.

def create_firstvec_shifter(my_array: numpy.ndarray) -> Shifter:
154def create_firstvec_shifter(my_array: np.ndarray) -> Shifter:
155    '''Shifts the data by the first vector of a data matrix.'''
156    shift_vector = my_array[:, :, 0]
157    shifter = _Shifter(shift_vector)
158    return shifter

Shifts the data by the first vector of a data matrix.