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
17 changes: 7 additions & 10 deletions lib/components/normal-components/Chip.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import { chipProps } from "@tscircuit/props"
import { pcb_component_invalid_layer_error } from "circuit-json"
import { NormalComponent } from "lib/components/base-components/NormalComponent"
import { type SchematicBoxDimensions } from "lib/utils/schematic/getAllDimensionsForSchematicBox"
import { Trace } from "lib/components/primitive-components/Trace/Trace"
import { Port } from "lib/components/primitive-components/Port"
import type { z } from "zod"
import { Trace } from "lib/components/primitive-components/Trace/Trace"
import { type SchematicBoxDimensions } from "lib/utils/schematic/getAllDimensionsForSchematicBox"

export class Chip<PinLabels extends string = never> extends NormalComponent<
typeof chipProps,
PinLabels
> {
schematicBoxDimensions: SchematicBoxDimensions | null = null

constructor(props: z.input<typeof chipProps>) {
super(props)
}

get config() {
return {
componentName: "Chip",
Expand Down Expand Up @@ -85,13 +80,15 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
const { db } = this.root!
const { _parsedProps: props } = this

const source_component = db.source_component.insert({
ftype: "simple_chip",
const sourceComponentInput = {
ftype: "simple_chip" as const,
name: this.name,
manufacturer_part_number: props.manufacturerPartNumber,
supplier_part_numbers: props.supplierPartNumbers,
display_name: props.displayName,
})
is_simulation_boundary: props.simulationBoundary,
}
const source_component = db.source_component.insert(sourceComponentInput)

this.source_component_id = source_component.source_component_id!
}
Expand Down
23 changes: 16 additions & 7 deletions lib/components/primitive-components/AnalogSimulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,27 @@ export class AnalogSimulation extends PrimitiveComponent<

doInitialSimulationRender(): void {
const { db } = this.root!
const { duration, name, startTime, timePerStep, spiceOptions } =
this._parsedProps
const {
duration,
name,
simulationType,
startTime,
timePerStep,
timeout,
spiceOptions,
} = this._parsedProps

const durationMs = duration || 10 // ms
const timePerStepMs = timePerStep || 0.01 // ms
const isTransient = simulationType === "spice_transient_analysis"
const durationMs = isTransient ? duration || 10 : undefined
const timePerStepMs = isTransient ? timePerStep || 0.01 : undefined

const simulationExperiment = db.simulation_experiment.insert({
name: name ?? "spice_transient_analysis",
experiment_type: "spice_transient_analysis" as const,
name: name ?? simulationType,
experiment_type: simulationType,
end_time_ms: durationMs,
start_time_ms: startTime,
start_time_ms: isTransient ? startTime : undefined,
time_per_step: timePerStepMs,
timeout_ms: timeout,
spice_options: spiceOptions,
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SpiceNetlist, circuitJsonToSpice } from "circuit-json-to-spice"
import type { AnyCircuitElement, SimulationCurrentProbe } from "circuit-json"
import { SpiceNetlist, circuitJsonToSpice } from "circuit-json-to-spice"
import Debug from "debug"
import { getTransientVoltageGraphNamesFromSpiceNetlist } from "lib/utils/simulation/get-transient-voltage-graph-names-from-spice-netlist"
import { resetSimulationColorState } from "lib/utils/simulation/getSimulationColorForId"
Expand All @@ -8,13 +8,16 @@ import type { Ammeter } from "../../normal-components/Ammeter"
import type { AnalogSimulation } from "../AnalogSimulation"
import type { VoltageProbe } from "../VoltageProbe"
import type { GraphDisplayOverrides } from "./GraphDisplayOverrides"
import type { Group } from "./Group"
import type { InsertedSimulationGraph } from "./InsertedSimulationGraph"
import {
getAmmeterGraphDisplayOverrides,
getVoltageProbeGraphDisplayOverrides,
} from "./getGraphDisplayOverrides"
import type { InsertedSimulationGraph } from "./InsertedSimulationGraph"
import type { Group } from "./Group"
import { insertSimulationExperimentError } from "./insert-simulation-experiment-error"
import { insertIndependentAxisScopeTraces } from "./insertIndependentAxisScopeTraces"
import { isOperatingPointCurrent } from "./is-operating-point-current"
import { isOperatingPointVoltage } from "./is-operating-point-voltage"
import { isCircuitElementInput } from "./isCircuitElementInput"
import { isCurrentGraph } from "./isCurrentGraph"
import { isVoltageGraph } from "./isVoltageGraph"
Expand Down Expand Up @@ -64,7 +67,10 @@ const getCircuitJsonForAnalogSimulation = ({
if (
element.type === "simulation_transient_voltage_graph" ||
element.type === "simulation_transient_current_graph" ||
element.type === "simulation_unknown_experiment_error"
element.type === "simulation_operating_point_voltage" ||
element.type === "simulation_operating_point_current" ||
element.type === "simulation_unknown_experiment_error" ||
element.type === "simulation_experiment_error"
) {
return false
}
Expand Down Expand Up @@ -125,10 +131,10 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
debug(`Generated SPICE string:\n${spiceString}`)
} catch (error) {
debug(`Failed to convert circuit JSON to SPICE: ${error}`)
root.db.simulation_unknown_experiment_error.insert({
simulation_experiment_id: simulationExperimentId,
error_type: "simulation_unknown_experiment_error",
message: error instanceof Error ? error.message : String(error),
insertSimulationExperimentError({
db: root.db,
simulationExperimentId,
error,
})
continue
}
Expand Down Expand Up @@ -210,11 +216,32 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
const spiceEngine = spiceEngineMap[engineName]

if (!spiceEngine) {
throw new Error(
`SPICE engine "${engineName}" not found in platform config. Available engines: ${JSON.stringify(
Object.keys(spiceEngineMap).filter((k) => k !== "spicey"),
)}`,
)
insertSimulationExperimentError({
db: root.db,
simulationExperimentId,
error: new Error(
`SPICE engine "${engineName}" not found in platform config. Available engines: ${JSON.stringify(
Object.keys(spiceEngineMap).filter((k) => k !== "spicey"),
)}`,
),
})
continue
}

if (
engineName === "spicey" &&
simulationExperiment.experiment_type === "spice_dc_operating_point"
) {
insertSimulationExperimentError({
db: root.db,
simulationExperimentId,
error: {
code: "unsupported_analysis",
message:
'The built-in "spicey" engine does not support DC operating-point analysis; configure an ngspice engine and set spiceEngine="ngspice".',
},
})
continue
}

const effectId = `spice-simulation-${engineName}-${simulationExperimentId}`
Expand All @@ -226,7 +253,9 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
group._queueAsyncEffect(effectId, async () => {
try {
debug(`Running simulation with engine: ${engineName}`)
const result = await spiceEngine.simulate(spiceString)
const result = await spiceEngine.simulate(spiceString, {
timeoutMs: analogSim._parsedProps.timeout,
})

debug(
`Simulation completed, received ${result.simulationResultCircuitJson.length} elements`,
Expand Down Expand Up @@ -273,6 +302,38 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
}
}

if (isOperatingPointVoltage(element)) {
element.simulation_experiment_id =
simulationExperiment.simulation_experiment_id

const probeMatch =
(element.simulation_voltage_probe_id
? voltageProbesById.get(element.simulation_voltage_probe_id)
: undefined) ??
(element.name ? graphNameToProbe.get(element.name) : undefined)
if (probeMatch) {
element.color = probeMatch.color ?? undefined
element.simulation_voltage_probe_id =
probeMatch.simulation_voltage_probe_id ?? undefined
}
}

if (isOperatingPointCurrent(element)) {
element.simulation_experiment_id =
simulationExperiment.simulation_experiment_id

const probeMatch =
(element.simulation_current_probe_id
? currentProbesById.get(element.simulation_current_probe_id)
: undefined) ??
(element.name ? currentProbesByName.get(element.name) : undefined)
if (probeMatch) {
element.color = probeMatch.color
element.simulation_current_probe_id =
probeMatch.simulation_current_probe_id
}
}

const insertedElement = root.db.insert(element)
if (isVoltageGraph(insertedElement)) {
insertedVoltageGraphs.push({
Expand Down Expand Up @@ -301,10 +362,10 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
group._markDirty("SimulationSpiceEngineRender")
} catch (error) {
debug(`Simulation failed for engine ${engineName}: ${error}`)
root.db.simulation_unknown_experiment_error.insert({
simulation_experiment_id: simulationExperimentId,
error_type: "simulation_unknown_experiment_error",
message: error instanceof Error ? error.message : String(error),
insertSimulationExperimentError({
db: root.db,
simulationExperimentId,
error,
})
// Don't throw - allow other engines to continue
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util"
import type { SimulationExperimentErrorCode } from "circuit-json"

const errorCodes = new Set<SimulationExperimentErrorCode>([
"non_convergent",
"timeout",
"missing_model",
"unsupported_analysis",
"invalid_netlist",
"engine_error",
])

const inferErrorCode = (message: string): SimulationExperimentErrorCode => {
const normalized = message.toLowerCase()
if (/timed?\s*out|timeout/.test(normalized)) return "timeout"
if (
/converg|timestep too small|iteration limit|singular matrix/.test(
normalized,
)
) {
return "non_convergent"
}
if (/missing .*model|unknown subckt|unknown model/.test(normalized)) {
return "missing_model"
}
if (/unsupported .*analysis/.test(normalized)) return "unsupported_analysis"
if (/invalid netlist|syntax error|parse error/.test(normalized)) {
return "invalid_netlist"
}
return "engine_error"
}

export const insertSimulationExperimentError = ({
db,
simulationExperimentId,
error,
}: {
db: CircuitJsonUtilObjects
simulationExperimentId: string
error: unknown
}) => {
const errorObject =
error && typeof error === "object"
? (error as {
code?: unknown
diagnostics?: unknown
message?: unknown
})
: undefined
const message =
typeof errorObject?.message === "string"
? errorObject.message
: error instanceof Error
? error.message
: String(error)
const explicitCode = errorObject?.code
const errorCode =
typeof explicitCode === "string" &&
errorCodes.has(explicitCode as SimulationExperimentErrorCode)
? (explicitCode as SimulationExperimentErrorCode)
: inferErrorCode(message)
const rawDiagnostics = errorObject?.diagnostics
const diagnostics = Array.isArray(rawDiagnostics)
? rawDiagnostics.filter((item): item is string => typeof item === "string")
: typeof rawDiagnostics === "string"
? [rawDiagnostics]
: undefined

return db.simulation_experiment_error.insert({
error_type: "simulation_experiment_error",
simulation_experiment_id: simulationExperimentId,
error_code: errorCode,
message,
diagnostics: diagnostics?.length ? diagnostics : undefined,
is_fatal: true,
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { AnyCircuitElementInput } from "circuit-json"

export const isOperatingPointCurrent = (
element: AnyCircuitElementInput,
): element is Extract<
AnyCircuitElementInput,
{ type: "simulation_operating_point_current" }
> => element.type === "simulation_operating_point_current"
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { AnyCircuitElementInput } from "circuit-json"

export const isOperatingPointVoltage = (
element: AnyCircuitElementInput,
): element is Extract<
AnyCircuitElementInput,
{ type: "simulation_operating_point_voltage" }
> => element.type === "simulation_operating_point_voltage"
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
"@tscircuit/matchpack": "^0.0.34",
"@tscircuit/math-utils": "^0.0.36",
"@tscircuit/miniflex": "^0.0.4",
"@tscircuit/ngspice-spice-engine": "^0.0.19",
"@tscircuit/props": "^0.0.577",
"@tscircuit/ngspice-spice-engine": "^0.0.20",
"@tscircuit/props": "^0.0.578",
"@tscircuit/schematic-match-adapt": "^0.0.18",
"@tscircuit/schematic-trace-solver": "^0.0.96",
"@tscircuit/solver-utils": "^0.0.16",
Expand All @@ -64,12 +64,12 @@
"bun-match-svg": "0.0.12",
"calculate-elbow": "^0.0.12",
"chokidar-cli": "^3.0.0",
"circuit-json": "^0.0.447",
"circuit-json": "^0.0.449",
"circuit-json-to-bpc": "^0.0.13",
"circuit-json-to-connectivity-map": "^0.0.23",
"circuit-json-to-gltf": "^0.0.105",
"circuit-json-to-spice": "^0.0.43",
"circuit-to-svg": "^0.0.387",
"circuit-json-to-spice": "^0.0.45",
"circuit-to-svg": "^0.0.388",
"concurrently": "^9.1.2",
"connectivity-map": "^1.0.0",
"debug": "^4.3.6",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading