lammpskit.ecellmodel.plot_atomic_charge_distribution

lammpskit.ecellmodel.plot_atomic_charge_distribution(file_list, labels, skip_rows, z_bins, analysis_name, output_dir=os.getcwd(), columns_to_read=None, **kwargs)[source]

Generate electrostatic charge distribution analysis for HfTaO electrochemical devices.

Computes and visualizes spatial charge profiles across the electrode-to-electrode axis to analyze electrostatic field formation, charge redistribution during switching cycles, and ionic polarization effects in resistive memory devices. Provides both total charge density and species-specific mean charge characterization essential for understanding resistance switching mechanisms and device reliability.

Parameters:
  • file_list (list of str) – List of file paths to LAMMPS trajectory files containing atomic coordinates and charges. Files must include charge information (column 2) for electrostatic analysis. Example: [“initial_state.lammpstrj”, “charged_state.lammpstrj”]

  • labels (list of str) – Descriptive labels for each trajectory file, used in plot legends and output filenames. Must match file_list length. Example: [“Initial”, “Polarized”, “Relaxed”]

  • skip_rows (int) – Number of header rows to skip before atomic data in trajectory files. Standard LAMMPS dump format typically uses 9 header rows.

  • z_bins (int) – Number of spatial bins for z-direction charge profile discretization. Recommended: 15-100 bins for optimal field resolution vs. statistical significance.

  • analysis_name (str) – Base identifier for output files and plot titles. Should describe analysis context. Example: “voltage_cycling_analysis”, “field_redistribution_study”

  • output_dir (str, optional) – Directory path for saving generated charge distribution plots. Default: current working directory. Creates directory if non-existent.

  • columns_to_read (tuple, optional) – Column indices for trajectory file parsing. If None, uses DEFAULT_COLUMNS_TO_READ. Standard format: (id, type, charge, x, y, z) with charge in column 2.

  • **kwargs

    Additional visualization parameters passed to plot_multiple_cases():

    • ylimithi/xlimithi: Upper axis limits for charge magnitude scaling

    • ylimitlo/xlimitlo: Lower axis limits for field detail resolution

    • yaxis: Y-axis origin position for bipolar charge visualization

    • markerindex: Marker style index for multi-case differentiation

Returns:

charge_figures – Dictionary of matplotlib Figure objects for comprehensive charge analysis:

Total Charge Analysis: - ‘net_charge’: All-frame total charge density profiles - ‘initial_net_charge’: First-frame charge distribution baseline - ‘final_net_charge’: Last-frame charge distribution for comparison

Species-Specific Mean Charge: - ‘metal_charge’: Mean charge per metal atom (Hf + Ta combined) - ‘initial_metal_charge’: Initial metal charge state reference - ‘oxygen_charge’: Mean charge per oxygen atom (ion migration indicator) - ‘initial_oxygen_charge’: Initial oxygen charge state reference

All figures include publication-ready formatting with axis labels, legends, and consistent styling for comparative analysis across experimental conditions.

Return type:

dict[str, plt.Figure]

Notes

Electrostatic Field Physics: - Total Charge: Reveals space charge regions and internal field gradients - Metal Charges: Indicate oxidation state changes and electron transfer - Oxygen Charges: Track ionic polarization and vacancy formation regions - Temporal Evolution: Initial vs. final states show switching-induced changes

Mathematical Foundation:

Raw charge distribution calculation:

Q(z) = Σ q_i × δ(z_i - z_bin)

Mean charge per species:

<q>_species(z) = Q_species(z) / N_species(z)

Where safe division prevents numerical errors when N_species(z) = 0.

Performance Characteristics: - Memory Complexity: O(N_files × N_atoms × N_bins) for charge histogram storage - Processing Speed: ~2-15s per file depending on atom count and bin resolution - Output Quality: Publication-ready SVG figures with customizable styling - Numerical Stability: Robust handling of zero-atom bins and extreme charge values

Integration with LAMMPSKit Analysis Pipeline: - Charge Calculation: Uses calculate_charge_distributions() for species separation - Coordinate Processing: Integrates with read_coordinates() for trajectory parsing - Atomic Context: Requires calculate_atomic_distributions() for normalization - Visualization: Leverages plot_multiple_cases() for consistent scientific plotting

Examples

Basic charge redistribution analysis:

>>> from lammpskit.ecellmodel.filament_layer_analysis import plot_atomic_charge_distribution
>>> # Analyze charge evolution during switching
>>> trajectory_files = ["before_switch.lammpstrj", "after_switch.lammpstrj"]
>>> state_labels = ["Before", "After"]
>>>
>>> charge_figures = plot_atomic_charge_distribution(trajectory_files, state_labels,
...                                                skip_rows=9, z_bins=50,
...                                                analysis_name="switching_charges")
>>> print(f"Generated {len(charge_figures)} charge analysis plots")

Voltage-dependent charge analysis:

>>> # Multi-voltage charge redistribution study
>>> voltage_files = ["0V.lammpstrj", "1V.lammpstrj", "2V.lammpstrj", "3V.lammpstrj"]
>>> voltage_labels = ["0V", "1V", "2V", "3V"]
>>>
>>> # High-resolution field analysis
>>> charge_figures = plot_atomic_charge_distribution(voltage_files, voltage_labels,
...                                                skip_rows=9, z_bins=100,
...                                                analysis_name="voltage_dependence",
...                                                output_dir="./charge_analysis")
>>>
>>> # Examine field gradient evolution
>>> net_charge_fig = charge_figures['net_charge']
>>> metal_charge_fig = charge_figures['metal_charge']
>>> print("Voltage-dependent field analysis completed")

Temporal charge evolution tracking:

>>> # Long-term charge stability analysis
>>> time_series_files = [f"time_{i}ps.lammpstrj" for i in [0, 100, 500, 1000, 5000]]
>>> time_labels = ["0ps", "100ps", "500ps", "1000ps", "5000ps"]
>>>
>>> charge_figures = plot_atomic_charge_distribution(time_series_files, time_labels,
...                                                skip_rows=9, z_bins=75,
...                                                analysis_name="temporal_stability")
>>>
>>> # Compare initial vs. final charge states
>>> initial_charge = charge_figures['initial_net_charge']
>>> final_charge = charge_figures['final_net_charge']
>>> print("Temporal charge stability analysis completed")

Species-specific charge state analysis:

>>> # Detailed oxidation state characterization
>>> trajectory_files = ["pristine.lammpstrj", "oxidized.lammpstrj"]
>>> condition_labels = ["Pristine", "Oxidized"]
>>>
>>> # Custom axis limits for charge detail resolution
>>> custom_limits = {
...     'ylimithi': 100,    # Extended charge range
...     'ylimitlo': -100,   # Bipolar charge visualization
...     'xlimitlo': -15,    # Full device width
...     'xlimithi': 25,
...     'yaxis': 0          # Center y-axis at zero charge
... }
>>>
>>> charge_figures = plot_atomic_charge_distribution(trajectory_files, condition_labels,
...                                                skip_rows=9, z_bins=60,
...                                                analysis_name="oxidation_study",
...                                                **custom_limits)
>>>
>>> # Analyze species-specific charge states
>>> metal_charges = charge_figures['metal_charge']       # Metal oxidation states
>>> oxygen_charges = charge_figures['oxygen_charge']     # Oxygen polarization
>>> initial_metal = charge_figures['initial_metal_charge'] # Reference state
>>> print("Oxidation state analysis completed")

Interface charge characterization:

>>> # Electrode-oxide interface charge analysis
>>> interface_files = ["bottom_electrode.lammpstrj", "top_electrode.lammpstrj"]
>>> interface_labels = ["Bottom", "Top"]
>>>
>>> # Focus on interface region with appropriate limits
>>> interface_params = {
...     'xlimitlo': -5,     # Near bottom electrode
...     'xlimithi': 35,     # Near top electrode
...     'ylimithi': 50,     # Moderate charge range
...     'yaxis': 0,         # Bipolar visualization
...     'markerindex': 1    # Distinctive markers
... }
>>>
>>> charge_figures = plot_atomic_charge_distribution(interface_files, interface_labels,
...                                                skip_rows=9, z_bins=80,
...                                                analysis_name="interface_charges",
...                                                **interface_params)
>>> print("Interface charge analysis completed")

Comparative analysis with atomic distributions:

>>> # Combined atomic and charge distribution analysis
>>> from lammpskit.ecellmodel.filament_layer_analysis import plot_atomic_distribution
>>>
>>> trajectory_files = ["device_state.lammpstrj"]
>>> analysis_labels = ["Device"]
>>>
>>> # Atomic distribution analysis
>>> atomic_figures = plot_atomic_distribution(trajectory_files, analysis_labels,
...                                         skip_rows=9, z_bins=50,
...                                         analysis_name="comprehensive_study")
>>>
>>> # Charge distribution analysis
>>> charge_figures = plot_atomic_charge_distribution(trajectory_files, analysis_labels,
...                                                skip_rows=9, z_bins=50,
...                                                analysis_name="comprehensive_study")
>>>
>>> # Correlate atomic and charge profiles
>>> total_analyses = len(atomic_figures) + len(charge_figures)
>>> print(f"Comprehensive analysis: {total_analyses} figures generated")
>>> print("Atomic composition:", list(atomic_figures.keys()))
>>> print("Charge distributions:", list(charge_figures.keys()))

Batch processing for parameter studies:

>>> import os
>>> # Systematic parameter variation study
>>> parameters = ["low_field", "medium_field", "high_field"]
>>> all_charge_analyses = {}
>>>
>>> for param in parameters:
...     try:
...         param_files = [f"{param}_initial.lammpstrj", f"{param}_final.lammpstrj"]
...         param_labels = ["Initial", "Final"]
...
...         if all(os.path.exists(f) for f in param_files):
...             figures = plot_atomic_charge_distribution(param_files, param_labels,
...                                                     skip_rows=9, z_bins=50,
...                                                     analysis_name=f"{param}_charges",
...                                                     output_dir=f"./analysis_{param}")
...             all_charge_analyses[param] = figures
...             print(f"Completed charge analysis for {param}")
...         else:
...             print(f"Missing trajectory files for {param}")
...     except Exception as e:
...         print(f"Error analyzing {param}: {e}")
>>>
>>> successful_analyses = len(all_charge_analyses)
>>> print(f"Successfully completed {successful_analyses} parameter studies")

Advanced visualization customization:

>>> # Publication-quality charge analysis with custom styling
>>> publication_files = ["experiment_data.lammpstrj"]
>>> publication_labels = ["Experimental"]
>>>
>>> # Publication-specific parameters
>>> pub_styling = {
...     'ylimithi': 80,      # Appropriate charge scale
...     'ylimitlo': -80,     # Symmetric bipolar range
...     'xlimitlo': -10,     # Device boundaries
...     'xlimithi': 40,
...     'yaxis': 0,          # Zero-centered
...     'markerindex': 2     # Publication markers
... }
>>>
>>> charge_figures = plot_atomic_charge_distribution(publication_files, publication_labels,
...                                                skip_rows=9, z_bins=75,
...                                                analysis_name="publication_charges",
...                                                output_dir="./publication_figures",
...                                                **pub_styling)
>>>
>>> print("Publication-ready charge distribution figures generated")
>>> print("Available figures:", list(charge_figures.keys()))