Source code for cideMOD.simulation_interface.error_check

#
# Copyright (c) 2023 CIDETEC Energy Storage.
#
# This file is part of cideMOD.
#
# cideMOD is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os
from mpi4py import MPI
from numpy import array, ndarray

from cideMOD.numerics.fem_handler import interpolate, assign
from cideMOD.numerics.triggers import SolverCrashed


[docs] class ErrorCheck: # TODO: Adapt ErrorCheck to the modular structure. Ask the models the check their variables. # Move it to cell or models module. def __init__(self, problem, status, name='', debug=False): self.comm = problem._comm self.problem = problem self.subdomains = problem.P1_map.get_subdomains_dofs() if isinstance(status, SolverCrashed) and problem.save_path: self.log = [] try: self.print('\nSolver crashed, performing failure checks...') self.print('\n------------LAST TIMESTEP------------\n') self.check_electrolyte_depleted() self._compute_electrode_charge() self.check_electrode_depleted() self.check_electrode_overloaded() self.check_coeffs() if debug: self.print('\n------------PREVIOUS TIMESTEP------------\n') # FIXME: Pass f to the method and do not reset f_1 assign(self.problem.u_1, self.problem.u_0) self.check_electrolyte_depleted() self._compute_electrode_charge() self.check_electrode_depleted() self.check_electrode_overloaded() self.check_coeffs() except Exception as e: self.print('Error writing error_check file') self.print(str(e)) # raise e # NOTE: Debug only finally: if self.comm.rank == 0: filepath = os.path.join(problem.save_path, f'error_check_{name}.txt') with open(filepath, 'w') as f: f.writelines(self.log) def _compute_electrode_charge(self): if self.problem.model_options.particle_model.startswith('SGM'): var = self.problem._vars x_a = [interpolate(var.x_a_surf[i], self.problem.V) for i in range(var.n_mat_a)] x_c = [interpolate(var.x_c_surf[i], self.problem.V) for i in range(var.n_mat_c)] else: raise NotImplementedError self.x_a = [x.vector.array[self.subdomains.anode] for x in x_a] self.x_c = [x.vector.array[self.subdomains.cathode] for x in x_c]
[docs] def check_electrolyte_depleted(self): """ If electrolyte concentration is zero or below, numeric crashes. """ c_e = interpolate(self.problem._vars.c_e, self.problem.V) c_e = c_e.vector.array[self.subdomains.electrolyte] min_c_e = min(1e12, min(c_e)) min_c_e = self.comm.allreduce(min_c_e, MPI.MIN) if min_c_e <= 0: self.print(f"\tERROR - Electrolyte has depleted!! (min c_e = {min_c_e:.2e})") else: self.print(f"\tOK - Minimum electrolyte concentration {min_c_e:.2e}")
[docs] def check_electrode_overloaded(self): """ If electrode surface concentration exceeds maximum surface concentration, numeric crashes """ for i, x in enumerate(self.x_a): if len(x) > 0: max_x = max(x) else: max_x = 0 max_x_a = self.comm.allreduce(max_x, MPI.MAX) if max_x_a > 1: self.print(f"\tERROR - Anode material {i} overloaded! ", f"(max c_s_a = {100*max_x_a:.2f}%)") else: self.print(f"\tOK - Max Anode material {i} concentration {100*max_x_a:.2f}%") for i, x in enumerate(self.x_c): if len(x) > 0: max_x = max(x) else: max_x = 0 max_x_c = self.comm.allreduce(max_x, MPI.MAX) if max_x_c > 1: self.print(f"\tERROR - Cathode material {i} overloaded! ", f"(max c_s_c = {100*max_x_c:.2f}%)") else: self.print(f"\tOK - Max Cathode material {i} concentration {100*max_x_c:.2f}%")
[docs] def check_electrode_depleted(self): """ If electrode surface concentration is below zero, numeric crashes """ for i, x in enumerate(self.x_a): if len(x) > 0: min_x = min(x) else: min_x = 1 min_x_a = self.comm.allreduce(min_x, MPI.MIN) if min_x_a <= 0: self.print(f"\tERROR - Anode material {i} depleted! ", f"(min c_s_a = {100*min_x_a:.2f}%)") else: self.print(f"\tOK - Min Anode material {i} concentration {100*min_x_a:.2f}%") for i, x in enumerate(self.x_c): if len(x) > 0: min_x = min(x) else: min_x = 1 min_x_c = self.comm.allreduce(min_x, MPI.MIN) if min_x_c <= 0: self.print(f"\tERROR - Cathode material {i} depleted! ", f"(min c_s_c = {100*min_x_c:.2f}%)") else: self.print(f"\tOK - Min Cathode material {i} concentration {100*min_x_c:.2f}%")
[docs] def check_temperatures(self): """ Check temperatures are in a good range 0-60ÂșC """ pass
[docs] def check_coeffs(self): """ Coefficients could be nonlinear and diverge for some values of internal variables, according to their expressions. """ cell = self.problem.cell self.print('\nChecking coeffs') D_e_a = self._check_coeff(cell.anode.D_e, self.subdomains.anode) D_e_s = self._check_coeff(cell.separator.D_e, self.subdomains.separator) D_e_c = self._check_coeff(cell.cathode.D_e, self.subdomains.cathode) self.print("\tElectrolyte diffusivity:") self.print("\t\tAnode: max: {:.2e}, min: {:.2e}".format(D_e_a[0], D_e_a[1])) self.print("\t\tSeparator: max: {:.2e}, min: {:.2e}".format(D_e_s[0], D_e_s[1])) self.print("\t\tCathode: max: {:.2e}, min: {:.2e}".format(D_e_c[0], D_e_c[1])) k_e_a = self._check_coeff(cell.anode.kappa, self.subdomains.anode) k_e_s = self._check_coeff(cell.separator.kappa, self.subdomains.separator) k_e_c = self._check_coeff(cell.cathode.kappa, self.subdomains.cathode) self.print("\tElectrolyte ionic conductivity:") self.print("\t\tAnode: max: {:.2e}, min: {:.2e}".format(k_e_a[0], k_e_a[1])) self.print("\t\tSeparator: max: {:.2e}, min: {:.2e}".format(k_e_s[0], k_e_s[1])) self.print("\t\tCathode: max: {:.2e}, min: {:.2e}".format(k_e_c[0], k_e_c[1])) k_d_e_a = self._check_coeff(cell.anode.kappa_D, self.subdomains.anode) k_d_e_s = self._check_coeff(cell.separator.kappa_D, self.subdomains.separator) k_d_e_c = self._check_coeff(cell.cathode.kappa_D, self.subdomains.cathode) self.print("\tElectrolyte concentration effective conductivity:") self.print("\t\tAnode: max: {:.2e}, min: {:.2e}".format(k_d_e_a[0], k_d_e_a[1])) self.print("\t\tSeparator: max: {:.2e}, min: {:.2e}".format(k_d_e_s[0], k_d_e_s[1])) self.print("\t\tCathode: max: {:.2e}, min: {:.2e}".format(k_d_e_c[0], k_d_e_c[1])) sig_a = self._check_coeff(cell.anode.sigma, self.subdomains.anode) sig_c = self._check_coeff(cell.cathode.sigma, self.subdomains.cathode) self.print("\tElectrode electronic conductivity:") self.print("\t\tAnode: max: {:.2e}, min: {:.2e}".format(sig_a[0], sig_a[1])) self.print("\t\tCathode: max: {:.2e}, min: {:.2e}".format(sig_c[0], sig_c[1]))
def _check_coeff(self, coeff, subdomain): if not isinstance(subdomain, ndarray): subdomain = array(subdomain) v = interpolate(coeff, self.problem.V) v = v.vector.array if subdomain.any(): v = v[subdomain] vmax = v.max() vmin = v.min() else: vmax = -9e99 vmin = 9e99 vmax = self.comm.allreduce(vmax, MPI.MAX) vmin = self.comm.allreduce(vmin, MPI.MIN) return [vmax, vmin]
[docs] def print(self, *args): self.log.append(' '.join(str(arg) for arg in args) + '\n')