eng-calc / app.py
System Administrator
Add engineering calculator with multi-mode functionality
5f25c87
import gradio as gr
import numpy as np
from typing import Dict, Any, Tuple
import traceback
from calculator.core import (
InfinitePrecisionCalculator,
ScientificCalculator,
ProgrammingCalculator,
ComplexNumber
)
from engineering.impedance import (
PowerSystemImpedanceCalculator,
ConductorParams,
Coordinate,
create_test_data
)
class CalculatorApp:
"""Main calculator application with multiple modes."""
def __init__(self):
self.standard_calc = InfinitePrecisionCalculator()
self.scientific_calc = ScientificCalculator()
self.programming_calc = ProgrammingCalculator()
self.impedance_calc = PowerSystemImpedanceCalculator()
self.current_display = "0"
self.current_operation = None
self.operand = None
self.chat_history = []
def standard_button_click(self, button: str, display: str) -> str:
"""Handle standard calculator button clicks."""
try:
if button == "C":
return "0"
elif button == "CE":
return "0"
elif button == "±":
if display != "0":
return str(-float(display))
return display
elif button == "=":
if self.current_operation and self.operand is not None:
try:
if self.current_operation == "+":
result = self.standard_calc.add(self.operand, float(display))
elif self.current_operation == "-":
result = self.standard_calc.subtract(self.operand, float(display))
elif self.current_operation == "×":
result = self.standard_calc.multiply(self.operand, float(display))
elif self.current_operation == "÷":
result = self.standard_calc.divide(self.operand, float(display))
self.current_operation = None
self.operand = None
return str(result)
except Exception as e:
return f"Error: {str(e)}"
elif button in ["+", "-", "×", "÷"]:
self.current_operation = button
self.operand = float(display)
return "0"
elif button == ".":
if "." not in display:
return display + "."
return display
else: # Number button
if display == "0":
return button
else:
return display + button
except Exception as e:
return f"Error: {str(e)}"
return display
def scientific_calculate(self, function: str, x: str, y: str = None) -> str:
"""Handle scientific calculator functions."""
try:
if not x.strip():
return "Error: Please enter a value"
x_val = float(x)
if function == "sin":
result = self.scientific_calc.sin(x_val)
elif function == "cos":
result = self.scientific_calc.cos(x_val)
elif function == "tan":
result = self.scientific_calc.tan(x_val)
elif function == "asin":
result = self.scientific_calc.asin(x_val)
elif function == "acos":
result = self.scientific_calc.acos(x_val)
elif function == "atan":
result = self.scientific_calc.atan(x_val)
elif function == "csc":
result = self.scientific_calc.csc(x_val)
elif function == "sec":
result = self.scientific_calc.sec(x_val)
elif function == "cot":
result = self.scientific_calc.cot(x_val)
elif function == "log":
if y and y.strip():
result = self.scientific_calc.log(x_val, float(y))
else:
result = self.scientific_calc.log(x_val)
elif function == "ln":
result = self.scientific_calc.log(x_val)
elif function == "sqrt":
result = self.scientific_calc.square_root(x_val)
elif function == "factorial":
result = self.scientific_calc.factorial(int(x_val))
elif function == "power":
if y and y.strip():
result = self.scientific_calc.power(x_val, float(y))
else:
return "Error: Power function requires two values"
else:
return f"Error: Unknown function {function}"
return str(result)
except Exception as e:
return f"Error: {str(e)}"
def programming_calculate(self, operation: str, input_val: str, base_from: str = "decimal", base_to: str = "decimal") -> str:
"""Handle programming calculator operations."""
try:
if operation == "convert":
if base_from == "decimal":
num = int(input_val)
if base_to == "binary":
return self.programming_calc.to_binary(num)
elif base_to == "hexadecimal":
return self.programming_calc.to_hexadecimal(num)
elif base_to == "octal":
return self.programming_calc.to_octal(num)
elif base_from == "binary":
num = self.programming_calc.from_binary(input_val)
if base_to == "decimal":
return str(num)
elif base_to == "hexadecimal":
return self.programming_calc.to_hexadecimal(num)
elif base_to == "octal":
return self.programming_calc.to_octal(num)
elif base_from == "hexadecimal":
num = self.programming_calc.from_hexadecimal(input_val)
if base_to == "decimal":
return str(num)
elif base_to == "binary":
return self.programming_calc.to_binary(num)
elif base_to == "octal":
return self.programming_calc.to_octal(num)
elif base_from == "octal":
num = self.programming_calc.from_octal(input_val)
if base_to == "decimal":
return str(num)
elif base_to == "binary":
return self.programming_calc.to_binary(num)
elif base_to == "hexadecimal":
return self.programming_calc.to_hexadecimal(num)
return "Conversion completed"
except Exception as e:
return f"Error: {str(e)}"
def engineering_calculate(self, function: str, **kwargs) -> str:
"""Handle engineering calculations."""
try:
if function == "3ph_impedance":
return self.calculate_3ph_impedance(**kwargs)
else:
return f"Error: Unknown engineering function {function}"
except Exception as e:
return f"Error: {str(e)}\n{traceback.format_exc()}"
def calculate_3ph_impedance(self, coord_a_x: float, coord_a_y: float,
coord_b_x: float, coord_b_y: float,
coord_c_x: float, coord_c_y: float,
coord_n_x: float, coord_n_y: float,
coord_pe_x: float, coord_pe_y: float,
resist_a: float, gmr_a: float,
resist_b: float, gmr_b: float,
resist_c: float, gmr_c: float,
resist_n: float, gmr_n: float,
resist_pe: float, gmr_pe: float) -> str:
"""Calculate 3-phase impedance from input parameters."""
# Create coordinates
coordinates = {
'a': Coordinate(x=coord_a_x, y=coord_a_y),
'b': Coordinate(x=coord_b_x, y=coord_b_y),
'c': Coordinate(x=coord_c_x, y=coord_c_y),
'n': Coordinate(x=coord_n_x, y=coord_n_y),
'pe': Coordinate(x=coord_pe_x, y=coord_pe_y)
}
# Create conductor parameters
conductor_params = {
'a': ConductorParams(resistance=resist_a, gmr=gmr_a),
'b': ConductorParams(resistance=resist_b, gmr=gmr_b),
'c': ConductorParams(resistance=resist_c, gmr=gmr_c),
'n': ConductorParams(resistance=resist_n, gmr=gmr_n),
'pe': ConductorParams(resistance=resist_pe, gmr=gmr_pe)
}
# Calculate impedance
primitive_matrix, reduced_matrix, distances = self.impedance_calc.calculate_impedance_from_coordinates(
coordinates, conductor_params
)
# Format results
return self.impedance_calc.format_results_for_display(
primitive_matrix, reduced_matrix, distances
)
def load_test_data(self) -> Tuple[float, ...]:
"""Load test data for 3-phase impedance calculation."""
conductor_params, coordinates = create_test_data()
return (
# Coordinates
coordinates['a'].x, coordinates['a'].y,
coordinates['b'].x, coordinates['b'].y,
coordinates['c'].x, coordinates['c'].y,
coordinates['n'].x, coordinates['n'].y,
coordinates['pe'].x, coordinates['pe'].y,
# Conductor parameters
conductor_params['a'].resistance, conductor_params['a'].gmr,
conductor_params['b'].resistance, conductor_params['b'].gmr,
conductor_params['c'].resistance, conductor_params['c'].gmr,
conductor_params['n'].resistance, conductor_params['n'].gmr,
conductor_params['pe'].resistance, conductor_params['pe'].gmr
)
def get_history(self) -> str:
"""Get calculation history."""
history = self.standard_calc.get_history()
if not history:
return "No calculations yet"
return "\n".join(history[-10:]) # Show last 10 calculations
def clear_history(self) -> str:
"""Clear calculation history."""
self.standard_calc.clear_history()
self.scientific_calc.clear_history()
self.programming_calc.clear_history()
return "History cleared"
def chat_with_ai(self, message: str, history: list) -> Tuple[str, list]:
"""Simple AI chat interface (placeholder)."""
# This is a placeholder for AI integration
response = f"I received your message: '{message}'. This is a placeholder for AI chat functionality. In a real implementation, this would connect to an AI service to help with calculations and mathematical questions."
history.append([message, response])
return "", history
def create_interface():
"""Create the Gradio interface."""
app = CalculatorApp()
with gr.Blocks(title="Engineering Calculator", theme=gr.themes.Soft()) as interface:
gr.Markdown("# 🧮 Engineering Calculator")
gr.Markdown("Multi-mode calculator with standard, scientific, engineering, and programming capabilities")
with gr.Tabs():
# Standard Calculator Tab
with gr.Tab("Standard"):
with gr.Row():
with gr.Column(scale=2):
display = gr.Textbox(value="0", label="Display", interactive=False, text_align="right")
with gr.Row():
gr.Button("C", size="sm").click(lambda: app.standard_button_click("C", ""), outputs=display)
gr.Button("CE", size="sm").click(lambda: app.standard_button_click("CE", ""), outputs=display)
gr.Button("±", size="sm").click(lambda x: app.standard_button_click("±", x), inputs=display, outputs=display)
gr.Button("÷", size="sm").click(lambda x: app.standard_button_click("÷", x), inputs=display, outputs=display)
with gr.Row():
gr.Button("7", size="sm").click(lambda x: app.standard_button_click("7", x), inputs=display, outputs=display)
gr.Button("8", size="sm").click(lambda x: app.standard_button_click("8", x), inputs=display, outputs=display)
gr.Button("9", size="sm").click(lambda x: app.standard_button_click("9", x), inputs=display, outputs=display)
gr.Button("×", size="sm").click(lambda x: app.standard_button_click("×", x), inputs=display, outputs=display)
with gr.Row():
gr.Button("4", size="sm").click(lambda x: app.standard_button_click("4", x), inputs=display, outputs=display)
gr.Button("5", size="sm").click(lambda x: app.standard_button_click("5", x), inputs=display, outputs=display)
gr.Button("6", size="sm").click(lambda x: app.standard_button_click("6", x), inputs=display, outputs=display)
gr.Button("-", size="sm").click(lambda x: app.standard_button_click("-", x), inputs=display, outputs=display)
with gr.Row():
gr.Button("1", size="sm").click(lambda x: app.standard_button_click("1", x), inputs=display, outputs=display)
gr.Button("2", size="sm").click(lambda x: app.standard_button_click("2", x), inputs=display, outputs=display)
gr.Button("3", size="sm").click(lambda x: app.standard_button_click("3", x), inputs=display, outputs=display)
gr.Button("+", size="sm").click(lambda x: app.standard_button_click("+", x), inputs=display, outputs=display)
with gr.Row():
gr.Button("0", size="sm").click(lambda x: app.standard_button_click("0", x), inputs=display, outputs=display)
gr.Button(".", size="sm").click(lambda x: app.standard_button_click(".", x), inputs=display, outputs=display)
gr.Button("=", size="sm").click(lambda x: app.standard_button_click("=", x), inputs=display, outputs=display)
with gr.Column(scale=1):
gr.Markdown("### Memory & History")
with gr.Row():
gr.Button("MS").click(lambda x: app.standard_calc.memory_store(x), inputs=display)
gr.Button("MR").click(lambda: str(app.standard_calc.memory_recall()), outputs=display)
gr.Button("MC").click(lambda: app.standard_calc.memory_clear())
history_display = gr.Textbox(label="History", lines=10, interactive=False)
gr.Button("Show History").click(app.get_history, outputs=history_display)
gr.Button("Clear History").click(app.clear_history, outputs=history_display)
# Scientific Calculator Tab
with gr.Tab("Scientific"):
with gr.Row():
with gr.Column():
angle_mode = gr.Radio(["degrees", "radians"], value="degrees", label="Angle Mode")
x_input = gr.Number(label="X Value", value=0)
y_input = gr.Number(label="Y Value (for functions requiring 2 inputs)", value=0)
function_dropdown = gr.Dropdown([
"sin", "cos", "tan", "asin", "acos", "atan",
"csc", "sec", "cot", "log", "ln", "sqrt", "factorial", "power"
], label="Function", value="sin")
calculate_btn = gr.Button("Calculate", variant="primary")
result_display = gr.Textbox(label="Result", interactive=False)
def scientific_wrapper(func, x, y, mode):
app.scientific_calc.set_angle_mode('deg' if mode == 'degrees' else 'rad')
return app.scientific_calculate(func, str(x), str(y) if y != 0 else None)
calculate_btn.click(
scientific_wrapper,
inputs=[function_dropdown, x_input, y_input, angle_mode],
outputs=result_display
)
with gr.Column():
gr.Markdown("### Scientific Functions Reference")
gr.Markdown("""
**Trigonometric Functions:**
- sin, cos, tan: Basic trigonometric functions
- asin, acos, atan: Inverse trigonometric functions
- csc, sec, cot: Reciprocal trigonometric functions
**Other Functions:**
- log: Logarithm (base 10 or custom base if Y value provided)
- ln: Natural logarithm (base e)
- sqrt: Square root
- factorial: Factorial (integers only)
- power: X raised to the power of Y
""")
# Programming Calculator Tab
with gr.Tab("Programming"):
with gr.Row():
with gr.Column():
gr.Markdown("### Base Conversion")
input_value = gr.Textbox(label="Input Value", placeholder="Enter number to convert")
base_from = gr.Dropdown(["decimal", "binary", "hexadecimal", "octal"],
label="From Base", value="decimal")
base_to = gr.Dropdown(["decimal", "binary", "hexadecimal", "octal"],
label="To Base", value="binary")
convert_btn = gr.Button("Convert", variant="primary")
conversion_result = gr.Textbox(label="Result", interactive=False)
convert_btn.click(
lambda val, from_base, to_base: app.programming_calculate("convert", val, from_base, to_base),
inputs=[input_value, base_from, base_to],
outputs=conversion_result
)
with gr.Column():
gr.Markdown("### Bitwise Operations (Coming Soon)")
gr.Markdown("""
**Available Conversions:**
- Decimal ↔ Binary
- Decimal ↔ Hexadecimal
- Decimal ↔ Octal
- Cross conversions between all bases
**Input Formats:**
- Decimal: Regular numbers (123)
- Binary: Binary string (1010)
- Hexadecimal: Hex string (FF or 0xFF)
- Octal: Octal string (755 or 0o755)
""")
# Engineering Calculator Tab
with gr.Tab("Engineering"):
with gr.Row():
with gr.Column():
engineering_function = gr.Dropdown(
["3ph_impedance"],
label="Engineering Function",
value="3ph_impedance"
)
gr.Markdown("### 3-Phase Impedance Calculator")
gr.Markdown("Calculate equivalent impedance for 5-wire (A,B,C,N,PE) power systems")
with gr.Accordion("Conductor Coordinates (feet)", open=True):
with gr.Row():
coord_a_x = gr.Number(label="Phase A - X", value=0)
coord_a_y = gr.Number(label="Phase A - Y", value=42)
with gr.Row():
coord_b_x = gr.Number(label="Phase B - X", value=23.5)
coord_b_y = gr.Number(label="Phase B - Y", value=42)
with gr.Row():
coord_c_x = gr.Number(label="Phase C - X", value=47)
coord_c_y = gr.Number(label="Phase C - Y", value=42)
with gr.Row():
coord_n_x = gr.Number(label="Neutral - X", value=10)
coord_n_y = gr.Number(label="Neutral - Y", value=74)
with gr.Row():
coord_pe_x = gr.Number(label="PE - X", value=37)
coord_pe_y = gr.Number(label="PE - Y", value=72)
with gr.Accordion("Conductor Parameters", open=True):
with gr.Row():
resist_a = gr.Number(label="Phase A Resistance (Ω/mile)", value=0.055)
gmr_a = gr.Number(label="Phase A GMR (feet)", value=0.038)
with gr.Row():
resist_b = gr.Number(label="Phase B Resistance (Ω/mile)", value=0.055)
gmr_b = gr.Number(label="Phase B GMR (feet)", value=0.038)
with gr.Row():
resist_c = gr.Number(label="Phase C Resistance (Ω/mile)", value=0.055)
gmr_c = gr.Number(label="Phase C GMR (feet)", value=0.038)
with gr.Row():
resist_n = gr.Number(label="Neutral Resistance (Ω/mile)", value=8.0)
gmr_n = gr.Number(label="Neutral GMR (feet)", value=0.012)
with gr.Row():
resist_pe = gr.Number(label="PE Resistance (Ω/mile)", value=8.0)
gmr_pe = gr.Number(label="PE GMR (feet)", value=0.012)
with gr.Row():
load_test_btn = gr.Button("Load Test Data")
calculate_imp_btn = gr.Button("Calculate Impedance", variant="primary")
engineering_result = gr.Textbox(label="Results", lines=20, interactive=False)
# Load test data functionality
test_data_outputs = [
coord_a_x, coord_a_y, coord_b_x, coord_b_y, coord_c_x, coord_c_y,
coord_n_x, coord_n_y, coord_pe_x, coord_pe_y,
resist_a, gmr_a, resist_b, gmr_b, resist_c, gmr_c,
resist_n, gmr_n, resist_pe, gmr_pe
]
load_test_btn.click(app.load_test_data, outputs=test_data_outputs)
# Calculate impedance functionality
impedance_inputs = [
coord_a_x, coord_a_y, coord_b_x, coord_b_y, coord_c_x, coord_c_y,
coord_n_x, coord_n_y, coord_pe_x, coord_pe_y,
resist_a, gmr_a, resist_b, gmr_b, resist_c, gmr_c,
resist_n, gmr_n, resist_pe, gmr_pe
]
calculate_imp_btn.click(
app.calculate_3ph_impedance,
inputs=impedance_inputs,
outputs=engineering_result
)
# AI Chat Tab
with gr.Tab("AI Assistant"):
gr.Markdown("### Calculator AI Assistant")
gr.Markdown("Get help with calculations, mathematical concepts, and engineering problems")
chatbot = gr.Chatbot(label="Chat History", height=400)
msg = gr.Textbox(label="Your Message", placeholder="Ask me about calculations or math...")
def chat_fn(message, history):
return app.chat_with_ai(message, history)
msg.submit(chat_fn, inputs=[msg, chatbot], outputs=[msg, chatbot])
gr.Markdown("""
**Note:** This is a placeholder for AI integration. In a full implementation,
this would connect to an AI service to provide:
- Help with mathematical concepts
- Step-by-step calculation guidance
- Engineering problem assistance
- Formula explanations
""")
return interface
if __name__ == "__main__":
interface = create_interface()
interface.launch(server_name="0.0.0.0", server_port=7860, share=False)