|
| 1 | +# ----------------------------------------------------------------------------- |
| 2 | +# Author : Albert Akhriev, albert_akhriev@ie.ibm.com |
| 3 | +# Copyright : IBM Research Ireland, 2017-2018 |
| 4 | +# ----------------------------------------------------------------------------- |
| 5 | + |
| 6 | +import sys, traceback, os, re, getopt, math, argparse, subprocess |
| 7 | +import numpy as np |
| 8 | +from timeit import default_timer as timer |
| 9 | +from Configuration import Configuration |
| 10 | +from Utility import * |
| 11 | + |
| 12 | +# Full-path name of C++ executable. |
| 13 | +AMDADOS_EXE = "build/app/amdados" |
| 14 | + |
| 15 | + |
| 16 | +def Amdados2D(config_file, demo): |
| 17 | + """ Advection-diffusion PDE forward solver. |
| 18 | + """ |
| 19 | + # Initialize parameters, global indices and output directory. |
| 20 | + conf = Configuration(config_file) |
| 21 | + conf = InitDependentParams(conf) |
| 22 | + glo_idx = GlobalIndices(conf) |
| 23 | + if demo: conf.PrintParameters() |
| 24 | + MakePath(conf.output_dir + os.sep) |
| 25 | + |
| 26 | + # Load sensor locations previously generated by C++ 'amdados' application: |
| 27 | + # './build/app/amdados --scenario sensors' |
| 28 | + # If does not exist, the file of sensor locations will be regenerated. |
| 29 | + sensors_filename = MakeFileName(conf, "sensors") |
| 30 | + if not os.path.exists(sensors_filename): |
| 31 | + print("") |
| 32 | + print("") |
| 33 | + print("WARNING: missed file of sensor locations: " + sensors_filename) |
| 34 | + print("generating a new one ...") |
| 35 | + assert os.path.exists(AMDADOS_EXE), ( |
| 36 | + "Application 'amdados' must be built before running this script") |
| 37 | + subprocess.Popen([AMDADOS_EXE, "--scenario", "sensors", |
| 38 | + "--config", config_file]) |
| 39 | + os.sync() |
| 40 | + print("") |
| 41 | + print("") |
| 42 | + sensor_idx = LoadSensorLocations(conf) |
| 43 | + |
| 44 | + Nt = round(conf.Nt) |
| 45 | + writer = Writer(conf) |
| 46 | + for k in range(Nt): |
| 47 | + # Write the field entries at sensors into the file of observations from random number |
| 48 | + writer.WriteField(sensor_idx, k) |
| 49 | + |
| 50 | + # Run forward simulation and record the "true" solutions into a file. |
| 51 | + # with open(MakeFileName(conf, "true_field"), "wb") as fid: |
| 52 | + # ForwardSolver(conf, glo_idx, sensor_idx, fid, demo) |
| 53 | + |
| 54 | + |
| 55 | +############################################################################### |
| 56 | +# Initialization. |
| 57 | +############################################################################### |
| 58 | + |
| 59 | +def InitDependentParams(conf): |
| 60 | + """ Function initializes dependent parameters given |
| 61 | + the primary ones specified by user. |
| 62 | + """ |
| 63 | + # Ensure integer values. |
| 64 | + conf.nx = round(conf.num_subdomains_x * conf.subdomain_x) |
| 65 | + conf.ny = round(conf.num_subdomains_y * conf.subdomain_y) |
| 66 | + conf.integration_nsteps = round(conf.integration_nsteps) |
| 67 | + |
| 68 | + # Diffusion coefficient must be positive float value. |
| 69 | + D = float(conf.diffusion_coef) |
| 70 | + assert D > 0 |
| 71 | + |
| 72 | + # Deduce space discretization steps. |
| 73 | + conf.problem_size = int(conf.nx * conf.ny) |
| 74 | + dx = float(conf.domain_size_x) / float(conf.nx-1) |
| 75 | + dy = float(conf.domain_size_y) / float(conf.ny-1) |
| 76 | + assert (dx > 0) and (dy > 0) |
| 77 | + conf.dx = dx |
| 78 | + conf.dy = dy |
| 79 | + |
| 80 | + # Deduce the optimal time step from the stability criteria. |
| 81 | + tiny = np.finfo(float).tiny / np.finfo(float).eps**3 |
| 82 | + dt_base = float(conf.integration_period) / float(conf.integration_nsteps) |
| 83 | + max_vx = float(conf.flow_model_max_vx) |
| 84 | + max_vy = float(conf.flow_model_max_vy) |
| 85 | + dt = min(dt_base, min( min(dx**2, dy**2)/(2.0*D + tiny), |
| 86 | + 1.0/(abs(max_vx)/dx + abs(max_vy)/dy + tiny) )) |
| 87 | + assert(dt > 0) |
| 88 | + conf.dt = dt |
| 89 | + conf.Nt = round(math.ceil(float(conf.integration_period) / dt)) |
| 90 | + |
| 91 | + # Compute coefficients that will be used in the finite-difference scheme. |
| 92 | + conf.rho_x = float(D * dt / dx**2) |
| 93 | + conf.rho_y = float(D * dt / dy**2) |
| 94 | + |
| 95 | + conf.v0x = float(2.0 * dx / dt) |
| 96 | + conf.v0y = float(2.0 * dy / dt) |
| 97 | + return conf |
| 98 | + |
| 99 | + |
| 100 | +def GlobalIndices(conf): |
| 101 | + """ Each nodal point gets a unique global index on the grid. |
| 102 | + Function initializes a 2D array of indices: |
| 103 | + index of (x,y) = glo_idx(x,y). |
| 104 | + """ |
| 105 | + glo_idx = np.arange(round(conf.problem_size)).reshape((conf.nx, conf.ny)) |
| 106 | + return glo_idx |
| 107 | + |
| 108 | + |
| 109 | +def LoadSensorLocations(conf): |
| 110 | + """ Function loads sensor locations, i.e. indices of domain |
| 111 | + points occupied by sensors. |
| 112 | + """ |
| 113 | + print("Loading sensor locations ...") |
| 114 | + Nx = conf.nx |
| 115 | + Ny = conf.ny |
| 116 | + Np = round(math.ceil(Nx * Ny * conf.sensor_fraction)) |
| 117 | + data = np.loadtxt(MakeFileName(conf, "sensors"), dtype=int) |
| 118 | + assert (data is not None) and (data.size > 0), "empty file of sensors" |
| 119 | + assert len(data.shape) == 2 and data.shape[1] == 2, "wrong layout" |
| 120 | + assert data.shape[0] <= Nx * Ny, "too many records" |
| 121 | + assert data.dtype == int, "type mismatch" |
| 122 | + assert np.all(data >= 0), "negative index" |
| 123 | + assert np.all(data[:,0] < Nx), "x-index is out of bound" |
| 124 | + assert np.all(data[:,1] < Ny), "y-index is out of bound" |
| 125 | + return data |
| 126 | + |
| 127 | +############################################################################### |
| 128 | +# Utilities. |
| 129 | +############################################################################### |
| 130 | + |
| 131 | +class Writer: |
| 132 | + """ Class for writing simulated solution at sensor locations in a text file. |
| 133 | + """ |
| 134 | + def __init__(self, conf): |
| 135 | + """ Constructor. TODO: could be useful to add a header to file: |
| 136 | + """ |
| 137 | + # N O T E: np.savetxt() expects (!?) file opened in binary format "wb". |
| 138 | + self.fid = open(MakeFileName(conf, "analytic"), "wb") |
| 139 | + |
| 140 | + def __del__(self): |
| 141 | + """ This method is not a destructor, but a normal method that is always |
| 142 | + called before the garbage collector destroys the object. |
| 143 | + https://stackoverflow.com/questions/37852560/is-del-really-a-destructor |
| 144 | + """ |
| 145 | + if self.fid is not None: |
| 146 | + self.fid.flush() |
| 147 | + self.fid.close() |
| 148 | + self.fid = None |
| 149 | + |
| 150 | + def WriteField(self, sensor_idx, discrete_time): |
| 151 | + """ Function appends the new field to the output file. |
| 152 | + Only values at sensor locations are written. |
| 153 | + column 1: discrete time; |
| 154 | + column 2: sensor abscissas (point indices) on the grid; |
| 155 | + column 3: sensor ordinates (point indices) on the grid; |
| 156 | + column 4: values at sensor locations. |
| 157 | + """ |
| 158 | + assert isinstance(discrete_time, int) and discrete_time >= 0 |
| 159 | + assert len(sensor_idx.shape) == 2 and sensor_idx.shape[1] == 2 |
| 160 | + |
| 161 | + xind = sensor_idx[:, 0].astype(int) |
| 162 | + yind = sensor_idx[:, 1].astype(int) |
| 163 | + # For the purpose of performance analysis just set vals as random number |
| 164 | + vals = np.random.rand(len(xind)).astype(float) |
| 165 | + assert np.isnan(vals).any() == False, "NaN in observations" |
| 166 | + vals[np.fabs(vals) <= np.finfo(np.float64).eps]=0 |
| 167 | + data = np.column_stack((xind, yind, vals)) |
| 168 | + num = data.shape[0] |
| 169 | + np.savetxt(self.fid, np.array([discrete_time, num]), fmt="%d") |
| 170 | + np.savetxt(self.fid, data, fmt="%d %d %g"); |
| 171 | + |
| 172 | +############################################################################### |
| 173 | +# Entry point. |
| 174 | +############################################################################### |
| 175 | +if __name__ == "__main__": |
| 176 | + try: |
| 177 | + # CheckPythonVersion() |
| 178 | + parser = argparse.ArgumentParser() |
| 179 | + parser.add_argument("--config", |
| 180 | + type=str, default="amdados.conf", |
| 181 | + help="path to configuration file") |
| 182 | + parser.add_argument("--demo", nargs="?", const=True, |
| 183 | + type=bool, default=False, |
| 184 | + help="show live progress in a window") |
| 185 | + param = parser.parse_args() |
| 186 | + param.config = os.path.expanduser(param.config) |
| 187 | + print("Options:") |
| 188 | + print("Visualization: " + str(param.demo)) |
| 189 | + print("Configuration files: " + param.config) |
| 190 | + print("") |
| 191 | + Amdados2D(param.config, param.demo) |
| 192 | + |
| 193 | + except subprocess.CalledProcessError as error: |
| 194 | + traceback.print_exc() |
| 195 | + if error.output is not None: |
| 196 | + print("ERROR: " + str(error.output)) |
| 197 | + else: |
| 198 | + print("CalledProcessError") |
| 199 | + except AssertionError as error: |
| 200 | + traceback.print_exc() |
| 201 | + print("ERROR: " + str(error.args)) |
| 202 | + except ValueError as error: |
| 203 | + traceback.print_exc() |
| 204 | + print("ERROR: " + str(error.args)) |
| 205 | + except Exception as error: |
| 206 | + traceback.print_exc() |
| 207 | + print("ERROR: " + str(error.args)) |
0 commit comments