def do_model_cli(self, range, ya, model, method_nr=0, do_norm_denorm=True): # Clear status and params label boxes # Import the model script, or reload it if already imported self.model = model self.model = reload(self.model) # Append data to form full set for modeling mag = np.array(ya.inputData[M]) phase = np.array(np.radians(ya.inputData[P])) freq = np.array(range.xa["Hz"]) load = np.array(range.load_array) # Radian frequency w = 2 * np.pi * freq # Multiple data segments. # Create null weighting array, same size as m but full of 1's weight = mag.copy() weight.fill(1.0) # Complex impedance target z = mag * np.exp(1j * phase) # Instantiate clean class for lmfit fitter params = Parameters() params.clear() # Init list of name/value tuples values = [] # Get selected fitting method method = METHODS[method_nr][1] # Do actual modeling. # Make working copy of PARAMS list from model param_list = list(self.model.PARAMS) # Adjust min and max if necessary for phase in param_list: phase["min"] = self._min_max_set(phase["min"], method, phase["init"] / 1e2) phase["max"] = self._min_max_set(phase["max"], method, phase["init"] * 1e2) if do_norm_denorm: # Normalize component, frequency, and impedance values # Determine frequency and Z scaling factors from initial values fsf, zsf = self._find_sf(param_list) # Normalize each component value, min, and max for phase in param_list: type = phase["name"][0].upper() norm = self._normalize(phase["init"], phase["min"], phase["max"], type, fsf, zsf) phase["init"] = norm["init"] phase["min"] = norm["min"] phase["max"] = norm["max"] # Normalize frequency, target Z, and load w = w / fsf z = z / zsf # TODO: check what does gui in case of None #load = load / zsf else: fsf, zsf = 1.0, 1.0 # Add modified params to lmfit Parameter class # .add converts min/max of None to -/+inf for phase in param_list: params.add(phase["name"], value=phase["init"], vary=phase["vary"], min=phase["min"], max=phase["max"]) # Perform weighted model optimization. # Errors will be caught and displayed by zfit_excepthook() in main window. kw_args = {"load": load, "fsf": fsf, "zsf": zsf} result = minimize(self._fcn2min, params, args=(w, z, weight), kws=kw_args, method=method) # Don't use params class after minimize -- some values are scrambled or changed. # Populate values[] with modeling results, denormalized if necessary for phase in param_list: name = phase["name"] val = result.params[name].value if do_norm_denorm: comp_type = name[0] val = self._denormalize(val, comp_type, fsf, zsf) v = (name, val) values.append(v) if do_norm_denorm: # Denormalize frequency, target Z, and load w = w * fsf z = z * zsf # TODO: check what does gui in case of None #load = load * zsf # Write denormalized modeling results to file with open(PARAM_FILE, mode='w', encoding='utf-8') as f: print('name, value', file=f) for param in values: print('{}, {}'.format(param[0], param[1]), file=f) print('{}, {}'.format(param[0], param[1])) # Convert list of tuples to a single dict for the model, to be compatible # with the way minimize() uses the model values_d = {param[0]: param[1] for param in values} # Get complex impedance of model using modeled or locked parameters # Use denormalized values kw_args = {"load": load, "fsf": 1.0, "zsf": 1.0} zfit = self.model.model(w, values_d, **kw_args) # Break into magnitude and degree phase magfit = np.abs(zfit) phasefit = np.angle(zfit, deg=True) # TODO: understand why a, b, i are 0!!! # # Split into segments as required, add to data list # a, b, i = 0, 0, 0 # ya.modeledData[M] = magfit[a:b] # ya.modeledData[P] = phasefit[a:b] ya.modeledData[M] = magfit ya.modeledData[P] = phasefit self.ya = ya status = "Number of function calls: " + str(result.nfev) if result.aborted: status = "Process aborted" print(status) report_fit(result)
def do_model(self): # Clear status and params label boxes self.amw.labelParams.setText("") self.amw.labelParams.repaint() self.amw.labelStatus.setText("Modeling...") self.amw.labelStatus.repaint() # Import the model script, or reload it if already imported self.model = import_module("Models." + self.amw.lineEditModel.text()) self.model = reload(self.model) # Clear any previous modeling, M and P axes for line in self.ya.ax[M].get_lines() + self.ya.ax[P].get_lines(): if line.get_label() == "modeledZPlot": line.remove() # Create local concatenated arrays for freq, mag, phase, and load. # Overwrites data in ya class m = np.array([]) p = np.array([]) f = np.array([]) l = np.array([]) for y, r in zip(self.ya_list, self.range_list): # Copy data from lists to working objects self.ya.data_unbundle(y) self.range.data_unbundle(r) # Append data to form full set for modeling m = np.append(m, self.ya.inputData[M]) p = np.append(p, np.radians(self.ya.inputData[P])) f = np.append(f, self.range.xa["Hz"]) l = np.append(l, self.range.load_array) # Radian frequency w = 2 * np.pi * f # Use drawn curves if they exist if self.ya.drawnData[M] is not None: # Drawn data exists for magnitude, use it instead m = self.ya.drawnData[M] if self.ya.drawnData[P] is not None: # Drawn data exists for phase, use it instead p = np.radians(self.ya.drawnData[P]) if len(self.ya_list) > 1: # Multiple data segments. # Create null weighting array, same size as m but full of 1's weight = m.copy() weight.fill(1.0) else: weight = self.ya.drawnData[W] # Complex impedance target z = m * np.exp(1j * p) # Instantiate clean class for lmfit fitter params = Parameters() params.clear() # Init list of name/value tuples values = [] # Get selected fitting method method = METHODS[self.amw.comboBoxMethod.currentIndex()][1] if self.amw.checkBoxLocked.isChecked(): # Read last saved or edited params data (denormalized) with open(PARAM_FILE, mode='r', encoding='utf-8', newline='') as f: reader = csv.reader(f) next(f) # skip header line for line in reader: v = (line[0], float(line[1])) # Build a list of name/value tuples values.append(v) else: # Do actual modeling. # Make working copy of PARAMS list from model param_list = list(self.model.PARAMS) # Adjust min and max if necessary for p in param_list: p["min"] = self._min_max_set(p["min"], method, p["init"] / 1e2) p["max"] = self._min_max_set(p["max"], method, p["init"] * 1e2) if self.amw.do_norm_denorm: # Normalize component, frequency, and impedance values # Determine frequency and Z scaling factors from initial values fsf, zsf = self._find_sf(param_list) # Normalize each component value, min, and max for p in param_list: type = p["name"][0].upper() norm = self._normalize(p["init"], p["min"], p["max"], type, fsf, zsf) p["init"] = norm["init"] p["min"] = norm["min"] p["max"] = norm["max"] # Normalize frequency, target Z, and load w = w / fsf z = z / zsf l = l / zsf else: fsf, zsf = 1.0, 1.0 # Add modified params to lmfit Parameter class # .add converts min/max of None to -/+inf for p in param_list: params.add(p["name"], value=p["init"], vary=p["vary"], min=p["min"], max=p["max"]) # Perform weighted model optimization. # Errors will be caught and displayed by zfit_excepthook() in main window. kw_args = {"load": l, "fsf": fsf, "zsf": zsf} result = minimize(self._fcn2min, params, args=(w, z, weight), kws=kw_args, method=method, iter_cb=self._prog_bar_update) # Don't use params class after minimize -- some values are scrambled or changed. # Populate values[] with modeling results, denormalized if necessary for p in param_list: name = p["name"] val = result.params[name].value if self.amw.do_norm_denorm: comp_type = name[0] val = self._denormalize(val, comp_type, fsf, zsf) v = (name, val) values.append(v) if self.amw.do_norm_denorm: # Denormalize frequency, target Z, and load w = w * fsf z = z * zsf l = l * zsf # Write denormalized modeling results to file with open(PARAM_FILE, mode='w', encoding='utf-8') as f: print('name, value', file=f) for p in values: print('{}, {}'.format(p[0], p[1]), file=f) self.amw.progressBar.setValue(0) # Convert list of tuples to a single dict for the model, to be compatible # with the way minimize() uses the model values_d = {p[0]: p[1] for p in values} # Get complex impedance of model using modeled or locked parameters # Use denormalized values kw_args = {"load": l, "fsf": 1.0, "zsf": 1.0} zfit = self.model.model(w, values_d, **kw_args) # Break into magnitude and degree phase magfit = np.abs(zfit) phasefit = np.angle(zfit, deg=True) # Split into segments as required, add to data list a, b, i = 0, 0, 0 for y in self.ya_list: self.ya.data_unbundle(y) seg_length = len(self.ya.inputData[M]) b += seg_length self.ya.modeledData[M] = magfit[a:b] self.ya.modeledData[P] = phasefit[a:b] a += seg_length self.ya_list[i] = self.ya.data_bundle() i += 1 # Refresh working classes with currently indexed segment data self.ya.data_unbundle(self.ya_list[self.range.segment_index]) self.range.data_unbundle(self.range_list[self.range.segment_index]) # Add to plot self.ya.ax[M].plot(self.range.xa["Hz"], self.ya.modeledData[M], self.ya.modeledLinePlot[M], ls=self.ya.modeledLineStyle[M], lw=1, label="modeledZPlot") self.ya.ax[P].plot(self.range.xa["Hz"], self.ya.modeledData[P], self.ya.modeledLinePlot[P], ls=self.ya.modeledLineStyle[P], lw=1, label="modeledZPlot") # Update results text box self.print_results(values) if self.amw.checkBoxLocked.isChecked(): self.amw.labelStatus.setText("") else: # Append "written to" text to results box outstr = self.amw.labelParams.text() outstr += "<br>Written to<br>" + PARAM_FILE self.amw.labelParams.setText(outstr) # Print optimization info status = "Number of function calls: " + str(result.nfev) + "<br>" if result.aborted: status = RICH_TEXT_RED + "Process aborted:<br>" #status += result.lmdif_message self.amw.labelStatus.setText(status) self.draw_formatted()