Skip to content

Commit

Permalink
separated wolfram call from internal computation
Browse files Browse the repository at this point in the history
  • Loading branch information
krachwal committed Jan 3, 2024
1 parent 4053d73 commit 50cadcf
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 32 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ To run the web portal using the Dockerfile:

`docker build . -t ramanujan-machine-web-portal:latest`

`docker run -p 8080:3000 ramanujan-machine-web-portal:latest`
`docker run -p 8080:5137 ramanujan-machine-web-portal:latest`

Note that the first port is the port you can access via your web browser, e.g. `http://localhost:8080` is where you would be able to interact with the app given the above configuration. You can change `8080` to whatever port you wish, but the application runs on port `3000` inside the container.
Note that the first port is the port you can access via your web browser, e.g. `http://localhost:8080` is where you would be able to interact with the app given the above configuration. You can change `8080` to whatever port you wish, but the application runs on port `5137` inside the container.

## Run with Docker
Refer to [React Frontend README](./react-frontend/README.md) to run locally.
8 changes: 4 additions & 4 deletions python-backend/graph_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def error_coordinates(expression: sympy.core, symbol: Symbol, limit: sympy.core.
:param limit: the computed limit of the expression as it goes to infinity
:return: array of [x,y] pairs for graphing purposes
"""
return [[val, float(math_utils.error(expression, symbol, limit, val))] for val in X_VALUES if
type(math_utils.error(expression, symbol, limit, val)) in GRAPHABLE_TYPES]
y_values = [math_utils.error(expression, symbol, limit, val) for val in X_VALUES]
return [[x, float(y)] for x, y in zip(X_VALUES, y_values) if type(y) in GRAPHABLE_TYPES]


def delta_coordinates(expression: sympy.core,
Expand All @@ -33,5 +33,5 @@ def delta_coordinates(expression: sympy.core,
:return: array of [x,y] pairs for graphing purposes
"""
# graph coords of error delta: -1 * (log(|Pn/Qn - L|) / log(Qn)) - 1
return [[val, -1 * float(math_utils.delta(expression, denominator, symbol, limit, val))] for val in X_VALUES if
type(-1 * float(math_utils.delta(expression, denominator, symbol, limit, val))) in GRAPHABLE_TYPES]
y_values = [-1 * math_utils.delta(expression, denominator, symbol, limit, val) for val in X_VALUES]
return [[x, float(y)] for x, y in zip(X_VALUES, y_values) if type(y) in GRAPHABLE_TYPES]
14 changes: 13 additions & 1 deletion python-backend/input.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
""" Processing of the post body from the frontend """
import logging
import re

from pydantic import BaseModel

logger = logging.getLogger('rm_web_app')


class Input(BaseModel):
"""Structure of user form data"""
Expand All @@ -18,4 +21,13 @@ def convert(polynomial: str) -> str:
:param polynomial: incoming polynomial entered by user in web frontend
:return: python parse-able polynomial
"""
return re.sub(r'([0-9])+([a-zA-Z])', '\\1*\\2', polynomial.replace('^', '**'))
expression = re.sub(r'([0-9]+)([a-zA-Z])', '\\1*\\2',
polynomial.replace('^', '**').replace(' ', '').replace(')(', ')*('))
expression = re.sub(r'([0-9a-zA-Z])(\()', '\\1*\\2', expression)
logger.debug(f"input: {polynomial} output: {expression}")
return expression


class Expression(BaseModel):
"""Structure of user form data"""
expression: str
8 changes: 4 additions & 4 deletions python-backend/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

FORMAT = (
'%(asctime)s %(levelname)s %(module)s:%(filename)s:%(lineno)d Function: %(funcName)s Exception: %(exc_info)s %('
'message)s\nStack: %(stack_info)s')
'message)s')


def config() -> logging.Logger:
def config(local: bool = False) -> logging.Logger:
"""
Set up a file logger and a console logger
:return: configured logger for the web server
Expand All @@ -15,11 +15,11 @@ def config() -> logging.Logger:
logger.setLevel(logging.DEBUG)

fh = logging.FileHandler('rm_web_app.log')
fh.setLevel(logging.WARN)
fh.setLevel(logging.DEBUG if local else logging.WARN)
fh.setFormatter(logging.Formatter(fmt=FORMAT))

ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
ch.setLevel(logging.DEBUG if local else logging.ERROR)
ch.setFormatter(logging.Formatter(fmt=FORMAT))

logger.addHandler(fh)
Expand Down
29 changes: 15 additions & 14 deletions python-backend/main.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
"""Entrypoint for the application and REST API handlers"""
import json
import uuid
from typing import Tuple

import mpmath
import sympy
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sympy import simplify, sympify, oo, Symbol, Union, Add, Mul, Float
from sympy import simplify, sympify, oo, Symbol, Mul, Add
from sympy.core.numbers import Infinity

import constants
import logger
from graph_utils import error_coordinates, delta_coordinates
from input import Input, convert
from input import Input, convert, Expression
from wolfram_client import WolframClient

app = FastAPI()
Expand All @@ -31,7 +33,7 @@
allow_headers=["*"])


def parse(data: Input) -> ( Union<int, float, Float, Add, Mul>, Union<int, float, Float, Add, Mul>, Symbol):
def parse(data: Input) -> Tuple[Mul, Add, Symbol]:
"""
Process user inputs into math expressions
:param data: User form inputs
Expand All @@ -49,6 +51,8 @@ def parse(data: Input) -> ( Union<int, float, Float, Add, Mul>, Union<int, float
@app.post("/analyze")
async def analyze(request: Request):
"""
Take user form inputs and parse them into mathematical expressions, then assess them in various ways and return
chart coordinates to be rendered by the frontend
:param request: HTTP request
:return: HTTP response indicating success of parsing inputs with a 200 or a 500 to indicate failure parsing inputs
"""
Expand All @@ -58,17 +62,16 @@ async def analyze(request: Request):
data = Input(**(await request.json()))
(expression, denominator, symbol) = parse(data)

q_limit = sympy.limit(denominator, symbol, oo)
limit = sympy.limit(expression, symbol, oo)

body = {
"limit": json.dumps(float(limit)),
"denominator_limit": json.dumps(float(q_limit)),
"expression": json.dumps(str(expression)),
"limit": json.dumps("Infinity" if type(limit) is Infinity else str(limit)),
"log_error": json.dumps(error_coordinates(expression, symbol, limit)),
"delta": json.dumps(delta_coordinates(expression, denominator, symbol, limit)),
"computed_value": json.dumps(float(limit)) # @TODO: replace with actual computation to i
"computed_value": 0 # @TODO: replace with actual computation to i
}

logger.debug(f"Response: {body}")
response = JSONResponse(content=body)
response.set_cookie(key="trm", value=str(uuid.uuid4()))
return response
Expand All @@ -82,21 +85,19 @@ async def analyze(request: Request):
@app.post("/verify")
async def analyze(request: Request):
"""
Take sanitized user expression and see what Wolfram Alpha has to say about it, returning the results to the frontend
:param request: HTTP request
:return: HTTP response indicating success of parsing inputs with a 200 or a 500 to indicate failure parsing inputs
"""
# parse posted body as Input
# parse posted body as Expression

try:
data = Input(**(await request.json()))
(expression, _, _) = parse(data)

expression = Expression(**(await request.json()))
body = {
"wolfram_limit": WolframClient.limit(str(expression))
"wolfram_says": WolframClient.raw(expression.expression.replace('**', '^'))
}

response = JSONResponse(content=body)
response.set_cookie(key="trm", value=str(uuid.uuid4()))
return response

except Exception as e:
Expand Down
6 changes: 5 additions & 1 deletion python-backend/math_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
"""Utility functions that perform mathematical operations with preset parameters"""
import logging

import sympy
from sympy import Symbol

import constants

PRECISION = constants.PRECISION

logger = logging.getLogger('rm_web_app')


def error(expression: sympy.core, symbol: Symbol, limit: sympy.core.numbers, val: int) -> sympy.core.numbers:
"""
Expand Down Expand Up @@ -44,4 +48,4 @@ def delta(expression: sympy.core,
:param val: the x value and the value to substitute in for the symbol in the expression
:return: the delta or y coordinate at the val provided
"""
return error(expression, symbol, limit, val) / log(denominator, symbol, val) - 1
return -1 * error(expression, symbol, limit, val) / log(denominator, symbol, val) - 1
5 changes: 4 additions & 1 deletion python-backend/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
CONVERSION_3 = "4*x**2+3*x**5-1"
SYMPY_3 = "3*x**5+4*x**2-1"
TEST_INPUT_4 = "4x^2 + 3x^5 - 1"
CONVERSION_4 = "4*x**2 + 3*x**5 - 1"
CONVERSION_4 = "4*x**2+3*x**5-1"
SYMPY_4 = "3*x**5+4*x**2-1"
TEST_INPUT_5 = "2x^2"
TEST_INPUT_6 = "x"
Expand All @@ -29,6 +29,8 @@
SYMBOL = Symbol('x', real=True)
P = "4*x**2"
Q = "5*x**3-6*x**2+2"
TEST_INPUT_9 = "(1 + 2 n) (5 + 17 n (1 + n))"
CONVERSION_10 = "(1+2*n)*(5+17*n*(1+n))"


def test_convert() -> None:
Expand All @@ -37,6 +39,7 @@ def test_convert() -> None:
assert convert(TEST_INPUT_2) == CONVERSION_2
assert convert(TEST_INPUT_3) == CONVERSION_3
assert convert(TEST_INPUT_4) == CONVERSION_4
assert convert(TEST_INPUT_9) == CONVERSION_10


def test_sympify() -> None:
Expand Down
15 changes: 14 additions & 1 deletion python-backend/wolfram_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,24 @@ def limit(expression: str) -> str:
try:
result = WolframClient.ask(query="limit of {}".format(expression), include_pod="Limit")
# only want to return: queryresult -> pods[0] -> subpods

return result["queryresult"]["pods"][0]["subpods"]
except Exception as e:
logger.error("Failed to obtain Wolfram API result", e)

@staticmethod
def raw(expression: str) -> str:
"""
Query the Wolfram results API with a string expression
:param expression: Mathematical expression for which we would like to get more information
:return: Wolfram API response
"""
try:
result = WolframClient.ask(query=f"{expression}")
# just pass the result through
return result["queryresult"]
except Exception as e:
logger.error("Failed to obtain Wolfram API result", e)

@staticmethod
def continued_fraction(expression: str) -> str:
"""
Expand Down
2 changes: 1 addition & 1 deletion react-frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Ensure that you have all project dependencies installed using `npm ci` or `npm i
#### `npm start`

Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
Open [http://localhost:5137](http://localhost:5137) to view it in the browser.

#### `npx cypress run`

Expand Down
16 changes: 15 additions & 1 deletion react-frontend/src/components/Charts.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import axios from 'axios';
import React from 'react';
import { CategoryScale, Chart, Legend, LinearScale, LineElement, PointElement } from 'chart.js';
import { Line } from 'react-chartjs-2';
Expand Down Expand Up @@ -30,8 +31,10 @@ const labels: { [key: string]: string } = {
};

Chart.register(CategoryScale, Legend, LinearScale, LineElement, PointElement);

function Charts({ results = {}, toggleDisplay }: ChartProps) {
const computePairs = (dataset: string) => {
const computePairs = (dataset: string) => {
checkResult();
return {
labels: range(0, 1000, 25),
datasets: [
Expand All @@ -45,6 +48,17 @@ function Charts({ results = {}, toggleDisplay }: ChartProps) {
]
};
};
const checkResult = function() {
axios.post('http://localhost:8000/verify', {expression: results.expression})
.then((response) => {
if (response.status == 200) {
console.log('success');
} else {
console.warn(response.data.error);
}
})
.catch((error) => console.log(error));
}
return (
<div className="chart-container">
<p>This is the value of the Polynomial Continued Fraction: {results.computed_value}</p>
Expand Down
1 change: 0 additions & 1 deletion react-frontend/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ function Form() {
symbol: polynomialA.match(/([a-zA-Z])/)?.[0],
i: iterationCount
};
console.log(body);
axios
.post('http://localhost:8000/analyze', body)
.then((response) => {
Expand Down
2 changes: 1 addition & 1 deletion react-frontend/src/components/PolynomialInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ function PolynomialInput({

const sanitize = function (input: string) {
// replace all characters that are not valid math expression characters
return input.replaceAll(/[^^()a-zA-Z0-9*./ +-]*/g, '');
return input.replaceAll(/[^^()a-zA-Z0-9* ./+-]+/g, '');
};

const onlyOneSymbol = function (input: string) {
Expand Down

0 comments on commit 50cadcf

Please sign in to comment.