From 9ebeba1ef62fb5eca93c28b476668d86ea21f84c Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Sat, 27 May 2023 00:22:08 +1200 Subject: [PATCH 1/5] Added simultaneous_linear_equation_solver.py --- maths/simultaneous_linear_equation_solver.py | 160 +++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 maths/simultaneous_linear_equation_solver.py diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py new file mode 100644 index 000000000000..f405588a6a73 --- /dev/null +++ b/maths/simultaneous_linear_equation_solver.py @@ -0,0 +1,160 @@ +""" +https://en.wikipedia.org/wiki/Augmented_matrix + +This algorithm solves simultaneous linear equations of the form +λa + λb + λc + λd + ... = λ as [λ, λ, λ, λ, ..., λ] +Where λ are individual coefficients, the no. of equations = no. of coefficients - 1 + +Note in order to work there must exist 1 equation where all instances of λ != 0 +""" + + +class EquationSolver: + def __init__(self, data): + if len(data) == 0: + raise IndexError("EquationSolver() requires n lists of length n+1") + self.size = len(data) + _length = self.size + 1 + if any(len(item) != _length for item in data): + raise IndexError("EquationSolver() requires n lists of length n+1") + for row in data: + if any(not isinstance(column, (int, float)) for column in row): + raise ValueError("EquationSolver() requires lists of integers") + self.data = data + + def __repr__(self): + string_builder = "[" + for i in range(self.size): + string_builder += "[" + for j in range(self.size): + if j == self.size - 1: + string_builder += f"{self.data[i][j]: >4}" + continue + string_builder += f"{str(self.data[i][j]) + ',': >5}" + string_builder += f"{'|': ^5}{self.data[i][-1]:<2}]\n " + string_builder = string_builder[:-2] + "]" + return string_builder + + def solve(self): + useable_form = self.make_useable_form() + # Generate a simplified upper triangular matrix + simplified = self.simplify(useable_form) + # Reverse the matrix + simplified = simplified[::-1] + solutions = [] + for row in simplified: + current_solution = row[-1] + if not solutions: + if row[-2] == 0: + solutions.append(0) + continue + solutions.append(current_solution / row[-2]) + continue + temp_row = row.copy()[: len(row) - 1 :] + while temp_row[0] == 0: + temp_row.pop(0) + if len(temp_row) == 0: + solutions.append(0) + continue + temp_row = temp_row[1::] + temp_row = temp_row[::-1] + for column_index, column in enumerate(temp_row): + current_solution -= column * solutions[column_index] + solutions.append(current_solution) + final = [] + for item in solutions: + final.append(round(item, 5)) + return final[::-1] + + def make_useable_form(self): + data_set = self.data.copy() + if any(0 in row for row in data_set): + temp_data = data_set.copy() + full_row = [] + for row_index, row in enumerate(temp_data): + if 0 not in row: + full_row = data_set.pop(row_index) + break + if not full_row: + raise ValueError("EquationSolver() requires at least 1 full equation") + data_set.insert(0, full_row) + return data_set + + def simplify(self, current_set): + # Divide each row by magnitude of first term --> creates 'unit' matrix + duplicate_set = current_set.copy() + for row_index, row in enumerate(duplicate_set): + magnitude = row[0] + for column_index, column in enumerate(row): + if magnitude == 0: + current_set[row_index][column_index] = column + continue + current_set[row_index][column_index] = column / magnitude + # Subtract to cancel term + first_row = current_set[0] + final_set = [first_row] + current_set = current_set[1::] + for row in current_set: + temp_row = [] + # If first term is 0, it is already in form we want, so we preserve it + if row[0] == 0: + final_set.append(row) + continue + for column_index in range(len(row)): + temp_row.append(first_row[column_index] - row[column_index]) + final_set.append(temp_row) + # Create next recursion iteration set + if len(final_set[0]) != 3: + current_first_row = final_set[0] + current_first_column = [] + next_iteration = [] + for row in final_set[1::]: + current_first_column.append(row[0]) + next_iteration.append(row[1::]) + resultant = self.simplify(next_iteration) + for i in range(len(resultant)): + resultant[i].insert(0, current_first_column[i]) + resultant.insert(0, current_first_row) + final_set = resultant + return final_set + + +def solve_simultaneous(equations): + """ + >>> solve_simultaneous([[1, 2, 3],[4, 5, 6]]) + [-1.0, 2.0] + >>> solve_simultaneous([[0, -3, 1, 7],[3, 2, -1, 11],[5, 1, -2, 12]]) + [6.4, 1.2, 10.6] + >>> solve_simultaneous([]) + Traceback (most recent call last): + ... + IndexError: EquationSolver() requires n lists of length n+1 + >>> solve_simultaneous([[1, 2, 3],[1, 2]]) + Traceback (most recent call last): + ... + IndexError: EquationSolver() requires n lists of length n+1 + >>> solve_simultaneous([[1, 2, 3],["a", 7, 8]]) + Traceback (most recent call last): + ... + ValueError: EquationSolver() requires lists of integers + >>> solve_simultaneous([[0, 2, 3],[4, 0, 6]]) + Traceback (most recent call last): + ... + ValueError: EquationSolver() requires at least 1 full equation + """ + aug = EquationSolver(equations) + return aug.solve() + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + eq = [ + [2, 1, 1, 1, 1, 4], + [1, 2, 1, 1, 1, 5], + [1, 1, 2, 1, 1, 6], + [1, 1, 1, 2, 1, 7], + [1, 1, 1, 1, 2, 8], + ] + print(solve_simultaneous(eq)) From ab2da48f5c64a837f0572bc46234c1a29412a9ab Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Sun, 28 May 2023 19:00:32 +1200 Subject: [PATCH 2/5] Removed Augment class, replaced with recursive functions --- maths/simultaneous_linear_equation_solver.py | 207 +++++++++---------- 1 file changed, 93 insertions(+), 114 deletions(-) diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py index f405588a6a73..5656901a8730 100644 --- a/maths/simultaneous_linear_equation_solver.py +++ b/maths/simultaneous_linear_equation_solver.py @@ -9,117 +9,52 @@ """ -class EquationSolver: - def __init__(self, data): - if len(data) == 0: - raise IndexError("EquationSolver() requires n lists of length n+1") - self.size = len(data) - _length = self.size + 1 - if any(len(item) != _length for item in data): - raise IndexError("EquationSolver() requires n lists of length n+1") - for row in data: - if any(not isinstance(column, (int, float)) for column in row): - raise ValueError("EquationSolver() requires lists of integers") - self.data = data - - def __repr__(self): - string_builder = "[" - for i in range(self.size): - string_builder += "[" - for j in range(self.size): - if j == self.size - 1: - string_builder += f"{self.data[i][j]: >4}" - continue - string_builder += f"{str(self.data[i][j]) + ',': >5}" - string_builder += f"{'|': ^5}{self.data[i][-1]:<2}]\n " - string_builder = string_builder[:-2] + "]" - return string_builder - - def solve(self): - useable_form = self.make_useable_form() - # Generate a simplified upper triangular matrix - simplified = self.simplify(useable_form) - # Reverse the matrix - simplified = simplified[::-1] - solutions = [] - for row in simplified: - current_solution = row[-1] - if not solutions: - if row[-2] == 0: - solutions.append(0) - continue - solutions.append(current_solution / row[-2]) - continue - temp_row = row.copy()[: len(row) - 1 :] - while temp_row[0] == 0: - temp_row.pop(0) - if len(temp_row) == 0: - solutions.append(0) - continue - temp_row = temp_row[1::] - temp_row = temp_row[::-1] - for column_index, column in enumerate(temp_row): - current_solution -= column * solutions[column_index] - solutions.append(current_solution) - final = [] - for item in solutions: - final.append(round(item, 5)) - return final[::-1] - - def make_useable_form(self): - data_set = self.data.copy() - if any(0 in row for row in data_set): - temp_data = data_set.copy() - full_row = [] - for row_index, row in enumerate(temp_data): - if 0 not in row: - full_row = data_set.pop(row_index) - break - if not full_row: - raise ValueError("EquationSolver() requires at least 1 full equation") - data_set.insert(0, full_row) - return data_set - - def simplify(self, current_set): - # Divide each row by magnitude of first term --> creates 'unit' matrix - duplicate_set = current_set.copy() - for row_index, row in enumerate(duplicate_set): - magnitude = row[0] - for column_index, column in enumerate(row): - if magnitude == 0: - current_set[row_index][column_index] = column - continue - current_set[row_index][column_index] = column / magnitude - # Subtract to cancel term - first_row = current_set[0] - final_set = [first_row] - current_set = current_set[1::] - for row in current_set: - temp_row = [] - # If first term is 0, it is already in form we want, so we preserve it - if row[0] == 0: - final_set.append(row) +def simplify(current_set) -> list[list]: + """ + >>> simplify([[1, 2, 3], [4, 5, 6]]) + [[1.0, 2.0, 3.0], [0.0, 0.75, 1.5]] + >>> simplify([[5, 2, 5], [5, 1, 10]]) + [[1.0, 0.4, 1.0], [0.0, 0.2, -1.0]] + """ + # Divide each row by magnitude of first term --> creates 'unit' matrix + duplicate_set = current_set.copy() + for row_index, row in enumerate(duplicate_set): + magnitude = row[0] + for column_index, column in enumerate(row): + if magnitude == 0: + current_set[row_index][column_index] = column continue - for column_index in range(len(row)): - temp_row.append(first_row[column_index] - row[column_index]) - final_set.append(temp_row) - # Create next recursion iteration set - if len(final_set[0]) != 3: - current_first_row = final_set[0] - current_first_column = [] - next_iteration = [] - for row in final_set[1::]: - current_first_column.append(row[0]) - next_iteration.append(row[1::]) - resultant = self.simplify(next_iteration) - for i in range(len(resultant)): - resultant[i].insert(0, current_first_column[i]) - resultant.insert(0, current_first_row) - final_set = resultant - return final_set + current_set[row_index][column_index] = column / magnitude + # Subtract to cancel term + first_row = current_set[0] + final_set = [first_row] + current_set = current_set[1::] + for row in current_set: + temp_row = [] + # If first term is 0, it is already in form we want, so we preserve it + if row[0] == 0: + final_set.append(row) + continue + for column_index in range(len(row)): + temp_row.append(first_row[column_index] - row[column_index]) + final_set.append(temp_row) + # Create next recursion iteration set + if len(final_set[0]) != 3: + current_first_row = final_set[0] + current_first_column = [] + next_iteration = [] + for row in final_set[1::]: + current_first_column.append(row[0]) + next_iteration.append(row[1::]) + resultant = simplify(next_iteration) + for i in range(len(resultant)): + resultant[i].insert(0, current_first_column[i]) + resultant.insert(0, current_first_row) + final_set = resultant + return final_set -def solve_simultaneous(equations): +def solve_simultaneous(equations) -> list: """ >>> solve_simultaneous([[1, 2, 3],[4, 5, 6]]) [-1.0, 2.0] @@ -128,22 +63,66 @@ def solve_simultaneous(equations): >>> solve_simultaneous([]) Traceback (most recent call last): ... - IndexError: EquationSolver() requires n lists of length n+1 + IndexError: solve_simultaneous() requires n lists of length n+1 >>> solve_simultaneous([[1, 2, 3],[1, 2]]) Traceback (most recent call last): ... - IndexError: EquationSolver() requires n lists of length n+1 + IndexError: solve_simultaneous() requires n lists of length n+1 >>> solve_simultaneous([[1, 2, 3],["a", 7, 8]]) Traceback (most recent call last): ... - ValueError: EquationSolver() requires lists of integers + ValueError: solve_simultaneous() requires lists of integers >>> solve_simultaneous([[0, 2, 3],[4, 0, 6]]) Traceback (most recent call last): ... - ValueError: EquationSolver() requires at least 1 full equation + ValueError: solve_simultaneous() requires at least 1 full equation """ - aug = EquationSolver(equations) - return aug.solve() + if len(equations) == 0: + raise IndexError("solve_simultaneous() requires n lists of length n+1") + _length = len(equations) + 1 + if any(len(item) != _length for item in equations): + raise IndexError("solve_simultaneous() requires n lists of length n+1") + for row in equations: + if any(not isinstance(column, (int, float)) for column in row): + raise ValueError("solve_simultaneous() requires lists of integers") + data_set = equations.copy() + if any(0 in row for row in data_set): + temp_data = data_set.copy() + full_row = [] + for row_index, row in enumerate(temp_data): + if 0 not in row: + full_row = data_set.pop(row_index) + break + if not full_row: + raise ValueError("solve_simultaneous() requires at least 1 full equation") + data_set.insert(0, full_row) + useable_form = data_set.copy() + simplified = simplify(useable_form) + simplified = simplified[::-1] + solutions: list = [] + for row in simplified: + current_solution = row[-1] + if not solutions: + if row[-2] == 0: + solutions.append(0) + continue + solutions.append(current_solution / row[-2]) + continue + temp_row = row.copy()[: len(row) - 1:] + while temp_row[0] == 0: + temp_row.pop(0) + if len(temp_row) == 0: + solutions.append(0) + continue + temp_row = temp_row[1::] + temp_row = temp_row[::-1] + for column_index, column in enumerate(temp_row): + current_solution -= column * solutions[column_index] + solutions.append(current_solution) + final = [] + for item in solutions: + final.append(float(round(item, 5))) + return final[::-1] if __name__ == "__main__": From 8759511e597d7c410a0ef7c9aa6e531b5ce57e47 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 28 May 2023 07:01:02 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- maths/simultaneous_linear_equation_solver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py index 5656901a8730..49341b9dcd03 100644 --- a/maths/simultaneous_linear_equation_solver.py +++ b/maths/simultaneous_linear_equation_solver.py @@ -108,7 +108,7 @@ def solve_simultaneous(equations) -> list: continue solutions.append(current_solution / row[-2]) continue - temp_row = row.copy()[: len(row) - 1:] + temp_row = row.copy()[: len(row) - 1 :] while temp_row[0] == 0: temp_row.pop(0) if len(temp_row) == 0: From 1e4eb0c22583e3252404ee9ccfcf56691e7f6707 Mon Sep 17 00:00:00 2001 From: Chris O <46587501+ChrisO345@users.noreply.github.com> Date: Fri, 2 Jun 2023 10:36:23 +1200 Subject: [PATCH 4/5] fixed edge cases --- .vscode/settings.json | 5 +++++ maths/simultaneous_linear_equation_solver.py | 13 ++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..058954976e90 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} \ No newline at end of file diff --git a/maths/simultaneous_linear_equation_solver.py b/maths/simultaneous_linear_equation_solver.py index 49341b9dcd03..1287b2002d00 100644 --- a/maths/simultaneous_linear_equation_solver.py +++ b/maths/simultaneous_linear_equation_solver.py @@ -2,14 +2,14 @@ https://en.wikipedia.org/wiki/Augmented_matrix This algorithm solves simultaneous linear equations of the form -λa + λb + λc + λd + ... = λ as [λ, λ, λ, λ, ..., λ] -Where λ are individual coefficients, the no. of equations = no. of coefficients - 1 +λa + λb + λc + λd + ... = γ as [λ, λ, λ, λ, ..., γ] +Where λ & γ are individual coefficients, the no. of equations = no. of coefficients - 1 -Note in order to work there must exist 1 equation where all instances of λ != 0 +Note in order to work there must exist 1 equation where all instances of λ and γ != 0 """ -def simplify(current_set) -> list[list]: +def simplify(current_set: list[list]) -> list[list]: """ >>> simplify([[1, 2, 3], [4, 5, 6]]) [[1.0, 2.0, 3.0], [0.0, 0.75, 1.5]] @@ -54,7 +54,7 @@ def simplify(current_set) -> list[list]: return final_set -def solve_simultaneous(equations) -> list: +def solve_simultaneous(equations: list[list]) -> list: """ >>> solve_simultaneous([[1, 2, 3],[4, 5, 6]]) [-1.0, 2.0] @@ -85,6 +85,8 @@ def solve_simultaneous(equations) -> list: for row in equations: if any(not isinstance(column, (int, float)) for column in row): raise ValueError("solve_simultaneous() requires lists of integers") + if len(equations) == 1: + return [equations[0][-1] / equations[0][0]] data_set = equations.copy() if any(0 in row for row in data_set): temp_data = data_set.copy() @@ -137,3 +139,4 @@ def solve_simultaneous(equations) -> list: [1, 1, 1, 1, 2, 8], ] print(solve_simultaneous(eq)) + print(solve_simultaneous([[4, 2]])) From b30488206dfb7b7836d70394e376189b85efd11c Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Fri, 2 Jun 2023 07:04:50 +0200 Subject: [PATCH 5/5] Update settings.json --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 058954976e90..ef16fa1aa7ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,4 +2,4 @@ "githubPullRequests.ignoredPullRequestBranches": [ "master" ] -} \ No newline at end of file +}