def main(trainsize, num_modes): """Project lifted, scaled snapshot training data to the subspace spanned by the columns of the POD basis V; compute velocity information for the projected snapshots; and save the projected data. Parameters ---------- trainsize : int The number of snapshots to use in the computation. There must exist a file of exactly `trainsize` lifted, scaled snapshots (see step2a_lift.py). num_modes : int or list(int) The number of POD modes (left singular vectors) to use in the projection, which determines the dimension of the resulting ROM. There must exist a file of at least `num_modes` left singular vectors computed from exactly `trainsize` lifted, scaled snapshots (see step2b_basis.py). """ utils.reset_logger(trainsize) if np.isscalar(num_modes): num_modes = [int(num_modes)] # Load lifted, scaled snapshot data. X, time_domain, scales = utils.load_scaled_data(trainsize) # Load the POD basis. V, _ = utils.load_basis(trainsize, max(num_modes)) # Project and save the data for each number of POD modes. for r in num_modes: project_and_save_data(trainsize, r, X, time_domain, scales, V)
def main(trainsize, num_modes, center=False): """Lift and scale the GEMS simulation data; compute a POD basis of the lifted, scaled snapshot training data; project the lifted, scaled snapshot training data to the subspace spanned by the columns of the POD basis V, and compute velocity information for the projected snapshots. Save lifted/scaled snapshots, the POD basis, and the projected data. Parameters ---------- trainsize : int Number of snapshots to lift / scale / save. num_modes : int or None The number of POD modes (left singular vectors) to use in the projection. This is the upper bound for the size of ROMs that can be trained with this data set. center : bool If True, center the scaled snapshots by the mean scaled snapshot before computing the POD basis. """ utils.reset_logger(trainsize) # STEP 2A: Lift and scale the data ---------------------------------------- try: # Attempt to load existing lifted, scaled data. training_data, time, qbar, scales = utils.load_scaled_data(trainsize) except utils.DataNotFoundError: # Lift the GEMS data, then scale the lifted snapshots by variable. lifted_data, time = step2a.load_and_lift_gems_data(trainsize) training_data, qbar, scales = step2a.scale_and_save_data( trainsize, lifted_data, time, center) del lifted_data # STEP 2B: Get the POD basis from the lifted, scaled data ----------------- try: # Attempt to load existing SVD data. basis, qbar, scales = utils.load_basis(trainsize, None) if basis.shape[1] < num_modes: raise utils.DataNotFoundError("not enough saved basis vectors") num_modes = basis.shape[1] # Use larger basis size if available. except utils.DataNotFoundError: # Compute and save the (randomized) SVD from the training data. basis = step2b.compute_and_save_pod_basis(num_modes, training_data, qbar, scales) # STEP 2C: Project data to the appropriate subspace ----------------------- return step2c.project_and_save_data(training_data, time, basis)
def simulate_rom(trainsize, r, regs, steps=None): """Load everything needed to simulate a given ROM, run the simulation, and return the simulation results and everything needed to reconstruct the results in the original high-dimensional space. Raise an Exception if any of the ingredients are missing. Parameters ---------- trainsize : int Number of snapshots used to train the ROM. r : int Dimension of the ROM. regs : two or three positive floats Regularization hyperparameters used to train the ROM. steps : int or None Number of time steps to simulate the ROM. Returns ------- t : (nt,) ndarray Time domain corresponding to the ROM outputs. V : (NUM_ROMVARS*DOF,r) ndarray POD basis used to project the training data (and for reconstructing the full-order scaled predictions). qbar : (NUM_ROMVARS*DOF,) ndarray Mean snapshot that the training data was shifted by after scaling but before projection. scales : (NUM_ROMVARS,4) ndarray Information for how the data was scaled. See data_processing.scale(). q_rom : (nt,r) ndarray Prediction results from the ROM. """ # Load the time domain, basis, initial conditions, and trained ROM. t = utils.load_time_domain(steps) V, qbar, scales = utils.load_basis(trainsize, r) Q_, _, _ = utils.load_projected_data(trainsize, r) rom = utils.load_rom(trainsize, r, regs) # Simulate the ROM over the full time domain. with utils.timed_block(f"Simulating ROM with k={trainsize:d}, r={r:d}, " f"{config.REGSTR(regs)} over full time domain"): q_rom = rom.predict(Q_[:, 0], t, config.U, method="RK45") return t, V, qbar, scales, q_rom
def simulate_rom(trainsize, r, reg, steps=None): """Load everything needed to simulate a given ROM, simulate the ROM, and return the simulation results and everything needed to reconstruct the results in the original high-dimensional space. Raise an Exception if any of the ingredients are missing. Parameters ---------- trainsize : int Number of snapshots used to train the ROM. r : int Dimension of the ROM. This is also the number of retained POD modes (left singular vectors) used to project the training data. reg : float Regularization parameter used to train the ROM. steps : int or None Number of time steps to simulate the ROM. Returns ------- t : (nt,) ndarray Time domain corresponding to the ROM outputs. V : (config*NUM_ROMVARS*config.DOF,r) ndarray POD basis used to project the training data (and for reconstructing the full-order scaled predictions). scales : (NUM_ROMVARS,4) ndarray Information for how the data was scaled. See data_processing.scale(). x_rom : (nt,r) ndarray Prediction results from the ROM. """ # Load the time domain, basis, initial conditions, and trained ROM. t = utils.load_time_domain(steps) V, _ = utils.load_basis(trainsize, r) X_, _, _, scales = utils.load_projected_data(trainsize, r) rom = utils.load_rom(trainsize, r, reg) # Simulate the ROM over the full time domain. with utils.timed_block(f"Simulating ROM with r={r:d}, " f"reg={reg:e} over full time domain"): x_rom = rom.predict(X_[:, 0], t, config.U, method="RK45") return t, V, scales, x_rom
def main(trainsize, num_modes): """Lift and scale the GEMS simulation data; compute a POD basis of the lifted, scaled snapshot training data; project the lifted, scaled snapshot training data to the subspace spanned by the columns of the POD basis V, and compute velocity information for the projected snapshots. Save lifted/scaled snapshots, the POD basis, and the projected data. Parameters ---------- trainsize : int Number of snapshots to lift / scale / save. num_modes : int or list(int) The number of POD modes (left singular vectors) to use in the projection, which determines the dimension of the resulting ROM. """ utils.reset_logger(trainsize) if np.isscalar(num_modes): num_modes = [int(num_modes)] # STEP 2A: Lift and scale the data ---------------------------------------- try: # Attempt to load existing lifted, scaled data. X, time_domain, scales = utils.load_scaled_data(trainsize) except utils.DataNotFoundError: # Lift the GEMS data, then scale the lifted snapshots by variable. lifted_data, time_domain = step2a.load_and_lift_gems_data(trainsize) X, scales = step2a.scale_and_save_data(trainsize, lifted_data, time_domain) # STEP 2B: Get the POD basis from the lifted, scaled data ----------------- try: # Attempt to load existing SVD data. V, _ = utils.load_basis(trainsize, max(num_modes)) except utils.DataNotFoundError: # Compute and save the (randomized) SVD from the training data. V, _ = step2b.compute_and_save_pod_basis(trainsize, max(num_modes), X, scales) # STEP 2C: Project data to the appropriate subspace ----------------------- for r in num_modes: step2c.project_and_save_data(trainsize, r, X, time_domain, scales, V)
def main(trainsize): """Project lifted, scaled snapshot training data to the subspace spanned by the columns of the POD basis V; compute velocity information for the projected snapshots; and save the projected data. Parameters ---------- trainsize : int The number of snapshots to use in the computation. There must exist a file of exactly `trainsize` lifted, scaled snapshots (see step2a_transform.py) and a basis for those snapshots (see step2b_basis.py). """ utils.reset_logger(trainsize) # Load lifted, scaled snapshot data. scaled_data, time_domain, _, _ = utils.load_scaled_data(trainsize) # Load the POD basis. V, _, _ = utils.load_basis(trainsize, None) # Project and save the data. return project_and_save_data(scaled_data, time_domain, V)
def main(timeindices, variables=None, snaptype=["gems", "rom", "error"], trainsize=None, r=None, reg=None): """Convert a snapshot in .h5 format to a .dat file that matches the format of grid.dat. The new file is saved in `config.tecplot_path()` with the same filename and the new file extension .dat. Parameters ---------- timeindices : ndarray(int) or int Indices (one-based) in the full time domain of the snapshots to save. variables : str or list(str) The variables to scale, a subset of config.ROM_VARIABLES. Defaults to all variables. snaptype : {"rom", "gems", "error"} or list(str) Which kinds of snapshots to save. Options: * "gems": snapshots from the full-order GEMS data; * "rom": reconstructed snapshots produced by a ROM; * "error": absolute error between the full-order data and the reduced-order reconstruction. If "rom" or "error" are selected, the ROM is selected by the remaining arguments. trainsize : int Number of snapshots used to train the ROM. r : int Number of retained modes in the ROM. reg : float Regularization factor used to train the ROM. """ utils.reset_logger(trainsize) # Parse parameters. timeindices = np.sort(np.atleast_1d(timeindices)) simtime = timeindices.max() t = utils.load_time_domain(simtime + 1) # Parse the variables. if variables is None: variables = config.ROM_VARIABLES elif isinstance(variables, str): variables = [variables] varnames = '\n'.join(f'"{v}"' for v in variables) if isinstance(snaptype, str): snaptype = [snaptype] for stype in snaptype: if stype not in ("gems", "rom", "error"): raise ValueError(f"invalid snaptype '{stype}'") # Read the grid file. with utils.timed_block("Reading Tecplot grid data"): # Parse the header. grid_path = config.grid_data_path() with open(grid_path, 'r') as infile: grid = infile.read() if int(re.findall(r"Elements=(\d+)", grid)[0]) != config.DOF: raise RuntimeError(f"{grid_path} DOF and config.DOF do not match") num_nodes = int(re.findall(r"Nodes=(\d+)", grid)[0]) end_of_header = re.findall(r"DT=.*?\n", grid)[0] headersize = grid.find(end_of_header) + len(end_of_header) # Extract geometry information. grid_data = grid[headersize:].split() x = grid_data[:num_nodes] y = grid_data[num_nodes:2 * num_nodes] cell_volume = grid_data[2 * num_nodes:3 * num_nodes] connectivity = grid_data[3 * num_nodes:] # Extract full-order data if needed. if ("gems" in snaptype) or ("error" in snaptype): gems_data, _ = utils.load_gems_data(cols=timeindices) with utils.timed_block("Lifting selected snapshots of GEMS data"): lifted_data = dproc.lift(gems_data) true_snaps = np.concatenate( [dproc.getvar(v, lifted_data) for v in variables]) # Simulate ROM if needed. if ("rom" in snaptype) or ("error" in snaptype): # Load the SVD data. V, _ = utils.load_basis(trainsize, r) # Load the initial conditions and scales. X_, _, _, scales = utils.load_projected_data(trainsize, r) # Load the appropriate ROM. rom = utils.load_rom(trainsize, r, reg) # Simulate the ROM over the time domain. with utils.timed_block(f"Simulating ROM with r={r:d}, reg={reg:.0e}"): x_rom = rom.predict(X_[:, 0], t, config.U, method="RK45") if np.any(np.isnan(x_rom)) or x_rom.shape[1] < simtime: raise ValueError("ROM unstable!") # Reconstruct the results (only selected variables / snapshots). with utils.timed_block("Reconstructing simulation results"): x_rec = dproc.unscale(V[:, :r] @ x_rom[:, timeindices], scales) x_rec = np.concatenate([dproc.getvar(v, x_rec) for v in variables]) dsets = {} if "rom" in snaptype: dsets["rom"] = x_rec if "gems" in snaptype: dsets["gems"] = true_snaps if "error" in snaptype: with utils.timed_block("Computing absolute error of reconstruction"): abs_err = np.abs(true_snaps - x_rec) dsets["error"] = abs_err # Save each of the selected snapshots in Tecplot format matching grid.dat. for j, tindex in enumerate(timeindices): header = HEADER.format(varnames, tindex, t[tindex], num_nodes, config.DOF, len(variables) + 2, "SINGLE " * len(variables)) for label, dset in dsets.items(): if label == "gems": save_path = config.gems_snapshot_path(tindex) if label in ("rom", "error"): folder = config.rom_snapshot_path(trainsize, r, reg) save_path = os.path.join(folder, f"{label}_{tindex:05d}.dat") with utils.timed_block(f"Writing {label} snapshot {tindex:05d}"): with open(save_path, 'w') as outfile: # Write the header. outfile.write(header) # Write the geometry data (x,y coordinates). for i in range(0, len(x), NCOLS): outfile.write(' '.join(x[i:i + NCOLS]) + '\n') for i in range(0, len(y), NCOLS): outfile.write(' '.join(y[i:i + NCOLS]) + '\n') # Write the data for each variable. for i in range(0, dset.shape[0], NCOLS): row = ' '.join(f"{v:.9E}" for v in dset[i:i + NCOLS, j]) outfile.write(row + '\n') # Write connectivity information. for i in range(0, len(connectivity), NCOLS): outfile.write(' '.join(connectivity[i:i + NCOLS]) + '\n')
def basis(trainsize, r, variables=None): """Export the POD basis vectors to Tecplot format. Parameters ---------- trainsize : int Number of snapshots used to compute the basis. r : int Number of basis vectors to save. variables : str or list(str) Variables to save, a subset of config.ROM_VARIABLES. Defaults to all variables. """ utils.reset_logger(trainsize) if variables is None: variables = config.ROM_VARIABLES elif isinstance(variables, str): variables = [variables] varnames = '\n'.join(f'"{v}"' for v in variables) # Read the grid file. with utils.timed_block("Reading Tecplot grid data"): # Parse the header. grid_path = config.grid_data_path() with open(grid_path, 'r') as infile: grid = infile.read() if int(re.findall(r"Elements=(\d+)", grid)[0]) != config.DOF: raise RuntimeError(f"{grid_path} DOF and config.DOF do not match") num_nodes = int(re.findall(r"Nodes=(\d+)", grid)[0]) end_of_header = re.findall(r"DT=.*?\n", grid)[0] headersize = grid.find(end_of_header) + len(end_of_header) # Extract geometry information. grid_data = grid[headersize:].split() x = grid_data[:num_nodes] y = grid_data[num_nodes:2 * num_nodes] # cell_volume = grid_data[2*num_nodes:3*num_nodes] connectivity = grid_data[3 * num_nodes:] # Load the basis and extract desired variables. V, _, _ = utils.load_basis(trainsize, r) V = np.concatenate([dproc.getvar(var, V) for var in variables]) # Save each of the basis vectors in Tecplot format matching grid.dat. for j in range(r): header = HEADER.format(varnames, j, j, num_nodes, config.DOF, len(variables) + 2, "DOUBLE " * len(variables)) save_folder = config._makefolder(config.tecplot_path(), "basis", config.TRNFMT(trainsize)) save_path = os.path.join(save_folder, f"vec_{j+1:03d}.dat") with utils.timed_block(f"Writing basis vector {j+1:d}"): with open(save_path, 'w') as outfile: # Write the header. outfile.write(header) # Write the geometry data (x,y coordinates). for i in range(0, len(x), NCOLS): outfile.write(' '.join(x[i:i + NCOLS]) + '\n') for i in range(0, len(y), NCOLS): outfile.write(' '.join(y[i:i + NCOLS]) + '\n') # Write the data for each variable. for i in range(0, V.shape[0], NCOLS): row = ' '.join(f"{v:.9E}" for v in V[i:i + NCOLS, j]) outfile.write(row + '\n') # Write connectivity information. for i in range(0, len(connectivity), NCOLS): outfile.write(' '.join(connectivity[i:i + NCOLS]) + '\n') print(f"Basis info exported to {save_folder}/*.dat.")