.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "examples/electrostatics/02_ewald_summation_example.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_examples_electrostatics_02_ewald_summation_example.py: Ewald Summation for Long-Range Electrostatics ============================================== This example demonstrates how to compute long-range electrostatic interactions in periodic systems using the Ewald summation method. Ewald splits the slowly converging Coulomb sum into rapidly converging real-space and reciprocal-space components. In this example you will learn: - How to set up and run Ewald summation with automatic parameter estimation - Using neighbor list and neighbor matrix formats - Understanding convergence with accuracy-based parameter estimation - Effect of the splitting parameter alpha - Batch evaluation for multiple systems - Computing charge gradients for ML potential training The Ewald energy is decomposed as: .. math:: E_{\text{total}} = E_{\text{real}} + E_{\text{reciprocal}} - E_{\text{self}} .. important:: This script is intended as an API demonstration. Do not use this script for performance benchmarking; refer to the `benchmarks` folder instead. .. GENERATED FROM PYTHON SOURCE LINES 40-44 Setup and Imports ----------------- First, we import the necessary modules. The Ewald API provides unified functions that handle both single-system and batched calculations. .. GENERATED FROM PYTHON SOURCE LINES 44-63 .. code-block:: Python from __future__ import annotations import numpy as np import torch from nvalchemiops.interactions.electrostatics import ( ewald_real_space, ewald_reciprocal_space, ewald_summation, ) from nvalchemiops.interactions.electrostatics.k_vectors import ( generate_k_vectors_ewald_summation, ) from nvalchemiops.interactions.electrostatics.parameters import ( estimate_ewald_parameters, ) from nvalchemiops.neighborlist import neighbor_list as neighbor_list_fn .. GENERATED FROM PYTHON SOURCE LINES 64-66 Configure Device ---------------- .. GENERATED FROM PYTHON SOURCE LINES 66-74 .. code-block:: Python if torch.cuda.is_available(): device = torch.device("cuda:0") print(f"Using CUDA device: {torch.cuda.get_device_name(0)}") else: device = torch.device("cpu") print("Using CPU") .. rst-class:: sphx-glr-script-out .. code-block:: none Using CUDA device: NVIDIA L4 .. GENERATED FROM PYTHON SOURCE LINES 75-79 Create a NaCl Crystal System ---------------------------- We define a helper function to create NaCl rock salt crystal supercells. NaCl has Na+ at (0,0,0) and Cl- at (0.5,0.5,0.5) in fractional coordinates. .. GENERATED FROM PYTHON SOURCE LINES 79-118 .. code-block:: Python def create_nacl_system(n_cells: int = 2, lattice_constant: float = 5.64): """Create a NaCl crystal supercell. Parameters ---------- n_cells : int Number of unit cells in each direction. lattice_constant : float NaCl lattice constant in Angstroms. Returns ------- positions, charges, cell, pbc : torch.Tensor System tensors. """ base_positions = np.array([[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]]) base_charges = np.array([1.0, -1.0]) positions = [] charges = [] for i in range(n_cells): for j in range(n_cells): for k in range(n_cells): offset = np.array([i, j, k]) for pos, charge in zip(base_positions, base_charges): positions.append((pos + offset) * lattice_constant) charges.append(charge) positions = torch.tensor(positions, dtype=torch.float64, device=device) charges = torch.tensor(charges, dtype=torch.float64, device=device) cell = torch.eye(3, dtype=torch.float64, device=device) * lattice_constant * n_cells cell = cell.unsqueeze(0) pbc = torch.tensor([[True, True, True]], dtype=torch.bool, device=device) return positions, charges, cell, pbc .. GENERATED FROM PYTHON SOURCE LINES 119-124 Basic Usage with Automatic Parameters ------------------------------------- The simplest way to use Ewald summation is with automatic parameter estimation. Given an accuracy tolerance, the API estimates optimal alpha, real-space cutoff, and reciprocal-space cutoff using the Kolafa-Perram formula. .. GENERATED FROM PYTHON SOURCE LINES 124-132 .. code-block:: Python # Create a small NaCl crystal (2×2×2 unit cells = 16 atoms) positions, charges, cell, pbc = create_nacl_system(n_cells=2) print(f"System: {len(positions)} atoms NaCl crystal") print(f"Cell size: {cell[0, 0, 0]:.2f} Å") print(f"Total charge: {charges.sum().item():.1f} (should be 0 for neutral)") .. rst-class:: sphx-glr-script-out .. code-block:: none /home/kinlongkelvi/Repos/nvalchemi-toolkit-ops/.venv/lib/python3.13/site-packages/torch/utils/_device.py:103: UserWarning: Creating a tensor from a list of numpy.ndarrays is extremely slow. Please consider converting the list to a single numpy.ndarray with numpy.array() before converting to a tensor. (Triggered internally at /pytorch/torch/csrc/utils/tensor_new.cpp:253.) return func(*args, **kwargs) System: 16 atoms NaCl crystal Cell size: 11.28 Å Total charge: 0.0 (should be 0 for neutral) .. GENERATED FROM PYTHON SOURCE LINES 133-134 Estimate optimal parameters for target accuracy: .. GENERATED FROM PYTHON SOURCE LINES 134-142 .. code-block:: Python params = estimate_ewald_parameters(positions.cpu(), cell.cpu(), accuracy=1e-6) print("\nEstimated parameters (accuracy=1e-6):") print(f" alpha = {params.alpha.item():.4f}") print(f" real_space_cutoff = {params.real_space_cutoff.item():.2f} Å") print(f" reciprocal_space_cutoff = {params.reciprocal_space_cutoff.item():.2f} Å⁻¹") .. rst-class:: sphx-glr-script-out .. code-block:: none Estimated parameters (accuracy=1e-6): alpha = 0.2494 real_space_cutoff = 14.90 Å reciprocal_space_cutoff = 1.85 Å⁻¹ .. GENERATED FROM PYTHON SOURCE LINES 143-144 Build neighbor list and run Ewald summation: .. GENERATED FROM PYTHON SOURCE LINES 144-170 .. code-block:: Python neighbor_list, neighbor_ptr, neighbor_shifts = neighbor_list_fn( positions, params.real_space_cutoff.item(), cell=cell, pbc=pbc, return_neighbor_list=True, ) energies, forces = ewald_summation( positions=positions, charges=charges, cell=cell, neighbor_list=neighbor_list, neighbor_ptr=neighbor_ptr, neighbor_shifts=neighbor_shifts, accuracy=1e-6, # Parameters estimated automatically compute_forces=True, ) total_energy = energies.sum().item() print("\nEwald Summation Results:") print(f" Total energy: {total_energy:.6f}") print(f" Energy per atom: {total_energy / len(positions):.6f}") print(f" Max force magnitude: {torch.norm(forces, dim=1).max().item():.6f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Ewald Summation Results: Total energy: -2.887038 Energy per atom: -0.180440 Max force magnitude: 0.000000 .. GENERATED FROM PYTHON SOURCE LINES 171-175 Neighbor List vs Neighbor Matrix Format --------------------------------------- The Ewald functions support two neighbor formats. Both produce identical results. We use the estimated parameters from above for consistency. .. GENERATED FROM PYTHON SOURCE LINES 175-197 .. code-block:: Python # Build both neighbor formats using the estimated real-space cutoff neighbor_list, neighbor_ptr, neighbor_shifts = neighbor_list_fn( positions, params.real_space_cutoff.item(), cell=cell, pbc=pbc, return_neighbor_list=True, ) neighbor_matrix, _, neighbor_matrix_shifts = neighbor_list_fn( positions, params.real_space_cutoff.item(), cell=cell, pbc=pbc, return_neighbor_list=False, ) print("\nNeighbor format comparison (accuracy=1e-6):") print( f" Using alpha={params.alpha.item():.4f}, k_cutoff={params.reciprocal_space_cutoff.item():.2f}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Neighbor format comparison (accuracy=1e-6): Using alpha=0.2494, k_cutoff=1.85 .. GENERATED FROM PYTHON SOURCE LINES 198-199 Using neighbor list format: .. GENERATED FROM PYTHON SOURCE LINES 199-212 .. code-block:: Python energies_list = ewald_summation( positions=positions, charges=charges, cell=cell, neighbor_list=neighbor_list, neighbor_ptr=neighbor_ptr, neighbor_shifts=neighbor_shifts, accuracy=1e-6, # Parameters estimated automatically ) print(f" List format energy: {energies_list.sum().item():.6f}") .. rst-class:: sphx-glr-script-out .. code-block:: none List format energy: -2.887038 .. GENERATED FROM PYTHON SOURCE LINES 213-214 Using neighbor matrix format: .. GENERATED FROM PYTHON SOURCE LINES 214-229 .. code-block:: Python energies_matrix = ewald_summation( positions=positions, charges=charges, cell=cell, neighbor_matrix=neighbor_matrix, neighbor_matrix_shifts=neighbor_matrix_shifts, accuracy=1e-6, # Same accuracy for comparison ) print(f" Matrix format energy: {energies_matrix.sum().item():.6f}") print( f" Difference: {abs(energies_list.sum().item() - energies_matrix.sum().item()):.2e}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Matrix format energy: -2.887038 Difference: 0.00e+00 .. GENERATED FROM PYTHON SOURCE LINES 230-235 Convergence with Accuracy Parameter ----------------------------------- The Ewald summation accuracy depends on the accuracy parameter, which controls both the real-space cutoff and the k-space cutoff. The parameter estimation uses the Kolafa-Perram formula to balance computational cost. .. GENERATED FROM PYTHON SOURCE LINES 235-282 .. code-block:: Python accuracies = [1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7] energies_acc = [] params_list = [] print("\nConvergence with Accuracy Target:") print(" Accuracy | alpha | r_cutoff | k_cutoff | Num k-vectors | Energy") print(" " + "-" * 75) for acc in accuracies: # Estimate optimal parameters for this accuracy params_acc = estimate_ewald_parameters(positions, cell, accuracy=acc) params_list.append(params_acc) # Build neighbor list with appropriate cutoff nl_acc, nptr_acc, ns_acc = neighbor_list_fn( positions, params_acc.real_space_cutoff.item(), cell=cell, pbc=pbc, return_neighbor_list=True, ) # Generate k-vectors for counting k_vectors = generate_k_vectors_ewald_summation( cell, params_acc.reciprocal_space_cutoff ) num_kvec = k_vectors.shape[1] # Run Ewald summation with estimated parameters energy = ewald_summation( positions=positions, charges=charges, cell=cell, neighbor_list=nl_acc, neighbor_ptr=nptr_acc, neighbor_shifts=ns_acc, accuracy=acc, ) total_e = energy.sum().item() energies_acc.append(total_e) print( f" {acc:.0e} | {params_acc.alpha.item():.3f} | {params_acc.real_space_cutoff.item():5.2f} |" f" {params_acc.reciprocal_space_cutoff.item():5.2f} | {num_kvec:10d} | {total_e:12.6f}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Convergence with Accuracy Target: Accuracy | alpha | r_cutoff | k_cutoff | Num k-vectors | Energy --------------------------------------------------------------------------- 1e-02 | 0.249 | 8.60 | 1.07 | 3 | -2.871073 1e-03 | 0.249 | 10.54 | 1.31 | 3 | -2.887249 1e-04 | 0.249 | 12.17 | 1.51 | 3 | -2.886954 1e-05 | 0.249 | 13.60 | 1.69 | 3 | -2.887049 1e-06 | 0.249 | 14.90 | 1.85 | 3 | -2.887038 1e-07 | 0.249 | 16.10 | 2.00 | 3 | -2.887038 .. GENERATED FROM PYTHON SOURCE LINES 283-284 Show convergence relative to highest accuracy: .. GENERATED FROM PYTHON SOURCE LINES 284-291 .. code-block:: Python ref_energy = energies_acc[-1] print("\nRelative error from reference (accuracy=1e-7):") for acc, e in zip(accuracies[:-1], energies_acc[:-1]): rel_err = abs((e - ref_energy) / ref_energy) print(f" accuracy={acc:.0e}: {rel_err:.2e}") .. rst-class:: sphx-glr-script-out .. code-block:: none Relative error from reference (accuracy=1e-7): accuracy=1e-02: 5.53e-03 accuracy=1e-03: 7.30e-05 accuracy=1e-04: 2.90e-05 accuracy=1e-05: 3.90e-06 accuracy=1e-06: 3.83e-08 .. GENERATED FROM PYTHON SOURCE LINES 292-296 Real-Space and Reciprocal-Space Components ------------------------------------------ The Ewald energy splits into real-space (short-range) and reciprocal-space (long-range) components. You can access these separately. .. GENERATED FROM PYTHON SOURCE LINES 296-315 .. code-block:: Python # Create a larger system for clearer demonstration positions_4, charges_4, cell_4, pbc_4 = create_nacl_system(n_cells=4) params_4 = estimate_ewald_parameters(positions_4, cell_4, accuracy=1e-4) k_vectors_4 = generate_k_vectors_ewald_summation( cell_4, params_4.reciprocal_space_cutoff ) neighbor_list_4, neighbor_ptr_4, neighbor_shifts_4 = neighbor_list_fn( positions_4, params_4.real_space_cutoff.item(), cell=cell_4, pbc=pbc_4, return_neighbor_list=True, ) print(f"\nEnergy Components ({len(positions_4)} atoms):") .. rst-class:: sphx-glr-script-out .. code-block:: none Energy Components (128 atoms): .. GENERATED FROM PYTHON SOURCE LINES 316-317 Real-space component: .. GENERATED FROM PYTHON SOURCE LINES 317-331 .. code-block:: Python real_energy = ewald_real_space( positions=positions_4, charges=charges_4, cell=cell_4, alpha=params_4.alpha, neighbor_list=neighbor_list_4, neighbor_ptr=neighbor_ptr_4, neighbor_shifts=neighbor_shifts_4, compute_forces=False, ) print(f" Real-space: {real_energy.sum().item():.6f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Real-space: -10.361826 .. GENERATED FROM PYTHON SOURCE LINES 332-333 Reciprocal-space component: .. GENERATED FROM PYTHON SOURCE LINES 333-346 .. code-block:: Python recip_energy = ewald_reciprocal_space( positions=positions_4, charges=charges_4, cell=cell_4, k_vectors=k_vectors_4, alpha=params_4.alpha, compute_forces=False, ) print(f" Reciprocal-space: {recip_energy.sum().item():.6f}") print(f" Total: {(real_energy.sum() + recip_energy.sum()).item():.6f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Reciprocal-space: -12.735138 Total: -23.096964 .. GENERATED FROM PYTHON SOURCE LINES 347-352 Charge Gradients for ML Potentials ---------------------------------- The Ewald functions support computing analytical charge gradients (∂E/∂q_i), which are useful for training machine learning potentials that predict charges. The charge gradient represents the electrostatic potential at each atom. .. GENERATED FROM PYTHON SOURCE LINES 352-394 .. code-block:: Python print("\nCharge Gradients:") # Compute real-space component with charge gradients real_energies, real_forces, real_charge_grads = ewald_real_space( positions=positions_4, charges=charges_4, cell=cell_4, alpha=params_4.alpha, neighbor_list=neighbor_list_4, neighbor_ptr=neighbor_ptr_4, neighbor_shifts=neighbor_shifts_4, compute_forces=True, compute_charge_gradients=True, ) print(f" Real-space charge gradients shape: {real_charge_grads.shape}") print( f" Real-space charge gradients range: [{real_charge_grads.min().item():.4f}, {real_charge_grads.max().item():.4f}]" ) # Compute reciprocal-space component with charge gradients recip_energies, recip_forces, recip_charge_grads = ewald_reciprocal_space( positions=positions_4, charges=charges_4, cell=cell_4, k_vectors=k_vectors_4, alpha=params_4.alpha, compute_forces=True, compute_charge_gradients=True, ) print( f" Reciprocal-space charge gradients range: [{recip_charge_grads.min().item():.4f}, {recip_charge_grads.max().item():.4f}]" ) # Total charge gradient is the sum of components total_charge_grads = real_charge_grads + recip_charge_grads print( f" Total charge gradients range: [{total_charge_grads.min().item():.4f}, {total_charge_grads.max().item():.4f}]" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Charge Gradients: Real-space charge gradients shape: torch.Size([128]) Real-space charge gradients range: [-0.1619, 0.1619] Reciprocal-space charge gradients range: [-0.1990, 0.1990] Total charge gradients range: [-0.3609, 0.3609] .. GENERATED FROM PYTHON SOURCE LINES 395-396 Verify charge gradients against autograd: .. GENERATED FROM PYTHON SOURCE LINES 396-426 .. code-block:: Python charges_4.requires_grad_(True) energies_total = ( ewald_real_space( positions=positions_4, charges=charges_4, cell=cell_4, alpha=params_4.alpha, neighbor_list=neighbor_list_4, neighbor_ptr=neighbor_ptr_4, neighbor_shifts=neighbor_shifts_4, ).sum() + ewald_reciprocal_space( positions=positions_4, charges=charges_4, cell=cell_4, k_vectors=k_vectors_4, alpha=params_4.alpha, ).sum() ) energies_total.backward() autograd_charge_grads = charges_4.grad.clone() charges_4.requires_grad_(False) charges_4.grad = None # Compare explicit vs autograd charge gradients charge_grad_diff = (total_charge_grads - autograd_charge_grads).abs().max().item() print(f"\n Explicit vs Autograd charge gradient max diff: {charge_grad_diff:.2e}") .. rst-class:: sphx-glr-script-out .. code-block:: none Explicit vs Autograd charge gradient max diff: 2.77e-09 .. GENERATED FROM PYTHON SOURCE LINES 427-431 Batch Evaluation ---------------- Multiple systems can be evaluated simultaneously using batch_idx. Each system can have different alpha values. .. GENERATED FROM PYTHON SOURCE LINES 431-451 .. code-block:: Python n_systems = 3 all_positions = [] all_charges = [] all_cells = [] all_pbc = [] batch_idx_list = [] print(f"\nBatch Evaluation: Creating {n_systems} systems...") for i in range(n_systems): n_cells = i + 2 # 2×2×2, 3×3×3, 4×4×4 pos, chrg, cell_i, pbc_i = create_nacl_system(n_cells=n_cells) batch_idx_list.extend([i] * len(pos)) all_positions.append(pos) all_charges.append(chrg) all_cells.append(cell_i) all_pbc.append(pbc_i) print(f" System {i}: {len(pos)} atoms ({n_cells}×{n_cells}×{n_cells})") .. rst-class:: sphx-glr-script-out .. code-block:: none Batch Evaluation: Creating 3 systems... System 0: 16 atoms (2×2×2) System 1: 54 atoms (3×3×3) System 2: 128 atoms (4×4×4) .. GENERATED FROM PYTHON SOURCE LINES 452-453 Concatenate all systems: .. GENERATED FROM PYTHON SOURCE LINES 453-470 .. code-block:: Python positions_batch = torch.cat(all_positions, dim=0) charges_batch = torch.cat(all_charges, dim=0) cells_batch = torch.cat(all_cells, dim=0) pbc_batch = torch.cat(all_pbc, dim=0) batch_idx = torch.tensor(batch_idx_list, dtype=torch.int32, device=device) # Estimate parameters for the batch with desired accuracy params_batch = estimate_ewald_parameters( positions_batch, cells_batch, batch_idx=batch_idx, accuracy=1e-5 ) print(f"\nTotal atoms: {len(positions_batch)}") print(f"Per-system alphas: {params_batch.alpha.tolist()}") print(f"Real-space cutoff: {params_batch.real_space_cutoff.max().item():.2f} Å") print(f"K-space cutoff: {params_batch.reciprocal_space_cutoff.max().item():.2f} Å⁻¹") .. rst-class:: sphx-glr-script-out .. code-block:: none Total atoms: 198 Per-system alphas: [0.24943219038054099, 0.20366053061902537, 0.17637519326429443] Real-space cutoff: 19.24 Å K-space cutoff: 1.69 Å⁻¹ .. GENERATED FROM PYTHON SOURCE LINES 471-472 Build batched neighbor list and run: .. GENERATED FROM PYTHON SOURCE LINES 472-504 .. code-block:: Python # Use the maximum real-space cutoff across all systems real_cutoff_batch = params_batch.real_space_cutoff.max().item() neighbor_matrix_batch, _, neighbor_matrix_shifts_batch = neighbor_list_fn( positions_batch, real_cutoff_batch, cell=cells_batch, pbc=pbc_batch, method="batch_naive", batch_idx=batch_idx, return_neighbor_list=False, ) energies_batch, forces_batch = ewald_summation( positions=positions_batch, charges=charges_batch, cell=cells_batch, batch_idx=batch_idx, neighbor_matrix=neighbor_matrix_batch, neighbor_matrix_shifts=neighbor_matrix_shifts_batch, compute_forces=True, accuracy=1e-5, # Parameters estimated automatically for batch ) print("\nPer-system results:") for i in range(n_systems): mask = batch_idx == i n_atoms = mask.sum().item() sys_energy = energies_batch[mask].sum().item() max_force = torch.norm(forces_batch[mask], dim=1).max().item() print(f" System {i}: {n_atoms} atoms, E={sys_energy:.4f}, |F|_max={max_force:.4f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Per-system results: System 0: 16 atoms, E=-2.8870, |F|_max=0.0000 System 1: 54 atoms, E=-9.7438, |F|_max=0.0000 System 2: 128 atoms, E=-23.0963, |F|_max=0.0000 .. GENERATED FROM PYTHON SOURCE LINES 505-506 Verify batch vs individual calculations: .. GENERATED FROM PYTHON SOURCE LINES 506-531 .. code-block:: Python print("\nVerification (individual calculations with same accuracy):") for i in range(n_systems): mask = batch_idx == i pos_i = positions_batch[mask] chrg_i = charges_batch[mask] cell_i = cells_batch[i : i + 1] pbc_i = pbc_batch[i : i + 1] # Use same cutoff as batch for fair comparison nl_i, nptr_i, ns_i = neighbor_list_fn( pos_i, real_cutoff_batch, cell=cell_i, pbc=pbc_i, return_neighbor_list=True ) e_i = ewald_summation( positions=pos_i, charges=chrg_i, cell=cell_i, neighbor_list=nl_i, neighbor_ptr=nptr_i, neighbor_shifts=ns_i, accuracy=1e-5, # Same accuracy as batch ) print(f" System {i}: E={e_i.sum().item():.4f}") .. rst-class:: sphx-glr-script-out .. code-block:: none Verification (individual calculations with same accuracy): System 0: E=-2.8870 System 1: E=-9.7438 System 2: E=-23.0963 .. GENERATED FROM PYTHON SOURCE LINES 532-551 Summary ------- This example demonstrated: 1. **Automatic parameter estimation** based on target accuracy using the Kolafa-Perram formula via ``estimate_ewald_parameters`` 2. **Neighbor format flexibility** with list and matrix formats 3. **Accuracy-based convergence** showing how the accuracy parameter controls both real-space and k-space cutoffs 4. **Component access** for real-space and reciprocal-space energies 5. **Charge gradients** (∂E/∂q_i) for ML potential training 6. **Batch evaluation** for multiple systems with automatic per-system alpha Key equations implemented: - Real-space: :math:`E_{\\text{real}} = \\frac{1}{2} \\sum_{i \\neq j} q_i q_j \\frac{\\mathrm{erfc}(\\alpha r_{ij})}{r_{ij}}` - Reciprocal: :math:`E_{\\text{recip}} = \\frac{1}{2V} \\sum_{\\mathbf{k} \\neq 0} \\frac{4\\pi}{k^2} e^{-k^2/4\\alpha^2} |S(\\mathbf{k})|^2` - Self-energy: :math:`E_{\\text{self}} = \\frac{\\alpha}{\\sqrt{\\pi}} \\sum_i q_i^2` - Charge gradient: :math:`\\frac{\\partial E}{\\partial q_i} = \\phi_i` (electrostatic potential) .. GENERATED FROM PYTHON SOURCE LINES 551-553 .. code-block:: Python print("\nEwald summation example complete!") .. rst-class:: sphx-glr-script-out .. code-block:: none Ewald summation example complete! .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 11.751 seconds) .. _sphx_glr_download_examples_electrostatics_02_ewald_summation_example.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 02_ewald_summation_example.ipynb <02_ewald_summation_example.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 02_ewald_summation_example.py <02_ewald_summation_example.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 02_ewald_summation_example.zip <02_ewald_summation_example.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_