Source Code - Python Implementation
Language: Python 3.7+
Dependencies: PyQt5, SymPy, Matplotlib, NumPy
Description: Complete implementation of the R.A Equation Solver application
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_())