Ready-to-Use Code Examples

Complete PCM Simulation Pipeline

Python Complete

Full end-to-end simulation including crystallization, pulse programming, and reliability analysis.

#!/usr/bin/env python3
"""
Complete PCM Simulation Pipeline
Demonstrates all major simulation capabilities
"""

import numpy as np
import matplotlib.pyplot as plt
from pcm import JMAKModel, PCMDevice, Reliability
from pcm.utils import create_reset_pulse, create_set_pulse, save_results

# Initialize models
jmak = JMAKModel(params={
    'Ea': 1.8,   # eV
    'k0': 1e16,  # 1/s
    'n': 3.0     # Avrami exponent
})

device = PCMDevice(params={
    'R_amorphous': 1e6,    # Ohms
    'R_crystalline': 1e3,   # Ohms
    'C_thermal': 1e-15,     # J/K
    'R_thermal': 1e7,       # K/W
})

reliability = Reliability()

# 1. Crystallization Kinetics
print("=" * 50)
print("1. CRYSTALLIZATION KINETICS")
print("=" * 50)

temps = [400, 450, 500, 550, 600]  # K
time = np.logspace(-9, -3, 100)    # 1ns to 1ms

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

for T in temps:
    X = jmak.simulate_isothermal(T, time)
    axes[0].semilogx(time * 1e6, X, label=f'{T} K')
    
    # Find t50
    t50_idx = np.argmin(np.abs(X - 0.5))
    t50 = time[t50_idx]
    print(f"  T={T}K: t50 = {t50*1e6:.2f} μs")

axes[0].set_xlabel('Time (μs)')
axes[0].set_ylabel('Crystalline Fraction')
axes[0].set_title('Isothermal Crystallization')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Arrhenius plot
inv_T = 1000 / np.array(temps)
t50_values = []
for T in temps:
    X = jmak.simulate_isothermal(T, time)
    t50_idx = np.argmin(np.abs(X - 0.5))
    t50_values.append(time[t50_idx])

axes[1].semilogy(inv_T, t50_values, 'o-', color='#9333ea', linewidth=2)
axes[1].set_xlabel('1000/T (K⁻¹)')
axes[1].set_ylabel('t₅₀ (s)')
axes[1].set_title('Arrhenius Analysis')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('crystallization_kinetics.png', dpi=150, facecolor='black')

# 2. Pulse Programming
print("\n" + "=" * 50)
print("2. PULSE PROGRAMMING")
print("=" * 50)

# RESET pulse
V_reset, t_reset = create_reset_pulse(V_reset=3.0, t_width=50e-9)
result_reset = device.simulate_voltage_pulse(V_reset, t_reset, X0=1.0)

print(f"RESET Pulse:")
print(f"  Peak Temperature: {np.max(result_reset['temperature']):.0f} K")
print(f"  Final State: {'Amorphous' if result_reset['phase'][-1] < 0.5 else 'Crystalline'}")
print(f"  Energy: {np.trapz(V_reset**2/result_reset['resistance'], t_reset)*1e12:.2f} pJ")

# SET pulse
V_set, t_set = create_set_pulse(V_set=1.5, t_width=500e-9)
result_set = device.simulate_voltage_pulse(V_set, t_set, X0=0.0)

print(f"\nSET Pulse:")
print(f"  Peak Temperature: {np.max(result_set['temperature']):.0f} K")
print(f"  Final State: {'Amorphous' if result_set['phase'][-1] < 0.5 else 'Crystalline'}")
print(f"  Energy: {np.trapz(V_set**2/result_set['resistance'], t_set)*1e12:.2f} pJ")

# Plot pulse results
fig, axes = plt.subplots(2, 3, figsize=(15, 8))

# RESET plots
axes[0,0].plot(t_reset*1e9, V_reset, 'r-', linewidth=2)
axes[0,0].set_xlabel('Time (ns)')
axes[0,0].set_ylabel('Voltage (V)')
axes[0,0].set_title('RESET Pulse')

axes[0,1].plot(t_reset*1e9, result_reset['temperature'], 'r-', linewidth=2)
axes[0,1].axhline(900, color='white', linestyle='--', alpha=0.5)
axes[0,1].set_xlabel('Time (ns)')
axes[0,1].set_ylabel('Temperature (K)')

axes[0,2].semilogy(t_reset*1e9, result_reset['resistance'], 'r-', linewidth=2)
axes[0,2].set_xlabel('Time (ns)')
axes[0,2].set_ylabel('Resistance (Ω)')

# SET plots
axes[1,0].plot(t_set*1e9, V_set, 'b-', linewidth=2)
axes[1,0].set_xlabel('Time (ns)')
axes[1,0].set_ylabel('Voltage (V)')
axes[1,0].set_title('SET Pulse')

axes[1,1].plot(t_set*1e9, result_set['temperature'], 'b-', linewidth=2)
axes[1,1].axhline(450, color='white', linestyle='--', alpha=0.5)
axes[1,1].set_xlabel('Time (ns)')
axes[1,1].set_ylabel('Temperature (K)')

axes[1,2].plot(t_set*1e9, result_set['phase'], 'b-', linewidth=2)
axes[1,2].set_xlabel('Time (ns)')
axes[1,2].set_ylabel('Crystalline Fraction')

plt.tight_layout()
plt.savefig('pulse_programming.png', dpi=150, facecolor='black')

# 3. Reliability Analysis
print("\n" + "=" * 50)
print("3. RELIABILITY ANALYSIS")
print("=" * 50)

# Retention
retention_data = reliability.retention_arrhenius()
print(f"Data Retention at 85°C: {retention_data['t50_85C']:.1f} years")

# Endurance
endurance_data = reliability.endurance_weibull(n_samples=10000)
print(f"Endurance B10 Life: {endurance_data['B10']:.2e} cycles")
print(f"Endurance B50 Life: {endurance_data['B50']:.2e} cycles")

# Save all results
results = {
    'crystallization': {'temps': temps, 'time': time},
    'reset': result_reset,
    'set': result_set,
    'retention': retention_data,
    'endurance': endurance_data
}

save_results(results, 'pcm_simulation_results.json')
print("\n✓ Results saved to pcm_simulation_results.json")

plt.show()

Monte Carlo Variability Analysis

Python Statistics

Statistical analysis of device-to-device and cycle-to-cycle variability using Monte Carlo methods.

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

def monte_carlo_variability(n_samples=10000, variation=0.2):
    """
    Monte Carlo simulation for PCM variability analysis
    
    Parameters:
    -----------
    n_samples : int
        Number of Monte Carlo samples
    variation : float
        Coefficient of variation (σ/μ)
    """
    
    # Device parameters with variability
    params = {
        'R_off': 1e6,      # Amorphous resistance
        'R_on': 1e3,       # Crystalline resistance
        'V_th': 1.2,       # Threshold voltage
        'T_c': 450,        # Crystallization temperature
        'E_a': 1.8         # Activation energy
    }
    
    # Generate random variations
    results = {}
    
    for param, mean_value in params.items():
        # Log-normal distribution for resistances
        if 'R_' in param:
            sigma = np.log(1 + variation**2)**0.5
            mu = np.log(mean_value) - sigma**2/2
            values = np.random.lognormal(mu, sigma, n_samples)
        # Normal distribution for other parameters
        else:
            values = np.random.normal(mean_value, mean_value*variation, n_samples)
        
        results[param] = values
    
    # Calculate derived quantities
    results['R_ratio'] = results['R_off'] / results['R_on']
    results['retention_85C'] = 10 * np.exp(results['E_a'] * 11605 / (85 + 273))
    
    # Plotting
    fig, axes = plt.subplots(2, 3, figsize=(15, 10))
    
    # R_off distribution
    axes[0,0].hist(results['R_off']/1e6, bins=50, color='#9333ea', alpha=0.7, edgecolor='white')
    axes[0,0].set_xlabel('R_off (MΩ)')
    axes[0,0].set_ylabel('Count')
    axes[0,0].set_title(f'Off Resistance (CV={variation:.0%})')
    
    # R_on distribution
    axes[0,1].hist(results['R_on']/1e3, bins=50, color='#ff6b6b', alpha=0.7, edgecolor='white')
    axes[0,1].set_xlabel('R_on (kΩ)')
    axes[0,1].set_ylabel('Count')
    axes[0,1].set_title('On Resistance')
    
    # R_ratio distribution
    axes[0,2].hist(np.log10(results['R_ratio']), bins=50, color='#4ecdc4', alpha=0.7, edgecolor='white')
    axes[0,2].set_xlabel('log₁₀(R_off/R_on)')
    axes[0,2].set_ylabel('Count')
    axes[0,2].set_title('Resistance Ratio')
    
    # V_th distribution
    axes[1,0].hist(results['V_th'], bins=50, color='#ffc300', alpha=0.7, edgecolor='white')
    axes[1,0].set_xlabel('V_th (V)')
    axes[1,0].set_ylabel('Count')
    axes[1,0].set_title('Threshold Voltage')
    
    # Retention scatter plot
    axes[1,1].scatter(results['E_a'], np.log10(results['retention_85C']), 
                     alpha=0.3, color='#9333ea', s=1)
    axes[1,1].set_xlabel('Activation Energy (eV)')
    axes[1,1].set_ylabel('log₁₀(Retention @ 85°C)')
    axes[1,1].set_title('Retention Correlation')
    
    # Cumulative distribution
    sorted_ratio = np.sort(results['R_ratio'])
    cdf = np.arange(1, len(sorted_ratio)+1) / len(sorted_ratio)
    axes[1,2].semilogx(sorted_ratio, cdf*100, linewidth=2, color='#9333ea')
    axes[1,2].grid(True, alpha=0.3)
    axes[1,2].set_xlabel('R_off/R_on')
    axes[1,2].set_ylabel('Cumulative Probability (%)')
    axes[1,2].set_title('CDF of Resistance Ratio')
    
    # Add percentile lines
    percentiles = [10, 50, 90]
    for p in percentiles:
        val = np.percentile(sorted_ratio, p)
        axes[1,2].axvline(val, linestyle='--', alpha=0.5, color='white')
        axes[1,2].text(val, p, f'P{p}', ha='right', color='white')
    
    plt.tight_layout()
    
    # Print statistics
    print("=" * 50)
    print("MONTE CARLO STATISTICS")
    print("=" * 50)
    
    for param in ['R_off', 'R_on', 'R_ratio', 'V_th']:
        mean = np.mean(results[param])
        std = np.std(results[param])
        cv = std/mean
        
        print(f"\n{param}:")
        print(f"  Mean: {mean:.2e}")
        print(f"  Std:  {std:.2e}")
        print(f"  CV:   {cv:.2%}")
        print(f"  P10:  {np.percentile(results[param], 10):.2e}")
        print(f"  P50:  {np.percentile(results[param], 50):.2e}")
        print(f"  P90:  {np.percentile(results[param], 90):.2e}")
    
    return results

# Run simulation
results = monte_carlo_variability(n_samples=10000, variation=0.2)
plt.show()

Interactive Web Simulation

JavaScript Plotly.js

Browser-based PCM simulation with interactive controls and real-time visualization.

// PCM Web Simulator Class
class PCMSimulator {
    constructor() {
        this.params = {
            Ea: 1.8,  // eV
            k0: 1e16, // 1/s
            n: 3.0,   // Avrami exponent
            kb: 8.617333262e-5  // eV/K
        };
        
        this.device = {
            R_amorphous: 1e6,  // Ohms
            R_crystalline: 1e3, // Ohms
            C_thermal: 1e-15,   // J/K
            R_thermal: 1e7,     // K/W
            T_ambient: 300      // K
        };
    }
    
    // JMAK crystallization model
    jmakRate(T) {
        return this.params.k0 * Math.exp(-this.params.Ea / (this.params.kb * T));
    }
    
    simulateIsothermal(T, timeArray) {
        const k = this.jmakRate(T);
        return timeArray.map(t => 
            1 - Math.exp(-Math.pow(k * t, this.params.n))
        );
    }
    
    // Pulse simulation
    simulatePulse(voltage, duration, initialState = 0) {
        const dt = duration / 1000; // 1000 time steps
        const time = [];
        const temperature = [];
        const resistance = [];
        const phase = [];
        
        let T = this.device.T_ambient;
        let X = initialState;
        
        for (let i = 0; i < 1000; i++) {
            const t = i * dt;
            time.push(t);
            
            // Calculate resistance
            const R = this.device.R_crystalline * X + 
                     this.device.R_amorphous * (1 - X);
            resistance.push(R);
            
            // Joule heating
            const P = voltage * voltage / R;
            
            // Temperature evolution
            const dT = (P - (T - this.device.T_ambient) / this.device.R_thermal) * 
                      dt / this.device.C_thermal;
            T += dT;
            temperature.push(T);
            
            // Phase change
            if (T > 900 && voltage > 2.5) {
                // Amorphization (RESET)
                X = Math.max(0, X - 0.1);
            } else if (T > 450) {
                // Crystallization (SET)
                const k = this.jmakRate(T);
                const dX = this.params.n * k * dt * Math.pow(1 - X, this.params.n);
                X = Math.min(1, X + dX);
            }
            phase.push(X);
        }
        
        return { time, temperature, resistance, phase };
    }
    
    // Create interactive plot
    createPlot(elementId, data) {
        const traces = [
            {
                x: data.time.map(t => t * 1e9), // Convert to ns
                y: data.temperature,
                name: 'Temperature',
                yaxis: 'y',
                line: { color: '#ff6b6b' }
            },
            {
                x: data.time.map(t => t * 1e9),
                y: data.phase,
                name: 'Crystalline Fraction',
                yaxis: 'y2',
                line: { color: '#9333ea' }
            }
        ];
        
        const layout = {
            title: 'PCM Device Simulation',
            xaxis: { 
                title: 'Time (ns)',
                gridcolor: '#333'
            },
            yaxis: { 
                title: 'Temperature (K)',
                gridcolor: '#333',
                titlefont: { color: '#ff6b6b' },
                tickfont: { color: '#ff6b6b' }
            },
            yaxis2: {
                title: 'Crystalline Fraction',
                titlefont: { color: '#9333ea' },
                tickfont: { color: '#9333ea' },
                overlaying: 'y',
                side: 'right',
                range: [0, 1]
            },
            paper_bgcolor: 'rgba(0,0,0,0)',
            plot_bgcolor: 'rgba(0,0,0,0.1)',
            font: { color: '#e0e0e0' },
            showlegend: true
        };
        
        Plotly.newPlot(elementId, traces, layout, {responsive: true});
    }
}

// Usage Example
const simulator = new PCMSimulator();

// Simulate RESET pulse
const resetData = simulator.simulatePulse(3.0, 50e-9, 1.0);
simulator.createPlot('reset-plot', resetData);

// Simulate SET pulse
const setData = simulator.simulatePulse(1.5, 500e-9, 0.0);
simulator.createPlot('set-plot', setData);

// Interactive controls
document.getElementById('voltage-slider').addEventListener('input', function() {
    const voltage = parseFloat(this.value);
    const duration = parseFloat(document.getElementById('duration-slider').value) * 1e-9;
    
    const data = simulator.simulatePulse(voltage, duration);
    simulator.createPlot('interactive-plot', data);
    
    // Update display
    document.getElementById('voltage-display').textContent = voltage.toFixed(1) + ' V';
    document.getElementById('peak-temp').textContent = 
        Math.max(...data.temperature).toFixed(0) + ' K';
    document.getElementById('final-state').textContent = 
        data.phase[data.phase.length - 1] > 0.5 ? 'Crystalline' : 'Amorphous';
});