def nr_step(self): """ Single step using Newton-Raphson method. Returns ------- float maximum absolute mismatch """ system = self.system # evaluate discrete, differential, algebraic, and Jacobians system.dae.clear_fg() system.l_update_var(self.models, niter=self.niter, err=self.mis[-1]) system.s_update_var(self.models) system.f_update(self.models) system.g_update(self.models) system.l_update_eq(self.models) system.fg_to_dae() if self.config.method == 'NR': system.j_update(models=self.models) elif self.config.method == 'dishonest': if self.niter < self.config.n_factorize: system.j_update(self.models) # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) if not self.config.linsolve: self.inc = self.solver.solve(self.A, self.inc) else: self.inc = self.solver.linsolve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) # find out variables associated with maximum mismatches fmax = 0 if system.dae.n > 0: fmax_idx = np.argmax(np.abs(system.dae.f)) fmax = system.dae.f[fmax_idx] logger.debug("Max. diff mismatch %.10g on %s", fmax, system.dae.x_name[fmax_idx]) gmax_idx = np.argmax(np.abs(system.dae.g)) gmax = system.dae.g[gmax_idx] logger.debug("Max. algeb mismatch %.10g on %s", gmax, system.dae.y_name[gmax_idx]) mis = max(abs(fmax), abs(gmax)) if self.niter == 0: self.mis[0] = mis else: self.mis.append(mis) system.vars_to_models() return mis
def nr_step(self): """ Single stepping for Newton Raphson method Returns ------- """ system = self.system # evaluate discrete, differential, algebraic, and jacobians system.e_clear() system.l_update_var() system.f_update() system.g_update() system.l_check_eq() system.l_set_eq() system.fg_to_dae() system.j_update() # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) self.inc = self.solver.solve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) mis = np.max(np.abs(system.dae.fg)) self.mis.append(mis) system.vars_to_models() return mis
def test_initialization(self): """ Update f and g to see if initialization is successful """ system = self.system system.e_clear(models=self.pflow_tds_models) system.l_update_var(models=self.pflow_tds_models) system.f_update(models=self.pflow_tds_models) system.g_update(models=self.pflow_tds_models) system.l_check_eq(models=self.pflow_tds_models) system.l_set_eq(models=self.pflow_tds_models) system.fg_to_dae() system.j_update(models=self.pflow_tds_models) if np.max(np.abs(system.dae.fg)) < self.config.tol: logger.debug('Initialization tests passed.') return True else: logger.warning('Suspect initialization issue!') fail_idx = np.where(abs(system.dae.fg) >= self.config.tol) fail_names = [ system.dae.xy_name[int(i)] for i in np.ravel(fail_idx) ] logger.warning(f"Check variables {', '.join(fail_names)}") return False
def run(self, **kwargs): """ Full Newton-Raphson method. Returns ------- bool convergence status """ system = self.system self.summary() self.init() if system.dae.m == 0: logger.error("Loaded case contains no power flow element.") system.exit_code = 1 return False t0, _ = elapsed() self.niter = 0 while True: mis = self.nr_step() logger.info(f'{self.niter}: |F(x)| = {mis:<10g}') if mis < self.config.tol: self.converged = True break elif self.niter > self.config.max_iter: break elif np.isnan(mis).any(): logger.error('NaN found in solution. Convergence not likely') self.niter = self.config.max_iter + 1 break elif mis > 1e4 * self.mis[0]: logger.error('Mismatch increased too fast. Convergence not likely.') break self.niter += 1 _, s1 = elapsed(t0) if not self.converged: if abs(self.mis[-1] - self.mis[-2]) < self.config.tol: max_idx = np.argmax(np.abs(system.dae.xy)) name = system.dae.xy_name[max_idx] logger.error('Mismatch is not correctable possibly due to large load-generation imbalance.') logger.error(f'Largest mismatch on equation associated with <{name}>') else: logger.error(f'Power flow failed after {self.niter + 1} iterations for {system.files.case}.') else: logger.info(f'Converged in {self.niter+1} iterations in {s1}.') if self.config.init_tds: system.TDS.init() if self.config.report: system.PFlow.report() system.exit_code = 0 if self.converged else 1 return self.converged
def run(self): """ Full Newton-Raphson method Returns ------- """ system = self.system logger.info('-> Power flow calculation with Newton Raphson method:') self._initialize() if system.dae.m == 0: logger.error("Loaded case file contains no element.") return False t0, _ = elapsed() self.niter = 0 while True: mis = self.nr_step() logger.info(f'{self.niter}: |F(x)| = {mis:<10g}') if mis < self.config.tol: self.converged = True break elif self.niter > self.config.max_iter: break elif mis > 1e4 * self.mis[0]: logger.error( 'Mismatch increased too fast. Convergence not likely.') break self.niter += 1 _, s1 = elapsed(t0) if not self.converged: if abs(self.mis[-1] - self.mis[-2]) < self.config.tol: max_idx = np.argmax(np.abs(system.dae.xy)) name = system.dae.xy_name[max_idx] logger.error( 'Mismatch is not correctable possibly due to large load-generation imbalance.' ) logger.error( f'Largest mismatch on equation associated with <{name}>') else: logger.error( f'Power flow failed after {self.niter + 1} iterations for {system.files.case}.' ) else: logger.info(f'Converged in {self.niter+1} iterations in {s1}.') if self.config.report: system.PFlow.write_report() return self.converged
def test_init(self): """ Test if the TDS initialization is successful. This function update ``dae.f`` and ``dae.g`` and checks if the residuals are zeros. """ system = self.system # fg_update is called in TDS.init() system.j_update(models=system.exist.pflow_tds) # reset diff. RHS where `check_init == False` system.dae.f[system.no_check_init] = 0.0 # warn if variables are initialized at limits if system.config.warn_limits: for model in system.exist.pflow_tds.values(): for item in model.discrete.values(): item.warn_init_limit() if np.max(np.abs(system.dae.fg)) < self.config.tol: logger.debug('Initialization tests passed.') return True # otherwise, show suspect initialization error fail_idx = np.ravel(np.where(abs(system.dae.fg) >= self.config.tol)) nan_idx = np.ravel(np.where(np.isnan(system.dae.fg))) bad_idx = np.hstack([fail_idx, nan_idx]) fail_names = [system.dae.xy_name[int(i)] for i in fail_idx] nan_names = [system.dae.xy_name[int(i)] for i in nan_idx] bad_names = fail_names + nan_names title = 'Suspect initialization issue! Simulation may crash!' err_data = { 'Name': bad_names, 'Var. Value': system.dae.xy[bad_idx], 'Eqn. Mismatch': system.dae.fg[bad_idx], } tab = Tab( title=title, header=err_data.keys(), data=list(map(list, zip(*err_data.values()))), ) logger.error(tab.draw()) if system.options.get('verbose') == 1: breakpoint() system.exit_code += 1 return False
def nr_step(self): """ Single step using Newton-Raphson method. Returns ------- float maximum absolute mismatch """ system = self.system # evaluate discrete, differential, algebraic, and Jacobians system.dae.clear_fg() system.l_update_var(self.models, niter=self.niter, err=self.mis[-1]) system.s_update_var(self.models) system.f_update(self.models) system.g_update(self.models) system.l_update_eq(self.models) system.fg_to_dae() if self.config.method == 'NR': system.j_update(models=self.models) elif self.config.method == 'dishonest': if self.niter < self.config.n_factorize: system.j_update(self.models) # prepare and solve linear equations self.inc = -matrix([matrix(system.dae.f), matrix(system.dae.g)]) self.A = sparse([[system.dae.fx, system.dae.gx], [system.dae.fy, system.dae.gy]]) if not self.config.linsolve: self.inc = self.solver.solve(self.A, self.inc) else: self.inc = self.solver.linsolve(self.A, self.inc) system.dae.x += np.ravel(np.array(self.inc[:system.dae.n])) system.dae.y += np.ravel(np.array(self.inc[system.dae.n:])) mis = np.max(np.abs(system.dae.fg)) if self.niter == 0: self.mis[0] = mis else: self.mis.append(mis) system.vars_to_models() return mis
def test_init(self): """ Update f and g to see if initialization is successful. """ system = self.system self.fg_update(system.exist.pflow_tds) system.j_update(models=system.exist.pflow_tds) # reset diff. RHS where `check_init == False` system.dae.f[system.no_check_init] = 0.0 # warn if variables are initialized at limits if system.config.warn_limits: for model in system.exist.pflow_tds.values(): for item in model.discrete.values(): item.warn_init_limit() if np.max(np.abs(system.dae.fg)) < self.config.tol: logger.debug('Initialization tests passed.') return True # otherwise, show suspect initialization error fail_idx = np.where(abs(system.dae.fg) >= self.config.tol) fail_names = [system.dae.xy_name[int(i)] for i in np.ravel(fail_idx)] title = 'Suspect initialization issue! Simulation may crash!' err_data = { 'Name': fail_names, 'Var. Value': system.dae.xy[fail_idx], 'Eqn. Mismatch': system.dae.fg[fail_idx], } tab = Tab( title=title, header=err_data.keys(), data=list(map(list, zip(*err_data.values()))), ) logger.error(tab.draw()) if system.options.get('verbose') == 1: breakpoint() system.exit_code += 1 return False
def _solve_g(self, verbose): system = self.system dae = system.dae self.converged = False self.niter = 0 self.mis = [] # check if the next step is critical time if self.is_switch_time(): self._last_switch_t = system.switch_times[self._switch_idx] system.switch_action(self.pflow_tds_models) while True: system.e_clear(models=self.pflow_tds_models) system.l_update_var(models=self.pflow_tds_models) system.g_update(models=self.pflow_tds_models) inc = -matrix(system.dae.g) system.j_update(models=self.pflow_tds_models) inc = self.solver.solve(dae.gy, inc) dae.y += np.ravel(np.array(inc)) system.vars_to_models() mis = np.max(np.abs(inc)) self.mis.append(mis) if verbose: print(f't={dae.t:<.4g}, iter={self.niter:<g}, mis={mis:<.4g}') if mis < self.config.tol: self.converged = True break elif self.niter > self.config.max_iter: raise NoConvergence(f'Convergence not reached after {self.config.max_iter} iterations') elif mis >= 1000 and (mis > 1e4 * self.mis[0]): raise NoConvergence('Mismatch increased too fast. Convergence not likely.') self.niter += 1
def _implicit_step(self): """ Integrate for a single given step. This function has an internal Newton-Raphson loop for algebraized semi-explicit DAE. The function returns the convergence status when done but does NOT progress simulation time. Returns ------- bool Convergence status in ``self.converged``. """ system = self.system dae = self.system.dae self.mis = [] self.niter = 0 self.converged = False self.x0 = np.array(dae.x) self.y0 = np.array(dae.y) self.f0 = np.array(dae.f) while True: system.e_clear(models=self.pflow_tds_models) system.l_update_var(models=self.pflow_tds_models) system.f_update(models=self.pflow_tds_models) system.g_update(models=self.pflow_tds_models) system.l_check_eq(models=self.pflow_tds_models) system.l_set_eq(models=self.pflow_tds_models) system.fg_to_dae() # lazy jacobian update if dae.t == 0 or self.niter > 3 or (dae.t - self._last_switch_t < 0.2): system.j_update(models=self.pflow_tds_models) self.solver.factorize = True # solve trapezoidal rule integration In = spdiag([1] * dae.n) self.Ac = sparse([[In - self.h * 0.5 * dae.fx, dae.gx], [-self.h * 0.5 * dae.fy, dae.gy]], 'd') # reset q as well q = dae.x - self.x0 - self.h * 0.5 * (dae.f + self.f0) for item in system.antiwindups: if len(item.x_set) > 0: for key, val in item.x_set: np.put(q, key[np.where(item.zi == 0)], 0) qg = np.hstack((q, dae.g)) inc = self.solver.solve(self.Ac, -matrix(qg)) # check for np.nan first if np.isnan(inc).any(): logger.error(f'NaN found in solution. Convergence not likely') self.niter = self.config.max_iter + 1 self.busted = True break # reset really small values to avoid anti-windup limiter flag jumps inc[np.where(np.abs(inc) < 1e-12)] = 0 # set new values dae.x += np.ravel(np.array(inc[:dae.n])) dae.y += np.ravel(np.array(inc[dae.n: dae.n + dae.m])) system.vars_to_models() # calculate correction mis = np.max(np.abs(inc)) self.mis.append(mis) self.niter += 1 # converged if mis <= self.config.tol: self.converged = True break # non-convergence cases if self.niter > self.config.max_iter: logger.debug(f'Max. iter. {self.config.max_iter} reached for t={dae.t:.6f}, ' f'h={self.h:.6f}, mis={mis:.4g} ' f'({system.dae.xy_name[np.argmax(inc)]})') break if mis > 1000 and (mis > 1e8 * self.mis[0]): logger.error(f'Error increased too quickly. Convergence not likely.') self.busted = True break if not self.converged: dae.x = np.array(self.x0) dae.y = np.array(self.y0) dae.f = np.array(self.f0) system.vars_to_models() return self.converged
def _itm_step(self): """ Integrate with Implicit Trapezoidal Method (ITM) to the current time. This function has an internal Newton-Raphson loop for algebraized semi-explicit DAE. The function returns the convergence status when done but does NOT progress simulation time. Returns ------- bool Convergence status in ``self.converged``. """ system = self.system dae = self.system.dae self.mis = 1 self.niter = 0 self.converged = False self.x0 = np.array(dae.x) self.y0 = np.array(dae.y) self.f0 = np.array(dae.f) while True: self._fg_update(models=system.exist.pflow_tds) # lazy Jacobian update if dae.t == 0 or \ self.config.honest or \ self.custom_event or \ not self.last_converged or \ self.niter > 4 or \ (dae.t - self._last_switch_t < 0.1): system.j_update(models=system.exist.pflow_tds) # set flag in `solver.worker.factorize`, not `solver.factorize`. self.solver.worker.factorize = True # `Tf` should remain constant throughout the simulation, even if the corresponding diff. var. # is pegged by the anti-windup limiters. # solve implicit trapezoidal method (ITM) integration self.Ac = sparse([[self.Teye - self.h * 0.5 * dae.fx, dae.gx], [-self.h * 0.5 * dae.fy, dae.gy]], 'd') # equation `self.qg[:dae.n] = 0` is the implicit form of differential equations using ITM self.qg[:dae.n] = dae.Tf * (dae.x - self.x0) - self.h * 0.5 * (dae.f + self.f0) # reset the corresponding q elements for pegged anti-windup limiter for item in system.antiwindups: for key, _, eqval in item.x_set: np.put(self.qg, key, eqval) self.qg[dae.n:] = dae.g if not self.config.linsolve: inc = self.solver.solve(self.Ac, matrix(self.qg)) else: inc = self.solver.linsolve(self.Ac, matrix(self.qg)) # check for np.nan first if np.isnan(inc).any(): self.err_msg = 'NaN found in solution. Convergence is not likely' self.niter = self.config.max_iter + 1 self.busted = True break # reset small values to reduce chattering inc[np.where(np.abs(inc) < self.tol_zero)] = 0 # set new values dae.x -= inc[:dae.n].ravel() dae.y -= inc[dae.n: dae.n + dae.m].ravel() # store `inc` to self for debugging self.inc = inc system.vars_to_models() # calculate correction mis = np.max(np.abs(inc)) # store initial maximum mismatch if self.niter == 0: self.mis = mis self.niter += 1 # converged if mis <= self.config.tol: self.converged = True break # non-convergence cases if self.niter > self.config.max_iter: tqdm.write(f'* Max. iter. {self.config.max_iter} reached for t={dae.t:.6f}, ' f'h={self.h:.6f}, mis={mis:.4g} ') # debug helpers g_max = np.argmax(abs(dae.g)) inc_max = np.argmax(abs(inc)) self._debug_g(g_max) self._debug_ac(inc_max) break if mis > 1e6 and (mis > 1e6 * self.mis): self.err_msg = 'Error increased too quickly. Convergence not likely.' self.busted = True break if not self.converged: dae.x[:] = np.array(self.x0) dae.y[:] = np.array(self.y0) dae.f[:] = np.array(self.f0) system.vars_to_models() self.last_converged = self.converged return self.converged
def run(self, **kwargs): """ Full Newton-Raphson method. Returns ------- bool convergence status """ system = self.system if self.config.check_conn == 1: self.system.connectivity() self.summary() self.init() if system.dae.m == 0: logger.error("Loaded case contains no power flow element.") system.exit_code = 1 return False t0, _ = elapsed() self.niter = 0 while True: mis = self.nr_step() logger.info('%d: |F(x)| = %.10g', self.niter, mis) if mis < self.config.tol: self.converged = True break if self.niter > self.config.max_iter: break if np.isnan(mis).any(): logger.error('NaN found in solution. Convergence not likely') self.niter = self.config.max_iter + 1 break if mis > 1e4 * self.mis[0]: logger.error('Mismatch increased too fast. Convergence not likely.') break self.niter += 1 _, s1 = elapsed(t0) if not self.converged: if abs(self.mis[-1] - self.mis[-2]) < self.config.tol: max_idx = np.argmax(np.abs(system.dae.xy)) name = system.dae.xy_name[max_idx] logger.error('Mismatch is not correctable possibly due to large load-generation imbalance.') logger.error('Largest mismatch on equation associated with <%s>', name) else: logger.error('Power flow failed after %d iterations for "%s".', self.niter + 1, system.files.case) else: logger.info('Converged in %d iterations in %s.', self.niter + 1, s1) # make a copy of power flow solutions self.x_sol = system.dae.x.copy() self.y_sol = system.dae.y.copy() if self.config.init_tds: system.TDS.init() if self.config.report: system.PFlow.report() system.exit_code = 0 if self.converged else 1 return self.converged
def _itm_step(self): """ Integrate with Implicit Trapezoidal Method (ITM) to the current time. This function has an internal Newton-Raphson loop for algebraized semi-explicit DAE. The function returns the convergence status when done but does NOT progress simulation time. Returns ------- bool Convergence status in ``self.converged``. """ system = self.system dae = self.system.dae self.mis = 1 self.niter = 0 self.converged = False self.x0 = np.array(dae.x) self.y0 = np.array(dae.y) self.f0 = np.array(dae.f) while True: self._fg_update(models=system.exist.pflow_tds) # lazy Jacobian update if dae.t == 0 or self.niter > 3 or (dae.t - self._last_switch_t < 0.2): system.j_update(models=system.exist.pflow_tds) self.solver.factorize = True # TODO: set the `Tf` corresponding to the pegged anti-windup limiters to zero. # Although this should not affect anything since corr. mismatches in `self.qg` are reset to zero # solve implicit trapezoidal method (ITM) integration self.Ac = sparse([[self.Teye - self.h * 0.5 * dae.fx, dae.gx], [-self.h * 0.5 * dae.fy, dae.gy]], 'd') # equation `self.qg[:dae.n] = 0` is the implicit form of differential equations using ITM self.qg[:dae.n] = dae.Tf * (dae.x - self.x0) - self.h * 0.5 * (dae.f + self.f0) # reset the corresponding q elements for pegged anti-windup limiter for item in system.antiwindups: for key, val in item.x_set: np.put(self.qg, key, 0) self.qg[dae.n:] = dae.g if not self.config.linsolve: inc = self.solver.solve(self.Ac, -matrix(self.qg)) else: inc = self.solver.linsolve(self.Ac, -matrix(self.qg)) # check for np.nan first if np.isnan(inc).any(): self.err_msg = 'NaN found in solution. Convergence not likely' self.niter = self.config.max_iter + 1 self.busted = True break # reset small values to reduce chattering inc[np.where(np.abs(inc) < self.tol_zero)] = 0 # set new values dae.x += inc[:dae.n].ravel() dae.y += inc[dae.n: dae.n + dae.m].ravel() system.vars_to_models() # calculate correction mis = np.max(np.abs(inc)) if self.niter == 0: self.mis = mis self.niter += 1 # converged if mis <= self.config.tol: self.converged = True break # non-convergence cases if self.niter > self.config.max_iter: logger.debug(f'Max. iter. {self.config.max_iter} reached for t={dae.t:.6f}, ' f'h={self.h:.6f}, mis={mis:.4g} ') # debug helpers g_max = np.argmax(abs(dae.g)) inc_max = np.argmax(abs(inc)) self._debug_g(g_max) self._debug_ac(inc_max) break if mis > 1000 and (mis > 1e8 * self.mis): self.err_msg = 'Error increased too quickly. Convergence not likely.' self.busted = True break if not self.converged: dae.x = np.array(self.x0) dae.y = np.array(self.y0) dae.f = np.array(self.f0) system.vars_to_models() return self.converged