R.A Equation Solver

Source Code - Python Implementation

2024 Python 609 Lines

main.py

Language: Python 3.7+

Dependencies: PyQt5, SymPy, Matplotlib, NumPy

Description: Complete implementation of the R.A Equation Solver application

Source Code

import sys
import requests
from bs4 import BeautifulSoup
import numpy as np
import sympy as sp
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from PyQt5.QtWidgets import (
	QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QLabel,
	QTextEdit, QGridLayout, QMessageBox, QDesktopWidget
)
from PyQt5.QtGui import QFont, QIcon, QDesktopServices
from PyQt5.QtCore import QUrl, Qt
import os

matplotlib.use('Qt5Agg')

COMMON_STYLE = """
QWidget {
    background-color: #2D2D30;
    color: #DCDCDC;
    font-family: 'Segoe UI', sans-serif;
    font-size: 16px;
}

QLabel {
    color: #8EB5FF;
    font-size: 18px;
    padding: 5px;
}

QLineEdit {
    background-color: #252526;
    border: 2px solid #3F3F46;
    border-radius: 5px;
    padding: 8px;
    font-size: 16px;
    selection-background-color: #3F3F46;
}

QLineEdit:focus {
    border: 2px solid #6C8CD5;
    background-color: #2D2D30;
}

QPushButton {
    background-color: #4A5B7D;
    border: none;
    border-radius: 5px;
    padding: 10px 20px;
    font-weight: bold;
    min-width: 100px;
    color: #DCDCDC;
}

QPushButton:hover {
    background-color: #5C6D8F;
}

QPushButton:pressed {
    background-color: #3A4B6D;
}

QTextEdit {
    background-color: #252526;
    border: 2px solid #3F3F46;
    border-radius: 5px;
    padding: 10px;
    font-family: 'Consolas', monospace;
    font-size: 14px;
    color: #DCDCDC;
}

QTextEdit:focus {
    border: 2px solid #6C8CD5;
}

QPushButton[text="sin"],
QPushButton[text="cos"],
QPushButton[text="tan"],
QPushButton[text="pi"],
QPushButton[text="exp"],
QPushButton[text="sqrt"] {
    background-color: #5D4A7D;
}

QPushButton[text="("],
QPushButton[text=")"] {
    background-color: #4D7D4A;
}

QPushButton[text="/"],
QPushButton[text="*"],
QPushButton[text="-"],
QPushButton[text="+"] {
    background-color: #7D4A4A;
}

QPushButton[text="x^y"] {
    background-color: #4A7D6C;
}
"""

LIGHT_STYLE = """
QWidget {
    background-color: #f9f9f9;
    color: #333333;
    font-family: 'Segoe UI', sans-serif;
    font-size: 16px;
}
QLabel {
    color: #333333;
    font-size: 18px;
    padding: 5px;
}
QLineEdit {
    background-color: #ffffff;
    border: 2px solid #cccccc;
    border-radius: 5px;
    padding: 8px;
    font-size: 16px;
    selection-background-color: #cccccc;
}
QLineEdit:focus {
    border: 2px solid #007acc;
    background-color: #ffffff;
}
QPushButton {
    background-color: #007acc;
    border: none;
    border-radius: 5px;
    padding: 10px 20px;
    font-weight: bold;
    min-width: 100px;
    color: #ffffff;
}
QPushButton:hover {
    background-color: #005a9e;
}
QPushButton:pressed {
    background-color: #004578;
}
QTextEdit {
    background-color: #ffffff;
    border: 2px solid #cccccc;
    border-radius: 5px;
    padding: 10px;
    font-family: 'Consolas', monospace;
    font-size: 14px;
    color: #333333;
}
QTextEdit:focus {
    border: 2px solid #007acc;
}
"""


class CircleSolverWindow(QWidget):
	def __init__(self):
		super().__init__()
		self.init_ui()
		self.setFixedSize(500, 350)
		self.center_window()

	def center_window(self):
		qr = self.frameGeometry()
		cp = QDesktopWidget().availableGeometry().center()
		qr.moveCenter(cp)
		self.move(qr.topLeft())

	def init_ui(self):
		layout = QVBoxLayout()
		instr_label = QLabel("Enter circle parameters:\n(x - h)² + (y - k)² = r²")
		layout.addWidget(instr_label)

		self.h_input = QLineEdit(placeholderText="Enter h (x-coordinate of center)")
		self.k_input = QLineEdit(placeholderText="Enter k (y-coordinate of center)")
		self.r_input = QLineEdit(placeholderText="Enter r (radius)")

		layout.addWidget(self.h_input)
		layout.addWidget(self.k_input)
		layout.addWidget(self.r_input)

		plot_button = QPushButton("Plot Circle", clicked=self.plot_circle)
		layout.addWidget(plot_button)
		self.setLayout(layout)
		self.setStyleSheet(COMMON_STYLE)

	def plot_circle(self):
		try:
			h = float(self.h_input.text())
			k = float(self.k_input.text())
			r = float(self.r_input.text())
		except ValueError:
			QMessageBox.warning(self, "Error", "Invalid numeric values")
			return

		plt.style.use('dark_background')
		fig = plt.figure(figsize=(6, 6))
		ax = fig.add_subplot(111)
		fig.patch.set_facecolor('#252526')
		ax.set_facecolor('#2D2D30')

		theta = np.linspace(0, 2 * np.pi, 300)
		x = h + r * np.cos(theta)
		y = k + r * np.sin(theta)

		ax.plot(x, y, color='#6C8CD5', label=f'(x - {h})² + (y - {k})² = {r ** 2}')
		ax.scatter([h], [k], color='#8EB5FF', zorder=3)
		ax.annotate(f"Center ({h}, {k})", (h, k), (h + 0.5, k + 0.5),
					arrowprops=dict(arrowstyle="->", color="#8EB5FF"),
					color='#8EB5FF', fontsize=12)

		ax.axhline(0, color='#8EB5FF', linewidth=1, linestyle='--', alpha=0.7)
		ax.axvline(0, color='#8EB5FF', linewidth=1, linestyle='--', alpha=0.7)

		ax.xaxis.label.set_color('#8EB5FF')
		ax.yaxis.label.set_color('#8EB5FF')
		ax.title.set_color('#8EB5FF')
		ax.tick_params(colors='#DCDCDC')
		ax.grid(color='#3F3F46')
		plt.axis('equal')
		plt.show()


class ThreeDPlotWindow(QWidget):
	def __init__(self):
		super().__init__()
		self.init_ui()
		self.setFixedSize(600, 400)
		self.center_window()
		self.function_history = []

	def center_window(self):
		qr = self.frameGeometry()
		cp = QDesktopWidget().availableGeometry().center()
		qr.moveCenter(cp)
		self.move(qr.topLeft())

	def init_ui(self):
		layout = QVBoxLayout()

		# Input Section
		input_layout = QGridLayout()
		self.function_input = QLineEdit(placeholderText="Enter z = f(x, y) (e.g., sin(x)*cos(y), sqrt(x**2 + y**2))")
		self.domain_start = QLineEdit("-5")
		self.domain_end = QLineEdit("5")
		self.resolution = QLineEdit("100")

		input_layout.addWidget(QLabel("Function z ="), 0, 0)
		input_layout.addWidget(self.function_input, 0, 1, 1, 3)
		input_layout.addWidget(QLabel("Domain:"), 1, 0)
		input_layout.addWidget(self.domain_start, 1, 1)
		input_layout.addWidget(QLabel("to"), 1, 2)
		input_layout.addWidget(self.domain_end, 1, 3)
		input_layout.addWidget(QLabel("Resolution:"), 2, 0)
		input_layout.addWidget(self.resolution, 2, 1)

		# Buttons
		btn_plot = QPushButton("Plot 3D Graph", clicked=self.plot_3d_graph)
		btn_contour = QPushButton("Contour Plot", clicked=self.plot_contour)

		control_layout = QHBoxLayout()
		control_layout.addWidget(btn_plot)
		control_layout.addWidget(btn_contour)

		layout.addLayout(input_layout)
		layout.addLayout(control_layout)
		self.setLayout(layout)
		self.setStyleSheet(COMMON_STYLE)

	def create_surface(self, expr):
		"""Create numerical function with error handling"""
		x, y = sp.symbols('x y')
		try:
			sympy_expr = sp.sympify(expr, locals={'x': x, 'y': y})
			f = sp.lambdify((x, y), sympy_expr, modules='numpy')

			# Generate grid
			start = float(self.domain_start.text())
			end = float(self.domain_end.text())
			res = int(self.resolution.text())
			x_vals = np.linspace(start, end, res)
			y_vals = np.linspace(start, end, res)
			X, Y = np.meshgrid(x_vals, y_vals)

			# Evaluate function with NaN handling
			with np.errstate(all='ignore'):
				Z = f(X, Y)
				Z = np.nan_to_num(Z, nan=0, posinf=0, neginf=0)

			return X, Y, Z, sympy_expr

		except Exception as e:
			QMessageBox.warning(self, "Error", f"Invalid function: {str(e)}")
			return None, None, None, None

	def plot_3d_graph(self):
		X, Y, Z, expr = self.create_surface(self.function_input.text())
		if X is None:
			return

		plt.style.use('dark_background')
		fig = plt.figure(figsize=(10, 8))
		ax = fig.add_subplot(111, projection='3d')

		# Surface plot with lighting
		surf = ax.plot_surface(
			X, Y, Z,
			cmap='viridis',
			edgecolor='none',
			rstride=2,
			cstride=2,
			alpha=0.8,
			antialiased=True
		)

		# Add contour projection
		offset = Z.min() - 0.1 * abs(Z.min())
		ax.contour(X, Y, Z, zdir='z', offset=offset, cmap='viridis', alpha=0.5)

		# Add colorbar
		cbar = fig.colorbar(surf, shrink=0.5, aspect=5)
		cbar.ax.yaxis.set_tick_params(color='white')
		plt.setp(cbar.ax.get_yticklabels(), color='white')

		# Formatting
		ax.set_xlabel('X', fontsize=12, color='#8EB5FF', labelpad=15)
		ax.set_ylabel('Y', fontsize=12, color='#8EB5FF', labelpad=15)
		ax.set_zlabel('Z', fontsize=12, color='#8EB5FF', labelpad=15)

		# Axis styling
		ax.tick_params(axis='both', which='major', labelsize=10, colors='#DCDCDC')
		ax.xaxis.pane.fill = False
		ax.yaxis.pane.fill = False
		ax.zaxis.pane.fill = False
		ax.xaxis.pane.set_edgecolor('#3F3F46')
		ax.yaxis.pane.set_edgecolor('#3F3F46')
		ax.zaxis.pane.set_edgecolor('#3F3F46')

		# Grid and rotation
		ax.view_init(elev=25, azim=45)
		ax.grid(color='#3F3F46', linestyle='--', linewidth=0.5)

		# Title with LaTeX
		title = f"$z = {sp.latex(expr)}$"
		plt.title(title, fontsize=14, color='#8EB5FF', pad=20)

		plt.tight_layout()
		plt.show()

	def plot_contour(self):
		X, Y, Z, expr = self.create_surface(self.function_input.text())
		if X is None:
			return

		plt.figure(figsize=(8, 6))
		plt.contourf(X, Y, Z, levels=20, cmap='viridis')
		plt.colorbar()
		plt.title(f"Contour Plot of $z = {sp.latex(expr)}$", color='#8EB5FF')
		plt.xlabel('X', color='#8EB5FF')
		plt.ylabel('Y', color='#8EB5FF')
		plt.gca().set_facecolor('#2D2D30')
		plt.show()


class SettingsWindow(QWidget):
	def __init__(self):
		super().__init__()
		self.setWindowTitle("Settings")
		self.setFixedSize(400, 300)
		self.init_ui()
		self.chosen_style = None  # Will store the selected style

	def init_ui(self):
		layout = QVBoxLayout()

		# Button to select Light Mode
		self.light_mode_button = QPushButton("Select Light Mode")
		self.light_mode_button.clicked.connect(self.select_light_mode)
		layout.addWidget(self.light_mode_button)

		# Button to select Dark Mode
		self.dark_mode_button = QPushButton("Select Dark Mode")
		self.dark_mode_button.clicked.connect(self.select_dark_mode)
		layout.addWidget(self.dark_mode_button)

		# Apply button to commit the selected style
		self.apply_button = QPushButton("Apply")
		self.apply_button.clicked.connect(self.apply_style)
		layout.addWidget(self.apply_button)

		# Additional info label
		self.info_label = QLabel("Customize your app appearance.")
		layout.addWidget(self.info_label)

		self.setLayout(layout)
		self.setStyleSheet(COMMON_STYLE)

	def select_light_mode(self):
		self.chosen_style = LIGHT_STYLE
		self.info_label.setText("Light Mode selected. Click Apply to change.")

	def select_dark_mode(self):
		self.chosen_style = COMMON_STYLE
		self.info_label.setText("Dark Mode selected. Click Apply to change.")

	def apply_style(self):
		if self.chosen_style is not None:
			app = QApplication.instance()
			app.setStyleSheet(self.chosen_style)
			# Update all top-level widgets:
			for widget in app.topLevelWidgets():
				widget.setStyleSheet(self.chosen_style)
			self.info_label.setText("Style applied!")
		else:
			self.info_label.setText("No style selected. Please choose a mode.")


class EquationSolver(QWidget):
	def __init__(self, screen_width, screen_height):
		super().__init__()
		self.screen_width = screen_width
		self.screen_height = screen_height
		self.circle_solver_window = None
		self.three_d_plot_window = None
		self.settings_window = None  # For the Settings window
		self.init_ui()

	def init_ui(self):
		self.setWindowTitle("R.A : Equation Solver")
		self.resize(int(self.screen_width * 0.6), int(self.screen_height * 0.7))
		self.center_window()
		self.setStyleSheet(COMMON_STYLE)

		layout = QVBoxLayout()
		self.label = QLabel("Enter an equation:")
		layout.addWidget(self.label)

		# Equation input remains unchanged in its placement.
		self.equation_input = QLineEdit()
		layout.addWidget(self.equation_input)

		self.solve_button = QPushButton("Solve it!", clicked=self.solve_and_plot)
		layout.addWidget(self.solve_button)

		# New Step-by-Step button
		self.step_button = QPushButton("Step-by-Step", clicked=self.step_by_step_solution)
		layout.addWidget(self.step_button)

		self.circle_solver_button = QPushButton("Circle Solver", clicked=self.open_circle_solver)
		layout.addWidget(self.circle_solver_button)
		self.three_d_plot_button = QPushButton("3D Plot", clicked=self.open_3d_plot)
		layout.addWidget(self.three_d_plot_button)
		self.result_display = QTextEdit(readOnly=True)
		self.calc_label = QLabel("Created by : Reza Afrasyabi")
		layout.addWidget(self.result_display)
		layout.addWidget(self.calc_label)

		# Calculator buttons grid (with Settings button added next to Eval)
		calc_layout = QGridLayout()
		buttons = [
			('7', '8', '9', '/'),
			('4', '5', '6', '*'),
			('1', '2', '3', '-'),
			('0', '.', '=', '+'),
			('sin', 'cos', 'tan', 'pi'),
			('(', ')', 'exp', 'sqrt'),
			('x', 'x^y', 'Eval', 'Settings')
		]

		for row, row_buttons in enumerate(buttons):
			for col, text in enumerate(row_buttons):
				btn = QPushButton(text)
				if text == "Settings":
					btn.clicked.connect(self.open_settings_window)
				else:
					btn.clicked.connect(self.on_calc_button_click)
				calc_layout.addWidget(btn, row, col)

		layout.addLayout(calc_layout)
		self.setLayout(layout)

	def center_window(self):
		qr = self.frameGeometry()
		cp = QDesktopWidget().availableGeometry().center()
		qr.moveCenter(cp)
		self.move(qr.topLeft())

	def on_calc_button_click(self):
		text = self.sender().text()
		current = self.equation_input.text()

		if text == "=" or text == "Eval":
			self.evaluate_expression()
		elif text == "x^y":
			self.equation_input.setText(current + "**")
		else:
			self.equation_input.setText(current + text)

	def evaluate_expression(self):
		expr = self.equation_input.text().strip()
		if not expr:
			return

		try:
			result = sp.sympify(expr).evalf()
			self.result_display.append(f"Result: {result}")
		except Exception as e:
			self.result_display.append(f"Error: {str(e)}")

	def solve_and_plot(self):
		equation = self.equation_input.text().strip()
		if not equation:
			return

		try:
			x = sp.Symbol('x')
			eq = sp.sympify(equation)
			solutions = sp.solve(eq, x)
			self.result_display.append(f"Solutions: {solutions}")

			f = sp.lambdify(x, eq, 'numpy')
			x_vals = np.linspace(-10, 10, 400)
			y_vals = f(x_vals)

			plt.style.use('dark_background')
			fig = plt.figure(figsize=(8, 6))
			ax = fig.add_subplot(111)
			fig.patch.set_facecolor('#252526')
			ax.set_facecolor('#2D2D30')

			ax.plot(x_vals, y_vals, color='#6C8CD5', label=f"y = {equation}")
			for sol in solutions:
				if sol.is_real:
					ax.scatter(float(sol), 0, color='#8EB5FF', zorder=3)

			ax.axhline(0, color='#8EB5FF', linewidth=1, linestyle='--', alpha=0.7)
			ax.axvline(0, color='#8EB5FF', linewidth=1, linestyle='--', alpha=0.7)

			ax.xaxis.label.set_color('#8EB5FF')
			ax.yaxis.label.set_color('#8EB5FF')
			ax.title.set_color('#8EB5FF')
			ax.tick_params(colors='#DCDCDC')
			ax.grid(color='#3F3F46')
			plt.legend()
			plt.title(f'R.A : Graph of {equation}')
			plt.show()

		except Exception as e:
			self.result_display.append(f"Error: {str(e)}")

	def step_by_step_solution(self):
		"""Generate a basic step-by-step solution for the entered equation."""
		equation = self.equation_input.text().strip()
		if not equation:
			self.result_display.append("Error: No equation entered.")
			return

		try:
			x = sp.Symbol('x')
			eq = sp.sympify(equation)
			steps = []
			steps.append("Step 1: Write the equation as f(x) = 0:")
			steps.append(f"    {equation} = 0")

			simplified = sp.simplify(eq)
			steps.append("Step 2: Simplify the equation:")
			steps.append(f"    {simplified}")

			solutions = sp.solve(eq, x)
			steps.append("Step 3: Solve the equation:")
			steps.append(f"    Solutions: {solutions}")

			step_text = "\n".join(steps)
			self.result_display.append(f"Step-by-Step Solution:\n{step_text}")
		except Exception as e:
			self.result_display.append(f"Error in step-by-step solution: {str(e)}")

	def open_circle_solver(self):
		if not self.circle_solver_window:
			self.circle_solver_window = CircleSolverWindow()
		self.circle_solver_window.show()

	def open_3d_plot(self):
		if not self.three_d_plot_window:
			self.three_d_plot_window = ThreeDPlotWindow()
		self.three_d_plot_window.show()

	def open_settings_window(self):
		if not self.settings_window:
			self.settings_window = SettingsWindow()
		self.settings_window.show()


if __name__ == "__main__":
	QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
	app = QApplication(sys.argv)
	app.setFont(QFont("Segoe UI", 12))
	app.setStyleSheet(COMMON_STYLE)
	screen = app.primaryScreen()
	screen_rect = screen.availableGeometry()
	screen_width = screen_rect.width()
	screen_height = screen_rect.height()
	equation_solver = EquationSolver(screen_width, screen_height)
	equation_solver.show()
	sys.exit(app.exec_())
Back to Project