Skip to content
Open
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
564 changes: 28 additions & 536 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": "file:/Users/will-personal/Documents/visiojs/package"
},
"devDependencies": {
"@eslint/js": "^9.34.0",
Expand Down
33 changes: 22 additions & 11 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { FreqAdjusters } from "./FreqAdjusters.jsx";
// import Grid from "@mui/material/Grid";
import { units, formatMathML } from "./common.js";
import { calcBilinear, new_calculate_tf } from "./new_solveMNA.js";
import { buildComponentValuesForSympy } from "./sympyValues.js";

import { NavBar } from "./NavBar.jsx";
import { ChoseTF } from "./ChoseTF.jsx";
Expand All @@ -25,18 +26,19 @@ import SnackbarContent from "@mui/material/SnackbarContent";
import Button from "@mui/material/Button";
import Grid from "@mui/material/Grid";

/** Shape indices in initialSchematic: inductor 3, resistor 4, capacitor 5 */
const initialComponents = {
L0: {
3: {
type: "inductor",
value: 1,
unit: "uH",
},
R0: {
4: {
type: "resistor",
value: 10,
unit: "KΩ",
},
C0: {
5: {
type: "capacitor",
value: 10,
unit: "fF",
Expand Down Expand Up @@ -66,8 +68,13 @@ function stateFromURL() {
if (componentsParam) {
urlContainsState = true; // Set the flag if components are present in the URL
modifiedComponents = componentsParam.split("__").reduce((acc, comp) => {
const [key, type, value, unit] = comp.split("_");
acc[key] = { type, value: parseFloat(value), unit };
const parts = comp.split("_");
if (parts.length < 4) return acc;
const id = parts[0];
const type = parts[1];
const value = parts[2];
const unit = parts.slice(3).join("_");
acc[id] = { type, value: parseFloat(value), unit };
return acc;
}, {});
// setComponentValues(componentsArray);
Expand Down Expand Up @@ -98,9 +105,12 @@ function compToURL(key, value) {
}

function App() {
const schematicRef = useRef(null);
const [nodes, setNodes] = useState([]);

const [fullyConnectedComponents, setFullyConnectedComponents] = useState({});
/** All placed shapes with connectors (from createNodeMap), including off the vin subgraph — used for names & duplicate-value UI */
const [schematicComponents, setSchematicComponents] = useState({});
const [results, setResults] = useState({ text: "", mathML: "", complexResponse: "", solver: null, probeName: "", drivers: [] });
const [numericResults, setNumericResults] = useState({ numericML: "", numericText: "" });
const [bilinearResults, setBilinearResults] = useState({ bilinearML: "", bilinearText: "" });
Expand Down Expand Up @@ -191,8 +201,8 @@ function App() {
);
}

const componentValuesSolved = {};
for (const key in componentValues) componentValuesSolved[key] = componentValues[key].value * units[componentValues[key].type][componentValues[key].unit];
// Numeric values keyed by SymPy symbol name for algebraic/numeric TF (canonical row per shared name)
const componentValuesSolved = buildComponentValuesForSympy(componentValues, schematicComponents);

const [freq_new, setFreqNew] = useState(null);
const [mag_new, setMagNew] = useState(null);
Expand All @@ -217,8 +227,7 @@ function App() {
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 componentValuesSolved2 = buildComponentValuesForSympy(componentValues, schematicComponents);
const { freq_new, mag_new, phase_new, numericML, numericText } = await new_calculate_tf(
results.solver,
fRange,
Expand All @@ -243,7 +252,7 @@ function App() {
clearTimeout(debounceTimerRef.current);
}
};
}, [results, settings, componentValues]);
}, [results, settings, componentValues, schematicComponents]);

function stateToURL() {
const url = new URL(window.location.href);
Expand Down Expand Up @@ -302,16 +311,18 @@ function App() {
<div className="row shadow-sm rounded bg-lightgreen my-2 py-0" id="schematic">
<div className="col-12">
<VisioJSSchematic
schematicApiRef={schematicRef}
setResults={setResults}
setNodes={setNodes}
history={schemHistory}
setHistory={setSchemHistory}
setComponentValues={setComponentValues}
setFullyConnectedComponents={setFullyConnectedComponents}
setSchematicComponents={setSchematicComponents}
/>
</div>
<div className="col-12">
<ComponentAdjuster componentValues={componentValues} setComponentValues={setComponentValues} />
<ComponentAdjuster componentValues={componentValues} setComponentValues={setComponentValues} schematicComponents={schematicComponents} schematicRef={schematicRef} />
</div>
</div>
<div className="row shadow-sm rounded bg-lightgreen my-2 py-3" id="schematic">
Expand Down
28 changes: 14 additions & 14 deletions src/ChoseTF.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import CircularProgress from "@mui/material/CircularProgress";
import { initPyodideAndSympy } from "./pyodideLoader";
import { emptyResults, formatMathML } from "./common.js"; // Import the emptyResults object

function probeFractionLabel(fcc, p) {
if (!p.includes("-")) return fcc[p]?.sympyName ?? p;
const [a, b] = p.split("-");
return `${fcc[a]?.sympyName ?? a}-${fcc[b]?.sympyName ?? b}`;
}

export function ChoseTF({ setResults, nodes, fullyConnectedComponents, componentValuesSolved, setUnsolveSnackbar }) {
const [loading, setLoading] = useState(false);
const [calculating, setCalculating] = useState(false);
Expand Down Expand Up @@ -47,13 +53,8 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
probes.push(`${vprobes[1]}-${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);
// componentValuesSolved is keyed by SymPy symbol; passed through to build_and_solve_mna / subs
// console.log("componentValuesSolved", componentValuesSolved, fullyConnectedComponents);
return (
<Grid container spacing={1} sx={{ mt: 1 }}>
{drivers.length == 0 || probes.length == 0 ? (
Expand All @@ -68,6 +69,7 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component

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

return (
<Grid size={{ xs: 4, sm: 2, lg: 1 }} key={p}>
Expand All @@ -80,23 +82,21 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
sx={{ py: 1, justifyContent: "center", fontSize: "1.4em" }}
onClick={async () => {
setCalculating(true);
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);
setResults({ ...emptyResults });
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, label, drivers);
setResults({
text: textResult,
mathML: editedMathMl,
complexResponse: "",
solver: loadedPyo,
probeName: p,
probeName: label,
drivers: drivers,
});
setCalculating(false);
Expand All @@ -110,7 +110,7 @@ export function ChoseTF({ setResults, nodes, fullyConnectedComponents, component
) : (
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mfrac>
<mi>{p}</mi>
<mi>{label}</mi>
{drivers[0] == "vin" ? (
<msub>
<mi>V</mi>
Expand Down
91 changes: 61 additions & 30 deletions src/ComponentAdjuster.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ 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 { useEffect, useState } from "react";
import { units } from "./common";
import { sympyNameGroupsForValueTypes, isCanonicalValueRow } from "./sympyValues.js";

function LabelField({ shapeId, sympyName, onCommit }) {
const [val, setVal] = useState(sympyName);
useEffect(() => {
setVal(sympyName);
}, [sympyName]);

return <TextField label="Name" value={val} size="small" sx={{ minWidth: "10ch" }} onChange={(e) => setVal(e.target.value)} onBlur={() => onCommit(shapeId, val)} />;
}

export function ComponentAdjuster({ componentValues, setComponentValues, schematicComponents, schematicRef }) {
const groups = sympyNameGroupsForValueTypes(schematicComponents);

export function ComponentAdjuster({ componentValues, setComponentValues }) {
function handleValueChange(name, value) {
setComponentValues((prevValues) => ({
...prevValues,
Expand All @@ -26,36 +38,55 @@ export function ComponentAdjuster({ componentValues, setComponentValues }) {
}));
}

function commitLabel(shapeId, raw) {
schematicRef?.current?.setShapeLabel(shapeId, raw);
}

const sortedIds = Object.keys(componentValues).sort((a, b) => Number(a) - Number(b));

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>
<TextField
name={key}
value={componentValues[key].value}
sx={{ width: "8ch" }}
size="small"
onChange={(e) => handleValueChange(key, e.target.value)}
// fullWidth
/>
<FormControl size="small">
<Select value={componentValues[key].unit} onChange={(e) => handleUnitChange(key, e.target.value)}>
{Object.keys(units[componentValues[key].type]).map((opt) => (
<MenuItem key={opt} value={opt} size="small">
{opt}
</MenuItem>
))}
</Select>
</FormControl>
</Stack>
</Card>
</Grid>
))}
{sortedIds.map((key) => {
const el = schematicComponents[key];
const sympyName = el?.sympyName ?? "";
const showValue = isCanonicalValueRow(key, schematicComponents, groups);
const dupList = el ? groups[el.sympyName] : null;
const sharedHint = dupList && dupList.length > 1 && !showValue;

return (
<Grid size={3} key={key}>
<Card sx={{ p: 1, m: 1, width: "100%" }}>

<Stack direction="row" spacing={0} alignItems="center">
<LabelField shapeId={Number(key)} sympyName={sympyName} onCommit={commitLabel} />

{sharedHint ? (
<Typography variant="body2" color="text.secondary" sx={{ ml: 1 }}>
Same symbol as {el.sympyName}; value is set there.
</Typography>
) : null}
{showValue ? (<>
<TextField
name={key}
value={componentValues[key].value}
sx={{ width: "8ch" }}
size="small"
onChange={(e) => handleValueChange(key, e.target.value)}
// fullWidth
/>
<Select value={componentValues[key].unit} onChange={(e) => handleUnitChange(key, e.target.value)} size="small">
{Object.keys(units[componentValues[key].type]).map((opt) => (
<MenuItem key={opt} value={opt} size="small">
{opt}
</MenuItem>
))}
</Select>
</>) : null}
</Stack>
</Card>
</Grid>
);
})}
</Grid>
);
}
Loading
Loading