def save_plot_settings(self): """ Saves data to the current JSON-file. """ if self.json_file: save_json_data(self.data, self.json_file) self.msg = MessageBox("Success", "Data saved to %s." % self.json_file) else: self.msg = MessageBox("Failed", "Could not save data. No file is chosen.") self.msg.exec()
def __init__(self, parent=None): super().__init__(parent=parent) self.setWindowTitle("About Thermopack") self.setMinimumSize(800, 600) self.setOpenExternalLinks(True) try: file = open(os.path.join(os.getcwd(), "gui", "about.html"), "r") except FileNotFoundError: msg = MessageBox("Error", "Could not open file containing the About Info") msg.exec_() return html = file.read() file.close() self.setHtml(html)
def delete_model_setup(self, name): """ Deletes a model setup :param name: Name of model setup to be deleted """ # Deleting entry in the data dictionary try: del self.data["Model setups"][name] except KeyError: msg = MessageBox("Error", "Could not delete model setup " + name) msg.exec_() return # Closing the composition tab for i in range(self.tabs.count()): if self.tabs.tabText(i)[-len(name):] == name: self.tabs.removeTab(i) break # Removing name from the menu for i in range(self.models_list.count()): if self.models_list.item(i).text() == name: self.models_list.takeItem(i) break
def open_file(self, file_path=None): """ Opens and loads an existing JSON-file and populates the main window. :param file_path: Path to JSON-file """ if not file_path: file_dialog = QFileDialog() file_dialog.setWindowTitle("Open File") file_dialog.setDirectory(os.getcwd()) file_dialog.setNameFilter('Text files (*.json)') if file_dialog.exec_() == QFileDialog.Accepted: file_path = file_dialog.selectedFiles()[0] else: return loaded_data = get_json_data(file_path) loaded_component_data = loaded_data["Component lists"] loaded_model_setup_data = loaded_data["Model setups"] loaded_units_data = loaded_data["Units"] loaded_plotting_preferences = loaded_data["Plotting preferences"] # If loaded data contains names which currently exist, an error message will be displayed loaded_data_is_compatible = True for i in range(self.compositions_list.count()): if self.compositions_list.item( i).text() in loaded_component_data.keys(): loaded_data_is_compatible = False name = self.compositions_list.item(i).text() for i in range(self.models_list.count()): if self.models_list.item( i).text() in loaded_model_setup_data.keys(): loaded_data_is_compatible = False name = self.models_list.item(i).text() if not loaded_data_is_compatible: error_msg = "Could not load data from %s. %s already exists. " \ "Either change its name or delete it before trying to load again." \ % (file_path, name) msg = MessageBox("Error", error_msg) msg.exec_() return self.data["Component lists"].update(loaded_component_data) self.data["Model setups"].update(loaded_model_setup_data) self.data["Units"].update(loaded_units_data) self.data["Plotting preferences"].update(loaded_plotting_preferences) self.json_file = file_path self.setWindowTitle(self.windowTitle() + " - " + self.json_file) # Populate menus to the left self.log("Opened and loaded " + self.json_file + " successfully!") # Clear menu for list_name in loaded_component_data.keys(): self.update_component_lists(list_name, is_new=True, old_name=None) for list_name in loaded_model_setup_data.keys(): model_select_widget = ModelSelectWidget(self.data, name=list_name, parent=self) model_select_widget.model_setup_deleted.connect( self.delete_model_setup) model_select_widget.model_name_changed.connect( self.update_model_lists) QListWidgetItem(list_name, parent=self.models_list) self.save()
def calculate(self): """ Calls thermopack's flash functions, and populates the table with the results """ fractions = np.array(self.component_data["Fractions"]) mole_fraction_sum = np.sum(fractions) if abs(mole_fraction_sum - 1.00) > 1e-8: msg_title = "Molar fractions error" msg_text = "Molar fractions have to add up to 1.00. Currently the sum is %s." % mole_fraction_sum msg = MessageBox(msg_title, msg_text) msg.exec_() return else: # Setting the last mol fraction to 1 - the rest of them, to ensure that the total sum is exactly 1 fractions[-1] = 1 - np.sum(fractions[:-1]) self.table.clearContents() self.set_table_units() init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) flash_mode = self.flash_mode_selection.currentText() self.tp.set_ph_tolerance(float(self.ph_tol.text())) if flash_mode == "TP": T = float(self.tp_t_input.text()) P = float(self.tp_p_input.text()) # Conversion to standard SI to be used in functions T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude # TODO: Need an exception thrown in two_phase_tpflash() to be caught in case calculation fails x, y, beta_vap, beta_liq, phase = self.tp.two_phase_tpflash( T, P, fractions) elif flash_mode == "PS": P = float(self.ps_p_input.text()) S = float(self.ps_s_input.text()) # Unit conversion P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude S = self.ureg.Quantity( S, self.units["Entropy"]).to("J / (degK * mol)").magnitude if self.ps_initial_guess.isChecked(): T = float(self.ps_t_guess.text()) T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude else: T = None try: T, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_psflash( press=P, z=fractions, entropy=S, temp=T) except Exception as error: msg = MessageBox("Error", str(error)) msg.exec_() return elif flash_mode == "PH": P = float(self.ph_p_input.text()) H = float(self.ph_h_input.text()) # Convert units P = self.ureg.Quantity(P, self.units["Pressure"]).to("Pa").magnitude H = self.ureg.Quantity( H, self.units["Enthalpy"]).to("J / mol").magnitude if self.ph_initial_guess.isChecked(): T = float(self.ph_t_guess.text()) T = self.ureg.Quantity( T, self.units["Temperature"]).to("degK").magnitude else: T = None try: T, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_phflash( press=P, z=fractions, enthalpy=H, temp=T) except Exception as error: msg = MessageBox("Error", str(error)) msg.exec_() return elif flash_mode == "UV": U = float(self.uv_u_input.text()) V = float(self.uv_v_input.text()) # Unit conversion U = self.ureg.Quantity( U, self.units["Internal energy"]).to("J / mol").magnitude V = self.ureg.Quantity( V, self.units["Specific volume"]).to("m ** 3 / mol").magnitude if self.uv_t_initial_guess.isChecked(): T = float(self.uv_t_guess.text()) T = self.ureg(T, self.units["Temperature"]).to("degK").magnitude else: T = None if self.uv_p_initial_guess.isChecked(): P = float(self.uv_p_guess.text()) P = self.ureg.Quantity( P, self.units["Pressure"]).to("Pa").magnitude else: P = None # TODO: Need an exception thrown in two_phase_uvflash() to be caught in case calculation fails T, P, x, y, beta_vap, beta_liq, phase = self.tp.two_phase_uvflash( z=fractions, specific_energy=U, specific_volume=V, temp=T, press=P) else: return phase_type = self.tp.get_phase_type(phase) LIQUID, VAPOR = 1, 2 is_liq, is_vap = True, True if phase_type == "TWO_PHASE": self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") pass elif phase_type == "LIQUID": is_vap = False self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") elif phase_type == "VAPOR": is_liq = False self.table.horizontalHeaderItem(1).setText("Vapor") self.table.horizontalHeaderItem(2).setText("Liquid") elif phase_type == "SINGLE": self.table.horizontalHeaderItem(1).setText("Single") self.table.horizontalHeaderItem(2).setText("Single") pass elif phase_type == "MINIMUM_GIBBS": self.table.horizontalHeaderItem(1).setText("Minimum Gibbs") self.table.horizontalHeaderItem(2).setText("Minimum Gibbs") pass elif phase_type == "FAKE": msg = MessageBox( "Error", "Currently no functionality for phase " + phase_type) msg.exec_() elif phase_type == "SOLID": msg = MessageBox( "Error", "Currently no functionality for phase " + phase_type) msg.exec_() else: return component_indices = [ self.tp.getcompindex(comp) for comp in self.component_data["Identities"] ] molecular_weights = [ self.tp.compmoleweight(index) for index in component_indices ] if is_liq: V_liq, dVdT_liq, dVdn_liq = self.tp.specific_volume(T, P, x, phase=LIQUID, dvdt=True, dvdn=True) H_liq, dHdT_liq, dHdP_liq, dHdn_liq = self.tp.enthalpy( T, P, x, phase=LIQUID, dhdt=True, dhdp=True, dhdn=True) S_liq, dSdT_liq, dSdP_liq, dSdn_liq = self.tp.entropy(T, P, x, phase=LIQUID, dsdt=True, dsdp=True, dsdn=True) U_liq, dUdT_liq, dUdV_liq = self.tp.internal_energy_tv(T, V_liq, x, dedt=True, dedv=True) sos_liq = self.tp.speed_of_sound(T, P, x, y, fractions, beta_vap, beta_liq, phase=LIQUID) U_liq = H_liq - P * V_liq G_liq = H_liq - T * S_liq Cp_liq = dHdT_liq Cv_liq = dUdT_liq mol_weight_liq = sum([ x[i] * molecular_weights[i] for i in range(len(molecular_weights)) ]) self.set_table_value("Temperature", "Liq", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Liq", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Liq", "m ** 3 / mol", self.units["Specific volume"], V_liq) self.set_table_value("Internal energy", "Liq", "J / mol", self.units["Internal energy"], U_liq) self.set_table_value("Enthalpy", "Liq", "J / mol", self.units["Enthalpy"], H_liq) self.set_table_value("Entropy", "Liq", "J / (K * mol)", self.units["Entropy"], S_liq) self.set_table_value("Gibbs energy", "Liq", "J / mol", self.units["Gibbs energy"], G_liq) self.set_table_value("Isobar heat capacity", "Liq", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_liq) self.set_table_value("Isochor heat capacity", "Liq", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_liq) self.set_table_value("Speed of sound", "Liq", "m / s", self.units["Speed of sound"], sos_liq) self.set_table_value("Phase fraction", "Liq", "mol / mol", self.units["Phase fraction"], beta_liq) self.set_table_value("Molecular weight", "Liq", "kg / mol", self.units["Molecular weight"], mol_weight_liq) if is_vap: V_vap, dVdT_vap, dVdP_vap, dVdn_vap = self.tp.specific_volume( T, P, x, phase=VAPOR, dvdt=True, dvdp=True, dvdn=True) H_vap, dHdT_vap, dHdP_vap, dHdn_vap = self.tp.enthalpy(T, P, x, phase=VAPOR, dhdt=True, dhdp=True, dhdn=True) S_vap, dSdT_vap, dSdP_vap, dSdn_vap = self.tp.entropy(T, P, x, phase=VAPOR, dsdt=True, dsdp=True, dsdn=True) U_vap, dUdT_vap, dUdV_vap = self.tp.internal_energy_tv(T, V_vap, x, dedt=True, dedv=True) sos_vap = self.tp.speed_of_sound(T, P, x, y, fractions, beta_vap, beta_liq, phase=VAPOR) U_vap = H_vap - P * V_vap G_vap = H_vap - T * S_vap Cp_vap = dHdT_vap Cv_vap = dUdT_vap mol_weight_vap = sum([ y[i] * molecular_weights[i] for i in range(len(molecular_weights)) ]) self.set_table_value("Temperature", "Vap", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Vap", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Vap", "m**3 / mol", self.units["Specific volume"], V_vap) self.set_table_value("Internal energy", "Vap", "J / mol", self.units["Internal energy"], U_vap) self.set_table_value("Enthalpy", "Vap", "J / mol", self.units["Enthalpy"], H_vap) self.set_table_value("Entropy", "Vap", "J / (K * mol)", self.units["Entropy"], S_vap) self.set_table_value("Gibbs energy", "Vap", "J / mol", self.units["Gibbs energy"], G_vap) self.set_table_value("Isobar heat capacity", "Vap", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_vap) self.set_table_value("Isochor heat capacity", "Vap", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_vap) self.set_table_value("Speed of sound", "Vap", "m / s", self.units["Speed of sound"], sos_vap) self.set_table_value("Phase fraction", "Vap", "mol / mol", self.units["Phase fraction"], beta_vap) self.set_table_value("Molecular weight", "Vap", "kg / mol", self.units["Molecular weight"], mol_weight_vap) if is_liq and is_vap: if beta_vap == -1 and beta_liq == -1: beta_vap, beta_liq = 0.5, 0.5 V_overall = V_vap * beta_vap + V_liq * beta_liq U_overall = U_vap * beta_vap + U_liq * beta_liq H_overall = H_vap * beta_vap + H_liq * beta_liq S_overall = S_vap * beta_vap + S_liq * beta_liq G_overall = G_vap * beta_vap + G_liq * beta_liq Cp_overall = Cp_vap * beta_vap + Cp_liq * beta_liq Cv_overall = Cv_vap * beta_vap + Cv_liq * beta_liq sos_overall = sos_vap * beta_vap + sos_liq * beta_liq frac_overall = beta_vap + beta_liq if mol_weight_liq == 0: mol_weight_overall = mol_weight_vap elif mol_weight_vap == 0: mol_weight_overall = mol_weight_liq else: mol_weight_overall = mol_weight_vap * beta_vap + mol_weight_liq * beta_liq elif is_liq: V_overall = V_liq U_overall = U_liq H_overall = H_liq S_overall = S_liq G_overall = G_liq Cp_overall = Cp_liq Cv_overall = Cv_liq sos_overall = sos_liq frac_overall = beta_liq mol_weight_overall = mol_weight_liq elif is_vap: V_overall = V_vap U_overall = U_vap H_overall = H_vap S_overall = S_vap G_overall = G_vap Cp_overall = Cp_vap Cv_overall = Cv_vap sos_overall = sos_vap frac_overall = beta_vap mol_weight_overall = mol_weight_vap if is_liq or is_vap: self.set_table_value("Temperature", "Overall", "degK", self.units["Temperature"], T) self.set_table_value("Pressure", "Overall", "Pa", self.units["Pressure"], P) self.set_table_value("Specific volume", "Overall", "m**3 / mol", self.units["Specific volume"], V_overall) self.set_table_value("Internal energy", "Overall", "J / mol", self.units["Internal energy"], U_overall) self.set_table_value("Enthalpy", "Overall", "J / mol", self.units["Enthalpy"], H_overall) self.set_table_value("Entropy", "Overall", "J / (K * mol)", self.units["Entropy"], S_overall) self.set_table_value("Gibbs energy", "Overall", "J / mol", self.units["Gibbs energy"], G_overall) self.set_table_value("Isobar heat capacity", "Overall", "J / (K * mol)", self.units["Isobar heat capacity"], Cp_overall) self.set_table_value("Isochor heat capacity", "Overall", "J / (K * mol)", self.units["Isochor heat capacity"], Cv_overall) self.set_table_value("Speed of sound", "Overall", "m / s", self.units["Speed of sound"], sos_overall) self.set_table_value("Phase fraction", "Overall", "mol / mol", self.units["Phase fraction"], frac_overall) self.set_table_value("Molecular weight", "Overall", "kg / mol", self.units["Molecular weight"], mol_weight_overall) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() self.download_csv_btn.setEnabled(True)
def plot_envelope(self, tp, prim_vars, fractions): """ Plots a phase envelope :param tp: Thermopack instance :param prim_vars: Primary variables for the plot (e.g. PT, PH, ..) :param fractions: List of molar fractions for the components """ tpv_settings = self.plotting_preferences["Phase envelope"]["TPV"] isopleth_settings = self.plotting_preferences["Phase envelope"][ "Isopleths"] critical_settings = self.plotting_preferences["Phase envelope"][ "Critical"] plot_settings = self.plotting_preferences["Phase envelope"]["Plotting"] p_initial = tpv_settings["Initial pressure"] t_min = tpv_settings["Minimum temperature"] p_max = tpv_settings["Maximum pressure"] step_size = tpv_settings["Step size"] # Calculate T, P, V T, P, V = tp.get_envelope_twophase(initial_pressure=p_initial, z=fractions, maximum_pressure=p_max, minimum_temperature=t_min, step_size=step_size, calc_v=True) H = np.array( [tp.enthalpy_tv(T[i], V[i], fractions) for i in range(len(T))]) S = np.array( [tp.entropy_tv(T[i], V[i], fractions) for i in range(len(T))]) global H_list global T_list global S_list global P_list n_isopleths = isopleth_settings["Number of isopleths"] H_list = np.linspace(np.min(H), np.max(H), n_isopleths) S_list = np.linspace(np.min(S), np.max(S), n_isopleths) T_list = np.linspace(np.min(T) * 0.60, np.max(T) * 1.40, n_isopleths) P_list = np.linspace(np.min(P) * 0.60, np.max(P) * 1.40, n_isopleths) temp = critical_settings["Temperature"] v = critical_settings["Volume"] tol = critical_settings["Error tolerance"] # Calculate critical variables try: T_c, V_c, P_c = tp.critical(n=fractions, temp=temp, v=v, tol=tol) H_c = tp.enthalpy_tv(T_c, V_c, fractions) S_c = tp.entropy_tv(T_c, V_c, fractions) except Exception as e: msg = MessageBox("Error", str(e)) msg.exec_() T_c, V_c, P_c, H_c, S_c = None, None, None, None, None # Set global variables, so that they are accessible in all phase envelope plot functions global isopleth_1_color global isopleth_2_color global P_min global P_max global T_min global T_max global nmax isopleth_1_color = plot_settings["Colors"][2] isopleth_2_color = plot_settings["Colors"][3] P_min = isopleth_settings["Minimum pressure"] P_max = isopleth_settings["Maximum pressure"] T_min = isopleth_settings["Minimum temperature"] T_max = isopleth_settings["Maximum temperature"] nmax = isopleth_settings["N max"] # Plot depending on which primary variables are chosen if prim_vars == "PT": x, y, crit_x, crit_y = self.plot_envelope_PT( tp, T, P, T_c, P_c, fractions) elif prim_vars == "PH": x, y, crit_x, crit_y = self.plot_envelope_PH( tp, P, H, P_c, H_c, fractions) elif prim_vars == "PS": x, y, crit_x, crit_y = self.plot_envelope_PS( tp, P, S, P_c, S_c, fractions) elif prim_vars == "TH": x, y, crit_x, crit_y = self.plot_envelope_TH( tp, T, H, T_c, H_c, fractions) elif prim_vars == "TS": x, y, crit_x, crit_y = self.plot_envelope_TS( tp, T, S, T_c, S_c, fractions) else: return # Plotting line_color = plot_settings["Colors"][0] point_color = plot_settings["Colors"][1] grid_on = plot_settings["Grid on"] xlabel = plot_settings["x label"] ylabel = plot_settings["y label"] title = plot_settings["Title"] self.axes.plot(x, y, color=line_color, label="Phase envelope") self.axes.scatter([crit_x], [crit_y], color=point_color, label="Critical point") self.axes.set_title(title) self.axes.grid(grid_on) self.axes.set_xlabel(xlabel) self.axes.set_ylabel(ylabel) # Sort entries in the legend legend = True if legend: if n_isopleths > 0: handles, labels = self.axes.get_legend_handles_labels() self.axes.legend( [handles[3], handles[2], handles[0], handles[1]], [labels[3], labels[2], labels[0], labels[1]], loc="best") else: self.axes.legend() self.draw()
def plot(self): """ Checks type of plot selected, gets the correct parameters, inits thermopack, and calls the correct plot function in MplCanvas """ category = self.settings["Model category"] plot_type = self.plot_type_btn_group.checkedButton().text() prim_vars = self.prim_vars_dropdown.currentText() if category in ["Cubic", "CPA"]: eos = self.model_btn_group.checkedButton().text() if self.settings["EOS"] != eos: self.settings["EOS"] = eos elif category == "SAFT-VR Mie": self.settings["Model options"]["A1"] = self.a1_checkbox.isChecked() self.settings["Model options"]["A2"] = self.a2_checkbox.isChecked() self.settings["Model options"]["A3"] = self.a3_checkbox.isChecked() self.settings["Model options"][ "Hard sphere"] = self.hard_sphere_checkbox.isChecked() self.settings["Model options"][ "Chain"] = self.chain_checkbox.isChecked() init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) fractions = np.array(self.component_data["Fractions"]) if self.canvas.empty: self.canvas.axes = self.canvas.fig.add_subplot(111) self.canvas.empty = False if self.redraw: self.canvas.axes.cla() self.isopleth_btn_stack.hide() self.download_csv_btn.setEnabled(True) if plot_type in ["Phase envelope", "Pressure density"]: mole_fraction_sum = np.sum(fractions) if abs(mole_fraction_sum - 1.00) > 1e-8: msg_title = "Molar fractions error" msg_text = "Molar fractions have to add up to 1.00. Currently the sum is %s." % mole_fraction_sum msg = MessageBox(msg_title, msg_text) msg.exec_() return else: # Setting the last mol fraction to 1 - the rest of them, to ensure that the total sum is exactly 1 fractions[-1] = 1 - np.sum(fractions[:-1]) if plot_type == "Phase envelope": self.canvas.plot_envelope(self.tp, prim_vars, fractions) self.canvas.show() self.mpl_toolbar.show() if self.plotting_preferences["Phase envelope"]["Isopleths"][ "Number of isopleths"] > 0: self.isopleth_btn_stack.show() elif plot_type == "Binary pxy": self.canvas.plot_binary_pxy(self.tp) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Pressure density": self.canvas.plot_pressure_density(self.tp, fractions) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Global binary": self.canvas.plot_global_binary(self.tp) self.canvas.show() self.mpl_toolbar.show() else: pass
class PlotMode(QMainWindow): """ A window where different types of (matplotlib) plots can be shown for a given composition and model setup. The user may change initial parameters for the calculations and specify some plotting preferences. When a plot is generated, the user may download a csv file containing the x and y data for each plotted line """ def __init__(self, data, json_file, component_list_name, model_settings_name, parent=None): super().__init__(parent=parent) loadUi("gui/layouts/plot_mode.ui", self) self.setWindowTitle("Thermopack - Plot Mode") self.showMaximized() self.data = data self.json_file = json_file self.component_data = self.data["Component lists"][component_list_name] self.comp_list_name = component_list_name self.settings = self.data["Model setups"][model_settings_name] self.units = self.data["Units"] self.set_toolbar() if self.data["Plotting preferences"]: self.plotting_preferences = self.data["Plotting preferences"] else: self.plotting_preferences = self.init_plotting_preferences() self.data["Plotting preferences"] = self.plotting_preferences # In case the user wants to reset settings self.default_plotting_preferences = self.init_plotting_preferences() self.redraw = True self.redraw_checkbox.setChecked(self.redraw) self.init_plot_modes() self.model_btn_group = QButtonGroup(parent=self.model_box) self.init_model_options() self.init_fractions() # Initiating thermopack self.tp = get_thermopack(category=self.settings["Model category"]) # Init function depends on settings init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) self.ph_env_toolbtn.clicked.connect(self.show_ph_env_options) self.bin_pxy_toolbtn.clicked.connect(self.show_bin_pxy_options) self.p_rho_toolbtn.clicked.connect(self.show_p_rho_options) self.global_binary_toolbtn.clicked.connect( self.show_global_binary_options) self.plot_type_btn_group.buttonClicked.connect(self.change_plot_type) # Setup for plot window self.canvas = MplCanvas(self.component_data["Names"], self.plotting_preferences) self.mpl_toolbar = NavigationToolbar2QT(self.canvas, self) self.mpl_toolbar.hide() self.canvas.hide() self.plot_layout.addWidget(self.mpl_toolbar) self.plot_layout.addWidget(self.canvas) self.init_isopleth_btns() self.redraw_checkbox.clicked.connect(self.toggle_redraw) self.plot_button.clicked.connect(self.plot) self.download_csv_btn.clicked.connect(self.export_csv) def set_toolbar(self): """ Creates the top toolbar """ # Logo logo = QLabel("Thermopack | Plot Mode ") logo.setStyleSheet( "color: #FF8B06; font: 75 28pt 'Agency FB'; padding: 5px 10px 5px 10px;" ) # Top toolbar toolbar = self.addToolBar("Tool bar") toolbar.setMovable(False) toolbar.actionTriggered.connect(self.handle_toolbar_action) toolbar.setStyleSheet("padding: 5px 10px 5px 10px;") toolbar.addWidget(logo) toolbar.addSeparator() action_group = QActionGroup(self) if self.json_file: action_group.addAction( toolbar.addAction(QIcon("gui/icons/save.png"), "Save")) self.action_save = self.file_menu.addAction( "Save", self.save_plot_settings, QKeySequence("Ctrl+S")) self.action_close = self.file_menu.addAction( "Close", self.close, QKeySequence("Ctrl+Q")) else: self.action_close = self.file_menu.addAction( "Close", self.close, QKeySequence("Ctrl+Q")) def handle_toolbar_action(self, action): """ Calls the correct function depending on which tool icon was clicked :param action: Type of tool clicked """ action = action.text() if action == "Save": self.save_plot_settings() @staticmethod def init_plotting_preferences(): """ :return: Dictionary for storing plotting preferences and parameters """ return { "Phase envelope": { "Isopleths": { "Minimum pressure": 100000.0, "Maximum pressure": 15000000.0, "Number of isopleths": 15, "Minimum temperature": 200.0, "Maximum temperature": 500.0, "N max": 50 }, "TPV": { "Initial pressure": 100000.0, "Maximum pressure": 15000000.0, "Minimum temperature": None, "Step size": 0.1, }, "Critical": { "Temperature": 0.0, "Volume": 0.0, "Error tolerance": 1.0e-7 }, "Plotting": { "Colors": ["#1f77b4", "#ff7f0e", "#ffd2d2", "#d5d3ff"], "Grid on": False, "Title": None, "x label": None, "y label": None } }, "Binary pxy": { "Calc": { "Temperature": 288.0, "Maximum pressure": 1.5e7, "Minimum pressure": 1.0e5, "Maximum dz": 0.003, "Maximum dlns": 0.01, }, "Plotting": { "Colors": ["#1f77b4", "#ff7f0e", "#ffd2d2", "#d5d3ff"], "Grid on": False, "Title": None, "x label": None, "y label": None } }, "Pressure density": { "Calc": { "Temperatures": [298.0], "Volume range start": 0.50, "Volume range end": 10.0, "Num points": 100, }, "Critical": { "Temperature": 0.0, "Volume": 0.0, "Error tolerance": 1.0e-7 }, "Plotting": { "Grid on": False, "Title": None, "x label": None, "y label": None }, "TPV": { "Initial pressure": 100000.0, "Maximum pressure": 15000000.0, "Minimum temperature": None, "Step size": 0.1 } }, "Global binary": { "Calc": { "Minimum pressure": 1.05e5, "Minimum temperature": 2.0, "Azeotropes": True, }, "Plotting": { "Colors": ["black", "blue", "red", "green"], "Linestyles": ["-", "--", ":", "-."], "Grid on": False, "Title": None, "x label": None, "y label": None } } } def init_plot_modes(self): """ Disables some plot options if there are too few or too many components """ if len(self.component_data["Names"]) != 2: self.binary_pxy_btn.setEnabled(False) self.global_binary_btn.setEnabled(False) def init_model_options(self): """ Adds model options to a widget depending on model category """ category = self.settings["Model category"] if category in ["Cubic", "CPA"]: pr_btn = QRadioButton("PR") srk_btn = QRadioButton("SRK") self.model_box_layout.addWidget(pr_btn) self.model_box_layout.addWidget(srk_btn) self.model_btn_group.addButton(pr_btn) self.model_btn_group.addButton(srk_btn) if self.settings["EOS"] == "PR": pr_btn.setChecked(True) elif self.settings["EOS"] == "SRK": srk_btn.setChecked(True) else: pass elif category == "PC-SAFT": # No model options for PC-SAFT pass elif category == "SAFT-VR Mie": self.a1_checkbox = QCheckBox("A1") self.a2_checkbox = QCheckBox("A2") self.a3_checkbox = QCheckBox("A3") self.hard_sphere_checkbox = QCheckBox("Hard sphere") self.chain_checkbox = QCheckBox("Chain") self.a1_checkbox.setChecked(self.settings["Model options"]["A1"]) self.a2_checkbox.setChecked(self.settings["Model options"]["A2"]) self.a3_checkbox.setChecked(self.settings["Model options"]["A3"]) self.hard_sphere_checkbox.setChecked( self.settings["Model options"]["Hard sphere"]) self.chain_checkbox.setChecked( self.settings["Model options"]["Chain"]) self.model_box_layout.addWidget(self.a1_checkbox) self.model_box_layout.addWidget(self.a2_checkbox) self.model_box_layout.addWidget(self.a3_checkbox) self.model_box_layout.addWidget(self.hard_sphere_checkbox) self.model_box_layout.addWidget(self.chain_checkbox) else: self.model_box.setVisible(False) def init_fractions(self): """ Adds component fraction widgets to window, depending on how many components are chosen """ components = self.component_data["Names"] self.component_data["Fractions"] = [0.00] * len(components) float_validator = FloatValidator() for i in range(len(components)): component = components[i] line_edit = QLineEdit() line_edit.setValidator(float_validator) line_edit.setText("0.00") line_edit.setObjectName(component) line_edit.editingFinished.connect( lambda comp=component: self.change_fraction(comp)) if len(components) == 1: line_edit.setText("1.00") line_edit.setEnabled(False) self.component_data["Fractions"][i] = 1.00 else: self.component_data["Fractions"][i] = 0.00 self.fractions_layout.addRow(components[i], line_edit) def init_isopleth_btns(self): """ Connects isopleth buttons to a show/hide function in MplCanvas """ self.PT_H_btn.clicked.connect(self.canvas.toggle_isenthalps) self.PT_S_btn.clicked.connect(self.canvas.toggle_isentropes) self.PH_T_btn.clicked.connect(self.canvas.toggle_isotherms) self.PH_S_btn.clicked.connect(self.canvas.toggle_isentropes) self.PS_T_btn.clicked.connect(self.canvas.toggle_isotherms) self.PS_H_btn.clicked.connect(self.canvas.toggle_isenthalps) self.TH_P_btn.clicked.connect(self.canvas.toggle_isobars) self.TH_S_btn.clicked.connect(self.canvas.toggle_isentropes) self.TS_P_btn.clicked.connect(self.canvas.toggle_isobars) self.TS_H_btn.clicked.connect(self.canvas.toggle_isenthalps) self.isopleth_btn_stack.hide() def change_plot_type(self, btn): """ :param btn: Selected radio button containing the selected plot type Enables/disables the different plot and model options depending on plot type """ if btn.text() == "Phase envelope": self.ph_env_toolbtn.setEnabled(True) self.p_rho_toolbtn.setEnabled(False) self.bin_pxy_toolbtn.setEnabled(False) self.global_binary_toolbtn.setEnabled(False) self.molar_fractions_box.setEnabled(True) self.primary_vars_box.setEnabled(True) elif btn.text() == "Binary pxy": self.ph_env_toolbtn.setEnabled(False) self.p_rho_toolbtn.setEnabled(False) self.bin_pxy_toolbtn.setEnabled(True) self.global_binary_toolbtn.setEnabled(False) self.molar_fractions_box.setEnabled(False) self.primary_vars_box.setEnabled(False) elif btn.text() == "Pressure density": self.ph_env_toolbtn.setEnabled(False) self.p_rho_toolbtn.setEnabled(True) self.bin_pxy_toolbtn.setEnabled(False) self.global_binary_toolbtn.setEnabled(False) self.molar_fractions_box.setEnabled(True) self.primary_vars_box.setEnabled(False) elif btn.text() == "Global binary": self.ph_env_toolbtn.setEnabled(False) self.p_rho_toolbtn.setEnabled(False) self.bin_pxy_toolbtn.setEnabled(False) self.global_binary_toolbtn.setEnabled(True) self.molar_fractions_box.setEnabled(False) self.primary_vars_box.setEnabled(False) else: pass def show_ph_env_options(self): """ Opens an option window where initial parameters for phase envelope plot can be set """ options_window = PhaseEnvelopeOptionsWindow( self.plotting_preferences, self.default_plotting_preferences) options_window.exec_() def show_bin_pxy_options(self): """ Opens an option window where initial parameters for binary pxy plot can be set """ options_window = BinaryPXYOptionsWindow( self.plotting_preferences, self.default_plotting_preferences) options_window.exec_() def show_p_rho_options(self): """ Opens an option window where initial parameters for pressure density plot can be set """ options_window = PRhoOptionsWindow(self.plotting_preferences, self.default_plotting_preferences) options_window.exec_() def show_global_binary_options(self): """ Opens an option window where initial parameters for global binary plot can be set """ options_window = GlobalBinaryOptionsWindow( self.plotting_preferences, self.default_plotting_preferences) options_window.exec_() def change_fraction(self, comp_name): """ Changes the mole fraction of a component :param str comp_name: Name of the component """ line_edit = self.molar_fractions_box.findChild(QLineEdit, comp_name) mol_frac = line_edit.text().replace(",", ".") index = self.component_data["Names"].index(comp_name) self.component_data["Fractions"][index] = float(mol_frac) def toggle_redraw(self, is_checked): self.redraw = is_checked def plot(self): """ Checks type of plot selected, gets the correct parameters, inits thermopack, and calls the correct plot function in MplCanvas """ category = self.settings["Model category"] plot_type = self.plot_type_btn_group.checkedButton().text() prim_vars = self.prim_vars_dropdown.currentText() if category in ["Cubic", "CPA"]: eos = self.model_btn_group.checkedButton().text() if self.settings["EOS"] != eos: self.settings["EOS"] = eos elif category == "SAFT-VR Mie": self.settings["Model options"]["A1"] = self.a1_checkbox.isChecked() self.settings["Model options"]["A2"] = self.a2_checkbox.isChecked() self.settings["Model options"]["A3"] = self.a3_checkbox.isChecked() self.settings["Model options"][ "Hard sphere"] = self.hard_sphere_checkbox.isChecked() self.settings["Model options"][ "Chain"] = self.chain_checkbox.isChecked() init_thermopack(self.tp, self.component_data, self.comp_list_name, self.settings) fractions = np.array(self.component_data["Fractions"]) if self.canvas.empty: self.canvas.axes = self.canvas.fig.add_subplot(111) self.canvas.empty = False if self.redraw: self.canvas.axes.cla() self.isopleth_btn_stack.hide() self.download_csv_btn.setEnabled(True) if plot_type in ["Phase envelope", "Pressure density"]: mole_fraction_sum = np.sum(fractions) if abs(mole_fraction_sum - 1.00) > 1e-8: msg_title = "Molar fractions error" msg_text = "Molar fractions have to add up to 1.00. Currently the sum is %s." % mole_fraction_sum msg = MessageBox(msg_title, msg_text) msg.exec_() return else: # Setting the last mol fraction to 1 - the rest of them, to ensure that the total sum is exactly 1 fractions[-1] = 1 - np.sum(fractions[:-1]) if plot_type == "Phase envelope": self.canvas.plot_envelope(self.tp, prim_vars, fractions) self.canvas.show() self.mpl_toolbar.show() if self.plotting_preferences["Phase envelope"]["Isopleths"][ "Number of isopleths"] > 0: self.isopleth_btn_stack.show() elif plot_type == "Binary pxy": self.canvas.plot_binary_pxy(self.tp) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Pressure density": self.canvas.plot_pressure_density(self.tp, fractions) self.canvas.show() self.mpl_toolbar.show() elif plot_type == "Global binary": self.canvas.plot_global_binary(self.tp) self.canvas.show() self.mpl_toolbar.show() else: pass def export_csv(self): """ Creates and saves a csv file with the (x,y) data from all the currently plotted lines. | Line 1 x-data | Line 1 y-data | Line 2 x-data | Line 2 y-data | ... | x | y | x | y | ... | . | . | . | . | ... | . | . | . | . | ... | . | . | . | . | ... """ file_dialog = QFileDialog() file_dialog.setWindowTitle('Save File') file_dialog.setDirectory(os.getcwd()) file_dialog.setAcceptMode(QFileDialog.AcceptSave) file_dialog.setNameFilter('Csv files (*.csv)') file_dialog.setDefaultSuffix('csv') if file_dialog.exec_() == QFileDialog.Accepted: path = file_dialog.selectedFiles()[0] if path: lines = self.canvas.axes.lines longest_line_length = 0 for line in lines: if len(line.get_xdata()) > longest_line_length: longest_line_length = len(line.get_xdata()) line_data = () for i in range(len(lines)): line = lines[i] x_data = list(line.get_xdata()) y_data = list(line.get_ydata()) while len(x_data) < longest_line_length: x_data.append(np.nan) while len(y_data) < longest_line_length: y_data.append(np.nan) xy_data = (np.array(x_data), np.array(y_data)) line_data += xy_data np.savetxt(path, np.column_stack(line_data), delimiter=",") def save_plot_settings(self): """ Saves data to the current JSON-file. """ if self.json_file: save_json_data(self.data, self.json_file) self.msg = MessageBox("Success", "Data saved to %s." % self.json_file) else: self.msg = MessageBox("Failed", "Could not save data. No file is chosen.") self.msg.exec()
def save_composition(self): """ Saves (Updates) the composition """ if self.selected_components: if self.set_name_edit.text(): list_name = self.set_name_edit.text() elif self.set_name_edit.placeHolderText(): list_name = self.set_name_edit.placeholderText() else: list_name = get_unique_name("Composition", self.final_compositions.keys()) self.set_name_edit.setPlaceholderText(list_name) if list_name == self.name: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[comp].json_data["ident"] for comp in component_list ] self.data["Component lists"][list_name] = { "Names": component_list, "Identities": id_list } self.component_list_updated.emit(list_name, False, self.name) self.name = list_name elif list_name not in self.data["Component lists"]: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[comp].json_data["ident"] for comp in component_list ] self.data["Component lists"][self.name] = { "Names": component_list, "Identities": id_list } self.data["Component lists"][list_name] = self.data[ "Component lists"].pop(self.name) self.component_list_updated.emit(list_name, False, self.name) self.name = list_name else: error_msg = "A component list with this name (%s) already exists" % list_name msg = MessageBox("Error", error_msg) self.set_name_edit.undo() msg.exec_() else: msg = MessageBox("Error", "No components are chosen.") msg.exec_()
def save_composition(self): """ Saves the selected composition to the session data, and emits a signal to indicate that a composition list is created or edited """ if self.selected_components: if self.selected_components not in self.final_compositions.values( ): if self.set_name_edit.text(): list_name = self.set_name_edit.text() else: list_name = self.set_name_edit.placeholderText() if list_name not in self.data["Component lists"]: component_list = self.selected_components.copy() self.final_compositions[list_name] = component_list id_list = [ self.fluids[name].json_data["ident"] for name in component_list ] self.data["Component lists"][list_name] = { "Names": component_list, "Identities": id_list } self.component_list_updated.emit(list_name, True, list_name) # Clear table and selection self.selected_components_table.setRowCount(0) self.selected_components = [] self.set_component_list_placeholder_name() else: error_msg = "A component list with this name (%s) already exists" % list_name msg = MessageBox("Error", error_msg) self.set_name_edit.undo() msg.exec_() else: error_msg = "This composition is already stored as " + str( list(self.final_compositions.keys())[list( self.final_compositions.values()).index( self.selected_components)]) msg = MessageBox("Error", error_msg) msg.exec_() else: msg = MessageBox("Error", "No components are chosen.") msg.exec_()