From 71181746393e6aecad7576dd1fcd9cc490553282 Mon Sep 17 00:00:00 2001 From: Adrian Usler Date: Mon, 30 Mar 2026 23:37:43 +0200 Subject: [PATCH 1/4] Frequencies as placeholder in circuit str instead of explicitly --- impedance/models/circuits/circuits.py | 1 + impedance/models/circuits/elements.py | 2 +- impedance/models/circuits/fitting.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/impedance/models/circuits/circuits.py b/impedance/models/circuits/circuits.py index b670d19..59855cc 100644 --- a/impedance/models/circuits/circuits.py +++ b/impedance/models/circuits/circuits.py @@ -135,6 +135,7 @@ def predict(self, frequencies, use_initial=False): """ frequencies = np.array(frequencies, dtype=float) + circuit_elements["frequencies"] = frequencies if self._is_fit() and not use_initial: return eval(buildCircuit(self.circuit, frequencies, *self.parameters_, diff --git a/impedance/models/circuits/elements.py b/impedance/models/circuits/elements.py index 643ff69..adbe945 100644 --- a/impedance/models/circuits/elements.py +++ b/impedance/models/circuits/elements.py @@ -92,7 +92,7 @@ def p(parallel): # populated by the element decorator - # this maps ex. 'R' to the function R to always give us a list of # active elements in any context -circuit_elements = {"s": s, "p": p} +circuit_elements = {"s": s, "p": p, "frequencies": np.array([1.])} @element(num_params=1, units=["Ohm"]) diff --git a/impedance/models/circuits/fitting.py b/impedance/models/circuits/fitting.py index 5fe1c30..1b4a374 100644 --- a/impedance/models/circuits/fitting.py +++ b/impedance/models/circuits/fitting.py @@ -235,6 +235,7 @@ def wrappedCircuit(frequencies, *parameters): """ + circuit_elements["frequencies"] = frequencies x = eval(buildCircuit(circuit, frequencies, *parameters, constants=constants, eval_string='', index=0)[0], @@ -350,7 +351,7 @@ def count_parens(string): index += 1 param_string += str(param_list) - new = raw_elem + '(' + param_string + ',' + str(frequencies) + ')' + new = raw_elem + '(' + param_string + ', frequencies)' eval_string += new if i == len(split) - 1: From edc554127cc20d9d28eaecda214e5b6f5ce840b4 Mon Sep 17 00:00:00 2001 From: Adrian Usler Date: Tue, 31 Mar 2026 00:24:08 +0200 Subject: [PATCH 2/4] Fix test: take frequencies out of circuit_elements dict --- impedance/models/circuits/circuits.py | 6 +++--- impedance/models/circuits/elements.py | 2 +- impedance/models/circuits/fitting.py | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/impedance/models/circuits/circuits.py b/impedance/models/circuits/circuits.py index 59855cc..ad10df5 100644 --- a/impedance/models/circuits/circuits.py +++ b/impedance/models/circuits/circuits.py @@ -135,20 +135,20 @@ def predict(self, frequencies, use_initial=False): """ frequencies = np.array(frequencies, dtype=float) - circuit_elements["frequencies"] = frequencies + arg_dict = {**circuit_elements, "frequencies": frequencies} if self._is_fit() and not use_initial: return eval(buildCircuit(self.circuit, frequencies, *self.parameters_, constants=self.constants, eval_string='', index=0)[0], - circuit_elements) + arg_dict) else: warnings.warn("Simulating circuit based on initial parameters") return eval(buildCircuit(self.circuit, frequencies, *self.initial_guess, constants=self.constants, eval_string='', index=0)[0], - circuit_elements) + arg_dict) def get_param_names(self): """ Converts circuit string to names and units """ diff --git a/impedance/models/circuits/elements.py b/impedance/models/circuits/elements.py index adbe945..643ff69 100644 --- a/impedance/models/circuits/elements.py +++ b/impedance/models/circuits/elements.py @@ -92,7 +92,7 @@ def p(parallel): # populated by the element decorator - # this maps ex. 'R' to the function R to always give us a list of # active elements in any context -circuit_elements = {"s": s, "p": p, "frequencies": np.array([1.])} +circuit_elements = {"s": s, "p": p} @element(num_params=1, units=["Ohm"]) diff --git a/impedance/models/circuits/fitting.py b/impedance/models/circuits/fitting.py index 1b4a374..7dd4aec 100644 --- a/impedance/models/circuits/fitting.py +++ b/impedance/models/circuits/fitting.py @@ -235,11 +235,11 @@ def wrappedCircuit(frequencies, *parameters): """ - circuit_elements["frequencies"] = frequencies + arg_dict = {**circuit_elements, "frequencies": frequencies} x = eval(buildCircuit(circuit, frequencies, *parameters, constants=constants, eval_string='', index=0)[0], - circuit_elements) + arg_dict) y_real = np.real(x) y_imag = np.imag(x) @@ -443,4 +443,6 @@ def check_and_eval(element): raise ValueError(f'{element} not in ' + f'allowed elements ({allowed_elements})') else: - return eval(element, circuit_elements) + dummy_frequency_array = np.array([1.]) + arg_dict = {**circuit_elements, "frequencies": dummy_frequency_array} + return eval(element, arg_dict) From cebd223cd4cd5113e3b181513dd3f80ba9130c23 Mon Sep 17 00:00:00 2001 From: Adrian Usler Date: Tue, 31 Mar 2026 00:37:28 +0200 Subject: [PATCH 3/4] Fix test_fitting --- impedance/models/circuits/fitting.py | 2 +- impedance/tests/test_fitting.py | 36 ++++++++++++++-------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/impedance/models/circuits/fitting.py b/impedance/models/circuits/fitting.py index 7dd4aec..2a09ce8 100644 --- a/impedance/models/circuits/fitting.py +++ b/impedance/models/circuits/fitting.py @@ -351,7 +351,7 @@ def count_parens(string): index += 1 param_string += str(param_list) - new = raw_elem + '(' + param_string + ', frequencies)' + new = raw_elem + '(' + param_string + ',frequencies)' eval_string += new if i == len(split) - 1: diff --git a/impedance/tests/test_fitting.py b/impedance/tests/test_fitting.py index 4bf9ca9..3a94e48 100644 --- a/impedance/tests/test_fitting.py +++ b/impedance/tests/test_fitting.py @@ -153,10 +153,10 @@ def test_buildCircuit(): assert buildCircuit(circuit, frequencies, *params, constants={})[0].replace(' ', '') == \ - 's([R([0.1],[1000.0,5.0,0.01]),' + \ - 'p([s([R([0.01],[1000.0,5.0,0.01]),' + \ - 'Wo([1.0,1000.0],[1000.0,5.0,0.01])]),' + \ - 'CPE([15.0,0.9],[1000.0,5.0,0.01])])])' + 's([R([0.1],frequencies),' + \ + 'p([s([R([0.01],frequencies),' + \ + 'Wo([1.0,1000.0],frequencies)]),' + \ + 'CPE([15.0,0.9],frequencies)])])' # Test multiple parallel elements circuit = 'R0-p(C1,R1,R2)' @@ -165,10 +165,10 @@ def test_buildCircuit(): assert buildCircuit(circuit, frequencies, *params, constants={})[0].replace(' ', '') == \ - 's([R([0.1],[1000.0,5.0,0.01]),' + \ - 'p([C([0.01],[1000.0,5.0,0.01]),' + \ - 'R([0.2],[1000.0,5.0,0.01]),' + \ - 'R([0.3],[1000.0,5.0,0.01])])])' + 's([R([0.1],frequencies),' + \ + 'p([C([0.01],frequencies),' + \ + 'R([0.2],frequencies),' + \ + 'R([0.3],frequencies)])])' # Test nested parallel groups circuit = 'R0-p(p(R1, C1)-R2, C2)' @@ -177,11 +177,11 @@ def test_buildCircuit(): assert buildCircuit(circuit, frequencies, *params, constants={})[0].replace(' ', '') == \ - 's([R([1],[1000.0,5.0,0.01]),' + \ - 'p([s([p([R([2],[1000.0,5.0,0.01]),' + \ - 'C([3],[1000.0,5.0,0.01])]),' + \ - 'R([4],[1000.0,5.0,0.01])]),' + \ - 'C([5],[1000.0,5.0,0.01])])])' + 's([R([1],frequencies),' + \ + 'p([s([p([R([2],frequencies),' + \ + 'C([3],frequencies)]),' + \ + 'R([4],frequencies)]),' + \ + 'C([5],frequencies)])])' # Test parallel elements at beginning and end circuit = 'p(C1,R1)-p(C2,R2)' @@ -190,10 +190,10 @@ def test_buildCircuit(): assert buildCircuit(circuit, frequencies, *params, constants={})[0].replace(' ', '') == \ - 's([p([C([0.1],[1000.0,5.0,0.01]),' + \ - 'R([0.01],[1000.0,5.0,0.01])]),' + \ - 'p([C([0.2],[1000.0,5.0,0.01]),' + \ - 'R([0.3],[1000.0,5.0,0.01])])])' + 's([p([C([0.1],frequencies),' + \ + 'R([0.01],frequencies)]),' + \ + 'p([C([0.2],frequencies),' + \ + 'R([0.3],frequencies)])])' # Test single element circuit circuit = 'R1' @@ -202,7 +202,7 @@ def test_buildCircuit(): assert buildCircuit(circuit, frequencies, *params, constants={})[0].replace(' ', '') == \ - 'R([100],[1000.0,5.0,0.01])' + 'R([100],frequencies)' def test_RMSE(): From ae249b4984168a9020c995142bd1a942b5c64a07 Mon Sep 17 00:00:00 2001 From: Adrian Usler Date: Tue, 31 Mar 2026 16:11:31 +0200 Subject: [PATCH 4/4] Take buildCircuit out of wrappedCircuit --- impedance/models/circuits/circuits.py | 21 ++++++++++++------ impedance/models/circuits/fitting.py | 32 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/impedance/models/circuits/circuits.py b/impedance/models/circuits/circuits.py index ad10df5..c7ee296 100644 --- a/impedance/models/circuits/circuits.py +++ b/impedance/models/circuits/circuits.py @@ -135,20 +135,27 @@ def predict(self, frequencies, use_initial=False): """ frequencies = np.array(frequencies, dtype=float) - arg_dict = {**circuit_elements, "frequencies": frequencies} + arg_dict = { + **circuit_elements, + "frequencies": frequencies, + "constants": self.constants + } if self._is_fit() and not use_initial: - return eval(buildCircuit(self.circuit, frequencies, + arg_dict.update({"parameters": self.parameters_}) + eval_str, index = buildCircuit(self.circuit, frequencies, *self.parameters_, constants=self.constants, eval_string='', - index=0)[0], - arg_dict) + index=0) + return eval(eval_str, arg_dict) else: warnings.warn("Simulating circuit based on initial parameters") - return eval(buildCircuit(self.circuit, frequencies, + arg_dict.update({"parameters": self.initial_guess}) + eval_str, index = buildCircuit(self.circuit, frequencies, *self.initial_guess, constants=self.constants, eval_string='', - index=0)[0], - arg_dict) + index=0) + + return eval(eval_str, arg_dict) def get_param_names(self): """ Converts circuit string to names and units """ diff --git a/impedance/models/circuits/fitting.py b/impedance/models/circuits/fitting.py index 2a09ce8..336be08 100644 --- a/impedance/models/circuits/fitting.py +++ b/impedance/models/circuits/fitting.py @@ -149,7 +149,8 @@ def circuit_fit(frequencies, impedances, circuit, initial_guess, constants={}, abs_Z = np.abs(Z) kwargs['sigma'] = np.hstack([abs_Z, abs_Z]) - popt, pcov = curve_fit(wrapCircuit(circuit, constants), f, + circuit_func = wrapCircuit(circuit, f, constants, initial_guess) + popt, pcov = curve_fit(circuit_func, f, np.hstack([Z.real, Z.imag]), p0=initial_guess, bounds=bounds, **kwargs) @@ -176,8 +177,8 @@ def opt_function(x): function Returns a function (RMSE as a function of parameters). """ - return rmse(wrapCircuit(circuit, constants)(f, *x), - np.hstack([Z.real, Z.imag])) + circuit_func = wrapCircuit(circuit, f, constants, x) + return rmse(circuit_func, np.hstack([Z.real, Z.imag])) class BasinhoppingBounds(object): """ Adapted from the basinhopping documetation @@ -216,8 +217,12 @@ def __call__(self, **kwargs): return popt, perror -def wrapCircuit(circuit, constants): +def wrapCircuit(circuit, frequencies, constants, parameters): """ wraps function so we can pass the circuit string """ + eval_str, _ = buildCircuit(circuit, frequencies, *parameters, + constants=constants, eval_string='', + index=0) + # maybe frequencies don't need to be passed as argument to buildCircuit def wrappedCircuit(frequencies, *parameters): """ returns a stacked array of real and imaginary impedance components @@ -235,11 +240,13 @@ def wrappedCircuit(frequencies, *parameters): """ - arg_dict = {**circuit_elements, "frequencies": frequencies} - x = eval(buildCircuit(circuit, frequencies, *parameters, - constants=constants, eval_string='', - index=0)[0], - arg_dict) + arg_dict = { + **circuit_elements, + "frequencies": frequencies, + "parameters": parameters, + "constants": constants + } + x = eval(eval_str, arg_dict) y_real = np.real(x) y_imag = np.imag(x) @@ -345,12 +352,13 @@ def count_parens(string): current_elem = elem if current_elem in constants.keys(): - param_list.append(constants[current_elem]) + param_list.append(f"constants['{current_elem}']") else: - param_list.append(parameters[index]) + param_list.append(f'parameters[{index}]') index += 1 - param_string += str(param_list) + # param_string += str(param_list) + param_string = '[' + ','.join(param_list) + ']' new = raw_elem + '(' + param_string + ',frequencies)' eval_string += new