diff --git a/impedance/models/circuits/circuits.py b/impedance/models/circuits/circuits.py index b670d19..c7ee296 100644 --- a/impedance/models/circuits/circuits.py +++ b/impedance/models/circuits/circuits.py @@ -135,19 +135,27 @@ def predict(self, frequencies, use_initial=False): """ frequencies = np.array(frequencies, dtype=float) + 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], - circuit_elements) + 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], - circuit_elements) + 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 5fe1c30..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,10 +240,13 @@ def wrappedCircuit(frequencies, *parameters): """ - x = eval(buildCircuit(circuit, frequencies, *parameters, - constants=constants, eval_string='', - index=0)[0], - circuit_elements) + 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) @@ -344,13 +352,14 @@ 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) - new = raw_elem + '(' + param_string + ',' + str(frequencies) + ')' + # param_string += str(param_list) + param_string = '[' + ','.join(param_list) + ']' + new = raw_elem + '(' + param_string + ',frequencies)' eval_string += new if i == len(split) - 1: @@ -442,4 +451,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) 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():