# Make a Simultaneous linear equation solver using Tkinter GUI

It's a simple program, the user is prompted to enter number of variables.

Then the user is prompted to enter coefficients and constants. That many equations will be there as many variables.

Once filled up, submit button is clicked and the solutions are displayed.

I've accomplished this much successfully. However I'm new to Tkinter. I would appreciate if someone can help me improve the user interface and the code in general. The code is here. No need to make any change to the solving algorithm. I'm not allowed to use numpy for this assignment :( I want to improve the interface and the design language, that's it.

def minor(matrix, i, j):
return [[matrix[r][c] for c in range(len(matrix[r])) if c != j]
for r in range(len(matrix)) if r != i]

def det(matrix):
if len(matrix) == len(matrix[0]) == 1:
return matrix[0][0]
return sum(matrix[0][i] * cofac(matrix, 0, i) for i in range(len(matrix[0])))

cofac = lambda matrix, i, j: (-1) ** ((i + j) % 2) * det(minor(matrix, i, j))

transpose = lambda matrix: [[matrix[r][c] for r in range(len(matrix))] for c in range(len(matrix[0]))]

return transpose([[cofac(matrix, r, c) for c in range(len(matrix[r]))] for r in range(len(matrix))])

def div_and_store(a, d):
toPrint = []
for i in a:
toPrint.append([])
for j in i:
if j % d == 0:
toPrint[-1].append(f'{j//d}')
else:
h = hcf(j, d)
denominator = d//h
numerator = j//h
if denominator > 0:
toPrint[-1].append(f'{numerator}/{denominator}')
else:
toPrint[-1].append(f'{-numerator}/{-denominator}')

hcf = lambda x, y: y if x == 0 else hcf(y % x, x)

def product(A, B):
if len(A[0]) != len(B):
return
Bt = transpose(B)
return [[sum(a * b for a, b in zip(i, j)) for j in Bt] for i in A]

from tkinter import *
root = Tk()
root.resizable(width=False, height=False)  # not resizable in both directions
root.title('Simultaneous liner equation Solver')
my_label = Label(root, text='How many variables?')
my_label.grid(row=0, column=0)
e = Entry(root, width=10, borderwidth=5)

def done():
global row, n
try:
A = []
S = []
for record in entries:
A.append([])
for i in range(0, len(record)-1):
entry = record[i].get()
if entry:
A[-1].append(int(entry))
else:
A[-1].append(0)
entry = record[-1].get()
S.append([int(entry)])
except ValueError:
new_label = Label(root, text='Invalid. Try again!')
new_label.grid(row=row, columnspan=n * 3, sticky='W')
new_label.after(1000, lambda: new_label.destroy())
return
for record in entries:
for entry in record:
entry['state'] = DISABLED
new_button['state'] = DISABLED
determinant = det(A)
if determinant == 0:
new_label = Label(root, text='No unique solution set!')
new_label.grid(row=row, columnspan=n * 3, sticky='W')
return
for i in range(n):
new_label = Label(root, text=chr(97+i) + ' = '+solution[i][0])
new_label.grid(row=row, columnspan=2, sticky='W')
row += 1

def submit():
try:
global n
n = int(e.get())
except ValueError:
label = Label(root, text="You're supposed to enter a number, Try again")
label.grid(row=1, column=0, columnspan=3)
label.after(1000, lambda: label.destroy())
return
if n < 2:
label = Label(root, text="At least two variables are required!")
label.grid(row=1, column=0, columnspan=3)
label.after(1000, lambda: label.destroy())
return
e['state'] = DISABLED
my_label.grid_forget()
my_button.grid_forget()
e.grid_forget()
global row, entries
row = 0
entries = []
for i in range(n):
Label(root, text='').grid(row=row, columnspan=3*n+1)
row += 1
entries.append([])
col = 0
for j in map(chr, range(97, 97+n)):
entry = Entry(root, width=5, borderwidth=2, justify='right')
entries[i].append(entry)
entry.grid(row=row, column=col)
col += 1
col += 1
if col == 3*n-1:
Label(root, text='=').grid(row=row, column=col)
col += 1
entry = Entry(root, width=5, borderwidth=2, justify='right')
entries[i].append(entry)
entry.grid(row=row, column=col)
else:
Label(root, text='+').grid(row=row, column=col)
col += 1
row += 1
Label(root, text='').grid(row=row)
row += 1
txt = 'May leave the blank empty if the coefficient is 0!'
Label(root, text=txt).grid(row=row, columnspan=3*n-1, sticky='W')
row += 1
global new_button
new_button = Button(root, text='Submit', command=done)
new_button.grid(row=row, column=3*n)
row += 1

my_button = Button(root, text='Submit', command=submit)
e.grid(row=0, column=2)
my_button.grid(row=0, column=3)
root.mainloop()

New contributor
Nothing special is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

• Always put your imports at the top of the file
• Avoid importing splat *; either import specific symbols, or import the module (optionally with an alias) like import tkinter as tk and refer to symbols within its namespace like tk.Button.
• Strongly consider using Sympy. It can natively do fractional matrix math, as well as more advanced stuff like thorough treatment of solution sets and degrees of freedom.
• PEP484 type-hint your method signatures.
• Expand your one-liner nested comprehensions, which are very difficult to read, into multi-line formatting with nested indentation
• Do not use lambdas when functions suffice.
• Variables like toPrint should be to_print by PEP8
• Avoid globals like root, my_label etc. being in the global namespace. Pass them around by function parameter or as a class.
• e, my_button and my_label are not good variable names.
• Do not call mainloop from the global namespace; call it from a main function so that it's possible for other people to reuse or test your code in pieces
• Your interface decision of having a "disposable program" that can only solve one system, after which all controls are disabled, is strange. I would instead expect
• do not have a Submit button at all
• do not disable anything upon solution
• update the output solution set whenever any field is edited, so long as the inputs are valid
• if the inputs are invalid, rather than temporarily showing an error message and then hiding it after a timer, immediately show an error message that persists until the inputs have been edited to be valid
• do not have a two-step UI that asks you for the number of parameters. Instead, have a one-step UI with a spinbox control that allows selection of the number of parameters, adjusting the appropriate input controls in real time.
• You currently reject float inputs. It's not necessary to do this - you can have a conversion stage e.g. from 3.7 to 37/10, and preserve your exact fractional math.

A light and partial refactor that touches on only a few of the above suggestions, particularly type hinting, is

from numbers import Real
from typing import List, Sequence
from tkinter import Button, Entry, Label, Tk, DISABLED

def minor(matrix: Sequence[Sequence[Real]], i: int, j: int) -> List[List[Real]]:
return [
[
matrix[r][c] for c in range(len(matrix[r]))
if c != j
]
for r in range(len(matrix)) if r != i
]

def det(matrix: Sequence[Sequence[Real]]) -> Real:
if len(matrix) == len(matrix[0]) == 1:
return matrix[0][0]
return sum(
matrix[0][i] * cofac(matrix, 0, i)
for i in range(len(matrix[0]))
)

def cofac(matrix: Sequence[Sequence[Real]], i: int, j: int) -> Real:
return (-1) ** ((i + j) % 2) * det(minor(matrix, i, j))

def transpose(matrix: Sequence[Sequence[Real]]) -> List[List[Real]]:
return [
[
matrix[r][c] for r in range(len(matrix))
] for c in range(len(matrix[0]))
]

return transpose(
[
[
cofac(matrix, r, c) for c in range(len(matrix[r]))
] for r in range(len(matrix))
]
)

def div_and_store(a: Sequence[Sequence[Real]], d: Real) -> List[List[Real]]:
to_print = []
for i in a:
to_print.append([])
for j in i:
if j % d == 0:
to_print[-1].append(f'{j//d}')
else:
h = hcf(j, d)
denominator = d//h
numerator = j//h
if denominator > 0:
to_print[-1].append(f'{numerator}/{denominator}')
else:
to_print[-1].append(f'{-numerator}/{-denominator}')

def hcf(x: Real, y: Real) -> Real:
return y if x == 0 else hcf(y % x, x)

def product(A: Sequence[Sequence[Real]], B: Sequence[Sequence[Real]]) -> List[List[Real]]:
if len(A[0]) != len(B):
raise ValueError()
Bt = transpose(B)
return [
[
sum(a * b for a, b in zip(i, j)) for j in Bt
] for i in A
]

def done() -> None:
global row, n
try:
A = []
S = []
for record in entries:
A.append([])
for i in range(0, len(record)-1):
entry = record[i].get()
if entry:
A[-1].append(int(entry))
else:
A[-1].append(0)
entry = record[-1].get()
S.append([int(entry)])
except ValueError:
new_label = Label(root, text='Invalid. Try again!')
new_label.grid(row=row, columnspan=n * 3, sticky='W')
new_label.after(1000, lambda: new_label.destroy())
return
for record in entries:
for entry in record:
entry['state'] = DISABLED
new_button['state'] = DISABLED
determinant = det(A)
if determinant == 0:
new_label = Label(root, text='No unique solution set!')
new_label.grid(row=row, columnspan=n * 3, sticky='W')
return
for i in range(n):
new_label = Label(root, text=chr(97+i) + ' = '+solution[i][0])
new_label.grid(row=row, columnspan=2, sticky='W')
row += 1

def submit() -> None:
try:
global n
n = int(e.get())
except ValueError:
label = Label(root, text="You're supposed to enter a number, Try again")
label.grid(row=1, column=0, columnspan=3)
label.after(1000, lambda: label.destroy())
return
if n < 2:
label = Label(root, text="At least two variables are required!")
label.grid(row=1, column=0, columnspan=3)
label.after(1000, lambda: label.destroy())
return
e['state'] = DISABLED
my_label.grid_forget()
my_button.grid_forget()
e.grid_forget()
global row, entries
row = 0
entries = []
for i in range(n):
Label(root, text='').grid(row=row, columnspan=3*n+1)
row += 1
entries.append([])
col = 0
for j in map(chr, range(97, 97+n)):
entry = Entry(root, width=5, borderwidth=2, justify='right')
entries[i].append(entry)
entry.grid(row=row, column=col)
col += 1
col += 1
if col == 3*n-1:
Label(root, text='=').grid(row=row, column=col)
col += 1
entry = Entry(root, width=5, borderwidth=2, justify='right')
entries[i].append(entry)
entry.grid(row=row, column=col)
else:
Label(root, text='+').grid(row=row, column=col)
col += 1
row += 1
Label(root, text='').grid(row=row)
row += 1
txt = 'May leave the blank empty if the coefficient is 0!'
Label(root, text=txt).grid(row=row, columnspan=3*n-1, sticky='W')
row += 1
global new_button
new_button = Button(root, text='Submit', command=done)
new_button.grid(row=row, column=3*n)
row += 1

root = Tk()
e = Entry(root, width=10, borderwidth=5)
my_label = Label(root, text='How many variables?')
my_button = Button(root, text='Submit', command=submit)

def main() -> None:
root.resizable(width=False, height=False)  # not resizable in both directions
root.title('Simultaneous linear equation Solver')
my_label.grid(row=0, column=0)

e.grid(row=0, column=2)
my_button.grid(row=0, column=3)
root.mainloop()

if __name__ == '__main__':
main()


With all suggestions, a refactor looks like

from fractions import Fraction
from string import ascii_lowercase
from typing import List, Optional, Callable, Dict

from sympy import Matrix, linsolve, FiniteSet
import tkinter as tk

MAX_VARS = len(ascii_lowercase)

ComplainCB = Callable[[str, Optional[str]], None]

class UICell:
def __init__(
self, parent: tk.Widget, complain: ComplainCB, row: int, col: int,
justify: str = tk.LEFT,
) -> None:
self.complain = complain
self.var_name = f'cell_{row}_{col}'

# not DoubleVar - it needs to be Fraction-parseable
self.var = tk.StringVar(
master=parent,
name=self.var_name,
value='0',
)
self.entry = tk.Entry(
master=parent,
textvariable=self.var,
width=6,
justify=justify,
)
self.entry.grid(row=row, column=col)
self.value: Optional[Fraction] = Fraction(0)

def destroy(self) -> None:
self.var.trace_remove('write', self.trace_id)
self.entry.destroy()

def changed(self, name: str, index: str, mode: str) -> None:
try:
self.value = Fraction(self.var.get())
except ValueError:
self.value = None

if self.value is None:
complaint = 'Invalid number'
colour = '#FCD0D0'
else:
complaint = None
colour = 'white'
self.entry.configure(background=colour)
self.complain(self.var_name, complaint)

class UIVarCell(UICell):
def __init__(self, parent: tk.Widget, complain: ComplainCB, row: int, col: int, letter: str) -> None:
super().__init__(parent, complain, row, col, tk.RIGHT)
self.letter = letter
self.separator = tk.Label(master=parent)
self.separator.grid(sticky=tk.W, row=row, column=1 + col)

def destroy(self) -> None:
super().destroy()
self.separator.destroy()

def set_separator(self, rightmost: bool) -> None:
if rightmost:
sep = '='
else:
sep = '+'
self.separator.config(text=f'{self.letter} {sep}')

class UIVariable:
def __init__(self, parent: tk.Widget, complain: ComplainCB, index: int) -> None:
self.parent, self.complain, self.index = parent, complain, index
self.letter = ascii_lowercase[index]
self.cells: List[UIVarCell] = []

self.result_var = tk.StringVar(
master=parent,
value='',
name=f'result_{index}',
)
self.result_entry = tk.Entry(
master=parent,
textvariable=self.result_var,
width=6,
justify=tk.RIGHT,
)
self.result_label = tk.Label(master=parent, text=f'={self.letter}')
self.result_entry.grid(sticky=tk.E, row=1 + MAX_VARS, column=2*index)
self.result_label.grid(sticky=tk.W, row=1 + MAX_VARS, column=2*index + 1)

for _ in range(index + 1):
self.grow()

def grow(self) -> None:
self.cells.append(UIVarCell(
parent=self.parent,
complain=self.complain,
row=1 + len(self.cells),
col=2*self.index,
letter=self.letter,
))

def shrink(self) -> None:
self.cells.pop().destroy()

def destroy(self) -> None:
for widget in (self.result_label, self.result_entry):
widget.destroy()
while self.cells:
self.shrink()

def set_result(self, s: str) -> None:
self.result_var.set(s)

class UIFrame:
def __init__(self, parent: tk.Tk):
self.root = root = tk.Frame(master=parent)

self.variables: List[UIVariable] = []
self.sums: List[UICell] = []
self.complaints: Dict[str, str] = {}

self.count = tk.IntVar(master=root, name='count', value=2)

tk.Label(
master=root, text='Variables'
).grid(row=0, column=0, sticky=tk.E)
tk.Spinbox(
master=root,
from_=2,
to=MAX_VARS,
increment=1,
width=4,  # characters
textvariable=self.count,  # triggers a first call to count_changed
).grid(row=0, column=1, columnspan=2, sticky=tk.W)

self.error_label = tk.Label(master=root, text='', foreground='red')
# Row indices do not need to be contiguous, so choose one that will
# always be at the bottom - one for the spinbox, max vars, and one for
# the result row.
self.error_label.grid(row=1 + MAX_VARS + 1, column=0, columnspan=MAX_VARS + 1)

root.pack()

def count_changed(self, name: str, index: str, mode: str) -> None:
try:
requested = self.count.get()
except tk.TclError:
return

if 2 <= requested <= MAX_VARS:
for var in self.variables:
var.set_result('')
while len(self.variables) < requested:
self.grow()
while len(self.variables) > requested:
self.shrink()

def grow(self) -> None:
for var in self.variables:
var.grow()
for cell in var.cells:
cell.set_separator(rightmost=False)

var = UIVariable(
parent=self.root, complain=self.complain,
index=len(self.variables),
)
for cell in var.cells:
cell.set_separator(rightmost=True)
self.variables.append(var)

self.sums.append(UICell(
parent=self.root, complain=self.complain,
row=1 + len(self.sums), col=2*MAX_VARS,
))

def shrink(self) -> None:
self.sums.pop().destroy()
self.variables.pop().destroy()
for var in self.variables:
var.shrink()
for cell in self.variables[-1].cells:
cell.set_separator(rightmost=True)

def complain(self, var_name: str, complaint: Optional[str]) -> None:
if complaint is None:
self.complaints.pop(var_name, None)
else:
self.complaints[var_name] = complaint
self.error_text = ', '.join(self.complaints.values())

if not self.complaints:
self.solve()

@property
def error_text(self) -> str:
return self.error_label.cget('text')

@error_text.setter
def error_text(self, s: str) -> None:
self.error_label.configure(text=s)

@property
def matrix(self) -> Matrix:
return Matrix([
[c.value for c in var.cells]
for var in self.variables
]).T

@property
def sum_vector(self) -> Matrix:
return Matrix([c.value for c in self.sums])

def solve(self) -> None:
res_set = linsolve((self.matrix, self.sum_vector))
if not isinstance(res_set, FiniteSet):
self.error_text = 'No finite set of solutions'
elif len(res_set) < 1:
self.error_text = 'No solution found'
elif len(res_set) > 1:
self.error_text = 'Non-unique solution space'
else:
self.error_text = ''
result, = res_set
for var, res in zip(self.variables, result):
var.set_result(res)
return

for var in self.variables:
var.set_result('')

def main() -> None:
parent = tk.Tk()
parent.title('Simultaneous linear equation solver')
UIFrame(parent)
parent.mainloop()

if __name__ == '__main__':
main()


Error highlighting:

Underdetermined systems:

Systems with no solution:

Solution:

• Your answer made me realize... I need to study tkinter in details before trying to do these kinda programs... Thanks a lot.... 7 hours ago