From 8b522afcbaca4b42979505dce9557d1dbbe631e3 Mon Sep 17 00:00:00 2001 From: 28raining Date: Tue, 9 Dec 2025 21:39:13 -0800 Subject: [PATCH 01/13] Remove Algebrite solver and related code; integrate SymPy as the sole algebra solver. --- package.json | 2 - src/ChoseTF.jsx | 137 +++++++++++++++------------------------- src/ReleaseNotes.jsx | 13 ++++ src/new_solveMNA.js | 60 ++---------------- src/pyodideLoader.js | 2 +- src/simplify_algebra.js | 23 ------- tests/circuit.test.js | 44 ++----------- toDo.md | 5 ++ 8 files changed, 80 insertions(+), 206 deletions(-) delete mode 100644 src/simplify_algebra.js diff --git a/package.json b/package.json index d055c64..5a05983 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,6 @@ "@hyvor/hyvor-talk-react": "^1.0.2", "@mui/icons-material": "^7.3.1", "@mui/material": "^7.3.1", - "@yaffle/expression": "^0.0.47", - "algebrite": "^1.4.0", "bootstrap": "^5.3.8", "echarts": "^5.6.0", "echarts-for-react": "^3.0.2", diff --git a/src/ChoseTF.jsx b/src/ChoseTF.jsx index d719d26..633baab 100644 --- a/src/ChoseTF.jsx +++ b/src/ChoseTF.jsx @@ -1,36 +1,44 @@ import Button from "@mui/material/Button"; -import Stack from "@mui/material/Stack"; -import ToggleButton from "@mui/material/ToggleButton"; -import ToggleButtonGroup from "@mui/material/ToggleButtonGroup"; import Box from "@mui/material/Box"; import Grid from "@mui/material/Grid"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { build_and_solve_mna } from "./new_solveMNA.js"; -import { styled } from "@mui/material/styles"; -import Tooltip, { tooltipClasses } from "@mui/material/Tooltip"; import CircularProgress from "@mui/material/CircularProgress"; import { initPyodideAndSympy } from "./pyodideLoader"; import { emptyResults } from "./common.js"; // Import the emptyResults object -const HtmlTooltip = styled(({ className, ...props }) => )(({ theme }) => ({ - [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: "#f5f5f9", - color: "rgba(0, 0, 0, 0.87)", - maxWidth: 220, - fontSize: theme.typography.pxToRem(12), - border: "1px solid #dadde9", - }, -})); - function formatMathML(mathml, p, drivers) { return `${p}${drivers[0] == "vin" ? "V" : "I"}in=${mathml}`; } export function ChoseTF({ setResults, nodes, fullyConnectedComponents, componentValuesSolved, setUnsolveSnackbar }) { - const [algebraic, setAlgebraic] = useState("algebrite"); const [loading, setLoading] = useState(false); + const [calculating, setCalculating] = useState(false); const [loadedPyo, setLoadedPyo] = useState(null); - // const algebraic = "algebraic"; + + // Auto-initialize Pyodide on component mount + useEffect(() => { + let isMounted = true; + const initializePyodide = async () => { + setLoading(true); + try { + const pyodide = await initPyodideAndSympy(); + if (isMounted) { + setLoadedPyo(pyodide); + setLoading(false); + } + } catch (err) { + console.error("Failed to initialize Pyodide:", err); + if (isMounted) { + setLoading(false); + } + } + }; + initializePyodide(); + return () => { + isMounted = false; + }; + }, []); const probes = []; const drivers = []; for (const c in fullyConnectedComponents) if (["vin", "iin"].includes(fullyConnectedComponents[c].type)) drivers.push(fullyConnectedComponents[c].type); @@ -70,22 +78,21 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component ); })} )} - {/* Enable this feature if this ticket gets solved! https://github.com/Yaffle/Expression/issues/15 */} - - - -
Chose Algebra solver
- {"These are JavaScript based solvers. It's already loaded and is fine for most cases"} - - } - > - { - setResults({ ...emptyResults }); - setLoadedPyo(null); - setAlgebraic("algebrite"); - }} - > - {loading ? : "Algebrite + Yaffle"} - -
- -
Chose Algebra solver
- { - "This is a Python based solver. Chosing this will download 10MB of files and run Python inside the browser, enabling more features such as giving a pretty numeric result, and more advanced algebraic simplification." - } - - } - > - { - setLoading(true); - setResults({ ...emptyResults }); - const pyodide = await initPyodideAndSympy(); - setLoadedPyo(pyodide); - setLoading(false); - setAlgebraic("sympy"); - }} - > - {loading ? : "SymPy"} - -
-
-
); } diff --git a/src/ReleaseNotes.jsx b/src/ReleaseNotes.jsx index 4332130..fa0291c 100644 --- a/src/ReleaseNotes.jsx +++ b/src/ReleaseNotes.jsx @@ -68,6 +68,19 @@ function ReleaseNotes() { simplify an algebraic matrix. However, SymPy can simplify better, and can swap out the algebra to make pretty numberic results + + + v2.1 + + + December 2025 + + +
    +
  • Removed Algebrite solver - now only using SymPy
  • +
+
+
diff --git a/src/new_solveMNA.js b/src/new_solveMNA.js index 5b34875..679ec9d 100644 --- a/src/new_solveMNA.js +++ b/src/new_solveMNA.js @@ -1,5 +1,3 @@ -import * as Algebrite from "algebrite"; -import simplify_algebra from "./simplify_algebra.js"; // import { loadPyodide } from "pyodide"; // import { initPyodideAndSympy } from "./pyodideLoader"; @@ -49,41 +47,9 @@ str(result_simplified), mathml(result_simplified, printer='presentation'), str(r } } -async function solveWithAlgebrite(matrixStr, mnaMatrix, resIndex, resIndex2) { - try { - Algebrite.eval("clearall"); - - if (mnaMatrix.length == 1) { - Algebrite.eval(`mna_vo_vi = 1/(${mnaMatrix[0]})`); //FIXME - is this correct? When is it hit, with Iin? - } else { - Algebrite.eval(`mna = [${matrixStr}]`); - Algebrite.eval("inv_mna = inv(mna)"); - if (resIndex2.length == 0) { - Algebrite.eval(`mna_vo_vi = inv_mna[${resIndex[0]}][${resIndex[1]}]`); - } else { - Algebrite.eval(`mna_vo_vi = inv_mna[${resIndex[0]}][${resIndex[1]}] - inv_mna[${resIndex2[0]}][${resIndex2[1]}]`); - } - } - - var strOut = Algebrite.eval("mna_vo_vi").toString(); //4ms - - const [textResult, mathml] = simplify_algebra(strOut); - - Algebrite.eval("complex_response = subst(s*i,s,mna_vo_vi)"); - Algebrite.eval("abs_complex_response = abs(complex_response)"); - - const complex_response = Algebrite.eval("abs_complex_response").toString(); - - return [textResult, mathml, complex_response]; - } catch (err) { - console.log("Building MNA matrix failed with this error:", err); - return ["", "", ""]; - } -} - // all these equations are based on // https://lpsa.swarthmore.edu/Systems/Electrical/mna/MNAAll.html -export async function build_and_solve_mna(numNodes, chosenPlot, fullyConnectedComponents, componentValuesSolved, pyodide, solver) { +export async function build_and_solve_mna(numNodes, chosenPlot, fullyConnectedComponents, componentValuesSolved, pyodide) { var i, vinNode, iinNode; //Are we plotting current or voltage? @@ -206,34 +172,20 @@ export async function build_and_solve_mna(numNodes, chosenPlot, fullyConnectedCo } var numericResult, textResult, mathml, complex_response, numericText; - if (solver === "algebrite") { - [textResult, mathml, complex_response] = await solveWithAlgebrite(nerdStr, mnaMatrix, resIndex, resIndex2); - } else { - [textResult, mathml, complex_response, numericResult, numericText] = await solveWithSymPy(nerdStr, mnaMatrix, elementMap, resIndex, resIndex2, componentValuesSolved, pyodide); - } + [textResult, mathml, complex_response, numericResult, numericText] = await solveWithSymPy(nerdStr, mnaMatrix, elementMap, resIndex, resIndex2, componentValuesSolved, pyodide); return [textResult, mathml, complex_response, numericResult, numericText]; } export async function calcBilinear(solver) { - if (solver) { - const sympyString = ` + const sympyString = ` T, Z = symbols("T Z") bilinear = result_simplified.subs(s,(2/T)*(Z-1)/(Z+1)) bilinear_simp = simplify(bilinear) str(bilinear_simp), mathml(bilinear_simp, printer='presentation') `; - const [res, mathml] = await solver.runPythonAsync(sympyString); - // console.log("bilinear transform", res, mathml); + const [res, mathml] = await solver.runPythonAsync(sympyString); + // console.log("bilinear transform", res, mathml); - return [res, removeFenced(mathml)]; - } else { - Algebrite.eval("bilinear = subst((2/T)*(Z-1)/(Z+1),s,mna_vo_vi)"); - try { - return simplify_algebra(Algebrite.eval("bilinear").toString()); - } catch (err) { - console.log(err); - return ["", "Having trouble calculating bilinear transform"]; - } - } + return [res, removeFenced(mathml)]; } diff --git a/src/pyodideLoader.js b/src/pyodideLoader.js index 39ff792..2752468 100644 --- a/src/pyodideLoader.js +++ b/src/pyodideLoader.js @@ -13,7 +13,7 @@ export async function initPyodideAndSympy() { // await micropip.install('sympy'); await pyodide.loadPackage("sympy"); var initStr = ` -from sympy import Matrix, symbols, simplify, Abs, I, fraction, solve +from sympy import Matrix, symbols, simplify, Abs, I, fraction, solve, arg from sympy.printing.mathml import mathml`; await pyodide.runPythonAsync(initStr); return pyodide; diff --git a/src/simplify_algebra.js b/src/simplify_algebra.js deleted file mode 100644 index 67f0f4d..0000000 --- a/src/simplify_algebra.js +++ /dev/null @@ -1,23 +0,0 @@ -// import Expression from "../js/expression/Expression.js"; -// import "../js/expression/complex.js"; -// import ExpressionParser from "../js/expression/ExpressionParser.js"; -// import Polynomial from "../js/expression/Polynomial.js"; -// import "../js/expression/polynomial-roots-finding.js"; -// import "../js/expression/toMathML.js"; - -// import {ExpressionParser} from './node_modules/@yaffle/expression/index.js'; -import { ExpressionParser, Polynomial, Expression } from "@yaffle/expression"; - -export default function simplify_algebra(expr) { - var z = expr.replace(/([CRL]+)([0-9]*)/g, "$1_$2"); //Swap R0 for R_0 so this new library can consume it - var matrix = ExpressionParser.parse(z); - var t = matrix.toString(); //Converts to a latex string - var t2 = t.replace(/([CRL]+)_([0-9]*)/g, "$1$2"); //Swap R_0 for R0 so this new library can consume it - - // console.log(matrix.toMathML()); - - // console.log(matrix.toMathML()) - // var zz = t.replace(/([a-zA-Z]+)_([0-9]*)/g,"$1$2") - // var matrix = ExpressionParser.parse('-1/(ab(-1/(ab)-1/(ac)-1/(bc)))-1/(ac(-1/(ab)-1/(ac)-1/(bc)))'); - return [t2, matrix.toMathML()]; -} diff --git a/tests/circuit.test.js b/tests/circuit.test.js index a32eb20..4ef4ef2 100644 --- a/tests/circuit.test.js +++ b/tests/circuit.test.js @@ -33,47 +33,13 @@ test("voltage in current probe - 1", async () => { R0: 10000, C0: 1.0000000000000002e-14, }; - const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide, "sympy"); + const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide); expect(textResult).toEqual("C0*L0*s**2/(C0*L0*R0*s**2 + L0*s + R0)"); // expect(_mathml).toEqual(null); expect(complex_response).toEqual("1.0e-24*s**2/sqrt(1.0e-40*s**4 - 1.0e-20*s**2 + 1)"); expect(numericText).toEqual("1.0e-20*s^2/(1.0e-16*s^2 + 1.0e-6*s + 10000)"); }); -test("voltage in current probe (algebrite) - 1", async () => { - const components = { - Y0: { - ports: [0, 1], - type: "iprobe", - }, - vin: { - ports: [0], - type: "vin", - }, - L0: { - ports: [0, 2], - type: "inductor", - }, - R0: { - ports: [1, 2], - type: "resistor", - }, - C0: { - ports: [2, null], - type: "capacitor", - }, - }; - const values = { - L0: 0.000001, - R0: 10000, - C0: 1.0000000000000002e-14, - }; - const [textResult, _mathml, complex_response] = await build_and_solve_mna(3, ["Y0"], components, values, null, "algebrite"); - expect(textResult).toEqual("C0*L0*s^2/(R0+C0*L0*R0*s^2+L0*s)"); - // expect(_mathml).toEqual(null); - expect(complex_response).toEqual("abs(C0)*abs(s)/((1-2*C0*R0^2/L0+C0^2*R0^2*s^2+R0^2/(L0^2*s^2))^(1/2))"); -}); - test("voltage in current probe - 2", async () => { const components = { R0: { @@ -102,7 +68,7 @@ test("voltage in current probe - 2", async () => { R0: 10000, C0: 1.0000000000000002e-14, }; - const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide, "sympy"); + const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide); expect(textResult).toEqual("C0*L0*s**2/(C0*L0*R0*s**2 + L0*s + R0)"); // expect(_mathml).toEqual(null); expect(complex_response).toEqual("1.0e-24*s**2/sqrt(1.0e-40*s**4 - 1.0e-20*s**2 + 1)"); @@ -137,7 +103,7 @@ test("current in current probe - 2", async () => { R0: 10000, C0: 1.0000000000000002e-14, }; - const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide, "sympy"); + const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["Y0"], components, values, pyodide); expect(textResult).toEqual("L0*s/(L0*s + R0)"); // expect(_mathml).toEqual(null); expect(complex_response).toEqual("1.0e-10*s/sqrt(1.0e-20*s**2 + 1)"); @@ -167,7 +133,7 @@ test("current in current probe VCIS - 3", async () => { G0: 0.001, R0: 1000000, }; - const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(2, ["Y0"], components, values, pyodide, "sympy"); + const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(2, ["Y0"], components, values, pyodide); expect(textResult).toEqual("G0*R0/(G0*R0 + 1)"); // expect(_mathml).toEqual(null); expect(complex_response).toEqual("0.999000999000999"); @@ -207,7 +173,7 @@ test("voltage in voltage probe VCVS", async () => { A0: 100, R0: 1000000, }; - const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["X0"], components, values, pyodide, "sympy"); + const [textResult, _mathml, complex_response, _numericResult, numericText] = await build_and_solve_mna(3, ["X0"], components, values, pyodide); expect(textResult).toEqual("A0*R0*(C0*L0*s**2 + 1)/(C0*L0*R0*s**2 + L0*s + R0)"); // expect(_mathml).toEqual(null); expect(complex_response).toEqual("Abs(1.0e-13*s**2 - 100000000)/(1000000*sqrt(1.0e-42*s**4 - 1.999999999e-21*s**2 + 1))"); //FIXME - this seems to be a sympy bug diff --git a/toDo.md b/toDo.md index e5c77d6..1dd2e4b 100644 --- a/toDo.md +++ b/toDo.md @@ -1,3 +1,8 @@ +# Todo Dec 3rd + +- Remove algebrite +- Move solving into Sympy, then can get mag and phase + # Todo Oct 23rd - Update all the packages - once Sympy 1.13.4 is integrated to Pyodide! From 1c3965b5bc69890bcf10386157fd6c31401db674 Mon Sep 17 00:00:00 2001 From: 28raining Date: Wed, 10 Dec 2025 22:21:37 -0800 Subject: [PATCH 02/13] moving full math solving to sympy --- src/App.jsx | 85 +++++++++++++-------------------------------- src/ChoseTF.jsx | 13 +++---- src/common.js | 2 +- src/new_solveMNA.js | 81 ++++++++++++++++++++++++++++++++++-------- 4 files changed, 100 insertions(+), 81 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index b53ec1e..63d89a8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,7 +9,7 @@ import { ComponentAdjuster } from "./ComponentAdjuster.jsx"; import { FreqAdjusters } from "./FreqAdjusters.jsx"; // import Grid from "@mui/material/Grid"; import { units } from "./common.js"; -import { calcBilinear } from "./new_solveMNA.js"; +import { calcBilinear, new_calculate_tf } from "./new_solveMNA.js"; import { NavBar } from "./NavBar.jsx"; import { ChoseTF } from "./ChoseTF.jsx"; @@ -93,58 +93,6 @@ function stateFromURL() { } const [modifiedComponents, modifiedSettings, modifiedSchematic] = stateFromURL(); -function new_calculate_tf(textResult, fRange, numSteps, components, setErrorSnackbar) { - // console.log("new_calculate_tf", { textResult, fRange, numSteps, components }); - if (textResult == "") return { freq_new: [], mag_new: [] }; - var complex_freq = textResult; - var rep; - for (const key in components) { - rep = RegExp(key, "g"); - complex_freq = complex_freq.replace(rep, components[key]); - } - - //Now only remaining variable is S, substitute that and solve. Also swap power ^ for ** - const re = /s/gi; - const reDollar = /\$/gi; - const re2 = /\^/gi; - var res = complex_freq.replace(re2, "**"); //swap ^ for ** - // const re3 = /abs/gi; //sometimes abs(C0) is left in the equation - // res = res.replace(re3, ""); - //now swap sqrt for '$' - const re3 = /sqrt/gi; //sometimes abs(C0) is left in the equation - res = res.replace(re3, "$"); //swap sqrt for $ - const re4 = /abs/gi; - res = res.replace(re4, ""); //swap Abs(...) for (...). I saw one case where Sympy left it in but it's ok to remove it like this - const re5 = /I/gi; - res = res.replace(re5, "1"); //swap I for 1. Sometimes sympy leaves in the I if the result is fully imaginary. This is dangerous and instead the numeric solving should go inside sympy - - var fstepdB_20 = Math.log10(fRange.fmax / fRange.fmin) / numSteps; - var fstep = 10 ** fstepdB_20; - var absNew, evalNew; - const freq = []; - const mag = []; - try { - for (var f = fRange.fmin; f < fRange.fmax; f = f * fstep) { - freq.push(f); - // const mathString = res.replace(re, 2 * Math.PI * f).replace(/\*\*/g, "^"); - // evalNew = evaluate(mathString); - const mathString = res.replace(re, 2 * Math.PI * f).replace(reDollar, "Math.sqrt"); - evalNew = eval(mathString); - - absNew = Math.abs(evalNew); - mag.push(20 * Math.log10(absNew)); - } - } catch (err) { - setErrorSnackbar((x) => { - if (!x) return true; - else return x; - }); - console.log("oh no", err); - } - - return { freq_new: freq, mag_new: mag }; -} - function compToURL(key, value) { return `${key}_${value.type}_${value.value}_${value.unit}`; } @@ -153,7 +101,7 @@ function App() { const [nodes, setNodes] = useState([]); const [fullyConnectedComponents, setFullyConnectedComponents] = useState({}); - const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", bilinearRaw: "", bilinearMathML: "" }); + const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", bilinearRaw: "", bilinearMathML: "", numericML: "", numericText: "", solver: null, probeName: "", drivers: [] }); const [componentValues, setComponentValues] = useState(modifiedComponents); const [settings, setSettings] = useState(modifiedSettings); const [schemHistory, setSchemHistory] = useState({ pointer: 0, state: [modifiedSchematic] }); @@ -247,12 +195,29 @@ function App() { const [freq_new, setFreqNew] = useState(null); const [mag_new, setMagNew] = useState(null); useEffect(() => { - const fRange = { fmin: settings.fmin * units.frequency[settings.fminUnit], fmax: settings.fmax * units.frequency[settings.fmaxUnit] }; - const componentValuesSolved2 = {}; - for (const key in componentValues) componentValuesSolved2[key] = componentValues[key].value * units[componentValues[key].type][componentValues[key].unit]; - const { freq_new, mag_new } = new_calculate_tf(results.complexResponse, fRange, settings.resolution, componentValuesSolved2, setErrorSnackbar); - setFreqNew(freq_new); - setMagNew(mag_new); + const calculateTF = async () => { + if (!results.solver || results.text === "") { + setFreqNew([]); + setMagNew([]); + return; + } + const fRange = { fmin: settings.fmin * units.frequency[settings.fminUnit], fmax: settings.fmax * units.frequency[settings.fmaxUnit] }; + const componentValuesSolved2 = {}; + for (const key in componentValues) componentValuesSolved2[key] = componentValues[key].value * units[componentValues[key].type][componentValues[key].unit]; + const { freq_new, mag_new, numericML, numericText } = await new_calculate_tf(results.solver, fRange, settings.resolution, componentValuesSolved2, setErrorSnackbar); + setFreqNew(freq_new); + setMagNew(mag_new); + if (numericML && numericText && results.probeName && results.drivers) { + // Format numericML with probe name and drivers (same as formatMathML in ChoseTF.jsx) + const formattedNumericML = `${results.probeName}${results.drivers[0] == "vin" ? "V" : "I"}in=${numericML}`; + setResults((prevResults) => ({ + ...prevResults, + numericML: formattedNumericML, + numericText: numericText, + })); + } + }; + calculateTF(); }, [results, settings, componentValues]); function stateToURL() { diff --git a/src/ChoseTF.jsx b/src/ChoseTF.jsx index 633baab..7c6fa7f 100644 --- a/src/ChoseTF.jsx +++ b/src/ChoseTF.jsx @@ -87,30 +87,31 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component setResults({ ...emptyResults }); // Reset results to empty //this console log is for collecting data for testing // console.log(nodes.length, int_probes, fullyConnectedComponents, valueForAlgebra, loadedPyo); - const [textResult, mathml, complex_response, numericResult, numericText] = await build_and_solve_mna( + const [textResult, mathml] = await build_and_solve_mna( nodes.length, int_probes, fullyConnectedComponents, valueForAlgebra, loadedPyo, ); - if (textResult === "" && mathml === "" && complex_response === "") { + if (textResult === "" && mathml === "") { setUnsolveSnackbar((x) => { if (!x) return true; else return x; }); } const editedMathMl = formatMathML(mathml, p, drivers); - const editedMathMlNumeric = formatMathML(numericResult, p, drivers); setResults({ text: textResult, mathML: editedMathMl, - complexResponse: complex_response, + complexResponse: "", bilinearRaw: "", bilinearMathML: "", - numericML: editedMathMlNumeric, - numericText: numericText, + numericML: "", + numericText: "", solver: loadedPyo, + probeName: p, + drivers: drivers, }); setCalculating(false); // setTextResult(textResult); diff --git a/src/common.js b/src/common.js index a886ce1..415ccf2 100644 --- a/src/common.js +++ b/src/common.js @@ -174,4 +174,4 @@ export const theme = createTheme({ }, }); -export const emptyResults = { text: "", mathML: "", complexResponse: "", bilinearRaw: "", bilinearMathML: "", numericML: "", numericText: "", solver: null }; +export const emptyResults = { text: "", mathML: "", complexResponse: "", bilinearRaw: "", bilinearMathML: "", numericML: "", numericText: "", solver: null, probeName: "", drivers: [] }; diff --git a/src/new_solveMNA.js b/src/new_solveMNA.js index 679ec9d..94ad734 100644 --- a/src/new_solveMNA.js +++ b/src/new_solveMNA.js @@ -25,25 +25,18 @@ ${ : `mna_vo_vi = mna_inv[${resIndex[0] - 1}, ${resIndex[1] - 1}] - mna_inv[${resIndex2[0] - 1}, ${resIndex2[1] - 1}]` }` } -mna_vo_vi_complex = mna_vo_vi.subs(s, s*I) result_simplified = simplify(mna_vo_vi) -result_numeric = result_simplified.subs(${JSON.stringify(componentValuesSolved).replaceAll('"', "")}) -result_numeric_simplified = simplify(result_numeric) -result_numeric_complex = result_numeric_simplified.subs(s, s*I) -rx = Abs(result_numeric_complex) -str(result_simplified), mathml(result_simplified, printer='presentation'), str(rx), mathml(result_numeric_simplified, printer='presentation'), str(result_numeric_simplified) +str(result_simplified), mathml(result_simplified, printer='presentation') `; try { - const [textResult, mathml, complex_response, numericML, numericText] = await pyodide.runPythonAsync(sympyString); - const newNumeric = numericML; - const newNumericText = numericText.replaceAll("**", "^"); + const [textResult, mathml] = await pyodide.runPythonAsync(sympyString); - return [textResult, removeFenced(mathml), complex_response, removeFenced(newNumeric), newNumericText]; + return [textResult, removeFenced(mathml)]; } catch (err) { console.log("Solving MNA matrix failed with this error:", err); - return ["", "", "", "", ""]; + return ["", ""]; } } @@ -170,11 +163,11 @@ export async function build_and_solve_mna(numNodes, chosenPlot, fullyConnectedCo else resIndex2.push(voutNode2 + 1, iinNode + 1); } } - var numericResult, textResult, mathml, complex_response, numericText; + var textResult, mathml; - [textResult, mathml, complex_response, numericResult, numericText] = await solveWithSymPy(nerdStr, mnaMatrix, elementMap, resIndex, resIndex2, componentValuesSolved, pyodide); + [textResult, mathml] = await solveWithSymPy(nerdStr, mnaMatrix, elementMap, resIndex, resIndex2, componentValuesSolved, pyodide); - return [textResult, mathml, complex_response, numericResult, numericText]; + return [textResult, mathml]; } export async function calcBilinear(solver) { @@ -189,3 +182,63 @@ str(bilinear_simp), mathml(bilinear_simp, printer='presentation') return [res, removeFenced(mathml)]; } + +export async function new_calculate_tf(pyodide, fRange, numSteps, componentValuesSolved, setErrorSnackbar) { + if (!pyodide) return { freq_new: [], mag_new: [], numericML: "", numericText: "" }; + + // Generate frequency array (logarithmic steps) + var fstepdB_20 = Math.log10(fRange.fmax / fRange.fmin) / numSteps; + var fstep = 10 ** fstepdB_20; + const freq = []; + for (var f = fRange.fmin; f < fRange.fmax; f = f * fstep) { + freq.push(f); + } + + try { + // Use sympy to calculate magnitudes for all frequencies and numeric representation + const sympyString = ` +from sympy import pi +import math + +result_numeric = result_simplified.subs(${JSON.stringify(componentValuesSolved).replaceAll('"', "")}) +result_numeric_simplified = simplify(result_numeric) + +# Calculate numeric MathML and text representation +numeric_mathml = mathml(result_numeric_simplified, printer='presentation') +numeric_text = str(result_numeric_simplified) + +freq_array = ${JSON.stringify(freq)} +mag_array = [] + +for f in freq_array: + s_val = 2 * pi * f * I + result_numeric_complex = result_numeric_simplified.subs(s, s_val) + mag = Abs(result_numeric_complex) + # Convert to dB: 20*log10(mag) + # Use Python's math.log10 for numeric evaluation + mag_eval = float(mag.evalf()) + if mag_eval > 0: + mag_db = 20 * math.log10(mag_eval) + else: + mag_db = float('-inf') + mag_array.append(mag_db) + +(numeric_mathml, numeric_text, mag_array) +`; + const [numericML, numericText, mag] = await pyodide.runPythonAsync(sympyString); + + return { + freq_new: freq, + mag_new: Array.from(mag), + numericML: removeFenced(numericML), + numericText: numericText.replaceAll("**", "^") + }; + } catch (err) { + setErrorSnackbar((x) => { + if (!x) return true; + else return x; + }); + console.log("Error calculating transfer function:", err); + return { freq_new: [], mag_new: [], numericML: "", numericText: "" }; + } +} From a6c4d87a0a59d959605f0409b56892717e25b5e1 Mon Sep 17 00:00:00 2001 From: 28raining Date: Fri, 19 Dec 2025 12:52:02 +0100 Subject: [PATCH 03/13] simplify the results object --- src/App.jsx | 26 +++++++++++++++----------- src/ChoseTF.jsx | 4 ---- src/common.js | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 63d89a8..5a4b746 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -101,7 +101,9 @@ function App() { const [nodes, setNodes] = useState([]); const [fullyConnectedComponents, setFullyConnectedComponents] = useState({}); - const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", bilinearRaw: "", bilinearMathML: "", numericML: "", numericText: "", solver: null, probeName: "", drivers: [] }); + const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", solver: null, probeName: "", drivers: [] }); + const [numericResults, setNumericResults] = useState({ numericML: "", numericText: "" }); + const [bilinearResults, setBilinearResults] = useState({ bilinearML: "", bilinearText: "" }); const [componentValues, setComponentValues] = useState(modifiedComponents); const [settings, setSettings] = useState(modifiedSettings); const [schemHistory, setSchemHistory] = useState({ pointer: 0, state: [modifiedSchematic] }); @@ -199,6 +201,8 @@ function App() { if (!results.solver || results.text === "") { setFreqNew([]); setMagNew([]); + setNumericResults({ numericML: "", numericText: "" }); + setBilinearResults({ bilinearML: "", bilinearText: "" }); return; } const fRange = { fmin: settings.fmin * units.frequency[settings.fminUnit], fmax: settings.fmax * units.frequency[settings.fmaxUnit] }; @@ -210,11 +214,10 @@ function App() { if (numericML && numericText && results.probeName && results.drivers) { // Format numericML with probe name and drivers (same as formatMathML in ChoseTF.jsx) const formattedNumericML = `${results.probeName}${results.drivers[0] == "vin" ? "V" : "I"}in=${numericML}`; - setResults((prevResults) => ({ - ...prevResults, + setNumericResults({ numericML: formattedNumericML, numericText: numericText, - })); + }); } }; calculateTF(); @@ -249,9 +252,10 @@ function App() { async function handleRequestBilin() { // console.log("handleRequestBilin", calcBilinear()); const [raw, bilin] = await calcBilinear(results.solver); - // setBilinearMathML(`${bilin}`); - // setBilinearRaw(raw); - setResults({ ...results, bilinearRaw: raw, bilinearMathML: `${bilin}` }); + setBilinearResults({ + bilinearML: `${bilin}`, + bilinearText: raw, + }); } // console.log(results); @@ -299,14 +303,14 @@ function App() { {results.text != "" && ( <> - {results.numericText != null && ( - + {numericResults.numericText != null && numericResults.numericText !== "" && ( + )}
- {results.bilinearMathML == "" ? ( + {bilinearResults.bilinearML == "" ? (