def _helper_get_impulse_response_handles(self, num_inputs, num_outputs): # Get state space system A, B, C = parallel.call_and_bcast( get_system_arrays, self.num_states, num_inputs, num_outputs) # Run impulse responses direct_vec_array = parallel.call_and_bcast( get_direct_impulse_response_array, A, B, self.num_steps) adjoint_vec_array = parallel.call_and_bcast( get_adjoint_impulse_response_array, A, C, self.num_steps, np.identity(self.num_states)) # Save data to disk direct_vec_handles = [ VecHandlePickle(self.direct_vec_path % i) for i in range(direct_vec_array.shape[1])] adjoint_vec_handles = [ VecHandlePickle(self.adjoint_vec_path % i) for i in range(adjoint_vec_array.shape[1])] if parallel.is_rank_zero(): for idx, handle in enumerate(direct_vec_handles): handle.put(direct_vec_array[:, idx]) for idx, handle in enumerate(adjoint_vec_handles): handle.put(adjoint_vec_array[:, idx]) parallel.barrier() return direct_vec_handles, adjoint_vec_handles
def test_Hankel(self): """Test forming Hankel array from first column and last row.""" for num_rows in [1, 4, 6]: for num_cols in [1, 3, 6]: # Generate simple integer values so structure of array is easy # to see. This doesn't affect the robustness of the test, as # all we are concerned about is structure. first_col = np.arange(1, num_rows + 1) last_row = np.arange(1, num_cols + 1) * 10 last_row[0] = first_col[-1] # Fill in Hankel array. Recall that along skew diagonals, i + # j is constant. Hankel_true = np.zeros((num_rows, num_cols)) for i in range(num_rows): for j in range(num_cols): # Upper left triangle of values. Fill skew diagonals # until we hit the lower left corner of the array, where # i + j = num_rows - 1. if i + j < num_rows: Hankel_true[i, j] = first_col[i + j] # Lower right triangle of values. Starting on skew # diagonal just to right of lower left corner of array, # fill in rest of values. else: Hankel_true[i, j] = last_row[i + j - num_rows + 1] # Compute Hankel array using util and test Hankel_test = util.Hankel(first_col, last_row) np.testing.assert_equal(Hankel_test, Hankel_true)
def test_Hankel(self): """Test forming Hankel array from first column and last row.""" for num_rows in [1, 4, 6]: for num_cols in [1, 3, 6]: # Generate simple integer values so structure of array is easy # to see. This doesn't affect the robustness of the test, as # all we are concerned about is structure. first_col = np.arange(1, num_rows + 1) last_row = np.arange(1, num_cols + 1) * 10 last_row[0] = first_col[-1] # Fill in Hankel array. Recall that along skew diagonals, i + # j is constant. Hankel_true = np.zeros((num_rows, num_cols)) for i in range(num_rows): for j in range(num_cols): # Upper left triangle of values. Fill skew diagonals # until we hit the lower left corner of the array, where # i + j = num_rows - 1. if i + j < num_rows: Hankel_true[i, j] = first_col[i + j] # Lower right triangle of values. Starting on skew # diagonal just to right of lower left corner of array, # fill in rest of values. else: Hankel_true[i, j] = last_row[i + j - num_rows + 1] # Compute Hankel array using util and test Hankel_test = util.Hankel(first_col, last_row) np.testing.assert_equal(Hankel_test, Hankel_true)
def _helper_get_impulse_response_handles(self, num_inputs, num_outputs): # Get state space system A, B, C = parallel.call_and_bcast(get_system_arrays, self.num_states, num_inputs, num_outputs) # Run impulse responses direct_vec_array = parallel.call_and_bcast( get_direct_impulse_response_array, A, B, self.num_steps) adjoint_vec_array = parallel.call_and_bcast( get_adjoint_impulse_response_array, A, C, self.num_steps, np.identity(self.num_states)) # Save data to disk direct_vec_handles = [ VecHandlePickle(self.direct_vec_path % i) for i in range(direct_vec_array.shape[1]) ] adjoint_vec_handles = [ VecHandlePickle(self.adjoint_vec_path % i) for i in range(adjoint_vec_array.shape[1]) ] if parallel.is_rank_zero(): for idx, handle in enumerate(direct_vec_handles): handle.put(direct_vec_array[:, idx]) for idx, handle in enumerate(adjoint_vec_handles): handle.put(adjoint_vec_array[:, idx]) parallel.barrier() return direct_vec_handles, adjoint_vec_handles
def test_assemble_Hankel(self): """ Tests Hankel arrays are symmetric given ``[CB CAB CA**P CA**(P+1)B ...]``.""" rtol = 1e-10 atol = 1e-12 for num_inputs in [1, 3]: for num_outputs in [1, 2, 4]: for sample_interval in [1]: num_time_steps = 50 num_states = 5 A, B, C = util.drss(num_states, num_inputs, num_outputs) time_steps = make_time_steps(num_time_steps, sample_interval) Markovs = util.impulse(A, B, C, time_steps[-1] + 1) Markovs = Markovs[time_steps] if sample_interval == 2: time_steps, Markovs = era.make_sampled_format( time_steps, Markovs) my_ERA = era.ERA(verbosity=0) my_ERA._set_Markovs(Markovs) my_ERA._assemble_Hankel() H = my_ERA.Hankel_array Hp = my_ERA.Hankel_array2 for row in range(my_ERA.mc): for col in range(my_ERA.mo): np.testing.assert_equal( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], H[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs]) np.testing.assert_equal( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], Hp[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs]) np.testing.assert_allclose( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2]).dot(B)), rtol=rtol, atol=atol) np.testing.assert_allclose( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2 + 1]).dot(B)), rtol=rtol, atol=atol)
def test_assemble_Hankel(self): """ Tests Hankel arrays are symmetric given ``[CB CAB CA**P CA**(P+1)B ...]``.""" rtol = 1e-10 atol = 1e-12 for num_inputs in [1,3]: for num_outputs in [1, 2, 4]: for sample_interval in [1]: num_time_steps = 50 num_states = 5 A, B, C = util.drss(num_states, num_inputs, num_outputs) time_steps = make_time_steps( num_time_steps, sample_interval) Markovs = util.impulse(A, B, C, time_steps[-1] + 1) Markovs = Markovs[time_steps] if sample_interval == 2: time_steps, Markovs = era.make_sampled_format( time_steps, Markovs) my_ERA = era.ERA(verbosity=0) my_ERA._set_Markovs(Markovs) my_ERA._assemble_Hankel() H = my_ERA.Hankel_array Hp = my_ERA.Hankel_array2 for row in range(my_ERA.mc): for col in range(my_ERA.mo): np.testing.assert_equal( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], H[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs]) np.testing.assert_equal( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], Hp[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs]) np.testing.assert_allclose( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2]).dot( B)), rtol=rtol, atol=atol) np.testing.assert_allclose( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2 + 1]).dot( B)), rtol=rtol, atol=atol)
def test_impulse(self): """Test impulse response of discrete system""" rtol = 1e-10 atol = 1e-12 for num_states in [1, 10]: for num_inputs in [1, 3]: for num_outputs in [1, 2, 3, 5]: # Generate state-space system arrays A, B, C = util.drss(num_states, num_inputs, num_outputs) # Check that can give time_steps as argument outputs = util.impulse(A, B, C) num_time_steps = len(outputs) outputs_true = np.zeros( (num_time_steps, num_outputs, num_inputs)) for ti in range(num_time_steps): outputs_true[ti] = C.dot( np.linalg.matrix_power(A, ti).dot(B)) np.testing.assert_allclose( outputs, outputs_true, rtol=rtol, atol=atol) # Check can give num_time_steps as an argument outputs = util.impulse( A, B, C, num_time_steps=num_time_steps) np.testing.assert_allclose( outputs, outputs_true, rtol=rtol, atol=atol)
def test_load_impulse_outputs(self): """ Test loading multiple signal files in [t sig1 sig2 ...] format. Creates signals, saves them, loads them, tests are equal to the originals. """ signal_path = join(self.test_dir, 'file%03d.txt') for num_paths in [1, 4]: for num_signals in [1, 2, 4, 5]: for num_time_steps in [1, 10, 100]: all_signals_true = np.random.random((num_paths, num_time_steps, num_signals)) # Time steps need not be sequential time_values_true = np.random.random(num_time_steps) signal_paths = [] # Save signals to file for path_num in range(num_paths): signal_paths.append(signal_path%path_num) data_to_save = np.concatenate( \ (time_values_true.reshape(len(time_values_true), 1), all_signals_true[path_num]), axis=1) util.save_array_text(data_to_save, signal_path%path_num) time_values, all_signals = util.load_multiple_signals( signal_paths) np.testing.assert_allclose(all_signals, all_signals_true) np.testing.assert_allclose(time_values, time_values_true)
def test_lin_combine(self): """ Test that linear combinations are correctly computed """ # Set test tolerances rtol = 1e-10 atol = 1e-12 # Generate data num_states = 100 num_vecs = 30 num_modes = 10 vecs_array = ( np.random.random((num_states, num_vecs)) + 1j * np.random.random((num_states, num_vecs))) coeffs_array = ( np.random.random((num_vecs, num_modes)) + 1j * np.random.random((num_vecs, num_modes))) modes_array_true = vecs_array.dot(coeffs_array) # Do computation using a vector space object. Check an explicit # mode_indices argument, as well as a None value. vec_space = vspc.VectorSpaceArrays() mode_indices_trunc = np.random.randint( 0, high=num_modes, size=num_modes // 2) for mode_idxs_arg, mode_idxs_vals in zip( [None, mode_indices_trunc], [range(num_modes), mode_indices_trunc]): modes_array = vec_space.lin_combine( vecs_array, coeffs_array, coeff_array_col_indices=mode_idxs_arg) np.testing.assert_allclose( modes_array, modes_array_true[:, mode_idxs_vals], rtol=rtol, atol=atol)
def setUp(self): # Specify output locations if not os.access('.', os.W_OK): raise RuntimeError('Cannot write to current directory') self.test_dir = 'files_POD_DELETE_ME' if not os.path.isdir(self.test_dir): parallel.call_from_rank_zero(os.mkdir, self.test_dir) self.vec_path = join(self.test_dir, 'vec_%03d.pkl') self.mode_path = join(self.test_dir, 'mode_%03d.pkl') # Specify data dimensions self.num_states = 30 self.num_vecs = 10 # Generate random data and write to disk using handles self.vecs_array = ( parallel.call_and_bcast(np.random.random, (self.num_states, self.num_vecs)) + 1j * parallel.call_and_bcast(np.random.random, (self.num_states, self.num_vecs))) self.vec_handles = [ VecHandlePickle(self.vec_path % i) for i in range(self.num_vecs) ] for idx, hdl in enumerate(self.vec_handles): hdl.put(self.vecs_array[:, idx]) parallel.barrier()
def setUp(self): # Specify output locations if not os.access('.', os.W_OK): raise RuntimeError('Cannot write to current directory') self.test_dir = 'files_POD_DELETE_ME' if not os.path.isdir(self.test_dir): parallel.call_from_rank_zero(os.mkdir, self.test_dir) self.vec_path = join(self.test_dir, 'vec_%03d.pkl') self.mode_path = join(self.test_dir, 'mode_%03d.pkl') # Specify data dimensions self.num_states = 30 self.num_vecs = 10 # Generate random data and write to disk using handles self.vecs_array = ( parallel.call_and_bcast( np.random.random, (self.num_states, self.num_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (self.num_states, self.num_vecs))) self.vec_handles = [ VecHandlePickle(self.vec_path % i) for i in range(self.num_vecs)] for idx, hdl in enumerate(self.vec_handles): hdl.put(self.vecs_array[:, idx]) parallel.barrier()
def test_impulse(self): """Test impulse response of discrete system""" rtol = 1e-10 atol = 1e-12 for num_states in [1, 10]: for num_inputs in [1, 3]: for num_outputs in [1, 2, 3, 5]: # Generate state-space system arrays A, B, C = util.drss(num_states, num_inputs, num_outputs) # Check that can give time_steps as argument outputs = util.impulse(A, B, C) num_time_steps = len(outputs) outputs_true = np.zeros( (num_time_steps, num_outputs, num_inputs)) for ti in range(num_time_steps): outputs_true[ti] = C.dot( np.linalg.matrix_power(A, ti).dot(B)) np.testing.assert_allclose(outputs, outputs_true, rtol=rtol, atol=atol) # Check can give num_time_steps as an argument outputs = util.impulse(A, B, C, num_time_steps=num_time_steps) np.testing.assert_allclose(outputs, outputs_true, rtol=rtol, atol=atol)
def test_lin_combine(self): """ Test that linear combinations are correctly computed """ # Set test tolerances rtol = 1e-10 atol = 1e-12 # Generate data num_states = 100 num_vecs = 30 num_modes = 10 vecs_array = ( np.random.random((num_states, num_vecs)) + 1j * np.random.random((num_states, num_vecs))) coeffs_array = ( np.random.random((num_vecs, num_modes)) + 1j * np.random.random((num_vecs, num_modes))) modes_array_true = vecs_array.dot(coeffs_array) # Do computation using a vector space object. Check an explicit # mode_indices argument, as well as a None value. vec_space = vspc.VectorSpaceArrays() mode_indices_trunc = np.random.randint( 0, high=num_modes, size=num_modes // 2) for mode_idxs_arg, mode_idxs_vals in zip( [None, mode_indices_trunc], [range(num_modes), mode_indices_trunc]): modes_array = vec_space.lin_combine( vecs_array, coeffs_array, coeff_array_col_indices=mode_idxs_arg) np.testing.assert_allclose( modes_array, modes_array_true[:, mode_idxs_vals], rtol=rtol, atol=atol)
def test_compute_proj_coeffs(self): rtol = 1e-10 atol = 1e-12 # Compute POD using modred. (The properties defining a projection onto # POD modes require manipulations involving the correct decomposition # and modes, so we cannot isolate the mode computation from those # computations.) POD = pod.PODHandles(inner_product=np.vdot, verbosity=0) POD.compute_decomp(self.vec_handles) mode_idxs = range(POD.eigvals.size) mode_handles = [VecHandlePickle(self.mode_path % i) for i in mode_idxs] POD.compute_modes(mode_idxs, mode_handles, vec_handles=self.vec_handles) # Compute true projection coefficients by computing the inner products # between modes and snapshots. proj_coeffs_true = POD.vec_space.compute_inner_product_array( mode_handles, self.vec_handles) # Compute projection coefficients using POD object, which avoids # actually manipulating handles and computing their inner products, # instead using elements of the decomposition for a more efficient # computations. proj_coeffs = POD.compute_proj_coeffs() # Test values np.testing.assert_allclose(proj_coeffs, proj_coeffs_true, rtol=rtol, atol=atol)
def test_load_impulse_outputs(self): """ Test loading multiple signal files in [t sig1 sig2 ...] format. Creates signals, saves them, loads them, tests are equal to the originals. """ signal_path = join(self.test_dir, 'file%03d.txt') for num_paths in [1, 4]: for num_signals in [1, 2, 4, 5]: for num_time_steps in [1, 10, 100]: all_signals_true = np.random.random( (num_paths, num_time_steps, num_signals)) # Time steps need not be sequential time_values_true = np.random.random(num_time_steps) signal_paths = [] # Save signals to file for path_num in range(num_paths): signal_paths.append(signal_path % path_num) data_to_save = np.concatenate( \ (time_values_true.reshape(len(time_values_true), 1), all_signals_true[path_num]), axis=1) util.save_array_text(data_to_save, signal_path % path_num) time_values, all_signals = util.load_multiple_signals( signal_paths) np.testing.assert_allclose(all_signals, all_signals_true) np.testing.assert_allclose(time_values, time_values_true)
def test_compute_proj_coeffs(self): rtol = 1e-10 atol = 1e-12 # Compute POD using modred. (The properties defining a projection onto # POD modes require manipulations involving the correct decomposition # and modes, so we cannot isolate the mode computation from those # computations.) POD = pod.PODHandles(np.vdot, verbosity=0) POD.compute_decomp(self.vec_handles) mode_idxs = range(POD.eigvals.size) mode_handles = [VecHandlePickle(self.mode_path % i) for i in mode_idxs] POD.compute_modes(mode_idxs, mode_handles, vec_handles=self.vec_handles) # Compute true projection coefficients by computing the inner products # between modes and snapshots. proj_coeffs_true = POD.vec_space.compute_inner_product_array( mode_handles, self.vec_handles) # Compute projection coefficients using POD object, which avoids # actually manipulating handles and computing their inner products, # instead using elements of the decomposition for a more efficient # computations. proj_coeffs = POD.compute_proj_coeffs() # Test values np.testing.assert_allclose( proj_coeffs, proj_coeffs_true, rtol=rtol, atol=atol)
def test_derivs(self): """Test can take derivs""" dt = 0.1 true_derivs = [] num_vecs = len(self.basis_vec_handles) for i in range(num_vecs): true_derivs.append(( self.A_on_basis_vec_handles[i].get() - self.basis_vec_handles[i].get()).squeeze() / dt) deriv_handles = [ VecHandlePickle(join(self.test_dir, 'deriv_test%d' % i)) for i in range(num_vecs)] lgp.compute_derivs_handles( self.basis_vec_handles, self.A_on_basis_vec_handles, deriv_handles, dt) derivs_loaded = [v.get() for v in deriv_handles] derivs_loaded = list(map(np.squeeze, derivs_loaded)) list(map(np.testing.assert_allclose, derivs_loaded, true_derivs))
def get_direct_impulse_response_array(A, B, num_steps): num_states, num_inputs = B.shape direct_vecs = np.zeros((num_states, num_steps * num_inputs), dtype=A.dtype) A_powers = np.identity(num_states) for idx in range(num_steps): direct_vecs[:, idx * num_inputs:(idx + 1) * num_inputs] =\ A_powers.dot(B) A_powers = A_powers.dot(A) return direct_vecs
def get_direct_impulse_response_array(A, B, num_steps): num_states, num_inputs = B.shape direct_vecs = np.zeros((num_states, num_steps * num_inputs), dtype=A.dtype) A_powers = np.identity(num_states) for idx in range(num_steps): direct_vecs[:, idx * num_inputs:(idx + 1) * num_inputs] =\ A_powers.dot(B) A_powers = A_powers.dot(A) return direct_vecs
def test_Hankel_chunks(self): """Test forming Hankel array using chunks.""" chunk_num_rows = 2 chunk_num_cols = 2 chunk_shape = (chunk_num_rows, chunk_num_cols) for num_row_chunks in [1, 4, 6]: for num_col_chunks in [1, 3, 6]: # Generate simple values that make it easy to see the array # structure first_col_chunks = [ np.ones(chunk_shape) * (i + 1) for i in range(num_row_chunks) ] last_row_chunks = [ np.ones(chunk_shape) * (j + 1) * 10 for j in range(num_col_chunks) ] last_row_chunks[0] = first_col_chunks[-1] # Fill in Hankel array chunk by chunk Hankel_true = np.zeros((num_row_chunks * chunk_shape[0], num_col_chunks * chunk_shape[1])) for i in range(num_row_chunks): for j in range(num_col_chunks): # Upper left triangle of values if i + j < num_row_chunks: Hankel_true[ i * chunk_num_rows:(i + 1) * chunk_num_rows, j * chunk_num_cols:(j + 1) * chunk_num_cols] =\ first_col_chunks[i + j] # Lower right triangle of values else: Hankel_true[ i * chunk_num_rows:(i + 1) * chunk_num_rows, j * chunk_num_cols:(j + 1) * chunk_num_cols] =\ last_row_chunks[i + j - num_row_chunks + 1] # Compute Hankel array using util and test Hankel_test = util.Hankel_chunks( first_col_chunks, last_row_chunks=last_row_chunks) np.testing.assert_equal(Hankel_test, Hankel_true)
def test_derivs(self): """Test can take derivs""" dt = 0.1 true_derivs = [] num_vecs = len(self.basis_vec_handles) for i in range(num_vecs): true_derivs.append( (self.A_on_basis_vec_handles[i].get() - self.basis_vec_handles[i].get()).squeeze() / dt) deriv_handles = [ VecHandlePickle(join(self.test_dir, 'deriv_test%d' % i)) for i in range(num_vecs) ] lgp.compute_derivs_handles(self.basis_vec_handles, self.A_on_basis_vec_handles, deriv_handles, dt) derivs_loaded = [v.get() for v in deriv_handles] derivs_loaded = list(map(np.squeeze, derivs_loaded)) list(map(np.testing.assert_allclose, derivs_loaded, true_derivs))
def test_tutorial_examples(self): """Runs all tutorial examples. If run without errors, passes test""" example_script = 'tutorial_ex%d.py' for example_num in range(1, 7): # Example 3 isn't meant to work in parallel if not (parallel.is_distributed() and example_num != 3): #printing(False) parallel.barrier() mr.run_script(join(examples_dir, example_script % example_num)) parallel.barrier()
def test_Hankel_chunks(self): """Test forming Hankel array using chunks.""" chunk_num_rows = 2 chunk_num_cols = 2 chunk_shape = (chunk_num_rows, chunk_num_cols) for num_row_chunks in [1, 4, 6]: for num_col_chunks in [1, 3, 6]: # Generate simple values that make it easy to see the array # structure first_col_chunks = [ np.ones(chunk_shape) * (i + 1) for i in range(num_row_chunks)] last_row_chunks = [ np.ones(chunk_shape) * (j + 1) * 10 for j in range(num_col_chunks)] last_row_chunks[0] = first_col_chunks[-1] # Fill in Hankel array chunk by chunk Hankel_true = np.zeros(( num_row_chunks * chunk_shape[0], num_col_chunks * chunk_shape[1])) for i in range(num_row_chunks): for j in range(num_col_chunks): # Upper left triangle of values if i + j < num_row_chunks: Hankel_true[ i * chunk_num_rows:(i + 1) * chunk_num_rows, j * chunk_num_cols:(j + 1) * chunk_num_cols] =\ first_col_chunks[i + j] # Lower right triangle of values else: Hankel_true[ i * chunk_num_rows:(i + 1) * chunk_num_rows, j * chunk_num_cols:(j + 1) * chunk_num_cols] =\ last_row_chunks[i + j - num_row_chunks + 1] # Compute Hankel array using util and test Hankel_test = util.Hankel_chunks( first_col_chunks, last_row_chunks=last_row_chunks) np.testing.assert_equal(Hankel_test, Hankel_true)
def get_adjoint_impulse_response_array(A, C, num_steps, weights_array): num_outputs, num_states = C.shape A_adjoint = np.linalg.inv(weights_array).dot(A.conj().T.dot(weights_array)) C_adjoint = np.linalg.inv(weights_array).dot(C.conj().T) adjoint_vecs = np.zeros((num_states, num_steps * num_outputs), dtype=A.dtype) A_adjoint_powers = np.identity(num_states) for idx in range(num_steps): adjoint_vecs[:, (idx * num_outputs):(idx + 1) * num_outputs] =\ A_adjoint_powers.dot(C_adjoint) A_adjoint_powers = A_adjoint_powers.dot(A_adjoint) return adjoint_vecs
def test_compute_inner_product_array_types(self): num_row_vecs = 4 num_col_vecs = 6 num_states = 7 row_vec_path = join(self.test_dir, 'row_vec_%03d.pkl') col_vec_path = join(self.test_dir, 'col_vec_%03d.pkl') # Check complex and real data for is_complex in [True, False]: # Generate data row_vec_array = np.random.random((num_states, num_row_vecs)) col_vec_array = np.random.random((num_states, num_col_vecs)) if is_complex: row_vec_array = row_vec_array * ( 1j * np.random.random((num_states, num_row_vecs))) col_vec_array = col_vec_array * ( 1j * np.random.random((num_states, num_col_vecs))) # Generate handles and save to file row_vec_paths = [row_vec_path % i for i in range(num_row_vecs)] col_vec_paths = [col_vec_path % i for i in range(num_col_vecs)] row_vec_handles = [ VecHandlePickle(path) for path in row_vec_paths] col_vec_handles = [ VecHandlePickle(path) for path in col_vec_paths] for idx, handle in enumerate(row_vec_handles): handle.put(row_vec_array[:, idx]) for idx, handle in enumerate(col_vec_handles): handle.put(col_vec_array[:, idx]) # Compute inner product array and check type inner_product_array = self.vec_space.compute_inner_product_array( row_vec_handles, col_vec_handles) symm_inner_product_array =\ self.vec_space.compute_symm_inner_product_array( row_vec_handles) self.assertEqual(inner_product_array.dtype, row_vec_array.dtype) self.assertEqual( symm_inner_product_array.dtype, row_vec_array.dtype)
def test_compute_inner_product_array_types(self): num_row_vecs = 4 num_col_vecs = 6 num_states = 7 row_vec_path = join(self.test_dir, 'row_vec_%03d.pkl') col_vec_path = join(self.test_dir, 'col_vec_%03d.pkl') # Check complex and real data for is_complex in [True, False]: # Generate data row_vec_array = np.random.random((num_states, num_row_vecs)) col_vec_array = np.random.random((num_states, num_col_vecs)) if is_complex: row_vec_array = row_vec_array * ( 1j * np.random.random((num_states, num_row_vecs))) col_vec_array = col_vec_array * ( 1j * np.random.random((num_states, num_col_vecs))) # Generate handles and save to file row_vec_paths = [row_vec_path % i for i in range(num_row_vecs)] col_vec_paths = [col_vec_path % i for i in range(num_col_vecs)] row_vec_handles = [ VecHandlePickle(path) for path in row_vec_paths] col_vec_handles = [ VecHandlePickle(path) for path in col_vec_paths] for idx, handle in enumerate(row_vec_handles): handle.put(row_vec_array[:, idx]) for idx, handle in enumerate(col_vec_handles): handle.put(col_vec_array[:, idx]) # Compute inner product array and check type inner_product_array = self.vec_space.compute_inner_product_array( row_vec_handles, col_vec_handles) symm_inner_product_array =\ self.vec_space.compute_symm_inner_product_array( row_vec_handles) self.assertEqual(inner_product_array.dtype, row_vec_array.dtype) self.assertEqual( symm_inner_product_array.dtype, row_vec_array.dtype)
def get_adjoint_impulse_response_array(A, C, num_steps, weights_array): num_outputs, num_states = C.shape A_adjoint = np.linalg.inv(weights_array).dot(A.conj().T.dot(weights_array)) C_adjoint = np.linalg.inv(weights_array).dot(C.conj().T) adjoint_vecs = np.zeros((num_states, num_steps * num_outputs), dtype=A.dtype) A_adjoint_powers = np.identity(num_states) for idx in range(num_steps): adjoint_vecs[:, (idx * num_outputs):(idx + 1) * num_outputs] =\ A_adjoint_powers.dot(C_adjoint) A_adjoint_powers = A_adjoint_powers.dot(A_adjoint) return adjoint_vecs
def test_all(self): # Set test tolerances. Separate, 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 = 1e-8 atol = 1e-10 rtol_sqr = 1e-8 atol_sqr = 1e-8 # Set tolerances for SVD step of BPOD. This is necessary to avoid # dealing with very uncontrollable/unobservable states, which can cause # the tests to fail. rtol_svd = 1e-6 atol_svd = 1e-12 # Generate weights to test different inner products. Keep most of the # weights close to one, to avoid overly weighting certain states over # others. This can dramatically affect the rate at which the tests # pass. weights_1D = np.random.random(self.num_states) weights_2D = np.identity(self.num_states, dtype=np.complex) weights_2D[0, 0] = 2. weights_2D[2, 1] = 0.3j weights_2D[1, 2] = weights_2D[2, 1].conj() weights_list = [None, weights_1D, weights_2D] weights_array_list = [ np.identity(self.num_states), np.diag(weights_1D), weights_2D] # Check different system sizes. Make sure to test a single input/output # in addition to multiple inputs/outputs. Also allow for the number of # inputs/outputs to exceed the number of states. for num_inputs in [1, np.random.randint(2, high=self.num_states + 2)]: for num_outputs in [ 1, np.random.randint(2, high=self.num_states + 2)]: # Get state space system A, B, C = get_system_arrays( self.num_states, num_inputs, num_outputs) # Compute direct impulse response direct_vecs_array = get_direct_impulse_response_array( A, B, self.num_steps) # Loop through different inner product weights for weights, weights_array in zip( weights_list, weights_array_list): # Define inner product based on weights IP = VectorSpaceArrays( weights=weights).compute_inner_product_array # Compute adjoint impulse response adjoint_vecs_array = get_adjoint_impulse_response_array( A, C, self.num_steps, weights_array) # Compute BPOD using modred. Use absolute tolerance to # avoid Hankel singular values that approach numerical # precision. 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_res = bpod.compute_BPOD_arrays( direct_vecs_array, adjoint_vecs_array, num_inputs=num_inputs, num_outputs=num_outputs, inner_product_weights=weights, rtol=rtol_svd, atol=atol_svd) # 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 = IP( adjoint_vecs_array, direct_vecs_array) np.testing.assert_allclose( BPOD_res.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 requires more # relaxed test tolerances. np.testing.assert_allclose( BPOD_res.Hankel_array.dot( BPOD_res.Hankel_array.conj().T.dot( BPOD_res.L_sing_vecs)), BPOD_res.L_sing_vecs.dot( np.diag(BPOD_res.sing_vals ** 2.)), rtol=rtol_sqr, atol=atol_sqr) np.testing.assert_allclose( BPOD_res.Hankel_array.conj().T.dot( BPOD_res.Hankel_array.dot( BPOD_res.R_sing_vecs)), BPOD_res.R_sing_vecs.dot( np.diag(BPOD_res.sing_vals ** 2.)), rtol=rtol_sqr, atol=atol_sqr) # Check that the modes diagonalize the gramians. This test # requires looser tolerances than the other tests, likely # due to the "squaring" of the arrays in computing the # gramians. np.testing.assert_allclose( IP(BPOD_res.adjoint_modes, direct_vecs_array).dot( IP(direct_vecs_array, BPOD_res.adjoint_modes)), np.diag(BPOD_res.sing_vals), rtol=rtol_sqr, atol=atol_sqr) np.testing.assert_allclose( IP(BPOD_res.direct_modes, adjoint_vecs_array).dot( IP(adjoint_vecs_array, BPOD_res.direct_modes)), np.diag(BPOD_res.sing_vals), rtol=rtol_sqr, atol=atol_sqr) # Check the value of the projection coefficients against a # projection onto the adjoint and direct modes, # respectively. np.testing.assert_allclose( BPOD_res.direct_proj_coeffs, IP(BPOD_res.adjoint_modes, direct_vecs_array), rtol=rtol, atol=atol) np.testing.assert_allclose( BPOD_res.adjoint_proj_coeffs, IP(BPOD_res.direct_modes, adjoint_vecs_array), rtol=rtol, atol=atol) # Check that if mode indices are passed in, the correct # modes are returned. Test both an explicit selection of # mode indices and a None argument. mode_indices_trunc = np.unique(np.random.randint( 0, high=BPOD_res.sing_vals.size, size=(BPOD_res.sing_vals.size // 2))) for mode_indices_arg, mode_indices_vals in zip( [None, mode_indices_trunc], [range(BPOD_res.sing_vals.size), mode_indices_trunc]): BPOD_res_sliced = bpod.compute_BPOD_arrays( direct_vecs_array, adjoint_vecs_array, direct_mode_indices=mode_indices_arg, adjoint_mode_indices=mode_indices_arg, num_inputs=num_inputs, num_outputs=num_outputs, inner_product_weights=weights, rtol=rtol_svd, atol=atol_svd) np.testing.assert_allclose( BPOD_res_sliced.direct_modes, BPOD_res.direct_modes[:, mode_indices_vals], rtol=rtol, atol=atol) np.testing.assert_allclose( BPOD_res_sliced.adjoint_modes, BPOD_res.adjoint_modes[:, mode_indices_vals], rtol=rtol, atol=atol)
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(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)
def test_lin_combine(self): # Set test tolerances rtol = 1e-10 atol = 1e-12 # Setup mode_path = join(self.test_dir, 'mode_%03d.pkl') vec_path = join(self.test_dir, 'vec_%03d.pkl') # Test cases where number of modes: # less, equal, more than num_states # less, equal, more than num_vecs # less, equal, more than total_num_vecs_in_mem # Also check the case of passing a None value to the mode_indices # argument. num_states = 20 num_vecs_list = [1, 15, 40] num_modes_list = [ None, 1, 8, 10, 20, 25, 45, int(np.ceil(self.total_num_vecs_in_mem / 2.)), self.total_num_vecs_in_mem, self.total_num_vecs_in_mem * 2] # Check for correct computations for num_vecs in num_vecs_list: for num_modes in num_modes_list: for squeeze in [True, False]: # Generate data and then broadcast to all procs vec_handles = [ VecHandlePickle(vec_path % i) for i in range(num_vecs)] vec_array, coeff_array, true_modes =\ parallel.call_and_bcast( self.generate_vecs_modes, num_states, num_vecs, num_modes=num_modes, squeeze=squeeze) if parallel.is_rank_zero(): for vec_index, vec_handle in enumerate(vec_handles): vec_handle.put(vec_array[:, vec_index]) parallel.barrier() # Choose which modes to compute if num_modes is None: mode_idxs_arg = None mode_idxs_vals = range(true_modes.shape[1]) elif num_modes == 1: mode_idxs_arg = 0 mode_idxs_vals = [0] else: mode_idxs_arg = np.unique( parallel.call_and_bcast( np.random.randint, 0, high=num_modes, size=num_modes // 2)) mode_idxs_vals = mode_idxs_arg mode_handles = [ VecHandlePickle(mode_path % mode_num) for mode_num in mode_idxs_vals] # Saves modes to files self.vec_space.lin_combine( mode_handles, vec_handles, coeff_array, coeff_array_col_indices=mode_idxs_arg) # Test modes one by one for mode_idx in mode_idxs_vals: computed_mode = VecHandlePickle( mode_path % mode_idx).get() np.testing.assert_allclose( computed_mode, true_modes[:, mode_idx], rtol=rtol, atol=atol) parallel.barrier() parallel.barrier() parallel.barrier() # Test that errors are caught for mismatched dimensions mode_handles = [ VecHandlePickle(mode_path % i) for i in range(10)] vec_handles = [ VecHandlePickle(vec_path % i) for i in range(15)] coeffs_array_too_short = np.zeros( (len(vec_handles) - 1, len(mode_handles))) coeffs_array_too_fat = np.zeros( (len(vec_handles), len(mode_handles) + 1)) index_list_too_long = range(len(mode_handles) + 1) self.assertRaises( ValueError, self.vec_space.lin_combine, mode_handles, vec_handles, coeffs_array_too_short) self.assertRaises( ValueError, self.vec_space.lin_combine, mode_handles, vec_handles, coeffs_array_too_fat)
def test_compute_inner_product_arrays(self): """Test computation of array of inner products.""" rtol = 1e-10 atol = 1e-12 num_row_vecs_list = [ 1, int(round(self.total_num_vecs_in_mem / 2.)), self.total_num_vecs_in_mem, self.total_num_vecs_in_mem * 2, parallel.get_num_procs() + 1] num_col_vecs_list = num_row_vecs_list num_states = 6 row_vec_path = join(self.test_dir, 'row_vec_%03d.pkl') col_vec_path = join(self.test_dir, 'col_vec_%03d.pkl') for num_row_vecs in num_row_vecs_list: for num_col_vecs in num_col_vecs_list: # Generate vecs parallel.barrier() row_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_row_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_row_vecs))) col_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_col_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_col_vecs))) row_vec_handles = [ VecHandlePickle(row_vec_path % i) for i in range(num_row_vecs)] col_vec_handles = [ VecHandlePickle(col_vec_path % i) for i in range(num_col_vecs)] # Save vecs if parallel.is_rank_zero(): for i, h in enumerate(row_vec_handles): h.put(row_vec_array[:, i]) for i, h in enumerate(col_vec_handles): h.put(col_vec_array[:, i]) parallel.barrier() # If number of rows/cols is 1, check case of passing a handle if len(row_vec_handles) == 1: row_vec_handles = row_vec_handles[0] if len(col_vec_handles) == 1: col_vec_handles = col_vec_handles[0] # Test ip computation. product_true = np.dot(row_vec_array.conj().T, col_vec_array) product_computed = self.vec_space.compute_inner_product_array( row_vec_handles, col_vec_handles) np.testing.assert_allclose( product_computed, product_true, rtol=rtol, atol=atol) # Test symm ip computation product_true = np.dot(row_vec_array.conj().T, row_vec_array) product_computed =\ self.vec_space.compute_symm_inner_product_array( row_vec_handles) np.testing.assert_allclose( product_computed, product_true, rtol=rtol, atol=atol)
def test_OKID(self): rtol = 1e-8 atol = 1e-10 for case in ['SISO', 'SIMO', 'MISO', 'MIMO']: inputs = util.load_array_text( join(join(self.test_dir, case), 'inputs.txt')) outputs = util.load_array_text( join(join(self.test_dir, case), 'outputs.txt')) (num_inputs, nt) = inputs.shape (num_outputs, nt2) = outputs.shape assert(nt2 == nt) Markovs_true = np.zeros((nt, num_outputs, num_inputs)) tmp = util.load_array_text( join(join(self.test_dir, case), 'Markovs_Matlab_output1.txt')) tmp = tmp.reshape((num_inputs, -1)) num_Markovs_OKID = tmp.shape[1] Markovs_Matlab = np.zeros( (num_Markovs_OKID, num_outputs, num_inputs)) for i_out in range(num_outputs): data = util.load_array_text( join(join( self.test_dir, case), 'Markovs_Matlab_output%d.txt' % (i_out + 1))) if num_inputs > 1: data = np.swapaxes(data, 0, 1) Markovs_Matlab[:, i_out, :] = data data = util.load_array_text(join( join(self.test_dir, case), 'Markovs_true_output%d.txt' % (i_out + 1))) if num_inputs > 1: data = np.swapaxes(data, 0, 1) Markovs_true[:,i_out,:] = data Markovs_python = OKID(inputs, outputs, num_Markovs_OKID) if plot: plt.figure(figsize=(14,10)) for output_num in range(num_outputs): for input_num in range(num_inputs): plt.subplot(num_outputs, num_inputs, output_num*(num_inputs) + input_num + 1) plt.hold(True) plt.plot(Markovs_true[:,output_num,input_num],'k*-') plt.plot(Markovs_Matlab[:,output_num,input_num],'b--') plt.plot(Markovs_python[:,output_num,input_num],'r.') plt.legend(['True', 'Matlab OKID', 'Python OKID']) plt.title('Input %d to output %d'%(input_num+1, output_num+1)) plt.show() np.testing.assert_allclose( Markovs_python.squeeze(), Markovs_Matlab.squeeze(), rtol=rtol, atol=atol) np.testing.assert_allclose( Markovs_python.squeeze(), Markovs_true[:num_Markovs_OKID].squeeze(), rtol=rtol, atol=atol)
def test_all(self): # Set test tolerances. Separate, 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 = 1e-8 atol = 1e-10 rtol_sqr = 1e-8 atol_sqr = 1e-8 # Set tolerances for SVD step of BPOD. This is necessary to avoid # dealing with very uncontrollable/unobservable states, which can cause # the tests to fail. rtol_svd = 1e-6 atol_svd = 1e-12 # Generate weights to test different inner products. Keep most of the # weights close to one, to avoid overly weighting certain states over # others. This can dramatically affect the rate at which the tests # pass. weights_1D = np.random.random(self.num_states) weights_2D = np.identity(self.num_states, dtype=np.complex) weights_2D[0, 0] = 2. weights_2D[2, 1] = 0.3j weights_2D[1, 2] = weights_2D[2, 1].conj() weights_list = [None, weights_1D, weights_2D] weights_array_list = [ np.identity(self.num_states), np.diag(weights_1D), weights_2D ] # Check different system sizes. Make sure to test a single input/output # in addition to multiple inputs/outputs. Also allow for the number of # inputs/outputs to exceed the number of states. for num_inputs in [1, np.random.randint(2, high=self.num_states + 2)]: for num_outputs in [ 1, np.random.randint(2, high=self.num_states + 2) ]: # Get state space system A, B, C = get_system_arrays(self.num_states, num_inputs, num_outputs) # Compute direct impulse response direct_vecs_array = get_direct_impulse_response_array( A, B, self.num_steps) # Loop through different inner product weights for weights, weights_array in zip(weights_list, weights_array_list): # Define inner product based on weights IP = VectorSpaceArrays( weights=weights).compute_inner_product_array # Compute adjoint impulse response adjoint_vecs_array = get_adjoint_impulse_response_array( A, C, self.num_steps, weights_array) # Compute BPOD using modred. Use absolute tolerance to # avoid Hankel singular values that approach numerical # precision. 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_res = bpod.compute_BPOD_arrays( direct_vecs_array, adjoint_vecs_array, num_inputs=num_inputs, num_outputs=num_outputs, inner_product_weights=weights, rtol=rtol_svd, atol=atol_svd) # 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 = IP(adjoint_vecs_array, direct_vecs_array) np.testing.assert_allclose(BPOD_res.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 requires more # relaxed test tolerances. np.testing.assert_allclose( BPOD_res.Hankel_array.dot( BPOD_res.Hankel_array.conj().T.dot( BPOD_res.L_sing_vecs)), BPOD_res.L_sing_vecs.dot( np.diag(BPOD_res.sing_vals**2.)), rtol=rtol_sqr, atol=atol_sqr) np.testing.assert_allclose( BPOD_res.Hankel_array.conj().T.dot( BPOD_res.Hankel_array.dot(BPOD_res.R_sing_vecs)), BPOD_res.R_sing_vecs.dot( np.diag(BPOD_res.sing_vals**2.)), rtol=rtol_sqr, atol=atol_sqr) # Check that the modes diagonalize the gramians. This test # requires looser tolerances than the other tests, likely # due to the "squaring" of the arrays in computing the # gramians. np.testing.assert_allclose(IP( BPOD_res.adjoint_modes, direct_vecs_array).dot( IP(direct_vecs_array, BPOD_res.adjoint_modes)), np.diag(BPOD_res.sing_vals), rtol=rtol_sqr, atol=atol_sqr) np.testing.assert_allclose(IP( BPOD_res.direct_modes, adjoint_vecs_array).dot( IP(adjoint_vecs_array, BPOD_res.direct_modes)), np.diag(BPOD_res.sing_vals), rtol=rtol_sqr, atol=atol_sqr) # Check the value of the projection coefficients against a # projection onto the adjoint and direct modes, # respectively. np.testing.assert_allclose(BPOD_res.direct_proj_coeffs, IP(BPOD_res.adjoint_modes, direct_vecs_array), rtol=rtol, atol=atol) np.testing.assert_allclose(BPOD_res.adjoint_proj_coeffs, IP(BPOD_res.direct_modes, adjoint_vecs_array), rtol=rtol, atol=atol) # Check that if mode indices are passed in, the correct # modes are returned. Test both an explicit selection of # mode indices and a None argument. mode_indices_trunc = np.unique( np.random.randint(0, high=BPOD_res.sing_vals.size, size=(BPOD_res.sing_vals.size // 2))) for mode_indices_arg, mode_indices_vals in zip( [None, mode_indices_trunc], [range(BPOD_res.sing_vals.size), mode_indices_trunc]): BPOD_res_sliced = bpod.compute_BPOD_arrays( direct_vecs_array, adjoint_vecs_array, direct_mode_indices=mode_indices_arg, adjoint_mode_indices=mode_indices_arg, num_inputs=num_inputs, num_outputs=num_outputs, inner_product_weights=weights, rtol=rtol_svd, atol=atol_svd) np.testing.assert_allclose( BPOD_res_sliced.direct_modes, BPOD_res.direct_modes[:, mode_indices_vals], rtol=rtol, atol=atol) np.testing.assert_allclose( BPOD_res_sliced.adjoint_modes, BPOD_res.adjoint_modes[:, mode_indices_vals], rtol=rtol, atol=atol)
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)
def test_assemble_Hankel(self): """ Tests Hankel arrays are symmetric and accurate given Markov params ``[CB CAB CA**P CA**(P+1)B ...]``.""" rtol = 1e-10 atol = 1e-12 for num_inputs in [1, 3]: for num_outputs in [1, 2, 4]: for sample_interval in [1]: num_time_steps = 50 num_states = 8 # A, B, C = util.drss(num_states, num_inputs, num_outputs) time_steps = make_time_steps( num_time_steps, sample_interval) A = util.load_array_text( join(self.data_dir, 'A_in%d_out%d.txt') % ( num_inputs, num_outputs)) B = util.load_array_text( join(self.data_dir, 'B_in%d_out%d.txt') % ( num_inputs, num_outputs)) C = util.load_array_text( join(self.data_dir, 'C_in%d_out%d.txt') % ( num_inputs, num_outputs)) impulse_response = util.impulse(A, B, C, time_steps[-1] + 1) Markovs = impulse_response[time_steps] if sample_interval == 2: time_steps, Markovs = era.make_sampled_format( time_steps, Markovs) my_ERA = era.ERA(verbosity=0) my_ERA._set_Markovs(Markovs) my_ERA._assemble_Hankel() H = my_ERA.Hankel_array Hp = my_ERA.Hankel_array2 for row in range(my_ERA.mc): for col in range(my_ERA.mo): # Test values in H are accurate using that, roughly, # H[r,c] = C * A^(r+c) * B. np.testing.assert_allclose( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2]).dot( B)), rtol=rtol, atol=atol) # Test values in H are accurate using that, roughly, # Hp[r,c] = C * A^(r+c+1) * B. np.testing.assert_allclose( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], C.dot( np.linalg.matrix_power( A, time_steps[(row + col) * 2 + 1]).dot( B)), rtol=rtol, atol=atol) # Test H is block symmetric np.testing.assert_equal( H[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], H[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs]) # Test Hp is block symmetric np.testing.assert_equal( Hp[row * num_outputs:(row + 1) * num_outputs, col * num_inputs:(col + 1) * num_inputs], Hp[col * num_outputs:(col + 1) * num_outputs, row * num_inputs:(row + 1) * num_inputs])
def generate_data_set(self, num_basis_vecs, num_adjoint_basis_vecs, num_states, num_inputs, num_outputs): """Generates random data, saves, and computes true reduced A,B,C.""" self.basis_vec_handles = [ VecHandlePickle(self.basis_vec_path % i) for i in range(self.num_basis_vecs)] self.adjoint_basis_vec_handles = [ VecHandlePickle(self.adjoint_basis_vec_path % i) for i in range(self.num_adjoint_basis_vecs)] self.A_on_basis_vec_handles = [ VecHandlePickle(self.A_on_basis_vec_path % i) for i in range(self.num_basis_vecs)] self.B_on_standard_basis_handles = [ VecHandlePickle(self.B_on_basis_path % i) for i in range(self.num_inputs)] self.C_on_basis_vec_handles = [ VecHandlePickle(self.C_on_basis_vec_path % i) for i in range(self.num_basis_vecs)] self.basis_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_basis_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_basis_vecs))) self.adjoint_basis_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_adjoint_basis_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_adjoint_basis_vecs))) self.A_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_states)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_states))) self.B_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_inputs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_inputs))) self.C_array = ( parallel.call_and_bcast( np.random.random, (num_outputs, num_states)) + 1j * parallel.call_and_bcast( np.random.random, (num_outputs, num_states))) self.basis_vecs = [ self.basis_vec_array[:, i].squeeze() for i in range(num_basis_vecs)] self.adjoint_basis_vecs = [ self.adjoint_basis_vec_array[:, i].squeeze() for i in range(num_adjoint_basis_vecs)] self.A_on_basis_vecs = [ self.A_array.dot(basis_vec).squeeze() for basis_vec in self.basis_vecs] self.B_on_basis = [ self.B_array[:, i].squeeze() for i in range(self.num_inputs)] self.C_on_basis_vecs = [ np.array(self.C_array.dot(basis_vec).squeeze(), ndmin=1) for basis_vec in self.basis_vecs] if parallel.is_rank_zero(): for handle,vec in zip(self.basis_vec_handles, self.basis_vecs): handle.put(vec) for handle,vec in zip( self.adjoint_basis_vec_handles, self.adjoint_basis_vecs): handle.put(vec) for handle,vec in zip( self.A_on_basis_vec_handles, self.A_on_basis_vecs): handle.put(vec) for handle,vec in zip( self.B_on_standard_basis_handles, self.B_on_basis): handle.put(vec) for handle,vec in zip( self.C_on_basis_vec_handles, self.C_on_basis_vecs): handle.put(vec) parallel.barrier() self.A_true = self.adjoint_basis_vec_array.conj().T.dot( self.A_array.dot(self.basis_vec_array)) self.B_true = self.adjoint_basis_vec_array.conj().T.dot(self.B_array) self.C_true = self.C_array.dot(self.basis_vec_array) self.proj_array = np.linalg.inv( self.adjoint_basis_vec_array.conj().T.dot(self.basis_vec_array)) self.A_true_non_orth = self.proj_array.dot(self.A_true) self.B_true_non_orth = self.proj_array.dot(self.B_true)
def test_lin_combine(self): # Set test tolerances rtol = 1e-10 atol = 1e-12 # Setup mode_path = join(self.test_dir, 'mode_%03d.pkl') vec_path = join(self.test_dir, 'vec_%03d.pkl') # Test cases where number of modes: # less, equal, more than num_states # less, equal, more than num_vecs # less, equal, more than total_num_vecs_in_mem # Also check the case of passing a None value to the mode_indices # argument. num_states = 20 num_vecs_list = [1, 15, 40] num_modes_list = [ None, 1, 8, 10, 20, 25, 45, int(np.ceil(self.total_num_vecs_in_mem / 2.)), self.total_num_vecs_in_mem, self.total_num_vecs_in_mem * 2] # Check for correct computations for num_vecs in num_vecs_list: for num_modes in num_modes_list: for squeeze in [True, False]: # Generate data and then broadcast to all procs vec_handles = [ VecHandlePickle(vec_path % i) for i in range(num_vecs)] vec_array, coeff_array, true_modes =\ parallel.call_and_bcast( self.generate_vecs_modes, num_states, num_vecs, num_modes=num_modes, squeeze=squeeze) if parallel.is_rank_zero(): for vec_index, vec_handle in enumerate(vec_handles): vec_handle.put(vec_array[:, vec_index]) parallel.barrier() # Choose which modes to compute if num_modes is None: mode_idxs_arg = None mode_idxs_vals = range(true_modes.shape[1]) elif num_modes == 1: mode_idxs_arg = 0 mode_idxs_vals = [0] else: mode_idxs_arg = np.unique( parallel.call_and_bcast( np.random.randint, 0, high=num_modes, size=num_modes // 2)) mode_idxs_vals = mode_idxs_arg mode_handles = [ VecHandlePickle(mode_path % mode_num) for mode_num in mode_idxs_vals] # Saves modes to files self.vec_space.lin_combine( mode_handles, vec_handles, coeff_array, coeff_array_col_indices=mode_idxs_arg) # Test modes one by one for mode_idx in mode_idxs_vals: computed_mode = VecHandlePickle( mode_path % mode_idx).get() np.testing.assert_allclose( computed_mode, true_modes[:, mode_idx], rtol=rtol, atol=atol) parallel.barrier() parallel.barrier() parallel.barrier() # Test that errors are caught for mismatched dimensions mode_handles = [ VecHandlePickle(mode_path % i) for i in range(10)] vec_handles = [ VecHandlePickle(vec_path % i) for i in range(15)] coeffs_array_too_short = np.zeros( (len(vec_handles) - 1, len(mode_handles))) coeffs_array_too_fat = np.zeros( (len(vec_handles), len(mode_handles) + 1)) index_list_too_long = range(len(mode_handles) + 1) self.assertRaises( ValueError, self.vec_space.lin_combine, mode_handles, vec_handles, coeffs_array_too_short) self.assertRaises( ValueError, self.vec_space.lin_combine, mode_handles, vec_handles, coeffs_array_too_fat)
def generate_data_set(self, num_basis_vecs, num_adjoint_basis_vecs, num_states, num_inputs, num_outputs): """Generates random data, saves, and computes true reduced A,B,C.""" self.basis_vec_handles = [ VecHandlePickle(self.basis_vec_path % i) for i in range(self.num_basis_vecs) ] self.adjoint_basis_vec_handles = [ VecHandlePickle(self.adjoint_basis_vec_path % i) for i in range(self.num_adjoint_basis_vecs) ] self.A_on_basis_vec_handles = [ VecHandlePickle(self.A_on_basis_vec_path % i) for i in range(self.num_basis_vecs) ] self.B_on_standard_basis_handles = [ VecHandlePickle(self.B_on_basis_path % i) for i in range(self.num_inputs) ] self.C_on_basis_vec_handles = [ VecHandlePickle(self.C_on_basis_vec_path % i) for i in range(self.num_basis_vecs) ] self.basis_vec_array = ( parallel.call_and_bcast(np.random.random, (num_states, num_basis_vecs)) + 1j * parallel.call_and_bcast(np.random.random, (num_states, num_basis_vecs))) self.adjoint_basis_vec_array = ( parallel.call_and_bcast(np.random.random, (num_states, num_adjoint_basis_vecs)) + 1j * parallel.call_and_bcast(np.random.random, (num_states, num_adjoint_basis_vecs))) self.A_array = (parallel.call_and_bcast(np.random.random, (num_states, num_states)) + 1j * parallel.call_and_bcast(np.random.random, (num_states, num_states))) self.B_array = (parallel.call_and_bcast(np.random.random, (num_states, num_inputs)) + 1j * parallel.call_and_bcast(np.random.random, (num_states, num_inputs))) self.C_array = ( parallel.call_and_bcast(np.random.random, (num_outputs, num_states)) + 1j * parallel.call_and_bcast(np.random.random, (num_outputs, num_states))) self.basis_vecs = [ self.basis_vec_array[:, i].squeeze() for i in range(num_basis_vecs) ] self.adjoint_basis_vecs = [ self.adjoint_basis_vec_array[:, i].squeeze() for i in range(num_adjoint_basis_vecs) ] self.A_on_basis_vecs = [ self.A_array.dot(basis_vec).squeeze() for basis_vec in self.basis_vecs ] self.B_on_basis = [ self.B_array[:, i].squeeze() for i in range(self.num_inputs) ] self.C_on_basis_vecs = [ np.array(self.C_array.dot(basis_vec).squeeze(), ndmin=1) for basis_vec in self.basis_vecs ] if parallel.is_rank_zero(): for handle, vec in zip(self.basis_vec_handles, self.basis_vecs): handle.put(vec) for handle, vec in zip(self.adjoint_basis_vec_handles, self.adjoint_basis_vecs): handle.put(vec) for handle, vec in zip(self.A_on_basis_vec_handles, self.A_on_basis_vecs): handle.put(vec) for handle, vec in zip(self.B_on_standard_basis_handles, self.B_on_basis): handle.put(vec) for handle, vec in zip(self.C_on_basis_vec_handles, self.C_on_basis_vecs): handle.put(vec) parallel.barrier() self.A_true = self.adjoint_basis_vec_array.conj().T.dot( self.A_array.dot(self.basis_vec_array)) self.B_true = self.adjoint_basis_vec_array.conj().T.dot(self.B_array) self.C_true = self.C_array.dot(self.basis_vec_array) self.proj_array = np.linalg.inv( self.adjoint_basis_vec_array.conj().T.dot(self.basis_vec_array)) self.A_true_non_orth = self.proj_array.dot(self.A_true) self.B_true_non_orth = self.proj_array.dot(self.B_true)
def test_compute_inner_product_arrays(self): """Test computation of array of inner products.""" rtol = 1e-10 atol = 1e-12 num_row_vecs_list = [ 1, int(round(self.total_num_vecs_in_mem / 2.)), self.total_num_vecs_in_mem, self.total_num_vecs_in_mem * 2, parallel.get_num_procs() + 1] num_col_vecs_list = num_row_vecs_list num_states = 6 row_vec_path = join(self.test_dir, 'row_vec_%03d.pkl') col_vec_path = join(self.test_dir, 'col_vec_%03d.pkl') for num_row_vecs in num_row_vecs_list: for num_col_vecs in num_col_vecs_list: # Generate vecs parallel.barrier() row_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_row_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_row_vecs))) col_vec_array = ( parallel.call_and_bcast( np.random.random, (num_states, num_col_vecs)) + 1j * parallel.call_and_bcast( np.random.random, (num_states, num_col_vecs))) row_vec_handles = [ VecHandlePickle(row_vec_path % i) for i in range(num_row_vecs)] col_vec_handles = [ VecHandlePickle(col_vec_path % i) for i in range(num_col_vecs)] # Save vecs if parallel.is_rank_zero(): for i, h in enumerate(row_vec_handles): h.put(row_vec_array[:, i]) for i, h in enumerate(col_vec_handles): h.put(col_vec_array[:, i]) parallel.barrier() # If number of rows/cols is 1, check case of passing a handle if len(row_vec_handles) == 1: row_vec_handles = row_vec_handles[0] if len(col_vec_handles) == 1: col_vec_handles = col_vec_handles[0] # Test ip computation. product_true = np.dot(row_vec_array.conj().T, col_vec_array) product_computed = self.vec_space.compute_inner_product_array( row_vec_handles, col_vec_handles) np.testing.assert_allclose( product_computed, product_true, rtol=rtol, atol=atol) # Test symm ip computation product_true = np.dot(row_vec_array.conj().T, row_vec_array) product_computed =\ self.vec_space.compute_symm_inner_product_array( row_vec_handles) np.testing.assert_allclose( product_computed, product_true, rtol=rtol, atol=atol)
def test_compute_modes(self): rtol = 1e-10 atol = 1e-12 # Generate weights to test different inner products. weights_1D = np.random.random(self.num_states) weights_2D = np.identity(self.num_states, dtype=np.complex) weights_2D[0, 0] = 2. weights_2D[2, 1] = 0.3j weights_2D[1, 2] = weights_2D[2, 1].conj() # Generate random snapshot data vecs_array = (np.random.random( (self.num_states, self.num_vecs)) + 1j * np.random.random( (self.num_states, self.num_vecs))) # Test both method of snapshots and direct method for method in ['snaps', 'direct']: if method == 'snaps': compute_POD = pod.compute_POD_arrays_snaps_method elif method == 'direct': compute_POD = pod.compute_POD_arrays_direct_method else: raise ValueError('Invalid method choice.') # Loop through different inner product weights for weights in [None, weights_1D, weights_2D]: IP = VectorSpaceArrays( weights=weights).compute_inner_product_array # Compute POD POD_res = compute_POD(vecs_array, inner_product_weights=weights) # For method of snapshots, test correlation array values if method == 'snaps': np.testing.assert_allclose(IP(vecs_array, vecs_array), POD_res.correlation_array, rtol=rtol, atol=atol) # Check POD eigenvalues and eigenvectors np.testing.assert_allclose(IP(vecs_array, vecs_array).dot(POD_res.eigvecs), POD_res.eigvecs.dot( np.diag(POD_res.eigvals)), rtol=rtol, atol=atol) # Check POD modes np.testing.assert_allclose( vecs_array.dot(IP(vecs_array, POD_res.modes)), POD_res.modes.dot(np.diag(POD_res.eigvals)), rtol=rtol, atol=atol) # Check projection coefficients np.testing.assert_allclose(POD_res.proj_coeffs, IP(POD_res.modes, vecs_array), rtol=rtol, atol=atol) # Choose a random subset of modes to compute, for testing mode # indices argument. Test both an explicit selection of mode # indices and a None argument. mode_indices_trunc = np.unique( np.random.randint(0, high=np.linalg.matrix_rank(vecs_array), size=np.linalg.matrix_rank(vecs_array) // 2)) for mode_idxs_arg, mode_idxs_vals in zip( [None, mode_indices_trunc], [range(POD_res.eigvals.size), mode_indices_trunc]): # Compute POD POD_res_sliced = compute_POD(vecs_array, mode_indices=mode_idxs_arg, inner_product_weights=weights) # Check that if mode indices are passed in, the correct # modes are returned. np.testing.assert_allclose(POD_res_sliced.modes, POD_res.modes[:, mode_idxs_vals], rtol=rtol, atol=atol)
def test_compute_modes(self): rtol = 1e-10 atol = 1e-12 # Generate weights to test different inner products. weights_1D = np.random.random(self.num_states) weights_2D = np.identity(self.num_states, dtype=np.complex) weights_2D[0, 0] = 2. weights_2D[2, 1] = 0.3j weights_2D[1, 2] = weights_2D[2, 1].conj() # Generate random snapshot data vecs_array = ( np.random.random((self.num_states, self.num_vecs)) + 1j * np.random.random((self.num_states, self.num_vecs))) # Test both method of snapshots and direct method for method in ['snaps', 'direct']: if method == 'snaps': compute_POD = pod.compute_POD_arrays_snaps_method elif method == 'direct': compute_POD = pod.compute_POD_arrays_direct_method else: raise ValueError('Invalid method choice.') # Loop through different inner product weights for weights in [None, weights_1D, weights_2D]: IP = VectorSpaceArrays( weights=weights).compute_inner_product_array # Compute POD POD_res = compute_POD(vecs_array, inner_product_weights=weights) # For method of snapshots, test correlation array values if method == 'snaps': np.testing.assert_allclose( IP(vecs_array, vecs_array), POD_res.correlation_array, rtol=rtol, atol=atol) # Check POD eigenvalues and eigenvectors np.testing.assert_allclose( IP(vecs_array, vecs_array).dot(POD_res.eigvecs), POD_res.eigvecs.dot(np.diag(POD_res.eigvals)), rtol=rtol, atol=atol) # Check POD modes np.testing.assert_allclose( vecs_array.dot(IP(vecs_array, POD_res.modes)), POD_res.modes.dot(np.diag(POD_res.eigvals)), rtol=rtol, atol=atol) # Check projection coefficients np.testing.assert_allclose( POD_res.proj_coeffs, IP(POD_res.modes, vecs_array), rtol=rtol, atol=atol) # Choose a random subset of modes to compute, for testing mode # indices argument. Test both an explicit selection of mode # indices and a None argument. mode_indices_trunc = np.unique(np.random.randint( 0, high=np.linalg.matrix_rank(vecs_array), size=np.linalg.matrix_rank(vecs_array) // 2)) for mode_idxs_arg, mode_idxs_vals in zip( [None, mode_indices_trunc], [range(POD_res.eigvals.size), mode_indices_trunc]): # Compute POD POD_res_sliced = compute_POD( vecs_array, mode_indices=mode_idxs_arg, inner_product_weights=weights) # Check that if mode indices are passed in, the correct # modes are returned. np.testing.assert_allclose( POD_res_sliced.modes, POD_res.modes[:, mode_idxs_vals], rtol=rtol, atol=atol)