Example #1
0
    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()
Example #2
0
    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)
Example #3
0
    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
Example #4
0
    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()
Example #5
0
    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)
Example #6
0
    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()
Example #7
0
    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
Example #8
0
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()
Example #9
0
    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_()
Example #10
0
    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_()