diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 479f0ca8..d78f64b5 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -6,6 +6,8 @@ on:
- '**'
tags:
- '**'
+ pull_request:
+ branches: [ master ]
jobs:
tests:
@@ -24,6 +26,14 @@ jobs:
# Upgrade to latest meson and ninja
sudo pip install --upgrade meson ninja
+ # needs fenics only to run test_poisson_nernst_planck_solver_fenics
+ - name: install_fenics
+ run: |
+ sudo apt-get install -y --no-install-recommends software-properties-common
+ sudo add-apt-repository -y ppa:fenics-packages/fenics
+ sudo apt-get update -qy
+ sudo apt-get install -y fenics python3-dolfin python3-mshr
+
- name: build_c
run: |
sudo pip install .[test]
diff --git a/.github/workflows/tests_fenics.yml b/.github/workflows/tests_fenics.yml
deleted file mode 100644
index 645257ea..00000000
--- a/.github/workflows/tests_fenics.yml
+++ /dev/null
@@ -1,66 +0,0 @@
-name: Test electrochemistry examples including fenics functionality
-
-on:
- push:
- branches:
- - '**'
- tags:
- - '**'
-
-jobs:
- tests:
-
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- - name: install_python
- run: |
- sudo apt-get update -qy
- sudo apt-get install -y python3-dev python3-pip python3-venv libxml2-dev libxslt-dev zlib1g-dev
- # Upgrade to latest meson and ninja
- sudo pip install --upgrade meson ninja
- sudo pip install --upgrade jupyter jupytext pandas
-
- - name: install_fenics
- run: |
- sudo apt-get install -y --no-install-recommends software-properties-common
- sudo add-apt-repository -y ppa:fenics-packages/fenics
- sudo apt-get update -qy
- sudo apt-get install -y fenics python3-dolfin python3-mshr
-
- - name: install matscipy
- run: |
- sudo pip install .
-
- - name: build sample ipynb and html
- run: |
- cd examples/electrochemistry
-
- # check that all sample html files are produced successfully
- html_files=(./*.html)
- rm *.html
-
- ls -1 *.py | grep -v to_html.py | bash to_html.sh
-
- for f in "${html_files[@]}"; do
- if ! test -f "$f"; then
- echo "Sample $f creation failed."
- exit 1
- fi
- done
-
- # use tar here, see https://github.com/actions/upload-artifact/issues/38
- - name: Pack build assets
- run: |
- bash -c "tar -cvf electrochemistry-sample-ipynb.tar examples/electrochemistry/*.ipynb examples/electrochemistry/*.html"
-
- - name: Upload sample ipynb artifact
- uses: actions/upload-artifact@v3
- with:
- name: electrochemistry-sample-ipynb
- path: electrochemistry-sample-ipynb.tar
- if-no-files-found: error
\ No newline at end of file
diff --git a/docs/applications/electrochemistry_1.py b/docs/applications/electrochemistry_1.py
index 6dad6907..edb6d9ef 100644
--- a/docs/applications/electrochemistry_1.py
+++ b/docs/applications/electrochemistry_1.py
@@ -55,10 +55,10 @@
pnp = PoissonNernstPlanckSystem(c=c, z=z, L=L, delta_u=delta_u)
# %% [markdown]
-# The method `useStandardInterfaceBC` will apply boundary conditions as shown in Figure 1, with Dirichlet boundary conditions on the potential and zero total flux boundary conditions on ion diffusion and electromigration.
+# The method `use_standard_interface_bc` will apply boundary conditions as shown in Figure 1, with Dirichlet boundary conditions on the potential and zero total flux boundary conditions on ion diffusion and electromigration.
# %%
-pnp.useStandardInterfaceBC()
+pnp.use_standard_interface_bc()
ui, nij, _ = pnp.solve()
# %% [markdown]
@@ -188,7 +188,7 @@ def make_patch_spines_invisible(ax):
# The only difference is the application of another set of predefined boundary conditions.
# %%
-pnp.useStandardCellBC()
+pnp.use_standard_cell_bc()
# %% [markdown]
# These boundary conditions assume no Stern layer, i.e. $\lambda_S = 0$
@@ -309,7 +309,7 @@ def make_patch_spines_invisible(ax):
# The following applies zero flux and, particularly, Robin boundary conditions as shown in Figure 2.
# %%
-pnp.useSternLayerCellBC()
+pnp.use_stern_layer_cell_bc()
pnp.output = True
xij = pnp.solve()
diff --git a/docs/applications/electrochemistry_2.py b/docs/applications/electrochemistry_2.py
index b5bc6af3..d29af178 100644
--- a/docs/applications/electrochemistry_2.py
+++ b/docs/applications/electrochemistry_2.py
@@ -63,12 +63,12 @@ def make_patch_spines_invisible(ax):
c, z, L, delta_u=delta_u, N=N,
solver="hybr", options={'xtol':1e-12})
-pnp['std_interface'].useStandardInterfaceBC()
+pnp['std_interface'].use_standard_interface_bc()
uij, nij, lamj = pnp['std_interface'].solve()
# define desired system
pnp['fenics_interface'] = PoissonNernstPlanckSystemFEniCS(c, z, L, delta_u=delta_u, N=N)
-pnp['fenics_interface'].useStandardInterfaceBC()
+pnp['fenics_interface'].use_standard_interface_bc()
uij, nij, _ = pnp['fenics_interface'].solve()
# %% [markdown]
@@ -184,7 +184,7 @@ def make_patch_spines_invisible(ax):
pnp['std_interface_high_potential'] = PoissonNernstPlanckSystem(
c, z, L, delta_u=delta_u,N=N,
solver="hybr", options={'xtol':1e-14})
-pnp['std_interface_high_potential'].useStandardInterfaceBC()
+pnp['std_interface_high_potential'].use_standard_interface_bc()
uij, nij, lamj = pnp['std_interface_high_potential'].solve()
# %% [markdown]
@@ -192,7 +192,7 @@ def make_patch_spines_invisible(ax):
# %%
pnp['fenics_interface_high_potential'] = PoissonNernstPlanckSystemFEniCS(c, z, L, delta_u=delta_u, N=200)
-pnp['fenics_interface_high_potential'].useStandardInterfaceBC()
+pnp['fenics_interface_high_potential'].use_standard_interface_bc()
uij, nij, _ = pnp['fenics_interface_high_potential'].solve()
# %% [markdown]
diff --git a/matscipy/cli/electrochemistry/poisson_nernst_planck_solver_cli.py b/matscipy/cli/electrochemistry/poisson_nernst_planck_solver_cli.py
index 83c234fc..add7614b 100644
--- a/matscipy/cli/electrochemistry/poisson_nernst_planck_solver_cli.py
+++ b/matscipy/cli/electrochemistry/poisson_nernst_planck_solver_cli.py
@@ -164,6 +164,37 @@ class ArgumentDefaultsAndRawDescriptionHelpFormatter(
logger = logging.getLogger()
logger.setLevel(loglevel)
+ # use FEniCS finite element solver if available,
+ # otherwise own controlled volume scheme
+ try:
+ import fenics
+ from matscipy.electrochemistry.poisson_nernst_planck_solver_fenics \
+ import PoissonNernstPlanckSystemFEniCS as PoissonNernstPlanckSystem
+ import dolfin
+ import ffc
+ dolfin.cpp.log.set_log_level(loglevel)
+
+ if not args.outfile:
+ # fenics writes some log messages to stdout. If we pipe the output,
+ # we don't want that, hence here we have to suppress fenics logging
+ # if no output file has been specified
+ ffc.log.set_level(logging.ERROR)
+ fenics.set_log_level(logging.ERROR)
+ fenics.set_log_active(False)
+ dolfin.cpp.log.set_log_level(logging.ERROR)
+ dolfin.cpp.log.set_log_active(False)
+
+ logging.getLogger('UFL').setLevel(logging.ERROR)
+ logging.getLogger('FFC').setLevel(logging.ERROR)
+
+ logger.info("Will use FEniCS finite element solver.")
+ except ModuleNotFoundError:
+ logger.warning(
+ "No FEniCS finite element solver found,"
+ " falling back to internal controlled-volume implementation."
+ " ATTENTION: Number conservation not exact.")
+ from matscipy.electrochemistry import PoissonNernstPlanckSystem
+
# remove all handlers
for h in logger.handlers:
logger.removeHandler(h)
@@ -181,22 +212,6 @@ class ArgumentDefaultsAndRawDescriptionHelpFormatter(
fh.setLevel(loglevel)
logger.addHandler(fh)
- # use FEniCS finite element solver if available,
- # otherwise own controlled volume scheme
- try:
- import fenics
- from matscipy.electrochemistry.poisson_nernst_planck_solver_fenics \
- import PoissonNernstPlanckSystemFEniCS as PoissonNernstPlanckSystem
- import dolfin
- dolfin.cpp.log.set_log_level(loglevel)
- logger.info("Will use FEniCS finite element solver.")
- except ModuleNotFoundError:
- logger.warning(
- "No FEniCS finite element solver found,"
- " falling back to internal controlled-volume implementation."
- " ATTENTION: Number conservation not exact.")
- from matscipy.electrochemistry import PoissonNernstPlanckSystem
-
# set up system
pnp = PoissonNernstPlanckSystem(
c=np.array(args.concentrations, dtype=float),
@@ -211,22 +226,16 @@ class ArgumentDefaultsAndRawDescriptionHelpFormatter(
relative_permittivity=float(args.relative_permittivity))
if args.boundary_conditions in ('cell-robin') and float(args.lambda_S) > 0:
- pnp.useSternLayerCellBC()
+ pnp.use_stern_layer_cell_bc()
elif ((args.boundary_conditions in ('cell-robin') and float(args.lambda_S) == 0)
or args.boundary_conditions == 'cell'):
- pnp.useStandardCellBC()
+ pnp.use_standard_cell_bc()
elif args.boundary_conditions == 'interface':
- pnp.useStandardInterfaceBC()
+ pnp.use_standard_interface_bc()
else:
raise ValueError("Boundary conditions '{}' not implemented!".format(
args.boundary_conditions))
- if not args.outfile:
- # fenics writes some log messages to stdout. If we pipe the output,
- # we don't want that, hence here we have to suppress fenics logging
- # if no output file has been specified
- dolfin.cpp.log.set_log_active(False)
-
pnp.solve()
extra_kwargs = {}
diff --git a/matscipy/electrochemistry/continuous2discrete.py b/matscipy/electrochemistry/continuous2discrete.py
index ac174e6d..09cb27f2 100644
--- a/matscipy/electrochemistry/continuous2discrete.py
+++ b/matscipy/electrochemistry/continuous2discrete.py
@@ -289,7 +289,7 @@ def generate_structure(distribution, box=np.array([50, 50, 100]), count=100, n_g
Z, _ = integrate.quad(d, support[-1][0], support[-1][1])
else: # discrete support
support.append(np.linspace(0, box[k], n_gridpoints[k]))
- Z = np.sum(d(support[-1])) # Normalization constant
+ Z = np.sum(d(support[-1])) # Normalization constant
logger.info("Normalizing 'distribution' {} by {}.".format(d, Z))
normalized_distribution.append(
diff --git a/matscipy/electrochemistry/poisson_boltzmann_distribution.py b/matscipy/electrochemistry/poisson_boltzmann_distribution.py
index 415b4829..6ffa9770 100644
--- a/matscipy/electrochemistry/poisson_boltzmann_distribution.py
+++ b/matscipy/electrochemistry/poisson_boltzmann_distribution.py
@@ -86,8 +86,7 @@ def debye(c, z,
lambda_D : float
Debye length, sqrt( epsR*eps*R*T/(2*F^2*I) ) [m]
"""
- I = ionic_strength(c, z)
- return np.sqrt(relative_permittivity*vacuum_permittivity*R*T/(2.0*F**2*I))
+ return np.sqrt(relative_permittivity*vacuum_permittivity*R*T/(2.0*F**2*ionic_strength(c, z)))
def gamma(u, T=298.15):
diff --git a/matscipy/electrochemistry/poisson_nernst_planck_solver.py b/matscipy/electrochemistry/poisson_nernst_planck_solver.py
index 0e3887d9..78f88d40 100644
--- a/matscipy/electrochemistry/poisson_nernst_planck_solver.py
+++ b/matscipy/electrochemistry/poisson_nernst_planck_solver.py
@@ -225,7 +225,6 @@ def solver_callback(self, xij, *_):
self.convergenceStepRelative = []
self.convergenceResidualAbsolute = []
-
dxij = np.zeros(self.N)
self.xij.append(xij)
@@ -257,7 +256,7 @@ def discretize(self):
"""Sets up discretization scheme and initial value"""
# indices
self.Ni = self.N+1
- I = np.arange(self.Ni)
+ index_i = np.arange(self.Ni)
self.logger.info('{:<{lwidth}s} {:> 8.4g}'.format(
'discretization segments N', self.N, lwidth=self.label_width))
@@ -271,7 +270,7 @@ def discretize(self):
'dx', self.dx, lwidth=self.label_width))
# positions (scaled)
- self.X = self.x0_scaled + I*self.dx
+ self.X = self.x0_scaled + index_i * self.dx
# Boundary & initial values
@@ -289,9 +288,6 @@ def discretize(self):
if self.zi0 is None:
self.zi0 = np.kron(self.z, np.ones((self.Ni, 1))).T # does not change
-
- # self.initial_values()
-
def initial_values(self):
"""
Solves decoupled linear system to get initial potential distribution.
@@ -379,7 +375,7 @@ def solve(self):
return self.uij, self.nij, self.lamj
# standard sets of boundary conditions:
- def useStandardInterfaceBC(self):
+ def use_standard_interface_bc(self):
"""Interface at left hand side and open bulk at right hand side"""
self.boundary_conditions = []
@@ -392,24 +388,24 @@ def useStandardInterfaceBC(self):
'Right hand side Dirichlet boundary condition: u1 = {:> 8.4g}'.format(self.u1))
self.boundary_conditions.extend([
- lambda x: self.leftPotentialDirichletBC(x, self.u0),
- lambda x: self.rightPotentialDirichletBC(x, self.u1)])
+ lambda x: self.left_potential_dirichlet_bc(x, self.u0),
+ lambda x: self.right_potential_dirichlet_bc(x, self.u1)])
for k in range(self.M):
self.logger.info(
'Ion species {:02d} left hand side concentration Flux boundary condition: j0 = {:> 8.4g}'.format(
- k,0))
+ k, 0))
self.logger.info(
'Ion species {:02d} right hand side concentration Dirichlet boundary condition: c1 = {:> 8.4g}'.format(
k, self.c_scaled[k]))
self.boundary_conditions.extend([
- lambda x, k=k: self.leftControlledVolumeSchemeFluxBC(x, k),
- lambda x, k=k: self.rightDirichletBC(x, k, self.c_scaled[k])])
+ lambda x, k=k: self.left_controlled_volume_scheme_flux_bc(x, k),
+ lambda x, k=k: self.right_dirichlet_bc(x, k, self.c_scaled[k])])
# counter-intuitive behavior of lambda in loop:
# https://stackoverflow.com/questions/2295290/what-do-lambda-function-closures-capture
# workaround: default parameter k=k
- def useStandardCellBC(self):
+ def use_standard_cell_bc(self):
"""Interfaces at left hand side and right hand side"""
self.boundary_conditions = []
@@ -421,8 +417,8 @@ def useStandardCellBC(self):
self.logger.info('{:>{lwidth}s} u1 = {:< 8.4g}'.format(
'Right hand side Dirichlet boundary condition', self.u1, lwidth=self.label_width))
self.boundary_conditions.extend([
- lambda x: self.leftPotentialDirichletBC(x, self.u0),
- lambda x: self.rightPotentialDirichletBC(x, self.u1)])
+ lambda x: self.left_potential_dirichlet_bc(x, self.u0),
+ lambda x: self.right_potential_dirichlet_bc(x, self.u1)])
N0 = self.L_scaled*self.c_scaled # total amount of species in cell
for k in range(self.M):
@@ -434,10 +430,10 @@ def useStandardCellBC(self):
N0[k], lwidth=self.label_width))
self.boundary_conditions.extend([
- lambda x, k=k: self.leftControlledVolumeSchemeFluxBC(x, k),
- lambda x, k=k, N0=N0[k]: self.numberConservationConstraint(x, k, N0)])
+ lambda x, k=k: self.left_controlled_volume_scheme_flux_bc(x, k),
+ lambda x, k=k, N0=N0[k]: self.number_conservation_constraint(x, k, N0)])
- def useSternLayerCellBC(self, implicit=False):
+ def use_stern_layer_cell_bc(self, implicit=False):
"""Interfaces at left hand side and right hand side,
Stern layer either by prescribing linear potential regime between cell
boundary and outer Helmholtz plane (OHP), or by applying Robin BC;
@@ -466,8 +462,8 @@ def useSternLayerCellBC(self, implicit=False):
self.logger.info('{:>{lwidth}s} u1 + lambda_S*dudx = {:< 8.4g}'.format(
'Right hand side Robin boundary condition', self.u1, lwidth=self.label_width))
self.boundary_conditions.extend([
- lambda x: self.leftPotentialRobinBC(x, self.lambda_S_scaled, self.u0),
- lambda x: self.rightPotentialRobinBC(x, self.lambda_S_scaled, self.u1)])
+ lambda x: self.left_potential_robin_bc(x, self.lambda_S_scaled, self.u0),
+ lambda x: self.right_potential_robin_bc(x, self.lambda_S_scaled, self.u1)])
else: # explicitly treat Stern layer via linear regime
self.logger.info('Explicitly treating Stern layer as uniformly charged regions')
@@ -481,9 +477,8 @@ def useSternLayerCellBC(self, implicit=False):
self.logger.info('{:>{lwidth}s} u1 = {:< 8.4g}'.format(
'Right hand side Dirichlet boundary condition', self.u1, lwidth=self.label_width))
self.boundary_conditions.extend([
- lambda x: self.leftPotentialDirichletBC(x, self.u0),
- lambda x: self.rightPotentialDirichletBC(x, self.u1)])
-
+ lambda x: self.left_potential_dirichlet_bc(x, self.u0),
+ lambda x: self.right_potential_dirichlet_bc(x, self.u1)])
N0 = self.L_scaled*self.c_scaled # total amount of species in cell
for k in range(self.M):
@@ -495,11 +490,11 @@ def useSternLayerCellBC(self, implicit=False):
N0[k], lwidth=self.label_width))
self.boundary_conditions.extend([
- lambda x, k=k: self.leftControlledVolumeSchemeFluxBC(x, k),
- lambda x, k=k, N0=N0[k]: self.numberConservationConstraint(x, k, N0)])
+ lambda x, k=k: self.left_controlled_volume_scheme_flux_bc(x, k),
+ lambda x, k=k, N0=N0[k]: self.number_conservation_constraint(x, k, N0)])
# TODO: meaningful test for Dirichlet BC
- def useStandardDirichletBC(self):
+ def use_standard_dirichlet_bc(self):
"""Dirichlet BC for all variables at all boundaries"""
self.boundary_conditions = []
@@ -513,22 +508,22 @@ def useStandardDirichletBC(self):
# set up boundary conditions
self.boundary_conditions.extend([
- lambda x: self.leftPotentialDirichletBC(x, self.u0),
- lambda x: self.rightPotentialDirichletBC(x, self.u1)])
+ lambda x: self.left_potential_dirichlet_bc(x, self.u0),
+ lambda x: self.right_potential_dirichlet_bc(x, self.u1)])
for k in range(self.M):
- self.logger.info(
- 'Ion species {:02d} left hand side concentration Dirichlet boundary condition: c0 = {:> 8.4g}'.format(
- k, self.c_scaled[k]))
- self.logger.info(
- 'Ion species {:02d} right hand side concentration Dirichlet boundary condition: c1 = {:> 8.4g}'.format(
- k, self.c_scaled[k]))
- self.boundary_conditions.extend([
- lambda x, k=k: self.leftDirichletBC(x, k, self.c_scaled[k]),
- lambda x, k=k: self.rightDirichletBC(x, k, self.c_scaled[k])])
+ self.logger.info(
+ 'Ion species {:02d} left hand side concentration Dirichlet boundary condition: c0 = {:> 8.4g}'.format(
+ k, self.c_scaled[k]))
+ self.logger.info(
+ 'Ion species {:02d} right hand side concentration Dirichlet boundary condition: c1 = {:> 8.4g}'.format(
+ k, self.c_scaled[k]))
+ self.boundary_conditions.extend([
+ lambda x, k=k: self.left_dirichlet_bc(x, k, self.c_scaled[k]),
+ lambda x, k=k: self.right_dirichlet_bc(x, k, self.c_scaled[k])])
# boundary conditions and constraints building blocks:
- def leftFiniteDifferenceSchemeFluxBC(self, x, k, j0=0):
+ def left_finite_difference_scheme_flux_bc(self, x, k, j0=0):
"""
Parameters
----------
@@ -559,9 +554,9 @@ def leftFiniteDifferenceSchemeFluxBC(self, x, k, j0=0):
dndx, self.zi0[k, 0], nijk[0], dudx, self.dx, j0))
return bcval
- def rightFiniteDifferenceSchemeFluxBC(self, x, k, j0=0):
+ def right_finite_difference_scheme_flux_bc(self, x, k, j0=0):
"""
- See ```leftFiniteDifferenceSchemeFluxBC```
+ See ```left_finite_difference_scheme_flux_bc```
"""
uij = x[:self.Ni]
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
@@ -579,7 +574,7 @@ def rightFiniteDifferenceSchemeFluxBC(self, x, k, j0=0):
dndx, self.zi0[k, -1], nijk[-1], dudx, self.dx, j0))
return bcval
- def leftControlledVolumeSchemeFluxBC(self, x, k, j0=0):
+ def left_controlled_volume_scheme_flux_bc(self, x, k, j0=0):
"""
Compute left hand side flux boundary condition residual in accord with
controlled volume scheme.
@@ -609,10 +604,10 @@ def leftControlledVolumeSchemeFluxBC(self, x, k, j0=0):
'CV flux BC F[0] = n1*B(z(u0-u1)) - n0*B(z(u1-u0)) - j0*dx = {:> 8.4g}'.format(bcval))
return bcval
- def rightControlledVolumeSchemeFluxBC(self, x, k, j0=0):
+ def right_controlled_volume_scheme_flux_bc(self, x, k, j0=0):
"""
Compute right hand side flux boundary condition residual in accord with
- controlled volume scheme. See ``leftControlledVolumeSchemeFluxBC``
+ controlled volume scheme. See ``left_controlled_volume_scheme_flux_bc``
"""
uij = x[:self.Ni]
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
@@ -626,25 +621,25 @@ def rightControlledVolumeSchemeFluxBC(self, x, k, j0=0):
'CV flux BC F[-1] = n[-1]*B(z(u[-2]-u[-1])) - n[-2]*B(z(u[-1]-u[-2])) - j0*dx = {:> 8.4g}'.format(bcval))
return bcval
- def leftPotentialDirichletBC(self, x, u0=0):
- return self.leftDirichletBC(x, -1, u0)
+ def left_potential_dirichlet_bc(self, x, u0=0):
+ return self.left_dirichlet_bc(x, -1, u0)
- def leftDirichletBC(self, x, k, x0=0):
+ def left_dirichlet_bc(self, x, k, x0=0):
"""Construct Dirichlet BC at left boundary"""
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
return nijk[0] - x0
- def rightPotentialDirichletBC(self, x, x0=0):
- return self.rightDirichletBC(x, -1, x0)
+ def right_potential_dirichlet_bc(self, x, x0=0):
+ return self.right_dirichlet_bc(x, -1, x0)
- def rightDirichletBC(self, x, k, x0=0):
+ def right_dirichlet_bc(self, x, k, x0=0):
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
return nijk[-1] - x0
- def leftPotentialRobinBC(self, x, lam, u0=0):
- return self.leftRobinBC(x, -1, lam, u0)
+ def left_potential_robin_bc(self, x, lam, u0=0):
+ return self.left_robin_bc(x, -1, lam, u0)
- def leftRobinBC(self, x, k, lam, x0=0):
+ def left_robin_bc(self, x, k, lam, x0=0):
"""
Compute left hand side Robin (u + lam*dudx = u0 ) BC at in accord with
2nd order finite difference scheme.
@@ -671,20 +666,20 @@ def leftRobinBC(self, x, k, lam, x0=0):
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
return nijk[0] + lam/(2*self.dx) * (3.0*nijk[0] - 4.0*nijk[1] + nijk[2]) - x0
- def rightPotentialRobinBC(self, x, lam, u0=0):
- return self.rightRobinBC(x, -1, lam, u0)
+ def right_potential_robin_bc(self, x, lam, u0=0):
+ return self.right_robin_bc(x, -1, lam, u0)
- def rightRobinBC(self, x, k, lam, x0=0):
+ def right_robin_bc(self, x, k, lam, x0=0):
"""Construct Robin (u + lam*dudx = u0 ) BC at right boundary."""
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
return nijk[-1] + lam/(2*self.dx) * (3.0*nijk[-1] - 4.0*nijk[-2] + nijk[-3]) - x0
- def numberConservationConstraint(self, x, k, N0):
+ def number_conservation_constraint(self, x, k, N0):
"""N0: total amount of species, k: ion species"""
nijk = x[(k+1)*self.Ni:(k+2)*self.Ni]
- ## TODO: this integration scheme assumes constant concentrations within
- ## an interval. Adapt to controlled volume scheme!
+ # TODO: this integration scheme assumes constant concentrations within
+ # an interval. Adapt to controlled volume scheme!
# rescale to fit interval
N = np.sum(nijk*self.dx) * self.N / self.Ni
@@ -696,7 +691,7 @@ def numberConservationConstraint(self, x, k, N0):
return constraint_val
# TODO: remove or standardize
- # def leftNeumannBC(self,x,j0):
+ # def left_neumann_bc(self,x,j0):
# """Construct finite difference Neumann BC (flux BC) at left boundary"""
# # right hand side first derivative of second order error
# # df0dx = 1 / (2*dx) * (-3 f0 + 4 f1 - f2 ) + O(dx^2) = j0
@@ -705,7 +700,7 @@ def numberConservationConstraint(self, x, k, N0):
# 'Neumann BC F[0] = -3*x[0] + 4*x[1] - x[2] = {:> 8.4g}'.format(bcval))
# return bcval
#
- # def rightNeumannBC(self,x,j0):
+ # def right_neumann_bc(self,x,j0):
# """Construct finite difference Neumann BC (flux BC) at right boundary"""
# # left hand side first derivative of second order error
# # dfndx = 1 / (2*dx) * (+3 fn - 4 fn-1 + fn-2 ) + O(dx^2) = 0
@@ -798,7 +793,7 @@ def nernst_planck_pde(self, x):
for k in range(self.M):
self.logger.debug(
'ion species {:02d} concentration range [c_min, c_max] = [ {:>.4g}, {:>.4g} ]'.format(
- k, np.min(nijk1[k, :]), np.max(nijk1[k, :])))
+ k, np.min(nijk1[k, :]), np.max(nijk1[k, :])))
Fn = np.zeros([self.M, self.Ni])
# loop over k = 1..M reduced Nernst-Planck equations:
@@ -858,12 +853,12 @@ def G(self, x):
return F
@property
- def I(self): # ionic strength
+ def ionic_strength(self): # ionic strength
"""Compute the system's ionic strength from charges and concentrations.
Returns
-------
- I : float
+ ionic_strength : float
ionic strength ( 1/2 * sum(z_i^2*c_i) )
[concentration unit, i.e. mol m^-3]
"""
@@ -880,7 +875,7 @@ def lambda_D(self):
"""
return np.sqrt(
self.relative_permittivity*self.vacuum_permittivity*self.R*self.T/(
- 2.0*self.F**2*self.I))
+ 2.0*self.F**2*self.ionic_strength))
# default 0.1 mM (i.e. mol/m^3) NaCl aqueous solution
def init(self,
@@ -938,7 +933,7 @@ def init(self,
absolute tolerance for Newton solver convergence (default: 1e-10)
maxit : int, optional
maximum number of Newton iterations (default: 20)
- solver: func( funx(x), x0), optional
+ solver: func( func(x), x0), optional
solver to use (default: None, will use own simple Newton solver)
potential0: (N+1,) ndarray, optional (default: None)
potential initial values
@@ -981,7 +976,6 @@ def init(self,
self.x0 = x0 # reference position
self.delta_u = delta_u # potential difference
-
self.relative_permittivity = relative_permittivity
self.vacuum_permittivity = vacuum_permittivity
# R = N_A * k_B
@@ -1025,7 +1019,7 @@ def init(self,
self.l_unit = self.lambda_D
# concentration unit is ionic strength
- self.c_unit = self.I
+ self.c_unit = self.ionic_strength
# no time unit for now, only steady state
# self.t_unit = self.l_unit**2 / self.Dn # fixes Dn_scaled = 1
diff --git a/matscipy/electrochemistry/poisson_nernst_planck_solver_fenics.py b/matscipy/electrochemistry/poisson_nernst_planck_solver_fenics.py
index 064e68dc..34a9054b 100644
--- a/matscipy/electrochemistry/poisson_nernst_planck_solver_fenics.py
+++ b/matscipy/electrochemistry/poisson_nernst_planck_solver_fenics.py
@@ -35,7 +35,7 @@
class Boundary(fn.SubDomain):
"""Mark a point to be within the domain boundary for fenics."""
- # Boundary causes crash kernel if __init__ doe not call super().__init__()
+ # Boundary causes crash kernel if __init__ does not call super().__init__()
def __init__(self, x0=0, tol=1e-14):
super().__init__()
self.tol = tol
@@ -119,44 +119,44 @@ def boundary_C(self, x, on_boundary):
"""Mark domain center."""
return fn.near(x[0], (self.x0_scaled + self.x1_scaled)/2., self.bctol)
- def applyLeftPotentialDirichletBC(self, u0):
+ def apply_left_potential_dirichlet_bc(self, u0):
"""FEniCS Dirichlet BC u0 for potential at left boundary."""
self.boundary_conditions.extend([
fn.DirichletBC(self.W.sub(0), u0,
lambda x, on_boundary: self.boundary_L(x, on_boundary))])
- def applyRightPotentialDirichletBC(self, u0):
+ def apply_right_potential_dirichlet_bc(self, u0):
"""FEniCS Dirichlet BC u0 for potential at right boundary."""
self.boundary_conditions.extend([
fn.DirichletBC(self.W.sub(0), u0,
lambda x, on_boundary: self.boundary_R(x, on_boundary))])
- def applyLeftConcentrationDirichletBC(self, k, c0):
+ def apply_left_concentration_dirichlet_bc(self, k, c0):
"""FEniCS Dirichlet BC c0 for k'th ion species at left boundary."""
self.boundary_conditions.extend([
fn.DirichletBC(self.W.sub(k+1), c0,
lambda x, on_boundary: self.boundary_L(x, on_boundary))])
- def applyRightConcentrationDirichletBC(self, k, c0):
+ def apply_right_concentration_dirichlet_bc(self, k, c0):
"""FEniCS Dirichlet BC c0 for k'th ion species at right boundary."""
self.boundary_conditions.extend([
fn.DirichletBC(self.W.sub(k+1), c0,
lambda x, on_boundary: self.boundary_R(x, on_boundary))])
- def applyCentralReferenceConcentrationConstraint(self, k, c0):
+ def apply_central_reference_concentration_constraint(self, k, c0):
"""FEniCS Dirichlet BC c0 for k'th ion species at right boundary."""
self.boundary_conditions.extend([
fn.DirichletBC(self.W.sub(k+1), c0,
lambda x, on_boundary: self.boundary_C(x, on_boundary))])
# TODO: Robin BC!
- def applyLeftPotentialRobinBC(self, u0, lam0):
+ def apply_left_potential_robin_bc(self, u0, lam0):
self.logger.warning("Not implemented!")
- def applyRightPotentialRobinBC(self, u0, lam0):
+ def apply_right_potential_robin_bc(self, u0, lam0):
self.logger.warning("Not implemented!")
- def applyNumberConservationConstraint(self, k, c0):
+ def apply_number_conservation_constraint(self, k, c0):
"""
Enforce number conservation constraint via Lagrange multiplier.
See https://fenicsproject.org/docs/dolfin/1.6.0/python/demo/documented/neumann-poisson/python/documentation.html
@@ -164,17 +164,17 @@ def applyNumberConservationConstraint(self, k, c0):
self.constraints += self.lam[k]*self.q[k]*fn.dx \
+ (self.p[k]-c0)*self.mu[k]*fn.dx
- def applyPotentialDirichletBC(self, u0, u1):
+ def apply_potential_dirichlet_bc(self, u0, u1):
"""Potential Dirichlet BC u0 and u1 on left and right boundary."""
- self.applyLeftPotentialDirichletBC(u0)
- self.applyRightPotentialDirichletBC(u1)
+ self.apply_left_potential_dirichlet_bc(u0)
+ self.apply_right_potential_dirichlet_bc(u1)
- def applyPotentialRobinBC(self, u0, u1, lam0, lam1):
+ def apply_potential_robin_bc(self, u0, u1, lam0, lam1):
"""Potential Robin BC on left and right boundary."""
- self.applyLeftPotentialRobinBC(u0, lam0)
- self.applyRightPotentialRobinBC(u1, lam1)
+ self.apply_left_potential_robin_bc(u0, lam0)
+ self.apply_right_potential_robin_bc(u1, lam1)
- def useStandardInterfaceBC(self):
+ def use_standard_interface_bc(self):
"""Interface at left hand side and open bulk at right hand side"""
self.boundary_conditions = []
@@ -185,14 +185,14 @@ def useStandardInterfaceBC(self):
self.logger.info('Left hand side Dirichlet boundary condition: u0 = {:> 8.4g}'.format(self.u0))
self.logger.info('Right hand side Dirichlet boundary condition: u1 = {:> 8.4g}'.format(self.u1))
- self.applyPotentialDirichletBC(self.u0, self.u1)
+ self.apply_potential_dirichlet_bc(self.u0, self.u1)
for k in range(self.M):
self.logger.info(('Ion species {:02d} right hand side concentration '
'Dirichlet boundary condition: c1 = {:> 8.4g}').format(k, self.c_scaled[k]))
- self.applyRightConcentrationDirichletBC(k, self.c_scaled[k])
+ self.apply_right_concentration_dirichlet_bc(k, self.c_scaled[k])
- def useStandardCellBC(self):
+ def use_standard_cell_bc(self):
"""
Interfaces at left hand side and right hand side, species-wise
number conservation within interval."""
@@ -211,7 +211,7 @@ def useStandardCellBC(self):
self.logger.info('{:>{lwidth}s} u1 = {:< 8.4g}'.format(
'Right hand side Dirichlet boundary condition', self.u1, lwidth=self.label_width))
- self.applyPotentialDirichletBC(self.u0, self.u1)
+ self.apply_potential_dirichlet_bc(self.u0, self.u1)
# Number conservation constraints
self.constraints = 0
@@ -220,9 +220,9 @@ def useStandardCellBC(self):
self.logger.info('{:>{lwidth}s} N0 = {:<8.4g}'.format(
'Ion species {:02d} number conservation constraint'.format(k),
N0[k], lwidth=self.label_width))
- self.applyNumberConservationConstraint(k, self.c_scaled[k])
+ self.apply_number_conservation_constraint(k, self.c_scaled[k])
- def useCentralReferenceConcentrationBasedCellBC(self):
+ def use_central_reference_concentration_based_cell_bc(self):
"""
Interfaces at left hand side and right hand side, species-wise
concentration fixed at cell center."""
@@ -241,15 +241,15 @@ def useCentralReferenceConcentrationBasedCellBC(self):
self.logger.info('{:>{lwidth}s} u1 = {:< 8.4g}'.format(
'Right hand side Dirichlet boundary condition', self.u1, lwidth=self.label_width))
- self.applyPotentialDirichletBC(self.u0, self.u1)
+ self.apply_potential_dirichlet_bc(self.u0, self.u1)
for k in range(self.M):
self.logger.info(
'Ion species {:02d} reference concentration condition: c1 = {:> 8.4g} at cell center'.format(
k, self.c_scaled[k]))
- self.applyCentralReferenceConcentrationConstraint(k, self.c_scaled[k])
+ self.apply_central_reference_concentration_constraint(k, self.c_scaled[k])
- def useSternLayerCellBC(self):
+ def use_stern_layer_cell_bc(self):
"""
Interfaces at left hand side and right hand side, species-wise
number conservation within interval."""
@@ -298,7 +298,7 @@ def useSternLayerCellBC(self):
self.logger.info('{:>{lwidth}s} N0 = {:<8.4g}'.format(
'Ion species {:02d} number conservation constraint'.format(k),
N0[k], lwidth=self.label_width))
- self.applyNumberConservationConstraint(k, self.c_scaled[k])
+ self.apply_number_conservation_constraint(k, self.c_scaled[k])
def discretize(self):
"""Builds function space, call again after introducing constraints"""
diff --git a/matscipy/electrochemistry/steric_correction.py b/matscipy/electrochemistry/steric_correction.py
index 1a8d5d64..72bcee6e 100644
--- a/matscipy/electrochemistry/steric_correction.py
+++ b/matscipy/electrochemistry/steric_correction.py
@@ -642,7 +642,7 @@ def neigh_list_based_target_function(x, r=1.0, constraints=None, Dij=None):
r: float or (N,) ndarray, optional (default=1.0)
steric radii of particles
constraints: callable, returns (float, (N,dim) ndarray)
- constraint function value and gradien
+ constraint function value and gradient
Dij: (N, N) ndarray, optional (default=None)
pairwise minimum allowed distance matrix, overrides r
@@ -870,6 +870,8 @@ def apply_steric_correction(
Forwarded to scipy minimzer.
https://docs.scipy.org/doc/scipy/reference/optimize.minimize-bfgs.html
(default: {'gtol':1.e-5,'maxiter':10,'disp':True,'eps':1.e-8})
+ method: str
+ scipy.optimize.minimize method
target_function: func, optional
One of the target functions within this submodule, or function
of same signature. (default: neigh_list_based_target_function)
diff --git a/tests/test_electrochemistry_cli.py b/tests/test_electrochemistry_cli.py
index dd028da6..b7453e04 100644
--- a/tests/test_electrochemistry_cli.py
+++ b/tests/test_electrochemistry_cli.py
@@ -182,15 +182,29 @@ def test_pnp_c2d_pipeline_mode(self):
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=tmpdir, encoding='utf-8', env=self.myenv)
- c2d = subprocess.Popen([ 'c2d' ],
- stdin=pnp.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ # detour via python string
+ pnp_output, pnp_error = pnp.communicate()
+
+ c2d = subprocess.Popen(['c2d'],
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
cwd=tmpdir, encoding='utf-8', env=self.myenv)
- pnp.stdout.close() # Allow pnp to receive a SIGPIPE if p2 exits.
+
+ # pnp.stdout.close() # Allow pnp to receive a SIGPIPE if p2 exits.
+
+ c2d_output, c2d_error = c2d.communicate(input=pnp_output)
+
+ print(" poisson-nernst-planck stdout")
+ print(pnp_output)
+ print(" poisson-nernst-planck stderr")
+ print(pnp_error)
+ print(" continuous2discrete output")
+ print(c2d_output)
+ print(" continuous2discrete stderr")
+ print(c2d_error)
self.assertEqual(c2d.wait(),0)
self.assertEqual(pnp.wait(),0)
- c2d_output = c2d.communicate()[0]
with io.StringIO(c2d_output) as xyz_instream:
xyz = ase.io.read(xyz_instream, format='extxyz')
self.assertEqual(len(xyz),len(self.ref_xyz))
diff --git a/tests/test_poisson_nernst_planck_solver.py b/tests/test_poisson_nernst_planck_solver.py
index 1f898e60..d622f523 100644
--- a/tests/test_poisson_nernst_planck_solver.py
+++ b/tests/test_poisson_nernst_planck_solver.py
@@ -39,7 +39,7 @@ def test_poisson_nernst_planck_solver_std_interface_bc(self):
pnp = PoissonNernstPlanckSystem(
c=[0.1,0.1], z=[1,-1], L=1e-7, delta_u=0.05,
N=200, e=1e-12, maxit=20)
- pnp.useStandardInterfaceBC()
+ pnp.use_standard_interface_bc()
pnp.solve()
self.assertArrayAlmostEqual(pnp.grid, self.ref_data ['x'])
diff --git a/tests/test_poisson_nernst_planck_solver_fenics.py b/tests/test_poisson_nernst_planck_solver_fenics.py
index 0279d08d..d9be8b19 100644
--- a/tests/test_poisson_nernst_planck_solver_fenics.py
+++ b/tests/test_poisson_nernst_planck_solver_fenics.py
@@ -26,12 +26,11 @@
try:
import fenics
+ from matscipy.electrochemistry.poisson_nernst_planck_solver_fenics \
+ import PoissonNernstPlanckSystemFEniCS as PoissonNernstPlanckSystem
except ImportError:
print("fenics not found: skipping fenics-dependent tests")
-from matscipy.electrochemistry.poisson_nernst_planck_solver_fenics \
- import PoissonNernstPlanckSystemFEniCS as PoissonNernstPlanckSystem
-
class PoissonNernstPlanckSolverTest(matscipytest.MatSciPyTestCase):
@@ -49,7 +48,7 @@ def test_poisson_nernst_planck_solver_fenics_std_interface_bc(self):
pnp = PoissonNernstPlanckSystem(
c=[0.1,0.1], z=[1,-1], L=1e-7, delta_u=0.05,
N=200, e=1e-12, maxit=20)
- pnp.useStandardInterfaceBC()
+ pnp.use_standard_interface_bc()
pnp.solve()
# Reference data has been generated with controlled-volume solver and is slightly off the FEM results,