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 src/any_circuit_element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export const any_circuit_element = z.union([
sim.simulation_switch,
sim.simulation_voltage_probe,
sim.simulation_current_probe,
sim.simulation_oscilloscope_trace,
sim.simulation_unknown_experiment_error,
sim.simulation_op_amp,
sim.simulation_spice_subcircuit,
Expand Down
1 change: 1 addition & 0 deletions src/simulation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from "./simulation_current_probe"
export * from "./simulation_unknown_experiment_error"
export * from "./simulation_op_amp"
export * from "./simulation_spice_subcircuit"
export * from "./simulation_oscilloscope_trace"
23 changes: 0 additions & 23 deletions src/simulation/simulation_current_probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import { z } from "zod"
import { getZodPrefixedIdWithDefault } from "src/common"
import { expectTypesMatch } from "src/utils/expect-types-match"

export const simulation_current_probe_display_options = z.object({
label: z.string().optional(),
center: z.number().optional(),
offset_divs: z.number().optional(),
units_per_div: z.number().optional(),
})

export const simulation_current_probe = z
.object({
type: z.literal("simulation_current_probe"),
Expand All @@ -23,7 +16,6 @@ export const simulation_current_probe = z
negative_source_net_id: z.string().optional(),
subcircuit_id: z.string().optional(),
color: z.string().optional(),
display_options: simulation_current_probe_display_options.optional(),
})
.describe(
"Defines a current probe for simulation. It measures current flowing from the positive endpoint to the negative endpoint.",
Expand Down Expand Up @@ -77,9 +69,6 @@ export type SimulationCurrentProbeInput = z.input<
typeof simulation_current_probe
>
type InferredSimulationCurrentProbe = z.infer<typeof simulation_current_probe>
type InferredSimulationCurrentProbeDisplayOptions = z.infer<
typeof simulation_current_probe_display_options
>

/**
* Defines a current probe for simulation. It measures current flowing from the
Expand All @@ -96,18 +85,6 @@ export interface SimulationCurrentProbe {
negative_source_net_id?: string
subcircuit_id?: string
color?: string
display_options?: SimulationCurrentProbeDisplayOptions
}

export interface SimulationCurrentProbeDisplayOptions {
label?: string
center?: number
offset_divs?: number
units_per_div?: number
}

expectTypesMatch<
SimulationCurrentProbeDisplayOptions,
InferredSimulationCurrentProbeDisplayOptions
>(true)
expectTypesMatch<SimulationCurrentProbe, InferredSimulationCurrentProbe>(true)
93 changes: 93 additions & 0 deletions src/simulation/simulation_oscilloscope_trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { z } from "zod"
import { getZodPrefixedIdWithDefault } from "src/common"
import { expectTypesMatch } from "src/utils/expect-types-match"

const hasValue = (value: unknown) => value !== undefined

export const simulation_oscilloscope_trace = z
.object({
type: z.literal("simulation_oscilloscope_trace"),
simulation_oscilloscope_trace_id: getZodPrefixedIdWithDefault(
"simulation_oscilloscope_trace",
),
simulation_transient_voltage_graph_id: z.string().optional(),
simulation_transient_current_graph_id: z.string().optional(),
simulation_voltage_probe_id: z.string().optional(),
simulation_current_probe_id: z.string().optional(),
display_name: z.string().optional(),
color: z.string().optional(),
volts_per_div: z.number().positive().optional(),
amps_per_div: z.number().positive().optional(),
vertical_center: z.number().optional(),
vertical_offset_divs: z.number().optional(),
})
.describe(
"Defines how a simulation probe or transient graph is rendered as an oscilloscope-style trace.",
)
.superRefine((data, ctx) => {
const voltageReferences = [
data.simulation_transient_voltage_graph_id,
data.simulation_voltage_probe_id,
].filter(hasValue).length
const currentReferences = [
data.simulation_transient_current_graph_id,
data.simulation_current_probe_id,
].filter(hasValue).length

if (voltageReferences + currentReferences !== 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"An oscilloscope trace must reference exactly one voltage graph, current graph, voltage probe, or current probe.",
})
}

if (voltageReferences > 0 && data.amps_per_div !== undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Voltage oscilloscope traces must use volts_per_div, not amps_per_div.",
})
}

if (currentReferences > 0 && data.volts_per_div !== undefined) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Current oscilloscope traces must use amps_per_div, not volts_per_div.",
})
}
})

export type SimulationOscilloscopeTraceInput = z.input<
typeof simulation_oscilloscope_trace
>
type InferredSimulationOscilloscopeTrace = z.infer<
typeof simulation_oscilloscope_trace
>

/**
* Defines how a simulation probe or transient graph is rendered as an
* oscilloscope-style trace. Scale and legend properties live here because they
* describe the relationship between a measurement and a graph, not the probe
* itself.
*/
export interface SimulationOscilloscopeTrace {
type: "simulation_oscilloscope_trace"
simulation_oscilloscope_trace_id: string
simulation_transient_voltage_graph_id?: string
simulation_transient_current_graph_id?: string
simulation_voltage_probe_id?: string
simulation_current_probe_id?: string
display_name?: string
color?: string
volts_per_div?: number
amps_per_div?: number
vertical_center?: number
vertical_offset_divs?: number
}

expectTypesMatch<
SimulationOscilloscopeTrace,
InferredSimulationOscilloscopeTrace
>(true)
23 changes: 0 additions & 23 deletions src/simulation/simulation_voltage_probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import { z } from "zod"
import { getZodPrefixedIdWithDefault } from "src/common"
import { expectTypesMatch } from "src/utils/expect-types-match"

export const simulation_voltage_probe_display_options = z.object({
label: z.string().optional(),
center: z.number().optional(),
offset_divs: z.number().optional(),
units_per_div: z.number().optional(),
})

export const simulation_voltage_probe = z
.object({
type: z.literal("simulation_voltage_probe"),
Expand All @@ -23,7 +16,6 @@ export const simulation_voltage_probe = z
reference_input_source_net_id: z.string().optional(),
subcircuit_id: z.string().optional(),
color: z.string().optional(),
display_options: simulation_voltage_probe_display_options.optional(),
})
.describe(
"Defines a voltage probe for simulation. If a reference input is not provided, it measures against ground. If a reference input is provided, it measures the differential voltage between two points.",
Expand Down Expand Up @@ -87,9 +79,6 @@ export type SimulationVoltageProbeInput = z.input<
typeof simulation_voltage_probe
>
type InferredSimulationVoltageProbe = z.infer<typeof simulation_voltage_probe>
type InferredSimulationVoltageProbeDisplayOptions = z.infer<
typeof simulation_voltage_probe_display_options
>

/**
* Defines a voltage probe for simulation. If a reference input is not provided,
Expand All @@ -107,18 +96,6 @@ export interface SimulationVoltageProbe {
reference_input_source_net_id?: string
subcircuit_id?: string
color?: string
display_options?: SimulationVoltageProbeDisplayOptions
}

export interface SimulationVoltageProbeDisplayOptions {
label?: string
center?: number
offset_divs?: number
units_per_div?: number
}

expectTypesMatch<
SimulationVoltageProbeDisplayOptions,
InferredSimulationVoltageProbeDisplayOptions
>(true)
expectTypesMatch<SimulationVoltageProbe, InferredSimulationVoltageProbe>(true)
16 changes: 1 addition & 15 deletions tests/simulation_current_probe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,12 @@ test("simulation_current_probe with one port id should throw", () => {
expect(() => simulation_current_probe.parse(input)).toThrow()
})

test("simulation_current_probe parses with name and display options", () => {
test("simulation_current_probe parses with name", () => {
const input: SimulationCurrentProbeInput = {
type: "simulation_current_probe",
positive_source_net_id: "net1",
negative_source_net_id: "net2",
name: "My Current Probe",
color: "#ff8c00",
display_options: {
label: "IO",
center: 0.01,
offset_divs: 2,
units_per_div: 0.001,
},
}

const result = simulation_current_probe.parse(input)
Expand All @@ -82,13 +75,6 @@ test("simulation_current_probe parses with name and display options", () => {
expect(probe.positive_source_net_id).toBe("net1")
expect(probe.negative_source_net_id).toBe("net2")
expect(probe.name).toBe("My Current Probe")
expect(probe.display_options).toEqual({
label: "IO",
center: 0.01,
offset_divs: 2,
units_per_div: 0.001,
})
expect(probe.color).toBe("#ff8c00")
expect(() => any_circuit_element.parse(input)).not.toThrow()
})

Expand Down
50 changes: 50 additions & 0 deletions tests/simulation_experiment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "../src/simulation/simulation_experiment"
import { simulation_transient_voltage_graph } from "../src/simulation/simulation_transient_voltage_graph"
import { simulation_transient_current_graph } from "../src/simulation/simulation_transient_current_graph"
import { simulation_oscilloscope_trace } from "../src/simulation/simulation_oscilloscope_trace"
import { any_circuit_element } from "../src/any_circuit_element"

test("simulation_experiment requires valid experiment_type", () => {
Expand Down Expand Up @@ -86,3 +87,52 @@ test("simulation_transient_current_graph parses required data", () => {
expect(graph.start_time_ms).toBe(0)
expect(graph.end_time_ms).toBe(2)
})

test("simulation_oscilloscope_trace configures voltage graph display", () => {
const trace = simulation_oscilloscope_trace.parse({
type: "simulation_oscilloscope_trace",
simulation_transient_voltage_graph_id:
"simulation_transient_voltage_graph_123",
display_name: "VOUT",
color: "#315cff",
volts_per_div: 0.05,
vertical_center: 3.3,
vertical_offset_divs: 3,
})

expect(trace.simulation_oscilloscope_trace_id).toBeString()
expect(trace.display_name).toBe("VOUT")
expect(trace.volts_per_div).toBe(0.05)
expect(() => any_circuit_element.parse(trace)).not.toThrow()
})

test("simulation_oscilloscope_trace configures current probe display", () => {
const trace = simulation_oscilloscope_trace.parse({
type: "simulation_oscilloscope_trace",
simulation_current_probe_id: "simulation_current_probe_123",
display_name: "IOUT",
amps_per_div: 0.001,
})

expect(trace.simulation_current_probe_id).toBe("simulation_current_probe_123")
expect(trace.amps_per_div).toBe(0.001)
expect(() => any_circuit_element.parse(trace)).not.toThrow()
})

test("simulation_oscilloscope_trace rejects ambiguous references and units", () => {
expect(() =>
simulation_oscilloscope_trace.parse({
type: "simulation_oscilloscope_trace",
simulation_voltage_probe_id: "simulation_voltage_probe_123",
simulation_current_probe_id: "simulation_current_probe_123",
}),
).toThrow()

expect(() =>
simulation_oscilloscope_trace.parse({
type: "simulation_oscilloscope_trace",
simulation_voltage_probe_id: "simulation_voltage_probe_123",
amps_per_div: 0.001,
}),
).toThrow()
})
27 changes: 0 additions & 27 deletions tests/simulation_voltage_probe.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,33 +96,6 @@ test("simulation_voltage_probe parses with name", () => {
expect(probe.name).toBe("My Probe")
})

test("simulation_voltage_probe parses with display options", () => {
const input: SimulationVoltageProbeInput = {
type: "simulation_voltage_probe",
signal_input_source_net_id: "net1",
name: "VOUT_PROBE",
color: "#315cff",
display_options: {
label: "VO",
center: 3.3,
offset_divs: 3,
units_per_div: 0.05,
},
}

const result = simulation_voltage_probe.parse(input)
const probe = result as SimulationVoltageProbe

expect(probe.display_options).toEqual({
label: "VO",
center: 3.3,
offset_divs: 3,
units_per_div: 0.05,
})
expect(probe.color).toBe("#315cff")
expect(() => any_circuit_element.parse(input)).not.toThrow()
})

test("simulation_voltage_probe (differential) with mixed connections should throw", () => {
const input: SimulationVoltageProbeInput = {
type: "simulation_voltage_probe",
Expand Down
Loading