diff --git a/lirec-grpc-server/server.py b/lirec-grpc-server/server.py index 6c6304b..fd5cbab 100644 --- a/lirec-grpc-server/server.py +++ b/lirec-grpc-server/server.py @@ -1,7 +1,7 @@ from concurrent import futures import grpc -from LIReC.db.access import db +from LIReC.db.access import db, PolyPSLQRelation import constants import lirec_pb2 @@ -14,10 +14,12 @@ class LIReCServicer(lirec_pb2_grpc.LIReCServicer): def Identify(self, request: lirec_pb2.IdentifyRequest, context: object) -> lirec_pb2.IdentifyResponse: logger.debug(f"Received request: <{type(request)}> {request}") - closed_forms = db.identify(values=[request.limit], wide_search=[1], min_prec=constants.DEFAULT_PRECISION) - logger.debug(f"Received response: <{type(closed_forms)}> {[str(item) for item in closed_forms]}") - return lirec_pb2.IdentifyResponse(closed_forms=[str(item) for item in closed_forms]) - + results = db.identify(values=[request.limit], wide_search=[1], min_prec=constants.DEFAULT_PRECISION, see_also=True) + logger.debug(f"Received response: <{type(results)}> {[str(item) for item in results]}") + if len(results) > 0 and type(results[0]) is list: + return lirec_pb2.IdentifyResponse(closed_forms=[str(item) for item in results[0]], see_also=[str(item) for item in results[1]]) + else: + return lirec_pb2.IdentifyResponse(closed_forms=[str(item) for item in results]) def serve() -> None: """ diff --git a/protos/lirec.proto b/protos/lirec.proto index ad4e5e6..855cd56 100644 --- a/protos/lirec.proto +++ b/protos/lirec.proto @@ -7,7 +7,6 @@ option objc_class_prefix = "HLW"; package lirec_rpc; service LIReC { - // Sends a greeting rpc Identify(IdentifyRequest) returns (IdentifyResponse) {} } @@ -17,4 +16,5 @@ message IdentifyRequest { message IdentifyResponse { repeated string closed_forms = 1; + repeated string see_also = 2; } \ No newline at end of file diff --git a/python-backend/call_wrapper.py b/python-backend/call_wrapper.py index 49a3572..ebbc694 100644 --- a/python-backend/call_wrapper.py +++ b/python-backend/call_wrapper.py @@ -40,7 +40,7 @@ def pcf_limit(a, b, n) -> str: signal.alarm(0) -def lirec_identify(limit) -> list[sympy.core.numbers.Number]: +def lirec_identify(limit) -> [list[sympy.core.numbers.Number], list[sympy.core.numbers.Number]]: """ Invokes LIReC pslq algorithm """ @@ -49,7 +49,7 @@ def lirec_identify(limit) -> list[sympy.core.numbers.Number]: stub = lirec_pb2_grpc.LIReCStub(channel) request = lirec_pb2.IdentifyRequest(limit=limit) response = stub.Identify(request) - return response.closed_forms + return [response.closed_forms, response.see_also] except TimeoutError: logger.error("Function execution timed out after {} seconds".format(EXTERNAL_PROCESS_TIMEOUT)) finally: diff --git a/python-backend/constants.py b/python-backend/constants.py index 4b4228f..8b90a90 100644 --- a/python-backend/constants.py +++ b/python-backend/constants.py @@ -5,6 +5,6 @@ # evalf allows for verbose output via param universally set to this: VERBOSE_EVAL = False # when calling ResearchTools, only way this many seconds before throwing a timeout exception -EXTERNAL_PROCESS_TIMEOUT = 10 +EXTERNAL_PROCESS_TIMEOUT = 30 # the free Wolfram API limits queries to 200 characters WOLFRAM_CHAR_LIMIT = 200 diff --git a/python-backend/main.py b/python-backend/main.py index 2cca3ba..49f77ce 100644 --- a/python-backend/main.py +++ b/python-backend/main.py @@ -138,7 +138,8 @@ async def data_socket(websocket: WebSocket): logger.debug(f"limit: {limit}") await websocket.send_json({"limit": "Infinity" if type(limit) is Infinity else str(limit)}) - computed_values: list[str] = call_wrapper.lirec_identify(limit) + [computed_values, see_also] = call_wrapper.lirec_identify(limit) + json_computed_values = [] for m in computed_values: logger.debug(f"identify returned: {m}") @@ -148,6 +149,15 @@ async def data_socket(websocket: WebSocket): {"converges_to": json.dumps(json_computed_values)} ) + json_see_also = [] + for m in see_also: + logger.debug(f"identify returned see_also: {m}") + json_see_also.append(str(m)) + + await websocket.send_json( + {"see_also": json.dumps(json_see_also)} + ) + await chart_coordinates(pcf=pcf.PCF(sympify(data.a), sympify(data.b)), limit=mpmath.mpf(limit), iterations=iterations, diff --git a/react-frontend/src/App.css b/react-frontend/src/App.css index d72dd0b..e125101 100644 --- a/react-frontend/src/App.css +++ b/react-frontend/src/App.css @@ -268,7 +268,7 @@ div.chart-container { padding: 1.2rem; text-align: left; font-size: 1.3rem; - width: 600px; + width: 900px; margin-left: auto; margin-right: auto; border-radius: var(--radius); @@ -291,6 +291,11 @@ div.closed-form { margin: 0.5rem; background-color: var(--muted-light); border-radius: var(--radius); + display: flex; + flex-wrap: wrap; + & span { + align-self: center; + } } p.footnote { font-size: 0.8rem; @@ -380,6 +385,9 @@ div.flex-child { flex: 1; align-self: baseline; } +div.align-self-center { + align-self: center; +} .metadata { font-size: 0.8rem; font-weight: normal; diff --git a/react-frontend/src/components/Charts.tsx b/react-frontend/src/components/Charts.tsx index d129432..02a8569 100644 --- a/react-frontend/src/components/Charts.tsx +++ b/react-frontend/src/components/Charts.tsx @@ -12,6 +12,7 @@ interface ChartProps { limit?: string; symbol: string; convergesTo?: string[]; + seeAlso?: string[]; deltaData?: CoordinatePair[]; toggleDisplay: () => void; } @@ -41,12 +42,14 @@ type ConstantMetadataWrapper = { [key: string]: ConstantMetadata; }; -function Charts({ a_n, b_n, limit, symbol, convergesTo, deltaData, toggleDisplay }: ChartProps) { +function Charts({ a_n, b_n, limit, symbol, convergesTo, seeAlso, deltaData, toggleDisplay }: ChartProps) { const [wolframResults, setWolframResults] = useState(); const [constantMetadata, setConstantMetadata] = useState>({}); const [lirecClosedForm, setLirecClosedForm] = useState(); + const [seeAlsoClosedForm, setSeeAlsoClosedForm] = useState(); const [pcf, setPcf] = useState(''); + // MathJax config const config = { tex: { inlineMath: [['$', '$']], @@ -58,9 +61,15 @@ function Charts({ a_n, b_n, limit, symbol, convergesTo, deltaData, toggleDisplay if (limit) verify(); }, [limit]); + useEffect(() => { + if (Array.isArray(seeAlso) && seeAlso.length > 0) { + setSeeAlsoClosedForm(restructureSeeAlso(seeAlso)); + } + }, [seeAlso]); + useEffect(() => { if (Array.isArray(convergesTo) && convergesTo.length > 0) { - setLirecClosedForm(replaceLirecChars()); + setLirecClosedForm(replaceLirecChars(convergesTo)); } }, [convergesTo]); @@ -77,42 +86,71 @@ function Charts({ a_n, b_n, limit, symbol, convergesTo, deltaData, toggleDisplay } }; - useEffect(() => { - let [a0, a1, a2, a3] = [a_n, a_n, a_n, a_n]; - let [b1, b2, b3] = [b_n, b_n, b_n]; - if (symbol) { - if (a_n.indexOf(symbol) >= 0) { - a0 = parse(a_n.replaceAll(symbol, '0').replaceAll('**','^')).evaluate(); - a1 = parse(a_n.replaceAll(symbol, '1').replaceAll('**','^')).evaluate(); - a2 = parse(a_n.replaceAll(symbol, '2').replaceAll('**','^')).evaluate(); - a3 = parse(a_n.replaceAll(symbol, '3').replaceAll('**','^')).evaluate(); - } - if (b_n.indexOf(symbol) >= 0) { - b1 = parse(b_n.replaceAll(symbol, '1').replaceAll('**','^')).evaluate(); - b2 = parse(b_n.replaceAll(symbol, '2').replaceAll('**','^')).evaluate(); - b3 = parse(b_n.replaceAll(symbol, '3').replaceAll('**','^')).evaluate(); - } - } - let parsed = parse(`${a0} + (${b1} / (${a1} + (${b2} / (${a2} + (${b3} / (${a3} + dots))))))`); + let formatPcf = function(_a_n: string, _b_n:string) { + // preset to input value + console.log('format inputs' ,_a_n, _b_n); + let a = _a_n.replaceAll('**','^'); + console.log('a', a); + let a_parsed = parse(a); + console.log('a parsed', a_parsed); + let b = _b_n.replaceAll('**','^'); + console.log('b', b); + let b_parsed = parse(b); + console.log('b parsed', b_parsed); + let [a0, a1, a2] = [a_parsed.evaluate({n: 0}), a_parsed.evaluate({n: 1}), a_parsed.evaluate({n: 2})]; + console.log('a0',a0, 'a1',a1,'a2',a2); + let [b1_eval, b2_eval, b3_eval] = [b_parsed.evaluate({n: 1}),b_parsed.evaluate({n: 2}),b_parsed.evaluate({n: 3})]; + let [,b1_sign, b2_sign, b3_sign] = [undefined, b1_eval > 0 ? '+': '-',b2_eval > 0 ? '+': '-',b3_eval > 0 ? '+': '-']; + console.log('b signs', b1_sign, b2_sign, b3_sign); + let parsed = parse(`${a0} ${b1_sign} (${b1_eval} / (${a1} ${b2_sign} (${b2_eval} / (${a2} ${b3_sign} (${b3_eval} / (dots + ${b} / (${a} + dots)))))))`); + console.log('parsed', parsed); let mathy = parsed.toTex({ parenthesis: 'auto' }); // this is a hack because mathjs chokes on the dots so we put them in after the expression is Texed - setPcf(`$$${mathy.replaceAll('dots', '...')}$$`); + return `$$${mathy.replaceAll('dots', '...')}$$`; + } + + useEffect(() => { + setPcf(formatPcf(a_n, b_n)); }, [a_n, b_n, symbol]); - const replaceLirecChars = () => { + const restructureSeeAlso = (input: string[]) => { + let result = new Array(); + for (const value of input!!) { + console.log('input',value); + const cleanString = value + .replaceAll('PCF[','') + .replaceAll('] =', '=') + .replaceAll('**', '^') + .replace(/\s\(-?[0-9]+\)$/, ''); + console.log('clean', cleanString); + const input = convertLirecConstants(cleanString); + console.log('converted', input); + let [pcf_a, remnant] = input.split(','); + console.log('pcf_a', pcf_a, 'remnant', remnant); + let [pcf_b, exp] = remnant.split('='); + console.log('pcf_b', pcf_b, 'exp', exp); + console.log('wrapped pcf', formatPcf(pcf_a, pcf_b)); + console.log('wrapped exp', wrapExpression(exp)); + result.push([wrapExpression(exp), formatPcf(pcf_a, pcf_b)]); + } + return result; + }; + + const replaceLirecChars = (input: string[]) => { // we are replacing the exponent operator from python to js syntax // we are also stripping the parentheses at the end of the expression returned from identify let result = new Array(); - for (const value of convergesTo!!) { + for (const value of input!!) { const cleanString = value .replaceAll('**', '^') .replace(' = 0', '') - .replace(/\s\([0-9]+\)$/, ''); + .replace(/\s\(-?[0-9]+\)$/, ''); const input = convertLirecConstants(cleanString); result.push(wrapExpression(input)); } return result; }; + // e.g. '(20*alpha_GW - 34)/(alpha_GW + 9)' // should convert to '(20*α[GW] - 34)/(α[GW] + 9)' const convertLirecConstants = (input: string) => { @@ -196,8 +234,7 @@ function Charts({ a_n, b_n, limit, symbol, convergesTo, deltaData, toggleDisplay const verify = () => { if (limit) { - axios - .post('/verify', { expression: limit }) + axios.post('/verify', { expression: limit }) .then((response) => { if (response.status != 200) { console.warn(response.data.error); @@ -336,6 +373,38 @@ function Charts({ a_n, b_n, limit, symbol, convergesTo, deltaData, toggleDisplay ) : ( '' )} + {seeAlso ? ( +
+

See also

+
+

+ + Results from{' '} + + LIReC + + +

+
+
+ {seeAlsoClosedForm?.map((pcf: string[]) => + (
+ + {pcf[0]} + + = + + {pcf[1]} + +
) + )} +
+
+ ) : ( + '' + )} {deltaData && deltaData?.length > 0 ? (

diff --git a/react-frontend/src/components/Form.tsx b/react-frontend/src/components/Form.tsx index 1c7f380..252a88b 100644 --- a/react-frontend/src/components/Form.tsx +++ b/react-frontend/src/components/Form.tsx @@ -21,6 +21,7 @@ function Form() { const [noConvergence, setNoConvergence] = useState(false); const [waitingForResponse, setWaitingForResponse] = useState(false); const [convergesTo, setConvergesTo] = useState([]); + const [seeAlso, setSeeAlso] = useState([]); const [limit, setLimit] = useState(''); const [deltaData, setDeltaData] = useState([]); @@ -29,6 +30,7 @@ function Form() { }, []); const resetState = function () { + setSeeAlso([]); setConvergesTo([]); setLimit(''); setDeltaData([]); @@ -113,6 +115,8 @@ function Form() { } } else if (Object.hasOwn(message, 'converges_to')) { setConvergesTo(JSON.parse(message.converges_to)); + } else if (Object.hasOwn(message, 'see_also')) { + setSeeAlso(JSON.parse(message.see_also)); } else if (Object.hasOwn(message, 'delta')) { const incomingDeltaData = JSON.parse(message.delta); if (incomingDeltaData.length > 0) { @@ -193,6 +197,7 @@ function Form() { limit={limit} symbol={isolateSymbol()} convergesTo={convergesTo} + seeAlso={seeAlso} deltaData={deltaData} toggleDisplay={() => { setNoConvergence(false); diff --git a/react-frontend/src/components/ScatterPlot.tsx b/react-frontend/src/components/ScatterPlot.tsx index c2c9663..7342981 100644 --- a/react-frontend/src/components/ScatterPlot.tsx +++ b/react-frontend/src/components/ScatterPlot.tsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import * as d3 from 'd3'; import { CoordinatePair } from '../lib/types'; -const svg_width = 560, +const svg_width = 860, svg_height = 400; const ScatterPlot = ({ id, data }: { id: string; data?: CoordinatePair[] }) => { diff --git a/react-frontend/src/lib/constants.ts b/react-frontend/src/lib/constants.ts index 1f327c4..ef38757 100644 --- a/react-frontend/src/lib/constants.ts +++ b/react-frontend/src/lib/constants.ts @@ -334,6 +334,11 @@ const constants: { [abbrev: string]: Constant } = { url: 'https://mathworld.wolfram.com/WeierstrassConstant.html' }, W: { name: 'Wallis Constant', url: 'https://mathworld.wolfram.com/WallissConstant.html' }, + Zeta2: { + replacement: 'ζ(2)', + name: 'Riemann Zeta Function', + url: 'https://en.wikipedia.org/wiki/Riemann_zeta_function' + }, Zeta3: { replacement: 'ζ(3)', name: 'Apery Constant',