Source code for impedance_agent.agent.tools.fitter_tools
# src/agent/tools/fitter_tools.py
from typing import Optional
import traceback
import numpy as np
import jax.numpy as jnp
from ...core.models import (
ImpedanceData,
FitResult,
DRTResult,
LinKKResult, # Add this import
)
from ...fitters.ecm import ECMFitter
from ...fitters.drt import DRTFitter
from ...fitters.linkk import LinKKFitter # Add this import too
[docs]
class FitterTools:
"""Tools for ECM and DRT fitting with error handling"""
[docs]
def run_ecm_fit(self, data: ImpedanceData, **kwargs) -> Optional[FitResult]:
"""Run ECM fitting"""
try:
print("ECM fitting arguments:", kwargs)
model_code = kwargs.get("model_code")
variables = kwargs.get("variables", [])
weighting = kwargs.get("weighting", "modulus")
if not model_code or not variables:
raise ValueError(
"Missing required parameters: model_code and variables"
)
# Extract parameters and convert to numpy arrays
p0 = np.array([var["initialValue"] for var in variables])
lb = np.array([var["lowerBound"] for var in variables])
ub = np.array([var["upperBound"] for var in variables])
# Create namespace with required imports
namespace = {
"jnp": jnp,
"np": jnp,
}
# Execute model code in namespace
exec(model_code, namespace)
model_func = list(namespace.values())[-1]
# Create and run fitter
fitter = ECMFitter(
model_func=model_func,
p0=p0,
freq=data.frequency,
impedance_data=data,
lb=lb,
ub=ub,
param_info=variables,
weighting=weighting,
)
result = fitter.fit()
if result is None:
raise ValueError("ECM fitting failed to produce a result")
return result
except Exception as e:
print(f"ECM fitting failed: {str(e)}")
import traceback
traceback.print_exc()
return None
[docs]
def run_drt_fit(self, data: ImpedanceData, **kwargs) -> Optional[DRTResult]:
try:
lambda_t = kwargs.get("lambda_t", 1e-14)
lambda_pg = kwargs.get("lambda_pg", 0.01)
mode = kwargs.get("mode", "real")
# Get data in original order (it's already in descending frequency)
freqs = np.array(data.frequency) # Already in descending order
omega = 2 * np.pi * freqs
zre = np.array(data.real)
zim = np.abs(np.array(data.imaginary)) # Make positive like original
print("\nData before shifting:")
print(f"Frequency: {freqs[:5]} ... {freqs[-5:]}")
print(f"Real: {zre[:5]} ... {zre[-5:]}")
print(f"Imag: {zim[:5]} ... {zim[-5:]}")
# Shift by high frequency point like original
zre = zre - zre[0]
print("\nAfter shifting:")
print(f"Real range: {zre[0]} to {zre[-1]}")
print(f"Expected Rpol = {zre[-1] - zre[0]}") # Should be positive now
fitter = DRTFitter(
zexp_re=zre,
zexp_im=zim,
omg=omega,
lam_t0=lambda_t,
lam_pg0=lambda_pg,
lower_bounds=jnp.array([1e-15, 1e-15]),
upper_bounds=jnp.array([1e15, 1e15]),
mode=mode,
)
return fitter.fit()
except Exception as e:
print(f"DRT fitting failed: {str(e)}")
traceback.print_exc()
return None
[docs]
def run_linkk_fit(self, data: ImpedanceData, **kwargs) -> Optional[LinKKResult]:
"""Run Lin-KK validation"""
try:
c = kwargs.get("c", 0.85)
max_M = kwargs.get("max_M", 100)
fitter = LinKKFitter(data)
return fitter.fit(c=c, max_M=max_M)
except Exception as e:
print(f"Lin-KK validation failed: {str(e)}")
import traceback
traceback.print_exc()
return None