Ejemplo n.º 1
0
    def test_puts_gets(self):
        """Test that put/get work in base class."""
        # Generate some random data
        Hankel_array_true = parallel.call_and_bcast(
            np.random.random, ((self.num_states, self.num_states)))
        L_sing_vecs_true, sing_vals_true, R_sing_vecs_true = \
            parallel.call_and_bcast(util.svd, Hankel_array_true)
        direct_proj_coeffs_true = parallel.call_and_bcast(
            np.random.random, ((self.num_steps, self.num_steps)))
        adj_proj_coeffs_true = parallel.call_and_bcast(
            np.random.random, ((self.num_steps, self.num_steps)))

        # Store the data in a BPOD object
        BPOD_save = bpod.BPODHandles(verbosity=0)
        BPOD_save.Hankel_array = Hankel_array_true
        BPOD_save.sing_vals = sing_vals_true
        BPOD_save.L_sing_vecs = L_sing_vecs_true
        BPOD_save.R_sing_vecs = R_sing_vecs_true
        BPOD_save.direct_proj_coeffs = direct_proj_coeffs_true
        BPOD_save.adjoint_proj_coeffs = adj_proj_coeffs_true

        # Use the BPOD object to save the data to disk
        sing_vals_path = join(self.test_dir, 'sing_vals.txt')
        L_sing_vecs_path = join(self.test_dir, 'L_sing_vecs.txt')
        R_sing_vecs_path = join(self.test_dir, 'R_sing_vecs.txt')
        Hankel_array_path = join(self.test_dir, 'Hankel_array.txt')
        direct_proj_coeffs_path = join(self.test_dir, 'direct_proj_coeffs.txt')
        adj_proj_coeffs_path = join(self.test_dir, 'adj_proj_coeffs.txt')
        BPOD_save.put_decomp(sing_vals_path, L_sing_vecs_path,
                             R_sing_vecs_path)
        BPOD_save.put_Hankel_array(Hankel_array_path)
        BPOD_save.put_direct_proj_coeffs(direct_proj_coeffs_path)
        BPOD_save.put_adjoint_proj_coeffs(adj_proj_coeffs_path)

        # Create a BPOD object and use it to load the data from disk
        BPOD_load = bpod.BPODHandles(verbosity=0)
        BPOD_load.get_decomp(sing_vals_path, L_sing_vecs_path,
                             R_sing_vecs_path)
        BPOD_load.get_Hankel_array(Hankel_array_path)
        BPOD_load.get_direct_proj_coeffs(direct_proj_coeffs_path)
        BPOD_load.get_adjoint_proj_coeffs(adj_proj_coeffs_path)

        # Compare loaded data or original data
        np.testing.assert_equal(BPOD_load.sing_vals, sing_vals_true)
        np.testing.assert_equal(BPOD_load.L_sing_vecs, L_sing_vecs_true)
        np.testing.assert_equal(BPOD_load.R_sing_vecs, R_sing_vecs_true)
        np.testing.assert_equal(BPOD_load.Hankel_array, Hankel_array_true)
        np.testing.assert_equal(BPOD_load.direct_proj_coeffs,
                                direct_proj_coeffs_true)
        np.testing.assert_equal(BPOD_load.adjoint_proj_coeffs,
                                adj_proj_coeffs_true)
Ejemplo n.º 2
0
    def test_compute_proj_coeffs(self):
        # Set test tolerances.  Use a slightly more relaxed absolute tolerance
        # here because the projection test uses modes that may correspond to
        # smaller Hankel singular values (i.e., less controllable/unobservable
        # states).  Those mode pairs are not as close to biorthogonal, so a more
        # relaxed tolerance is required.
        rtol = 1e-8
        atol = 1e-8

        # Test a single input/output as well as multiple inputs/outputs.  Allow
        # for more inputs/outputs than states.  (This is determined in setUp()).
        for num_inputs in self.num_inputs_list:
            for num_outputs in self.num_outputs_list:

                # Get impulse response data
                direct_vec_handles, adjoint_vec_handles =\
                self._helper_get_impulse_response_handles(
                    num_inputs, num_outputs)

                # Create BPOD object and compute decomposition, modes.  (The
                # properties defining a projection onto BPOD modes require
                # manipulations involving the correct decomposition and modes,
                # so we cannot isolate the projection step from those
                # computations.)  Use relative tolerance to avoid Hankel
                # singular values which may correspond to very
                # uncontrollable/unobservable states.  It is ok to use a
                # more relaxed tolerance here than in the actual test/assert
                # statements, as here we are saying it is ok to ignore
                # highly uncontrollable/unobservable states, rather than
                # allowing loose tolerances in the comparison of two
                # numbers.  Furthermore, it is likely that in actual use,
                # users would want to ignore relatively small Hankel
                # singular values anyway, as that is the point of doing a
                # balancing transformation.
                BPOD = bpod.BPODHandles(inner_product=np.vdot, verbosity=0)
                BPOD.compute_decomp(direct_vec_handles,
                                    adjoint_vec_handles,
                                    num_inputs=num_inputs,
                                    num_outputs=num_outputs,
                                    rtol=1e-6,
                                    atol=1e-12)
                mode_idxs = range(BPOD.sing_vals.size)
                direct_mode_handles = [
                    VecHandlePickle(self.direct_mode_path % i)
                    for i in mode_idxs
                ]
                adjoint_mode_handles = [
                    VecHandlePickle(self.adjoint_mode_path % i)
                    for i in mode_idxs
                ]
                BPOD.compute_direct_modes(
                    mode_idxs,
                    direct_mode_handles,
                    direct_vec_handles=direct_vec_handles)
                BPOD.compute_adjoint_modes(
                    mode_idxs,
                    adjoint_mode_handles,
                    adjoint_vec_handles=adjoint_vec_handles)

                # Compute true projection coefficients by computing the inner
                # products between modes and snapshots.
                direct_proj_coeffs_true =\
                BPOD.vec_space.compute_inner_product_array(
                    adjoint_mode_handles, direct_vec_handles)
                adjoint_proj_coeffs_true =\
                BPOD.vec_space.compute_inner_product_array(
                    direct_mode_handles, adjoint_vec_handles)

                # Compute projection coefficients using BPOD object, which
                # avoids actually manipulating handles and computing inner
                # products, instead using elements of the decomposition for a
                # more efficient computation.
                direct_proj_coeffs = BPOD.compute_direct_proj_coeffs()
                adjoint_proj_coeffs = BPOD.compute_adjoint_proj_coeffs()

                # Test values
                np.testing.assert_allclose(direct_proj_coeffs,
                                           direct_proj_coeffs_true,
                                           rtol=rtol,
                                           atol=atol)
                np.testing.assert_allclose(adjoint_proj_coeffs,
                                           adjoint_proj_coeffs_true,
                                           rtol=rtol,
                                           atol=atol)
Ejemplo n.º 3
0
    def test_compute_modes(self):
        """Test computing modes in serial and parallel."""
        # Set test tolerances.  More relaxed tolerances are required for testing
        # the BPOD modes, since that test requires "squaring" the gramians and
        # thus involves more ill-conditioned arrays.
        rtol_sqr = 1e-8
        atol_sqr = 1e-8

        # Test a single input/output as well as multiple inputs/outputs.  Allow
        # for more inputs/outputs than states.  (This is determined in setUp()).
        for num_inputs in self.num_inputs_list:
            for num_outputs in self.num_outputs_list:

                # Get impulse response data
                direct_vec_handles, adjoint_vec_handles =\
                    self._helper_get_impulse_response_handles(
                        num_inputs, num_outputs)

                # Create BPOD object and perform decomposition.  (The properties
                # defining a BPOD mode require manipulations involving the
                # correct decomposition, so we cannot isolate the mode
                # computation from the decomposition step.)  Use relative
                # tolerance to avoid Hankel singular values which may correspond
                # to very uncontrollable/unobservable states.  It is ok to use a
                # more relaxed tolerance here than in the actual test/assert
                # statements, as here we are saying it is ok to ignore highly
                # uncontrollable/unobservable states, rather than allowing loose
                # tolerances in the comparison of two numbers.  Furthermore, it
                # is likely that in actual use, users would want to ignore
                # relatively small Hankel singular values anyway, as that is the
                # point of doing a balancing transformation.
                BPOD = bpod.BPODHandles(inner_product=np.vdot, verbosity=0)
                BPOD.compute_decomp(direct_vec_handles,
                                    adjoint_vec_handles,
                                    num_inputs=num_inputs,
                                    num_outputs=num_outputs,
                                    rtol=1e-6,
                                    atol=1e-12)

                # Select a subset of modes to compute.  Compute at least half
                # the modes, and up to all of them.  Make sure to use unique
                # values.  (This may reduce the number of modes computed.)
                num_modes = parallel.call_and_bcast(np.random.randint,
                                                    BPOD.sing_vals.size // 2,
                                                    BPOD.sing_vals.size + 1)
                mode_idxs = np.unique(
                    parallel.call_and_bcast(np.random.randint, 0,
                                            BPOD.sing_vals.size, num_modes))

                # Create handles for the modes
                direct_mode_handles = [
                    VecHandlePickle(self.direct_mode_path % i)
                    for i in mode_idxs
                ]
                adjoint_mode_handles = [
                    VecHandlePickle(self.adjoint_mode_path % i)
                    for i in mode_idxs
                ]

                # Compute modes
                BPOD.compute_direct_modes(
                    mode_idxs,
                    direct_mode_handles,
                    direct_vec_handles=direct_vec_handles)
                BPOD.compute_adjoint_modes(
                    mode_idxs,
                    adjoint_mode_handles,
                    adjoint_vec_handles=adjoint_vec_handles)

                # Test modes against empirical gramians
                np.testing.assert_allclose(
                    BPOD.vec_space.compute_inner_product_array(
                        adjoint_mode_handles, direct_vec_handles).dot(
                            BPOD.vec_space.compute_inner_product_array(
                                direct_vec_handles, adjoint_mode_handles)),
                    np.diag(BPOD.sing_vals[mode_idxs]),
                    rtol=rtol_sqr,
                    atol=atol_sqr)
                np.testing.assert_allclose(
                    BPOD.vec_space.compute_inner_product_array(
                        direct_mode_handles, adjoint_vec_handles).dot(
                            BPOD.vec_space.compute_inner_product_array(
                                adjoint_vec_handles, direct_mode_handles)),
                    np.diag(BPOD.sing_vals[mode_idxs]),
                    rtol=rtol_sqr,
                    atol=atol_sqr)
Ejemplo n.º 4
0
    def test_compute_decomp(self):
        """Test that can take vecs, compute the Hankel and SVD arrays. """
        # Set test tolerances.  Separate, more relaxed tolerances may be
        # required for testing the SVD arrays, since that test requires
        # "squaring" the Hankel array and thus involves more ill-conditioned
        # arrays.
        rtol = 1e-8
        atol = 1e-10
        rtol_sqr = 1e-8
        atol_sqr = 1e-8

        # Test a single input/output as well as multiple inputs/outputs.  Allow
        # for more inputs/outputs than states.  (This is determined in setUp()).
        for num_inputs in self.num_inputs_list:
            for num_outputs in self.num_outputs_list:

                # Get impulse response data
                direct_vec_handles, adjoint_vec_handles =\
                self._helper_get_impulse_response_handles(
                    num_inputs, num_outputs)

                # Compute BPOD using modred.
                BPOD = bpod.BPODHandles(inner_product=np.vdot, verbosity=0)
                sing_vals, L_sing_vecs, R_sing_vecs = BPOD.compute_decomp(
                    direct_vec_handles,
                    adjoint_vec_handles,
                    num_inputs=num_inputs,
                    num_outputs=num_outputs)

                # Check Hankel array values.  These are computed fast
                # internally by only computing the first column and last row
                # of chunks.  Here, simply take all the inner products.
                Hankel_array_slow = BPOD.vec_space.compute_inner_product_array(
                    adjoint_vec_handles, direct_vec_handles)
                np.testing.assert_allclose(BPOD.Hankel_array,
                                           Hankel_array_slow,
                                           rtol=rtol,
                                           atol=atol)

                # Check properties of SVD of Hankel array.  Since the SVD
                # may be truncated, instead of checking orthogonality and
                # reconstruction of the Hankel array, check that the left
                # and right singular vectors satisfy eigendecomposition
                # properties with respect to the Hankel array.  Since this
                # involves "squaring" the Hankel array, it may require more
                # relaxed test tolerances.
                np.testing.assert_allclose(BPOD.Hankel_array.dot(
                    BPOD.Hankel_array.conj().T.dot(BPOD.L_sing_vecs)),
                                           BPOD.L_sing_vecs.dot(
                                               np.diag(BPOD.sing_vals**2.)),
                                           rtol=rtol_sqr,
                                           atol=atol_sqr)
                np.testing.assert_allclose(BPOD.Hankel_array.conj().T.dot(
                    BPOD.Hankel_array.dot(BPOD.R_sing_vecs)),
                                           BPOD.R_sing_vecs.dot(
                                               np.diag(BPOD.sing_vals**2.)),
                                           rtol=rtol_sqr,
                                           atol=atol_sqr)

                # Check that returned values match internal values
                np.testing.assert_equal(sing_vals, BPOD.sing_vals)
                np.testing.assert_equal(L_sing_vecs, BPOD.L_sing_vecs)
                np.testing.assert_equal(R_sing_vecs, BPOD.R_sing_vecs)
Ejemplo n.º 5
0
    def test_init(self):
        """Test arguments passed to the constructor are assigned properly"""
        def my_load(fname):
            pass

        def my_save(data, fname):
            pass

        def my_IP(vec1, vec2):
            pass

        data_members_default = {
            'put_array': util.save_array_text,
            'get_array': util.load_array_text,
            'verbosity': 0,
            'L_sing_vecs': None,
            'R_sing_vecs': None,
            'sing_vals': None,
            'direct_vec_handles': None,
            'adjoint_vec_handles': None,
            'direct_vec_handles': None,
            'adjoint_vec_handles': None,
            'Hankel_array': None,
            'vec_space': VectorSpaceHandles(inner_product=my_IP, verbosity=0)
        }

        # Get default data member values
        for k, v in util.get_data_members(
                bpod.BPODHandles(inner_product=my_IP, verbosity=0)).items():
            self.assertEqual(v, data_members_default[k])

        my_BPOD = bpod.BPODHandles(inner_product=my_IP, verbosity=0)
        data_members_modified = copy.deepcopy(data_members_default)
        data_members_modified['vec_space'] = VectorSpaceHandles(
            inner_product=my_IP, verbosity=0)
        for k, v in util.get_data_members(my_BPOD).items():
            self.assertEqual(v, data_members_modified[k])

        my_BPOD = bpod.BPODHandles(inner_product=my_IP,
                                   get_array=my_load,
                                   verbosity=0)
        data_members_modified = copy.deepcopy(data_members_default)
        data_members_modified['get_array'] = my_load
        for k, v in util.get_data_members(my_BPOD).items():
            self.assertEqual(v, data_members_modified[k])

        my_BPOD = bpod.BPODHandles(inner_product=my_IP,
                                   put_array=my_save,
                                   verbosity=0)
        data_members_modified = copy.deepcopy(data_members_default)
        data_members_modified['put_array'] = my_save
        for k, v in util.get_data_members(my_BPOD).items():
            self.assertEqual(v, data_members_modified[k])

        max_vecs_per_node = 500
        my_BPOD = bpod.BPODHandles(inner_product=my_IP,
                                   max_vecs_per_node=max_vecs_per_node,
                                   verbosity=0)
        data_members_modified = copy.deepcopy(data_members_default)
        data_members_modified['vec_space'].max_vecs_per_node = \
            max_vecs_per_node
        data_members_modified['vec_space'].max_vecs_per_proc = \
            max_vecs_per_node * parallel.get_num_nodes() / parallel.\
            get_num_procs()
        for k, v in util.get_data_members(my_BPOD).items():
            self.assertEqual(v, data_members_modified[k])