Skip to content
Draft
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
1 change: 1 addition & 0 deletions lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,6 @@ export { AnalogDcOperatingPointSimulation } from "./primitive-components/AnalogD
export { AnalogDcSweepSimulation } from "./primitive-components/AnalogDcSweepSimulation"
export { AnalogAcSweepSimulation } from "./primitive-components/AnalogAcSweepSimulation"
export { AnalogSweepParameter } from "./primitive-components/AnalogSweepParameter"
export { AnalogMeasurement } from "./primitive-components/AnalogMeasurement"
export { VoltageProbe } from "./primitive-components/VoltageProbe"
export { SpiceModel } from "./primitive-components/SpiceModel"
9 changes: 9 additions & 0 deletions lib/components/normal-components/CurrentSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class CurrentSource extends NormalComponent<
props.frequency !== undefined ||
props.peakToPeakCurrent !== undefined ||
props.waveShape !== undefined ||
props.currentWaveform !== undefined ||
props.acMagnitude !== undefined ||
props.acPhase !== undefined

Expand All @@ -123,6 +124,14 @@ export class CurrentSource extends NormalComponent<
wave_shape: props.waveShape,
phase: props.phase,
duty_cycle: props.dutyCycle,
current_waveform: props.currentWaveform
? {
timestamps_ms: props.currentWaveform.map((point) => point.time),
current_values: props.currentWaveform.map(
(point) => point.current,
),
}
: undefined,
ac_magnitude: props.acMagnitude,
ac_phase: props.acPhase,
})
Expand Down
6 changes: 6 additions & 0 deletions lib/components/normal-components/VoltageSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ export class VoltageSource extends NormalComponent<
fall_time: props.fallTime,
pulse_width: props.pulseWidth,
period: props.period,
voltage_waveform: props.voltageWaveform
? {
timestamps_ms: props.voltageWaveform.map((point) => point.time),
voltage_values: props.voltageWaveform.map((point) => point.voltage),
}
: undefined,
ac_magnitude: props.acMagnitude,
ac_phase: props.acPhase,
})
Expand Down
10 changes: 0 additions & 10 deletions lib/components/primitive-components/AnalogAnalysisSimulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,6 @@ export abstract class AnalogAnalysisSimulation<
)
}

const sweepParameterCount = this.children.filter(
(child) => child.componentName === "AnalogSweepParameter",
).length
if (sweepParameterCount > 1) {
this.renderError(
"An analog simulation can contain at most one sweep parameter.",
)
return null
}

const simulationExperiment = this.insertSimulationExperiment()
this.simulation_experiment_id =
simulationExperiment?.simulation_experiment_id ?? null
Expand Down
22 changes: 22 additions & 0 deletions lib/components/primitive-components/AnalogMeasurement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { analogMeasurementProps } from "@tscircuit/props"
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
import { AnalogTransientSimulation } from "./AnalogTransientSimulation"

export class AnalogMeasurement extends PrimitiveComponent<
typeof analogMeasurementProps
> {
get config() {
return {
componentName: "AnalogMeasurement",
zodProps: analogMeasurementProps,
}
}

doInitialSimulationRender(): void {
if (!(this.parent instanceof AnalogTransientSimulation)) {
this.renderError(
"analog.measurement must be nested directly in analog.transientsimulation.",
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ export const AnalogSweepParameter_doInitialSimulationRender = (
stop: props.stop!,
step: props.step!,
})
if (
props.displayValues &&
props.displayValues.length !== parameterSweepCoordinates.length
) {
analogSweepParameter.renderError(
"analog.sweepparameter displayValues must match the generated coordinate count.",
)
return
}
const simulationScope =
analogSweepParameter.parent.getGroup() ??
analogSweepParameter.parent.getSubcircuit()
Expand All @@ -109,6 +118,8 @@ export const AnalogSweepParameter_doInitialSimulationRender = (
parameter_type: props.parameterType,
parameter_values: parameterSweepCoordinates,
parameter_unit: parameterUnit,
display_parameter_values: props.displayValues,
display_parameter_unit: props.displayUnit,
}

let simulationParameterSweepId: SimulationParameterSweepId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,31 @@ import type { Ammeter } from "../../normal-components/Ammeter"
import type { AnalogAcSweepSimulation } from "../AnalogAcSweepSimulation"
import type { AnalogDcOperatingPointSimulation } from "../AnalogDcOperatingPointSimulation"
import type { AnalogDcSweepSimulation } from "../AnalogDcSweepSimulation"
import type { AnalogMeasurement } from "../AnalogMeasurement"
import type { AnalogSimulation } from "../AnalogSimulation"
import type { AnalogTransientSimulation } from "../AnalogTransientSimulation"
import { AnalogTransientSimulation } from "../AnalogTransientSimulation"
import type { VoltageProbe } from "../VoltageProbe"
import type { GraphDisplayOverrides } from "./GraphDisplayOverrides"
import type { Group } from "./Group"
import type { InsertedSimulationGraph } from "./InsertedSimulationGraph"
import { attachSweepCoordinateToSimulationResult } from "./attachSweepCoordinateToSimulationResult"
import { createSimulationRuns } from "./createSimulationRuns"
import { evaluateAnalogMeasurement } from "./evaluateAnalogMeasurement"
import {
getAmmeterGraphDisplayOverrides,
getVoltageProbeGraphDisplayOverrides,
} from "./getGraphDisplayOverrides"
import {
addImplicitMeasurementVoltageProbes,
isImplicitMeasurementVoltageProbeId,
} from "./implicit-measurement-voltage-probes"
import { insertIndependentAxisScopeTraces } from "./insertIndependentAxisScopeTraces"
import { isCircuitElementInput } from "./isCircuitElementInput"
import { isCurrentGraph } from "./isCurrentGraph"
import { isSimulationCurrentResult } from "./isSimulationCurrentResult"
import { isSimulationVoltageResult } from "./isSimulationVoltageResult"
import { isVoltageGraph } from "./isVoltageGraph"
import { runSpiceSimulations } from "./run-spice-simulations"

type AnalogSimulationComponent =
| AnalogSimulation
Expand Down Expand Up @@ -124,6 +131,7 @@ const getCircuitJsonForAnalogSimulation = ({
element.type === "simulation_dc_sweep_current_graph" ||
element.type === "simulation_ac_sweep_voltage_graph" ||
element.type === "simulation_ac_sweep_current_graph" ||
element.type === "simulation_measurement_result" ||
element.type === "simulation_unknown_experiment_error"
) {
return false
Expand Down Expand Up @@ -175,15 +183,26 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
}

const simulationScope = analogSimulation.getGroup() ?? group
const analogMeasurements =
analogSimulation instanceof AnalogTransientSimulation
? analogSimulation.children.filter(
(child): child is AnalogMeasurement =>
child.componentName === "AnalogMeasurement",
)
: []
const voltageProbes =
simulationScope.selectAll<VoltageProbe>("voltageprobe")
const ammeters = simulationScope.selectAll<Ammeter>("ammeter")
const circuitJson = getCircuitJsonForAnalogSimulation({
const scopedCircuitJson = getCircuitJsonForAnalogSimulation({
circuitJson: root.db.toArray(),
simulationExperimentId,
voltageProbes,
ammeters,
})
const circuitJson =
analogMeasurements.length > 0
? addImplicitMeasurementVoltageProbes(scopedCircuitJson)
: scopedCircuitJson

let simulationRuns: ReturnType<typeof createSimulationRuns>
try {
Expand Down Expand Up @@ -308,12 +327,30 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
// Add simulation results to the database
const insertedVoltageGraphs: InsertedSimulationGraph[] = []
const insertedCurrentGraphs: InsertedSimulationGraph[] = []
const measurementValuesByComponent = new Map<
AnalogMeasurement,
number[]
>(
analogMeasurements.map((analogMeasurement) => [
analogMeasurement,
[],
]),
)

for (const simulationRun of simulationRuns) {
debug(`Running simulation with engine: ${engineName}`)
const simulationResult = await spiceEngine.simulate(
simulationRun.spiceString,
)
const simulationResults = await runSpiceSimulations({
spiceEngine,
simulationRuns,
})

for (const [
simulationRunIndex,
simulationRun,
] of simulationRuns.entries()) {
const insertedSimulationGraphsForRun: InsertedSimulationGraph[] = []
const simulationResult = simulationResults[simulationRunIndex]
if (!simulationResult) {
throw new Error("SPICE simulation result is missing")
}

debug(
`Simulation completed, received ${simulationResult.simulationResultCircuitJson.length} elements`,
Expand All @@ -328,8 +365,8 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
const simulationResultWithSweepCoordinate =
attachSweepCoordinateToSimulationResult({
simulationResult: simulationResultCircuitElement,
simulationParameterSweepCoordinate:
simulationRun.simulationParameterSweepCoordinate,
simulationParameterSweepCoordinates:
simulationRun.simulationParameterSweepCoordinates,
})

if (isVoltageGraph(simulationResultWithSweepCoordinate)) {
Expand Down Expand Up @@ -410,27 +447,79 @@ export function Group_doInitialSimulationSpiceEngineRender(group: Group<any>) {
}
}

const insertedSimulationResult = root.db.insert(
simulationResultWithSweepCoordinate,
)
const isImplicitMeasurementGraph =
isVoltageGraph(simulationResultWithSweepCoordinate) &&
isImplicitMeasurementVoltageProbeId(
simulationResultWithSweepCoordinate.source_probe_id,
)
const insertedSimulationResult = isImplicitMeasurementGraph
? simulationResultWithSweepCoordinate
: root.db.insert(simulationResultWithSweepCoordinate)
if (isVoltageGraph(insertedSimulationResult)) {
insertedVoltageGraphs.push({
const insertedVoltageGraph: InsertedSimulationGraph = {
type: "voltage",
graph: insertedSimulationResult,
})
}
if (!isImplicitMeasurementGraph) {
insertedVoltageGraphs.push(insertedVoltageGraph)
}
insertedSimulationGraphsForRun.push(insertedVoltageGraph)
}
if (isCurrentGraph(insertedSimulationResult)) {
insertedCurrentGraphs.push({
const insertedCurrentGraph: InsertedSimulationGraph = {
type: "current",
graph: insertedSimulationResult,
})
}
insertedCurrentGraphs.push(insertedCurrentGraph)
insertedSimulationGraphsForRun.push(insertedCurrentGraph)
}
debug(
`Inserted ${simulationResultWithSweepCoordinate.type} into database`,
isImplicitMeasurementGraph
? `Collected ${simulationResultWithSweepCoordinate.type} for measurement`
: `Inserted ${simulationResultWithSweepCoordinate.type} into database`,
)
}

for (const analogMeasurement of analogMeasurements) {
const measurementValues =
measurementValuesByComponent.get(analogMeasurement)
if (!measurementValues) {
throw new Error("Analog measurement result collector is missing")
}
measurementValues.push(
evaluateAnalogMeasurement({
analogMeasurement,
simulationScope,
simulationGraphs: insertedSimulationGraphsForRun,
}),
)
}
}

for (const analogMeasurement of analogMeasurements) {
const measurementValues =
measurementValuesByComponent.get(analogMeasurement)
if (!measurementValues) {
throw new Error("Analog measurement result collector is missing")
}
const simulationParameterSweepCoordinateSets = simulationRuns.map(
(simulationRun) =>
simulationRun.simulationParameterSweepCoordinates ?? [],
)
const hasParameterSweep = simulationParameterSweepCoordinateSets.some(
(coordinateSet) => coordinateSet.length > 0,
)
root.db.simulation_measurement_result.insert({
simulation_experiment_id: simulationExperimentId,
name: analogMeasurement._parsedProps.name,
measurement_values: measurementValues,
measurement_unit: analogMeasurement._parsedProps.unit,
simulation_parameter_sweep_coordinate_sets: hasParameterSweep
? simulationParameterSweepCoordinateSets
: undefined,
})
}

if (analogSimulation.usesIndependentGraphAxes()) {
insertIndependentAxisScopeTraces({
db: root.db,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { isSimulationAnalysisResult } from "./isSimulationAnalysisResult"

export const attachSweepCoordinateToSimulationResult = ({
simulationResult,
simulationParameterSweepCoordinate,
simulationParameterSweepCoordinates,
}: {
simulationResult: AnyCircuitElementInput
simulationParameterSweepCoordinate?: SimulationParameterSweepCoordinate
simulationParameterSweepCoordinates?: SimulationParameterSweepCoordinate[]
}): AnyCircuitElementInput => {
if (
!simulationParameterSweepCoordinate ||
!simulationParameterSweepCoordinates?.length ||
!isSimulationAnalysisResult(simulationResult)
) {
return simulationResult
Expand All @@ -24,7 +24,12 @@ export const attachSweepCoordinateToSimulationResult = ({
simulationResultWithSweepCoordinate,
simulationResultIdKey,
)
simulationResultWithSweepCoordinate.simulation_parameter_sweep_coordinate =
simulationParameterSweepCoordinate
if (simulationParameterSweepCoordinates.length === 1) {
simulationResultWithSweepCoordinate.simulation_parameter_sweep_coordinate =
simulationParameterSweepCoordinates[0]
} else {
simulationResultWithSweepCoordinate.simulation_parameter_sweep_coordinates =
simulationParameterSweepCoordinates
}
return simulationResultWithSweepCoordinate
}
Loading
Loading