From 3fbea2330dfc3f2ff39832d3edcfd4475af53f9b Mon Sep 17 00:00:00 2001 From: PietroUngar Date: Wed, 13 Jul 2022 17:22:59 +0200 Subject: [PATCH] V0.6.0 - Major bug fixed --- .gitignore | 1 + EEETools/MainModules/main_module.py | 50 +++-- .../pf_diagram_generation_module.py | 2 +- .../API/Tools/sankey_diagram_generation.py | 22 ++- EEETools/Tools/Other/matrix_analyzer.py | 187 +++++++++++++++++- EEETools/version.py | 2 +- requirements.txt | 1 + test/test_import_module.py | 5 +- twine_upload/pip_upload.py | 50 +++++ 9 files changed, 288 insertions(+), 32 deletions(-) create mode 100644 .gitignore create mode 100644 twine_upload/pip_upload.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b539113 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/twine_upload/pipy_token diff --git a/EEETools/MainModules/main_module.py b/EEETools/MainModules/main_module.py index a9f59fa..f76b17c 100644 --- a/EEETools/MainModules/main_module.py +++ b/EEETools/MainModules/main_module.py @@ -31,6 +31,7 @@ def __init__(self, inputID, main_class, is_support_block=False): self.comp_cost = 0. self.difference_cost = 0. self.output_cost = 0. + self.dissipative_output_cost = 0. self.output_cost_decomposition = dict() self.input_connections = list() @@ -287,6 +288,14 @@ def calculate_coefficients(self, total_destruction): fuel_cost = 0. + if self.is_dissipative: + + c_prod = self.dissipative_output_cost + + else: + + c_prod = self.output_cost + for conn in self.input_connections: fuel_cost += conn.exergy_value * conn.rel_cost @@ -303,7 +312,7 @@ def calculate_coefficients(self, total_destruction): if not c_fuel == 0: - r = (self.output_cost - c_fuel) / c_fuel + r = (c_prod - c_fuel) / c_fuel if not (self.comp_cost + c_dest * abs(dest_loss_exergy)) == 0: @@ -313,12 +322,16 @@ def calculate_coefficients(self, total_destruction): y = dest_exergy / total_destruction - self.coefficients.update({"r": r, - "f": f, - "y": y, - "eta": eta, - "c_fuel": c_fuel, - "c_dest": c_dest}) + self.coefficients.update({ + + "r": r, + "f": f, + "y": y, + "eta": eta, + "c_fuel": c_fuel, + "c_dest": c_dest + + }) # - Support Methods - @@ -771,6 +784,17 @@ def n_non_empty_output(self): return counter + @property + def first_non_support_block(self): + + if not self.is_support_block: + + return self + + if self.is_support_block: + + return self.main_block.first_non_support_block + # ------------------------------------- # ------ EES Generation Methods ----- # ------ (to be eliminated) ----- @@ -1358,7 +1382,7 @@ def calculate(self): self.append_solution(sol) self.calculate_coefficients() - self.decompose_component_output_cost() + self.decompose_component_output_cost(matrix_analyzer) def append_solution(self, sol): @@ -1369,11 +1393,13 @@ def append_solution(self, sol): if not block.is_dissipative: block.append_output_cost(sol[i]) - i += 1 else: block.append_output_cost(0.) + block.dissipative_output_cost = sol[i] + + i += 1 self.__reset_IDs(reset_block=True) self.__reset_IDs(reset_block=False) @@ -1386,13 +1412,13 @@ def calculate_coefficients(self): block.calculate_coefficients(total_destruction) - def decompose_component_output_cost(self): + def decompose_component_output_cost(self, analizer:MatrixAnalyzer): if self.options.calculate_component_decomposition: try: - __inverse_matrix = np.linalg.inv(self.matrix) + __inverse_matrix = analizer.inverse_matrix for block in self.block_list: @@ -1972,7 +1998,7 @@ def __init__(self): self.valve_is_dissipative = True self.condenser_is_dissipative = True - self.redistribution_method = CalculationOptions.RELATIVE_COST + self.redistribution_method = CalculationOptions.EXERGY_DESTRUCTION self.calculate_component_decomposition = True @property diff --git a/EEETools/MainModules/pf_diagram_generation_module.py b/EEETools/MainModules/pf_diagram_generation_module.py index 1042bd6..e899ce2 100644 --- a/EEETools/MainModules/pf_diagram_generation_module.py +++ b/EEETools/MainModules/pf_diagram_generation_module.py @@ -163,7 +163,7 @@ def __identify_support_blocks(self): if prod_block.base_block.is_support_block: prod_block.is_support_block = True - prod_block.main_block = self.find_element(prod_block.base_block.main_block) + prod_block.main_block = self.find_element(prod_block.base_block.first_non_support_block) def generate_product_connection(self, input_connection: Connection, from_product_block=None, to_product_block=None): diff --git a/EEETools/Tools/API/Tools/sankey_diagram_generation.py b/EEETools/Tools/API/Tools/sankey_diagram_generation.py index f06498f..21d2546 100644 --- a/EEETools/Tools/API/Tools/sankey_diagram_generation.py +++ b/EEETools/Tools/API/Tools/sankey_diagram_generation.py @@ -9,37 +9,43 @@ def __init__(self): self.show_component_mixers = False self.colors = { - "Destruction": "180, 160, 0", - "Other": "150, 0, 0" + "Destruction": "150, 0, 0", + "Losses": "50, 125, 150", + "Default": "250, 210, 20" } self.opacity = { "nodes": 1, - "links": 0.4 + "links": 0.6, + "DL_links": 0.25 } def define_color(self, block_label, is_link): - return "rgba({}, {})".format(self.get_color(block_label), self.get_opacity(is_link)) + return "rgba({}, {})".format(self.get_color(block_label), self.get_opacity(block_label, is_link)) def get_color(self, block_label): - if not (block_label == "Destruction" or block_label == "Losses"): + if block_label == "Destruction" or block_label == "Losses": - color = self.colors["Destruction"] + color = self.colors[block_label] else: - color = self.colors["Other"] + color = self.colors["Default"] return color - def get_opacity(self, is_link): + def get_opacity(self, block_label, is_link): if is_link: + if block_label == "Destruction" or block_label == "Losses": + + return self.opacity["DL_links"] + return self.opacity["links"] else: diff --git a/EEETools/Tools/Other/matrix_analyzer.py b/EEETools/Tools/Other/matrix_analyzer.py index 37aab24..5a1327c 100644 --- a/EEETools/Tools/Other/matrix_analyzer.py +++ b/EEETools/Tools/Other/matrix_analyzer.py @@ -1,14 +1,19 @@ -import numpy +from scipy.linalg import diagsvd from copy import deepcopy +import numpy.linalg class MatrixAnalyzer: - def __init__(self, matrix: numpy.ndarray, vector: numpy.ndarray, main_matrix=None): + def __init__(self, matrix: numpy.ndarray, vector: numpy.ndarray, main_matrix=None, try_system_decomposition=False): self.matrix = deepcopy(matrix) self.vector = vector + self.__inv_matrix = None + self.re_matrix = self.matrix + self.re_vector = self.vector + self.main_matrix = main_matrix self.sub_matrix = None @@ -30,6 +35,7 @@ def __init__(self, matrix: numpy.ndarray, vector: numpy.ndarray, main_matrix=Non self.__check_assignments() self.__ill_conditioned_matrix_warning = False + self.try_system_decomposition = try_system_decomposition def __initialize_matrix(self): @@ -85,16 +91,53 @@ def solve(self): else: + self.re_matrix, self.re_vector = self.__rearrange_matrix(self.matrix, self.vector) + try: - sol = numpy.linalg.solve(self.matrix, self.vector) + sol = self.__std_solve() except: - sol = numpy.linalg.lstsq(self.matrix, self.vector) - sol = sol[0] + if self.try_system_decomposition: + + try: + + A, B = self.__decompose_matrix() + + sol_prev = self.__svd_solve(A, self.vector) + matrix = B + matrix_prev = A + + counter = 0 + while True: + + sol = self.__svd_solve(matrix, self.vector - matrix_prev.dot(sol_prev)) + error = abs(numpy.max(numpy.abs(sol_prev - sol)) / numpy.max(sol_prev)) + + if error < 0.01: + + break - self.__ill_conditioned_matrix_warning = True + else: + + sol_prev = (sol + sol_prev) / 2 + matrix_tmp = matrix_prev + matrix_prev = matrix + matrix = matrix_tmp + + counter += 1 + + if counter > 100: + raise + + except: + + sol = self.__lstsq_solve() + + else: + + sol = self.__lstsq_solve() for i in range(len(self.columns)): self.columns[i].set_solution(sol[i]) @@ -146,6 +189,130 @@ def __identify_new_matrix_and_vector(self, non_assignment_rows_indices): return {"new_matrix": new_matrix, "new_vector": new_vector} + def __decompose_matrix(self): + + m, n = self.matrix.shape + u, s, vt = numpy.linalg.svd(self.matrix) + s_new = s + + for i in range(len(s)): + + if i <= len(s) * 4 / 5: + + s_new[i] = s[i] + + else: + + s_new[i] = 0 + + s_mat = diagsvd(s_new, m, n) + A = u.dot(s_mat).dot(vt) + + # ext_a = (numpy.min(A), numpy.max(A)) + # A = numpy.round(A / (ext_a[1] - ext_a[0]), 3) * (ext_a[1] - ext_a[0]) + + B = self.matrix - A + + return A, B + + def __std_solve(self): + + new_matrix, new_vector, deleted_rows = self.__identify_empty_lines(self.re_matrix, self.re_vector) + self.__inv_matrix = numpy.linalg.inv(new_matrix) + sol = self.__inv_matrix.dot(new_vector) + return self.__append_zeroes_if_needed(sol, deleted_rows) + + def __lstsq_solve(self): + + new_matrix, new_vector, deleted_rows = self.__identify_empty_lines(self.re_matrix, self.re_vector) + sol = numpy.linalg.lstsq(new_matrix, new_vector) + self.__ill_conditioned_matrix_warning = True + return self.__append_zeroes_if_needed(sol[0], deleted_rows) + + @staticmethod + def __svd_solve(matrix, vector): + + m, n = matrix.shape + u, s, vt = numpy.linalg.svd(matrix) + r = numpy.linalg.matrix_rank(matrix) + + s[:r] = 1 / s[:r] + s_inv = diagsvd(s, m, n) + + return vt.T.dot(s_inv).dot(u.T).dot(vector) + + @staticmethod + def __rearrange_matrix(matrix, vector): + + m, n = matrix.shape + new_matrix = numpy.ones((m, n)) + new_vector = numpy.ones(m) + + for i in range(m): + + row = matrix[i, :] + ext_row = (numpy.min(row), numpy.max(row)) + + if not (ext_row[1] - ext_row[0]) == 0: + + new_matrix[i, :] = row / (ext_row[1] - ext_row[0]) + new_vector[i] = vector[i] / (ext_row[1] - ext_row[0]) + + else: + + new_matrix[i, :] = matrix[i, :] + new_vector[i] = vector[i] + + return new_matrix, new_vector + + @staticmethod + def __identify_empty_lines(matrix, vector): + + m, n = matrix.shape + deleted_rows = [] + + new_matrix = numpy.copy(matrix) + new_vector = numpy.copy(vector) + + for i in range(m): + + row = matrix[i, :] + ext_row = (numpy.min(row), numpy.max(row)) + + if (ext_row[1] - ext_row[0]) == 0 and ext_row[0] == 0: + + if vector[i] == 0: + + del_i = i - len(deleted_rows) + new_matrix = numpy.delete(new_matrix, del_i, 0) + new_matrix = numpy.delete(new_matrix, del_i, 1) + new_vector = numpy.delete(new_vector, del_i) + deleted_rows.append(i) + + else: + + raise + + return new_matrix, new_vector, deleted_rows + + def __append_zeroes_if_needed(self, sol, deleted_rows): + + for i in deleted_rows: + + sol = numpy.insert(sol, i, 0) + + if self.__inv_matrix is not None: + + m, n = self.__inv_matrix.shape + + new_row = numpy.zeros(m) + new_col = numpy.zeros(n + 1) + + self.__inv_matrix = numpy.insert(self.__inv_matrix, i, [new_row], axis=0) + self.__inv_matrix = numpy.insert(self.__inv_matrix, i, [new_col], axis=1) + + return sol + @property def is_solvable(self): @@ -182,6 +349,11 @@ def is_ill_conditioned(self): return self.sub_matrix.is_ill_conditioned + @property + def inverse_matrix(self): + + return self.__inv_matrix + class MatrixElement: @@ -434,7 +606,6 @@ def sorted_indices(self) -> list: self.sort() for element in self: - sorted_indices.append(element.initial_position) - return sorted_indices \ No newline at end of file + return sorted_indices diff --git a/EEETools/version.py b/EEETools/version.py index 9591a68..5a2867d 100644 --- a/EEETools/version.py +++ b/EEETools/version.py @@ -1 +1 @@ -VERSION = "0.5.2" +VERSION = "0.6.0" diff --git a/requirements.txt b/requirements.txt index 38a8d75..b11155b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ setuptools xlrd~=1.2.0 pywin32 numpy +scipy plotly \ No newline at end of file diff --git a/test/test_import_module.py b/test/test_import_module.py index 42bdb41..179c266 100644 --- a/test/test_import_module.py +++ b/test/test_import_module.py @@ -112,7 +112,7 @@ def test_download_link(self): def test_excel_calculate(self): import EEETools - EEETools.calculate() + EEETools.calculate(calculate_on_pf_diagram=True) self.assertTrue(True) def test_excel_update(self): @@ -138,7 +138,7 @@ def test_excel_update(self): def test_sankey(self): import EEETools - EEETools.plot_sankey(show_component_mixers=False) + EEETools.plot_sankey(generate_on_pf_diagram=True) self.assertTrue(True) def test_connection_check(self): @@ -149,4 +149,5 @@ def test_connection_check(self): if __name__ == '__main__': + unittest.main() diff --git a/twine_upload/pip_upload.py b/twine_upload/pip_upload.py new file mode 100644 index 0000000..ae7ee1b --- /dev/null +++ b/twine_upload/pip_upload.py @@ -0,0 +1,50 @@ +import os, shutil + + +def upload_files(activate_venv=True): + + venv_name = "venv" + setup_cmd = "python setup.py sdist" + + if activate_venv: + + venv_script = "{VENV}\\Scripts\\python {VENV}\\Lib\\site-packages\\".format( + + VENV=venv_name + + ) + + else: + + venv_script = "" + + twine_cmd = '{VENV_SCRIPT}twine upload dist/* -u {USER} -p {PSW} {OTHER}'.format( + + VENV_SCRIPT = venv_script, + USER="__token__", + PSW=__read_token(), + OTHER="--verbose" + + ) + + base_dir = os.path.dirname(os.path.dirname(__file__)) + os.chdir(base_dir) + os.system(setup_cmd) + os.system(twine_cmd) + + shutil.rmtree(os.path.join(base_dir, "dist")) + for sub_dir in os.listdir(base_dir): + if ".egg-info" in sub_dir: + shutil.rmtree(os.path.join(base_dir, sub_dir)) + + +def __read_token(): + + with open("pipy_token", "r") as file: + pypi_token = file.readline().strip("\n") + + return pypi_token + + +if __name__ == "__main__": + upload_files(activate_venv=False)