def run(self): """ Excute the genetic algorithm. Returns ------- boolean Failure flag; True if failed to converge, False is successful. """ model = self._problem.model ga = self._ga # Size design variables. desvars = self._designvars count = 0 for name, meta in iteritems(desvars): size = meta['size'] self._desvar_idx[name] = (count, count + size) count += size lower_bound = np.empty((count, )) upper_bound = np.empty((count, )) # Figure out bounds vectors. for name, meta in iteritems(desvars): i, j = self._desvar_idx[name] lower_bound[i:j] = meta['lower'] upper_bound[i:j] = meta['upper'] ga.elite = self.options['elitism'] pop_size = self.options['pop_size'] max_gen = self.options['max_gen'] user_bits = self.options['bits'] # Bits of resolution bits = np.ceil(np.log2(upper_bound - lower_bound + 1)).astype(int) prom2abs = model._var_allprocs_prom2abs_list['output'] for name, val in iteritems(user_bits): try: i, j = self._desvar_idx[name] except KeyError: abs_name = prom2abs[name][0] i, j = self._desvar_idx[abs_name] bits[i:j] = val # Automatic population size. if pop_size == 0: pop_size = 4 * np.sum(bits) desvar_new, obj, nfit = ga.execute_ga(lower_bound, upper_bound, bits, pop_size, max_gen, self._randomstate) # Pull optimal parameters back into framework and re-run, so that # framework is left in the right final state for name in desvars: i, j = self._desvar_idx[name] val = desvar_new[i:j] self.set_design_var(name, val) with Recording('SimpleGA', self.iter_count, self) as rec: model._solve_nonlinear() rec.abs = 0.0 rec.rel = 0.0 self.iter_count += 1 return False
def _single_iteration(self): """ Perform the operations in the iteration loop. """ system = self._system() Gm = self._update_inverse_jacobian() fxm = self.fxm delta_xm = -Gm.dot(fxm) if self.linesearch: self._solver_info.append_subsolver() self.set_states(self.xm) self.set_linear_vector(delta_xm) self.linesearch.solve() xm = self.get_vector(system._outputs) self._solver_info.pop() else: # Update the new states in the model. xm = self.xm + delta_xm self.set_states(xm) # Run the model. with Recording('Broyden', 0, self): self._solver_info.append_solver() self._gs_iter() self._solver_info.pop() self._run_apply() fxm1 = fxm.copy() self.fxm = fxm = self.get_vector(system._residuals) delta_fxm = fxm - fxm1 # States may have been further converged hierarchically. xm = self.get_vector(system._outputs) delta_xm = xm - self.xm # Determine whether to update Jacobian. self._recompute_jacobian = False opt = self.options if self._computed_jacobians <= opt['max_jacobians']: converge_ratio = self.compute_norm(fxm) / self.compute_norm(fxm1) if converge_ratio > opt['diverge_limit']: self._recompute_jacobian = True elif converge_ratio > opt['converge_limit']: self._converge_failures += 1 if self._converge_failures >= opt['max_converge_failures']: self._recompute_jacobian = True else: self._converge_failures = 0 # Cache for next iteration. self.delta_xm = delta_xm self.delta_fxm = delta_fxm self.fxm = fxm self.xm = xm self.Gm = Gm
def _solve(self): """ Run the iterative solver. """ maxiter = self.options['maxiter'] atol = self.options['atol'] rtol = self.options['rtol'] iprint = self.options['iprint'] self._mpi_print_header() self._iter_count = 0 norm0, norm = self._iter_initialize() self._norm0 = norm0 self._mpi_print(self._iter_count, norm, norm / norm0) while self._iter_count < maxiter and norm > atol and norm / norm0 > rtol: with Recording(type(self).__name__, self._iter_count, self) as rec: self._single_iteration() self._iter_count += 1 self._run_apply() norm = self._iter_get_norm() # With solvers, we want to record the norm AFTER the call, but the call needs to # be wrapped in the with for stack purposes, so we locally assign norm & norm0 # into the class. rec.abs = norm if norm0 == 0: norm0 = 1 rec.rel = norm / norm0 self._mpi_print(self._iter_count, norm, norm / norm0) system = self._system() if system.comm.rank == 0 or os.environ.get('USE_PROC_FILES'): prefix = self._solver_info.prefix + self.SOLVER # Solver terminated early because a Nan in the norm doesn't satisfy the while-loop # conditionals. if np.isinf(norm) or np.isnan(norm): msg = "Solver '{}' on system '{}': residuals contain 'inf' or 'NaN' after {} " + \ "iterations." if iprint > -1: print(prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver hit maxiter without meeting desired tolerances. elif (norm > atol and norm / norm0 > rtol): msg = "Solver '{}' on system '{}' failed to converge in {} iterations." if iprint > -1: print(prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver converged elif iprint == 1: print(prefix + ' Converged in {} iterations'.format(self._iter_count)) elif iprint == 2: print(prefix + ' Converged')
def _solve_linear(self, vec_names, mode, rel_systems): """ Apply inverse jac product. The model is assumed to be in a scaled state. Parameters ---------- vec_names : [str, ...] list of names of the right-hand-side vectors. mode : str 'fwd' or 'rev'. rel_systems : set of str Set of names of relevant systems based on the current linear solve. Returns ------- boolean Failure flag; True if failed to converge, False is successful. float absolute error. float relative error. """ if self._linear_solver is not None: with Recording(self.pathname + '._solve_linear', self.iter_count, self): result = self._linear_solver.solve(vec_names, mode, rel_systems) return result else: failed = False abs_errors = [0.0] rel_errors = [0.0] for vec_name in vec_names: if vec_name not in self._rel_vec_names: continue d_outputs = self._vectors['output'][vec_name] d_residuals = self._vectors['residual'][vec_name] with self._unscaled_context(outputs=[d_outputs], residuals=[d_residuals]): with Recording(self.pathname + '._solve_linear', self.iter_count, self): if d_outputs._ncol > 1: if self.has_solve_multi_linear: result = self.solve_multi_linear( d_outputs, d_residuals, mode) else: for i in range(d_outputs._ncol): # need to make the multivecs look like regular single vecs # since the component doesn't know about multivecs. d_outputs._icol = i d_residuals._icol = i result = self.solve_linear( d_outputs, d_residuals, mode) if isinstance(result, bool): failed |= result elif result is not None: failed = failed or result[0] abs_errors.append(result[1]) rel_errors.append(result[2]) d_outputs._icol = None d_residuals._icol = None else: result = self.solve_linear(d_outputs, d_residuals, mode) if isinstance(result, bool): failed |= result elif result is not None: failed = failed or result[0] abs_errors.append(result[1]) rel_errors.append(result[2]) return failed, np.linalg.norm(abs_errors), np.linalg.norm( rel_errors)
def _run_iterator(self): """ Run the iterative solver. Returns ------- boolean Failure flag; True if failed to converge, False is successful. float absolute error. float relative error. """ maxiter = self.options['maxiter'] atol = self.options['atol'] rtol = self.options['rtol'] iprint = self.options['iprint'] self._mpi_print_header() self._iter_count = 0 norm0, norm = self._iter_initialize() self._norm0 = norm0 self._mpi_print(self._iter_count, norm, norm / norm0) while self._iter_count < maxiter and \ norm > atol and norm / norm0 > rtol: with Recording(type(self).__name__, self._iter_count, self) as rec: self._iter_execute() self._iter_count += 1 self._run_apply() norm = self._iter_get_norm() # With solvers, we want to record the norm AFTER the call, but the call needs to # be wrapped in the with for stack purposes, so we locally assign norm & norm0 # into the class. rec.abs = norm rec.rel = norm / norm0 if norm0 == 0: norm0 = 1 self._mpi_print(self._iter_count, norm, norm / norm0) fail = (np.isinf(norm) or np.isnan(norm) or (norm > atol and norm / norm0 > rtol)) if self._system.comm.rank == 0 or os.environ.get('USE_PROC_FILES'): prefix = self._solver_info.prefix + self.SOLVER if fail: if iprint > -1: msg = ' Failed to Converge in {} iterations'.format( self._iter_count) print(prefix + msg) # Raise AnalysisError if requested. if self.options['err_on_maxiter']: msg = "Solver '{}' on system '{}' failed to converge." raise AnalysisError( msg.format(self.SOLVER, self._system.pathname)) elif iprint == 1: print(prefix + ' Converged in {} iterations'.format(self._iter_count)) elif iprint == 2: print(prefix + ' Converged') return fail, norm, norm / norm0
def _iter_execute(self): """ Perform the operations in the iteration loop. """ system = self._system Gm = self._update_inverse_jacobian() fxm = self.fxm delta_xm = -Gm.dot(fxm) if self.linesearch: self._solver_info.append_subsolver() self.set_linear_vector(delta_xm) self.linesearch.solve() xm = self.get_states() self._solver_info.pop() else: # Update the new states in the model. xm = self.xm + delta_xm self.set_states(xm) # Run the model. with Recording('Broyden', 0, self): self._solver_info.append_solver() for isub, subsys in enumerate(system._subsystems_allprocs): system._transfer('nonlinear', 'fwd', isub) if subsys in system._subsystems_myproc: subsys._solve_nonlinear() self._solver_info.pop() self._run_apply() fxm1 = fxm.copy() fxm = self.get_residuals() delta_fxm = fxm - fxm1 # States may have been further converged hierarchically. xm = self.get_states() delta_xm = xm - self.xm # Determine whether to update Jacobian. self._recompute_jacobian = False opt = self.options if self._computed_jacobians <= opt['max_jacobians']: converge_ratio = np.linalg.norm(fxm) / np.linalg.norm(fxm1) if converge_ratio > opt['diverge_limit']: self._recompute_jacobian = True elif converge_ratio > opt['converge_limit']: self._converge_failures += 1 if self._converge_failures >= opt['max_converge_failures']: self._recompute_jacobian = True else: self._converge_failures = 0 # Cache for next iteration. self.delta_xm = delta_xm self.delta_fxm = delta_fxm self.fxm = fxm self.xm = xm self.Gm = Gm
def _apply_linear(self, vec_names, rel_systems, mode, scope_out=None, scope_in=None): """ Compute jac-vec product. The model is assumed to be in a scaled state. Parameters ---------- vec_names : [str, ...] list of names of the right-hand-side vectors. rel_systems : set of str Set of names of relevant systems based on the current linear solve. mode : str 'fwd' or 'rev'. scope_out : set or None Set of absolute output names in the scope of this mat-vec product. If None, all are in the scope. scope_in : set or None Set of absolute input names in the scope of this mat-vec product. If None, all are in the scope. """ for vec_name in vec_names: if vec_name not in self._rel_vec_names: continue with self._matvec_context(vec_name, scope_out, scope_in, mode) as vecs: d_inputs, d_outputs, d_residuals = vecs # Jacobian and vectors are all scaled, unitless with self.jacobian_context() as J: J._apply(d_inputs, d_outputs, d_residuals, mode) # if we're not matrix free, we can skip the bottom of # this loop because apply_linear does nothing. if not self.matrix_free: continue # Jacobian and vectors are all unscaled, dimensional with self._unscaled_context(outputs=[self._outputs, d_outputs], residuals=[d_residuals]): with Recording(self.pathname + '._apply_linear', self.iter_count, self): if d_inputs._ncol > 1: if self.has_apply_multi_linear: self.apply_multi_linear( self._inputs, self._outputs, d_inputs, d_outputs, d_residuals, mode) else: for i in range(d_inputs._ncol): # need to make the multivecs look like regular single vecs # since the component doesn't know about multivecs. d_inputs._icol = i d_outputs._icol = i d_residuals._icol = i self.apply_linear(self._inputs, self._outputs, d_inputs, d_outputs, d_residuals, mode) d_inputs._icol = None d_outputs._icol = None d_residuals._icol = None else: self.apply_linear(self._inputs, self._outputs, d_inputs, d_outputs, d_residuals, mode)
def _solve(self): """ Run the iterative solver. """ maxiter = self.options['maxiter'] atol = self.options['atol'] rtol = self.options['rtol'] iprint = self.options['iprint'] self._mpi_print_header() self._iter_count = 0 norm0, norm = self._iter_initialize() self._norm0 = norm0 self._mpi_print(self._iter_count, norm, norm / norm0) while self._iter_count < maxiter and norm > atol and norm / norm0 > rtol: with Recording(type(self).__name__, self._iter_count, self) as rec: self._single_iteration() self._iter_count += 1 self._run_apply() norm = self._iter_get_norm() # Save the norm values in the context manager so they can also be recorded. rec.abs = norm if norm0 == 0: norm0 = 1 rec.rel = norm / norm0 self._mpi_print(self._iter_count, norm, norm / norm0) system = self._system() # flag for the print statements. we only print on root if USE_PROC_FILES is not set to True print_flag = system.comm.rank == 0 or os.environ.get('USE_PROC_FILES') prefix = self._solver_info.prefix + self.SOLVER # Solver terminated early because a Nan in the norm doesn't satisfy the while-loop # conditionals. if np.isinf(norm) or np.isnan(norm): msg = "Solver '{}' on system '{}': residuals contain 'inf' or 'NaN' after {} " + \ "iterations." if iprint > -1 and print_flag: print( prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver hit maxiter without meeting desired tolerances. elif (norm > atol and norm / norm0 > rtol): msg = "Solver '{}' on system '{}' failed to converge in {} iterations." if iprint > -1 and print_flag: print( prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver converged elif iprint == 1 and print_flag: print(prefix + ' Converged in {} iterations'.format(self._iter_count)) elif iprint == 2 and print_flag: print(prefix + ' Converged')
def _solve(self): """ Run the iterative solver. """ maxiter = self.options['maxiter'] atol = self.options['atol'] rtol = self.options['rtol'] iprint = self.options['iprint'] stall_limit = self.options['stall_limit'] stall_tol = self.options['stall_tol'] self._mpi_print_header() self._iter_count = 0 norm0, norm = self._iter_initialize() self._norm0 = norm0 self._mpi_print(self._iter_count, norm, norm / norm0) stalled = False stall_count = 0 if stall_limit > 0: stall_norm = norm0 while self._iter_count < maxiter and norm > atol and norm / norm0 > rtol and not stalled: with Recording(type(self).__name__, self._iter_count, self) as rec: if stall_count == 3 and not self.linesearch.options[ 'print_bound_enforce']: self.linesearch.options['print_bound_enforce'] = True if self._system().pathname: pathname = f"{self._system().pathname}." else: pathname = "" msg = ( f"Your model has stalled three times and may be violating the bounds. " f"In the future, turn on print_bound_enforce in your solver options " f"here: \n{pathname}nonlinear_solver.linesearch.options" f"['print_bound_enforce']=True. " f"\nThe bound(s) being violated now are:\n") issue_warning(msg, category=SolverWarning) self._single_iteration() self.linesearch.options['print_bound_enforce'] = False else: self._single_iteration() self._iter_count += 1 self._run_apply() norm = self._iter_get_norm() # Save the norm values in the context manager so they can also be recorded. rec.abs = norm if norm0 == 0: norm0 = 1 rec.rel = norm / norm0 # Check if convergence is stalled. if stall_limit > 0: rel_norm = rec.rel norm_diff = np.abs(stall_norm - rel_norm) if norm_diff <= stall_tol: stall_count += 1 if stall_count >= stall_limit: stalled = True else: stall_count = 0 stall_norm = rel_norm self._mpi_print(self._iter_count, norm, norm / norm0) system = self._system() # flag for the print statements. we only print on root if USE_PROC_FILES is not set to True print_flag = system.comm.rank == 0 or os.environ.get('USE_PROC_FILES') prefix = self._solver_info.prefix + self.SOLVER # Solver terminated early because a Nan in the norm doesn't satisfy the while-loop # conditionals. if np.isinf(norm) or np.isnan(norm): msg = "Solver '{}' on system '{}': residuals contain 'inf' or 'NaN' after {} " + \ "iterations." if iprint > -1 and print_flag: print( prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver hit maxiter without meeting desired tolerances. # Or solver stalled. elif (norm > atol and norm / norm0 > rtol) or stalled: if stalled: msg = "Solver '{}' on system '{}' stalled after {} iterations." else: msg = "Solver '{}' on system '{}' failed to converge in {} iterations." if iprint > -1 and print_flag: print( prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError( msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver converged elif iprint == 1 and print_flag: print(prefix + ' Converged in {} iterations'.format(self._iter_count)) elif iprint == 2 and print_flag: print(prefix + ' Converged')
def _apply_linear(self, jac, vec_names, rel_systems, mode, scope_out=None, scope_in=None): """ Compute jac-vec product. The model is assumed to be in a scaled state. Parameters ---------- jac : Jacobian or None If None, use local jacobian, else use jac. vec_names : [str, ...] list of names of the right-hand-side vectors. rel_systems : set of str Set of names of relevant systems based on the current linear solve. mode : str 'fwd' or 'rev'. scope_out : set or None Set of absolute output names in the scope of this mat-vec product. If None, all are in the scope. scope_in : set or None Set of absolute input names in the scope of this mat-vec product. If None, all are in the scope. """ J = self._jacobian if jac is None else jac with Recording(self.pathname + '._apply_linear', self.iter_count, self): for vec_name in vec_names: if vec_name not in self._rel_vec_names: continue with self._matvec_context(vec_name, scope_out, scope_in, mode) as vecs: d_inputs, d_outputs, d_residuals = vecs # Jacobian and vectors are all scaled, unitless with self.jacobian_context(J): J._apply(d_inputs, d_outputs, d_residuals, mode) # if we're not matrix free, we can skip the bottom of # this loop because compute_jacvec_product does nothing. if not self.matrix_free: continue # Jacobian and vectors are all unscaled, dimensional with self._unscaled_context(outputs=[self._outputs], residuals=[d_residuals]): # set appropriate vectors to read_only to help prevent user error self._inputs.read_only = True if mode == 'fwd': d_inputs.read_only = True elif mode == 'rev': d_residuals.read_only = True try: # We used to negate the residual here, and then re-negate after the hook if d_inputs._ncol > 1: if self.supports_multivecs: self.compute_multi_jacvec_product( self._inputs, d_inputs, d_residuals, mode) else: for i in range(d_inputs._ncol): # need to make the multivecs look like regular single vecs # since the component doesn't know about multivecs. d_inputs._icol = i d_residuals._icol = i self.compute_jacvec_product( self._inputs, d_inputs, d_residuals, mode) d_inputs._icol = None d_residuals._icol = None else: self.compute_jacvec_product( self._inputs, d_inputs, d_residuals, mode) finally: self._inputs.read_only = False d_inputs.read_only = d_residuals.read_only = False
def solve(self, vec_names, mode, rel_systems=None): """ Run the solver. Parameters ---------- vec_names : [str, ...] list of names of the right-hand-side vectors. mode : str 'fwd' or 'rev'. rel_systems : set of str Names of systems relevant to the current solve. Returns ------- boolean Failure flag; True if failed to converge, False is successful. float absolute error. float relative error. """ if len(vec_names) > 1: raise RuntimeError( "DirectSolvers with multiple right-hand-sides are not supported." ) self._vec_names = vec_names system = self._system with Recording('DirectSolver', 0, self) as rec: for vec_name in vec_names: if vec_name not in system._rel_vec_names: continue self._vec_name = vec_name d_residuals = system._vectors['residual'][vec_name] d_outputs = system._vectors['output'][vec_name] # assign x and b vectors based on mode if mode == 'fwd': x_vec = d_outputs b_vec = d_residuals trans_lu = 0 trans_splu = 'N' else: # rev x_vec = d_residuals b_vec = d_outputs trans_lu = 1 trans_splu = 'T' # AssembledJacobians are unscaled. if self._assembled_jac is not None: with system._unscaled_context(outputs=[d_outputs], residuals=[d_residuals]): b_data = b_vec.get_data() if (isinstance(self._assembled_jac._int_mtx, (COOMatrix, CSRMatrix, CSCMatrix))): x_data = self._lu.solve(b_data, trans_splu) else: x_data = scipy.linalg.lu_solve(self._lup, b_data, trans=trans_lu) x_vec.set_data(x_data) # MVP-generated jacobians are scaled. else: b_data = b_vec.get_data() x_data = scipy.linalg.lu_solve(self._lup, b_data, trans=trans_lu) x_vec.set_data(x_data) rec.abs = 0.0 rec.rel = 0.0 return False, 0., 0.
def _solve(self): """ Run the iterative solver. """ maxiter = self.options['maxiter'] atol = self.options['atol'] rtol = self.options['rtol'] iprint = self.options['iprint'] stall_limit = self.options['stall_limit'] stall_tol = self.options['stall_tol'] self._mpi_print_header() self._iter_count = 0 norm0, norm = self._iter_initialize() self._norm0 = norm0 self._mpi_print(self._iter_count, norm, norm / norm0) stalled = False if stall_limit > 0: stall_count = 0 stall_norm = norm0 while self._iter_count < maxiter and norm > atol and norm / norm0 > rtol and not stalled: with Recording(type(self).__name__, self._iter_count, self) as rec: self._single_iteration() self._iter_count += 1 self._run_apply() norm = self._iter_get_norm() # Save the norm values in the context manager so they can also be recorded. rec.abs = norm if norm0 == 0: norm0 = 1 rec.rel = norm / norm0 # Check if convergence is stalled. if stall_limit > 0: rel_norm = rec.rel norm_diff = np.abs(stall_norm - rel_norm) if norm_diff <= stall_tol: stall_count += 1 if stall_count >= stall_limit: stalled = True else: stall_count = 0 stall_norm = rel_norm self._mpi_print(self._iter_count, norm, norm / norm0) system = self._system() if system.comm.rank == 0 or os.environ.get('USE_PROC_FILES'): prefix = self._solver_info.prefix + self.SOLVER # Solver terminated early because a Nan in the norm doesn't satisfy the while-loop # conditionals. if np.isinf(norm) or np.isnan(norm): msg = "Solver '{}' on system '{}': residuals contain 'inf' or 'NaN' after {} " + \ "iterations." if iprint > -1: print(prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError(msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver hit maxiter without meeting desired tolerances. # Or solver stalled. elif (norm > atol and norm / norm0 > rtol) or stalled: if stalled: msg = "Solver '{}' on system '{}' stalled after {} iterations." else: msg = "Solver '{}' on system '{}' failed to converge in {} iterations." if iprint > -1: print(prefix + msg.format(self.SOLVER, system.pathname, self._iter_count)) # Raise AnalysisError if requested. if self.options['err_on_non_converge']: raise AnalysisError(msg.format(self.SOLVER, system.pathname, self._iter_count)) # Solver converged elif iprint == 1: print(prefix + ' Converged in {} iterations'.format(self._iter_count)) elif iprint == 2: print(prefix + ' Converged')