def save_statistical_features(): """Compute the (spatial) mean temperatures on the full time domain and save them for later. This only needs to be done once. """ # Load the full data set. gems_data, t = utils.load_gems_data() # Lift the data (convert to molar concentrations). with utils.timed_block("Lifting GEMS data"): lifted_data = dproc.lift(gems_data) # Compute statistical features. with utils.timed_block("Computing statistical features of variables"): mins, maxs, sums, stds, means = {}, {}, {}, {}, {} for var in config.ROM_VARIABLES: val = dproc.getvar(var, lifted_data) mins[var] = val.min(axis=0) maxs[var] = val.max(axis=0) sums[var] = val.sum(axis=0) stds[var] = val.std(axis=0) means[var] = sums[var] / val.shape[0] # Save the data. data_path = config.statistical_features_path() with utils.timed_block("Saving statistical features"): with h5py.File(data_path, 'w') as hf: for var in config.ROM_VARIABLES: hf.create_dataset(f"{var}_min", data=mins[var]) hf.create_dataset(f"{var}_max", data=maxs[var]) hf.create_dataset(f"{var}_sum", data=sums[var]) hf.create_dataset(f"{var}_std", data=stds[var]) hf.create_dataset(f"{var}_mean", data=means[var]) hf.create_dataset("time", data=t) logging.info(f"Statistical features saved to {data_path}")
def test_lift(testsize): """Read `testsize` random snapshots of GEMS data, lift them, and check that the before-and-after variables are consistent with each other. """ # Load the unlifted, unscaled snapshot data. testindices = np.random.choice(30000, size=testsize, replace=False) testindices.sort() gems_data, t = utils.load_gems_data(cols=testindices) # Lift the training data to the learning variables. with utils.timed_block("Lifting training data to new coordinates"): lifted_data = dproc.lift(gems_data) # Check that the first three variables are the same. with utils.timed_block("Verifying first four variables"): for i in range(4): s = slice(i * config.DOF, (i + 1) * config.DOF) assert np.allclose(lifted_data[s], gems_data[s]) # Verify inverse lifting. with utils.timed_block("Verifying inverse lifting"): unlifted_data = dproc.unlift(lifted_data) assert np.allclose(unlifted_data, gems_data) return lifted_data
def save_statistical_features(): """Compute the spatial and temporal statistics (min, max, mean, etc.) for each variable and save them for later. This only needs to be done once. """ # Load the full data set. gems_data, t = utils.load_gems_data() # Lift the data (convert to molar concentrations). with utils.timed_block("Lifting GEMS data"): lifted_data = dproc.lift(gems_data) # Compute statistical features. with utils.timed_block("Computing statistical features of variables"): mins, maxs, sums, stds, means = {}, {}, {}, {}, {} for var in config.ROM_VARIABLES: val = dproc.getvar(var, lifted_data) for axis, label in enumerate(["space", "time"]): name = f"{label}/{var}" print(f"\n\tmin_{label}({var})", end='..', flush=True) mins[name] = val.min(axis=axis) print(f"max_{label}({var})", end='..', flush=True) maxs[name] = val.max(axis=axis) print(f"sum_{label}({var})", end='..', flush=True) sums[name] = val.sum(axis=axis) print(f"std_{label}({var})", end='..', flush=True) stds[name] = val.std(axis=axis) means[f"space/{var}"] = sums[f"space/{var}"] / val.shape[0] means[f"time/{var}"] = sums[f"time/{var}"] / t.size # Save the data. data_path = config.statistical_features_path() with utils.timed_block("Saving statistical features"): with h5py.File(data_path, 'w') as hf: for var in config.ROM_VARIABLES: for prefix in ["space", "time"]: name = f"{prefix}/{var}" hf.create_dataset(f"{name}_min", data=mins[name]) hf.create_dataset(f"{name}_max", data=maxs[name]) hf.create_dataset(f"{name}_sum", data=sums[name]) hf.create_dataset(f"{name}_std", data=stds[name]) hf.create_dataset(f"{name}_mean", data=means[name]) hf.create_dataset("t", data=t) logging.info(f"Statistical features saved to {data_path}")
def load_and_lift_gems_data(trainsize): """Lift raw GEMS training snapshots (columnwise) to the learning variables. Parameters ---------- trainsize : int Number of snapshots to lift. Returns ------- lifted_data : (NUM_ROMVARS*DOF,trainsize) ndarray The lifted snapshots. time_domain : (trainsize,) ndarray The time domain corresponding to the lifted snapshots. """ # Load as many snapshots of GEMS training data as are needed. gems_data, time_domain = utils.load_gems_data(cols=trainsize) # Lift the training data to the learning variables. with utils.timed_block(f"Lifting {trainsize:d} GEMS snapshots"): lifted_data = dproc.lift(gems_data) return lifted_data, time_domain
def errors_in_time(trainsize, r, regs, cutoff=60000): """Plot spatially averaged errors, and the projection error, in time. Parameters ---------- trainsize : int Number of snapshots used to train the ROM. r : int Dimension of the ROM. regs : two positive floats Regularization hyperparameters used to train the ROM. cutoff : int Numer of time steps to plot. """ # Load and simulate the ROM. t, V, scales, q_rom = simulate_rom(trainsize, r, regs, cutoff) # Load and lift the true results. data, _ = utils.load_gems_data(cols=cutoff) with utils.timed_block("Lifting GEMS data"): data_gems = dproc.lift(data[:, :cutoff]) del data # Shift and project the data (unscaling done later by chunk). with utils.timed_block("Projecting GEMS data to POD subspace"): data_shifted, _ = dproc.scale(data_gems.copy(), scales) data_proj = V.T @ data_shifted del data_shifted # Initialize the figure. fig, axes = plt.subplots(3, 3, figsize=(12, 6), sharex=True) # Compute and plot errors in each variable. for var, ax in zip(config.ROM_VARIABLES, axes.flat): with utils.timed_block(f"Reconstructing results for {var}"): Vvar = dproc.getvar(var, V) gems_var = dproc.getvar(var, data_gems) proj_var = dproc.unscale(Vvar @ data_proj, scales, var) pred_var = dproc.unscale(Vvar @ q_rom, scales, var) with utils.timed_block(f"Calculating error in {var}"): denom = np.abs(gems_var).max(axis=0) proj_error = np.mean(np.abs(proj_var - gems_var), axis=0) / denom pred_error = np.mean(np.abs(pred_var - gems_var), axis=0) / denom # Plot results. ax.plot(t, proj_error, '-', lw=1, label="Projection Error", c=config.GEMS_STYLE['color']) ax.plot(t, pred_error, '-', lw=1, label="ROM Error", c=config.ROM_STYLE['color']) ax.axvline(t[trainsize], color='k') ax.set_ylabel(config.VARTITLES[var]) # Format the figure. for ax in axes[-1, :]: ax.set_xlim(t[0], t[-1]) ax.set_xticks(np.arange(t[0], t[-1] + .001, .002)) ax.set_xlabel("Time [s]", fontsize=12) # Make legend centered below the subplots. fig.tight_layout(rect=[0, .1, 1, 1]) leg = axes[0, 0].legend(ncol=2, fontsize=14, loc="lower center", bbox_to_anchor=(.5, 0), bbox_transform=fig.transFigure) for line in leg.get_lines(): line.set_linestyle('-') line.set_linewidth(5) # Save the figure. utils.save_figure(f"errors" f"_{config.TRNFMT(trainsize)}" f"_{config.DIMFMT(r)}" f"_{config.REGFMT(regs)}.pdf")
def point_traces(trainsize, r, regs, elems, cutoff=60000): """Plot the time trace of each variable in the original data at the monitor location, and the time trace of each variable of the ROM reconstruction at the same locations. One figure is generated per variable. Parameters ---------- trainsize : int Number of snapshots used to train the ROM. r : int Dimension of the ROM. regs : two positive floats Regularization hyperparameters used to train the ROM. elems : list(int) or ndarray(int) Indices in the spatial domain at which to compute the time traces. cutoff : int Numer of time steps to plot. """ if elems is None: elems = config.MONITOR_LOCATIONS # Get the indicies for each variable. elems = np.atleast_1d(elems) nelems = elems.size nrows = (nelems // 2) + (1 if nelems % 2 != 0 else 0) elems = np.concatenate( [elems + i * config.DOF for i in range(config.NUM_ROMVARS)]) # Load and lift the true results. data, _ = utils.load_gems_data(rows=elems[:nelems * config.NUM_GEMSVARS]) with utils.timed_block("Lifting GEMS time trace data"): traces_gems = dproc.lift(data[:, :cutoff]) # Load and simulate the ROM. t, V, scales, q_rom = simulate_rom(trainsize, r, regs, cutoff) # Reconstruct and rescale the simulation results. simend = q_rom.shape[1] with utils.timed_block("Reconstructing simulation results"): traces_rom = dproc.unscale(V[elems] @ q_rom, scales) # Save a figure for each variable. xticks = np.arange(t[0], t[-1] + .001, .002) for i, var in enumerate(config.ROM_VARIABLES): fig, axes = plt.subplots(nrows, 2 if nelems > 1 else 1, figsize=(9, 3 * nrows), sharex=True) axes = np.atleast_2d(axes) for j, ax in enumerate(axes.flat): idx = j + i * nelems ax.plot(t, traces_gems[idx, :], lw=1, **config.GEMS_STYLE) ax.plot(t[:simend], traces_rom[idx, :], lw=1, **config.ROM_STYLE) ax.axvline(t[trainsize], color='k', lw=1) ax.set_xlim(t[0], t[-1]) ax.set_xticks(xticks) ax.set_title(f"Location ${j+1}$", fontsize=12) ax.locator_params(axis='y', nbins=2) for ax in axes[-1, :]: ax.set_xlabel("Time [s]", fontsize=12) for ax in axes[:, 0]: ax.set_ylabel(config.VARLABELS[var], fontsize=12) # Single legend to the right of the subplots. fig.tight_layout(rect=[0, 0, .85, 1]) leg = axes[0, 0].legend(loc="center right", fontsize=14, bbox_to_anchor=(1, .5), bbox_transform=fig.transFigure) for line in leg.get_lines(): line.set_linewidth(2) # Save the figure. utils.save_figure("pointtrace" f"_{config.TRNFMT(trainsize)}" f"_{config.DIMFMT(r)}" f"_{config.REGFMT(regs)}_{var}.pdf")
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')