Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
600 changes: 304 additions & 296 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"pyodide": "^0.28.2",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"visiojs": "^0.0.7"
"visiojs": "^0.0.8"
},
"devDependencies": {
"@eslint/js": "^9.34.0",
Expand Down
64 changes: 51 additions & 13 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useState, useRef, useCallback } from "react";
import "./App.css";
import "visiojs/dist/visiojs.css"; // Import VisioJS styles
// import { startupSchematic } from "./startupSchematic.js";
Expand All @@ -8,7 +8,7 @@ import { VisioJSSchematic } from "./VisioJSSchematic.jsx";
import { ComponentAdjuster } from "./ComponentAdjuster.jsx";
import { FreqAdjusters } from "./FreqAdjusters.jsx";
// import Grid from "@mui/material/Grid";
import { units, formatMathML } from "./common.js";
import { units, formatMathML, addShapes } from "./common.js";
import { calcBilinear, new_calculate_tf } from "./new_solveMNA.js";

import { NavBar } from "./NavBar.jsx";
Expand All @@ -26,17 +26,17 @@ import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";

const initialComponents = {
L0: {
"11111111-1111-1111-1111-111111111101": {
type: "inductor",
value: 1,
unit: "uH",
},
R0: {
"11111111-1111-1111-1111-111111111102": {
type: "resistor",
value: 10,
unit: "KΩ",
},
C0: {
"11111111-1111-1111-1111-111111111103": {
type: "capacitor",
value: 10,
unit: "fF",
Expand All @@ -51,6 +51,7 @@ const initialSettings = {
resolution: 100,
};
import { initialSchematic } from "./initialSchematic.js";
import { ensureCircuitIds } from "./circuitIds.js";
var urlContainsState = false; // Flag to check if URL contains state

function stateFromURL() {
Expand All @@ -61,7 +62,7 @@ function stateFromURL() {

var modifiedComponents = initialComponents;
var modifiedSettings = initialSettings;
var modifiedSchematic = initialSchematic; // Default to initial schematic if no URL param is
var modifiedSchematic = ensureCircuitIds(structuredClone(initialSchematic)); // Default to initial schematic if no URL param is

if (componentsParam) {
urlContainsState = true; // Set the flag if components are present in the URL
Expand All @@ -87,7 +88,7 @@ function stateFromURL() {
// Decode the Base64 string back into a Uint8Array
const compressedBinary = Uint8Array.from(atob(decodeURIComponent(schematicParam)), (char) => char.charCodeAt(0));
const decompressed = pako.inflate(compressedBinary, { to: "string" }); // Decompress the data using pako
modifiedSchematic = JSON.parse(decompressed); // Parse the decompressed JSON string into an object
modifiedSchematic = ensureCircuitIds(JSON.parse(decompressed)); // Parse the decompressed JSON string into an object
}
return [modifiedComponents, modifiedSettings, modifiedSchematic];
}
Expand All @@ -101,7 +102,9 @@ function App() {
const [nodes, setNodes] = useState([]);

const [fullyConnectedComponents, setFullyConnectedComponents] = useState({});
const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", solver: null, probeName: "", drivers: [] });
/** All placed shapes with connectors (incl. not yet wired to vin); used for display names on new parts. */
const [schematicComponents, setSchematicComponents] = useState({});
const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", solver: null, probeName: "", probeDisplayLabel: "", drivers: [] });
const [numericResults, setNumericResults] = useState({ numericML: "", numericText: "" });
const [bilinearResults, setBilinearResults] = useState({ bilinearML: "", bilinearText: "" });
const [componentValues, setComponentValues] = useState(modifiedComponents);
Expand Down Expand Up @@ -192,7 +195,11 @@ function App() {
}

const componentValuesSolved = {};
for (const key in componentValues) componentValuesSolved[key] = componentValues[key].value * units[componentValues[key].type][componentValues[key].unit];
for (const id in componentValues) {
const sym = fullyConnectedComponents[id]?.sympySymbol;
if (!sym) continue;
componentValuesSolved[sym] = componentValues[id].value * units[componentValues[id].type][componentValues[id].unit];
}

const [freq_new, setFreqNew] = useState(null);
const [mag_new, setMagNew] = useState(null);
Expand All @@ -218,7 +225,11 @@ function App() {
}
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];
for (const id in componentValues) {
const sym = fullyConnectedComponents[id]?.sympySymbol;
if (!sym) continue;
componentValuesSolved2[sym] = componentValues[id].value * units[componentValues[id].type][componentValues[id].unit];
}
const { freq_new, mag_new, phase_new, numericML, numericText } = await new_calculate_tf(
results.solver,
fRange,
Expand All @@ -230,7 +241,7 @@ function App() {
setMagNew(mag_new);
setPhaseNew(phase_new);
if (numericML && numericText && results.probeName && results.drivers) {
const formattedNumericML = formatMathML(numericML, results.probeName, results.drivers);
const formattedNumericML = formatMathML(numericML, results.probeDisplayLabel || results.probeName, results.drivers);
setNumericResults({ numericML: formattedNumericML, numericText: numericText });
}
};
Expand All @@ -243,7 +254,26 @@ function App() {
clearTimeout(debounceTimerRef.current);
}
};
}, [results, settings, componentValues]);
}, [results, settings, componentValues, fullyConnectedComponents]);

const handleDisplayNameChange = useCallback((circuitId, text) => {
setSchemHistory((h) => {
const i = h.pointer;
const newState = JSON.parse(JSON.stringify(h.state[i]));
const sh = newState.shapes.find((s) => s && s.circuitId === circuitId);
if (sh) {
if (!sh.label) {
const shapeType = sh.image?.split(".")[0];
const template = shapeType ? addShapes[shapeType] : null;
if (template?.label) sh.label = JSON.parse(JSON.stringify(template.label));
}
if (sh.label) sh.label.text = text;
}
const state = [...h.state];
state[i] = newState;
return { ...h, state };
});
}, []);

function stateToURL() {
const url = new URL(window.location.href);
Expand Down Expand Up @@ -308,17 +338,25 @@ function App() {
setHistory={setSchemHistory}
setComponentValues={setComponentValues}
setFullyConnectedComponents={setFullyConnectedComponents}
setSchematicComponents={setSchematicComponents}
/>
</div>
<div className="col-12">
<ComponentAdjuster componentValues={componentValues} setComponentValues={setComponentValues} />
<ComponentAdjuster
componentValues={componentValues}
setComponentValues={setComponentValues}
fullyConnectedComponents={fullyConnectedComponents}
schematicComponents={schematicComponents}
onDisplayNameChange={handleDisplayNameChange}
/>
</div>
</div>
<div className="row shadow-sm rounded bg-lightgreen my-2 py-3" id="schematic">
<ChoseTF
setResults={setResults}
nodes={nodes}
fullyConnectedComponents={fullyConnectedComponents}
schematicComponents={schematicComponents}
componentValuesSolved={componentValuesSolved}
setUnsolveSnackbar={setUnsolveSnackbar}
/>
Expand Down
35 changes: 21 additions & 14 deletions src/ChoseTF.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ import CircularProgress from "@mui/material/CircularProgress";
import { initPyodideAndSympy } from "./pyodideLoader";
import { emptyResults, formatMathML } from "./common.js"; // Import the emptyResults object

export function ChoseTF({ setResults, nodes, fullyConnectedComponents, componentValuesSolved, setUnsolveSnackbar }) {
/** UUIDs contain "-"; differential probes must not use "-" as the pair delimiter. */
export const PROBE_PAIR_DELIM = "::";

function probeDisplayLabel(p, fcc, schematicComponents) {
if (p.includes(PROBE_PAIR_DELIM)) {
const [a, b] = p.split(PROBE_PAIR_DELIM);
const la = fcc[a]?.displayName ?? schematicComponents?.[a]?.displayName ?? a;
const lb = fcc[b]?.displayName ?? schematicComponents?.[b]?.displayName ?? b;
return `${la}-${lb}`;
}
return fcc[p]?.displayName ?? schematicComponents?.[p]?.displayName ?? p;
}

export function ChoseTF({ setResults, nodes, fullyConnectedComponents, schematicComponents, componentValuesSolved, setUnsolveSnackbar }) {
const [loading, setLoading] = useState(false);
const [calculating, setCalculating] = useState(false);
const [loadedPyo, setLoadedPyo] = useState(null);
Expand Down Expand Up @@ -43,17 +56,10 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
//if there's 2 vprobes, add P1-P0 and P0-P1 to the probes object
const vprobes = probes.filter((p) => fullyConnectedComponents[p].type == "vprobe");
if (vprobes.length == 2) {
probes.push(`${vprobes[0]}-${vprobes[1]}`);
probes.push(`${vprobes[1]}-${vprobes[0]}`);
probes.push(`${vprobes[0]}${PROBE_PAIR_DELIM}${vprobes[1]}`);
probes.push(`${vprobes[1]}${PROBE_PAIR_DELIM}${vprobes[0]}`);
}

// add a value field based on if user chose algebraic or numeric
const valueForAlgebra = {};
for (const c in fullyConnectedComponents) {
if (c in componentValuesSolved) valueForAlgebra[c] = componentValuesSolved[c];
// else valueForAlgebra[c] = c;
}
// console.log("componentValuesSolved", componentValuesSolved, fullyConnectedComponents, algebraic);
return (
<Grid container spacing={1} sx={{ mt: 1 }}>
{drivers.length == 0 || probes.length == 0 ? (
Expand All @@ -67,7 +73,7 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
<Grid size={12}>Calculate...</Grid>

{probes.map((p) => {
const int_probes = p.includes("-") ? p.split("-") : [p];
const int_probes = p.includes(PROBE_PAIR_DELIM) ? p.split(PROBE_PAIR_DELIM) : [p];

return (
<Grid size={{ xs: 4, sm: 2, lg: 1 }} key={p}>
Expand All @@ -83,20 +89,21 @@ 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] = await build_and_solve_mna(nodes.length, int_probes, fullyConnectedComponents, valueForAlgebra, loadedPyo);
const [textResult, mathml] = await build_and_solve_mna(nodes.length, int_probes, fullyConnectedComponents, componentValuesSolved, loadedPyo);
if (textResult === "" && mathml === "") {
setUnsolveSnackbar((x) => {
if (!x) return true;
else return x;
});
}
const editedMathMl = formatMathML(mathml, p, drivers);
const editedMathMl = formatMathML(mathml, probeDisplayLabel(p, fullyConnectedComponents, schematicComponents), drivers);
setResults({
text: textResult,
mathML: editedMathMl,
complexResponse: "",
solver: loadedPyo,
probeName: p,
probeDisplayLabel: probeDisplayLabel(p, fullyConnectedComponents, schematicComponents),
drivers: drivers,
});
setCalculating(false);
Expand All @@ -110,7 +117,7 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
) : (
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mfrac>
<mi>{p}</mi>
<mi>{probeDisplayLabel(p, fullyConnectedComponents, schematicComponents)}</mi>
{drivers[0] == "vin" ? (
<msub>
<mi>V</mi>
Expand Down
63 changes: 41 additions & 22 deletions src/ComponentAdjuster.jsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,67 @@
import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";

import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";

import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import FormControl from "@mui/material/FormControl";

import { units } from "./common";
import { enforceComponentNamePrefix } from "./componentNaming.js";

export function ComponentAdjuster({ componentValues, setComponentValues }) {
function handleValueChange(name, value) {
setComponentValues((prevValues) => ({
...prevValues,
[name]: { ...prevValues[name], value: value },
}));
export function ComponentAdjuster({ componentValues, setComponentValues, fullyConnectedComponents, schematicComponents, onDisplayNameChange }) {
function handleValueChange(id, value) {
setComponentValues((prevValues) => {
const sym = fullyConnectedComponents[id]?.sympySymbol;
const t = prevValues[id]?.type;
const next = { ...prevValues, [id]: { ...prevValues[id], value } };
if (sym && t) {
for (const k of Object.keys(next)) {
if (k !== id && fullyConnectedComponents[k]?.sympySymbol === sym && fullyConnectedComponents[k]?.type === t) {
next[k] = { ...next[k], value };
}
}
}
return next;
});
}
function handleUnitChange(name, value) {
setComponentValues((prevValues) => ({
...prevValues,
[name]: { ...prevValues[name], unit: value },
}));
function handleUnitChange(id, value) {
setComponentValues((prevValues) => {
const sym = fullyConnectedComponents[id]?.sympySymbol;
const t = prevValues[id]?.type;
const next = { ...prevValues, [id]: { ...prevValues[id], unit: value } };
if (sym && t) {
for (const k of Object.keys(next)) {
if (k !== id && fullyConnectedComponents[k]?.sympySymbol === sym && fullyConnectedComponents[k]?.type === t) {
next[k] = { ...next[k], unit: value };
}
}
}
return next;
});
}

function handleDisplayNameInput(id, text) {
const type = componentValues[id]?.type;
onDisplayNameChange(id, enforceComponentNamePrefix(type, text));
}

return (
<Grid container spacing={2}>
{Object.keys(componentValues).map((key) => (
<Grid size={{ md: 3 }} key={key}>
<Card sx={{ p: 1, m: 1, width: "100%" }}>
<Stack direction="row" spacing={0} alignItems="center" sx={{ borderRadius: 1 }}>
<Typography variant="h5" sx={{ mr: 1 }}>
{key}
</Typography>
<Stack direction="row" spacing={0} alignItems="center" sx={{ borderRadius: 1 }} flexWrap="wrap" useFlexGap>
<TextField
name={key}
value={componentValues[key].value}
sx={{ width: "8ch" }}
label="Name"
value={fullyConnectedComponents[key]?.displayName ?? schematicComponents[key]?.displayName ?? ""}
sx={{ width: "10ch", mr: 1, mb: 0.5 }}
size="small"
onChange={(e) => handleValueChange(key, e.target.value)}
// fullWidth
onChange={(e) => handleDisplayNameInput(key, e.target.value)}
/>
<TextField name={key} value={componentValues[key].value} sx={{ width: "8ch" }} size="small" onChange={(e) => handleValueChange(key, e.target.value)} />
<FormControl size="small">
<Select value={componentValues[key].unit} onChange={(e) => handleUnitChange(key, e.target.value)}>
{Object.keys(units[componentValues[key].type]).map((opt) => (
Expand Down
Loading
Loading