summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--circuit20
-rw-r--r--circuit.svg1
-rw-r--r--cirsim/component.go109
-rw-r--r--cirsim/models.go144
-rw-r--r--cirsim/node.go67
-rw-r--r--cirsim/simulation.go374
6 files changed, 389 insertions, 326 deletions
diff --git a/circuit b/circuit
new file mode 100644
index 0000000..6925e08
--- /dev/null
+++ b/circuit
@@ -0,0 +1,20 @@
+800 500
+
+730 242
+355 389
+212 128
+63 174
+178 363
+388 133
+580 236
+
+244 397 power 5 2
+681 112 power 7 1
+302 227 power 3 6
+113 330 resistor 4 5
+120 60 resistor 4 3
+122 132 resistor 4 3
+304 93 resistor 3 6
+467 382 resistor 2 7
+505 79 resistor 6 7
+670 373 resistor 7 1
diff --git a/circuit.svg b/circuit.svg
new file mode 100644
index 0000000..86c1d65
--- /dev/null
+++ b/circuit.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 500" style="stroke:#000;stroke-width:3;stroke-linecap:round"><line x1="178" y1="397" x2="189" y2="397"/><line x1="178" y1="397" x2="178" y2="363"/><line x1="299" y1="397" x2="355" y2="397"/><line x1="355" y1="397" x2="355" y2="389"/><line x1="580" y1="112" x2="626" y2="112"/><line x1="580" y1="112" x2="580" y2="236"/><line x1="730" y1="112" x2="736" y2="112"/><line x1="730" y1="112" x2="730" y2="242"/><line x1="212" y1="227" x2="247" y2="227"/><line x1="212" y1="227" x2="212" y2="128"/><line x1="357" y1="227" x2="388" y2="227"/><line x1="388" y1="227" x2="388" y2="133"/><line x1="58" y1="330" x2="63" y2="330"/><line x1="63" y1="330" x2="63" y2="174"/><line x1="168" y1="330" x2="178" y2="330"/><line x1="178" y1="330" x2="178" y2="363"/><line x1="63" y1="60" x2="65" y2="60"/><line x1="63" y1="60" x2="63" y2="174"/><line x1="175" y1="60" x2="212" y2="60"/><line x1="212" y1="60" x2="212" y2="128"/><line x1="63" y1="132" x2="67" y2="132"/><line x1="63" y1="132" x2="63" y2="174"/><line x1="177" y1="132" x2="212" y2="132"/><line x1="212" y1="132" x2="212" y2="128"/><line x1="212" y1="93" x2="249" y2="93"/><line x1="212" y1="93" x2="212" y2="128"/><line x1="359" y1="93" x2="388" y2="93"/><line x1="388" y1="93" x2="388" y2="133"/><line x1="355" y1="382" x2="412" y2="382"/><line x1="355" y1="382" x2="355" y2="389"/><line x1="522" y1="382" x2="580" y2="382"/><line x1="580" y1="382" x2="580" y2="236"/><line x1="388" y1="79" x2="450" y2="79"/><line x1="388" y1="79" x2="388" y2="133"/><line x1="560" y1="79" x2="580" y2="79"/><line x1="580" y1="79" x2="580" y2="236"/><line x1="580" y1="373" x2="615" y2="373"/><line x1="580" y1="373" x2="580" y2="236"/><line x1="725" y1="373" x2="730" y2="373"/><line x1="730" y1="373" x2="730" y2="242"/><line x1="229" y1="422" x2="229" y2="372"/><line x1="259" y1="412" x2="259" y2="382"/><line x1="214" y1="377" x2="214" y2="387"/><line x1="209" y1="382" x2="219" y2="382"/><line x1="269" y1="382" x2="279" y2="382"/><line x1="259" y1="397" x2="299" y2="397"/><line x1="189" y1="397" x2="229" y2="397"/><line x1="666" y1="137" x2="666" y2="87"/><line x1="696" y1="127" x2="696" y2="97"/><line x1="651" y1="92" x2="651" y2="102"/><line x1="646" y1="97" x2="656" y2="97"/><line x1="706" y1="97" x2="716" y2="97"/><line x1="696" y1="112" x2="736" y2="112"/><line x1="626" y1="112" x2="666" y2="112"/><line x1="287" y1="252" x2="287" y2="202"/><line x1="317" y1="242" x2="317" y2="212"/><line x1="272" y1="207" x2="272" y2="217"/><line x1="267" y1="212" x2="277" y2="212"/><line x1="327" y1="212" x2="337" y2="212"/><line x1="317" y1="227" x2="357" y2="227"/><line x1="247" y1="227" x2="287" y2="227"/><line x1="73" y1="350" x2="153" y2="350"/><line x1="73" y1="310" x2="153" y2="310"/><line x1="73" y1="350" x2="73" y2="310"/><line x1="153" y1="350" x2="153" y2="310"/><line x1="153" y1="330" x2="168" y2="330"/><line x1="58" y1="330" x2="73" y2="330"/><line x1="80" y1="80" x2="160" y2="80"/><line x1="80" y1="40" x2="160" y2="40"/><line x1="80" y1="80" x2="80" y2="40"/><line x1="160" y1="80" x2="160" y2="40"/><line x1="160" y1="60" x2="175" y2="60"/><line x1="65" y1="60" x2="80" y2="60"/><line x1="82" y1="152" x2="162" y2="152"/><line x1="82" y1="112" x2="162" y2="112"/><line x1="82" y1="152" x2="82" y2="112"/><line x1="162" y1="152" x2="162" y2="112"/><line x1="162" y1="132" x2="177" y2="132"/><line x1="67" y1="132" x2="82" y2="132"/><line x1="264" y1="113" x2="344" y2="113"/><line x1="264" y1="73" x2="344" y2="73"/><line x1="264" y1="113" x2="264" y2="73"/><line x1="344" y1="113" x2="344" y2="73"/><line x1="344" y1="93" x2="359" y2="93"/><line x1="249" y1="93" x2="264" y2="93"/><line x1="427" y1="402" x2="507" y2="402"/><line x1="427" y1="362" x2="507" y2="362"/><line x1="427" y1="402" x2="427" y2="362"/><line x1="507" y1="402" x2="507" y2="362"/><line x1="507" y1="382" x2="522" y2="382"/><line x1="412" y1="382" x2="427" y2="382"/><line x1="465" y1="99" x2="545" y2="99"/><line x1="465" y1="59" x2="545" y2="59"/><line x1="465" y1="99" x2="465" y2="59"/><line x1="545" y1="99" x2="545" y2="59"/><line x1="545" y1="79" x2="560" y2="79"/><line x1="450" y1="79" x2="465" y2="79"/><line x1="630" y1="393" x2="710" y2="393"/><line x1="630" y1="353" x2="710" y2="353"/><line x1="630" y1="393" x2="630" y2="353"/><line x1="710" y1="393" x2="710" y2="353"/><line x1="710" y1="373" x2="725" y2="373"/><line x1="615" y1="373" x2="630" y2="373"/></svg>
diff --git a/cirsim/component.go b/cirsim/component.go
index 5b03fd2..358eee3 100644
--- a/cirsim/component.go
+++ b/cirsim/component.go
@@ -12,26 +12,25 @@ import (
"github.com/wcharczuk/go-chart/v2/drawing"
)
-type Component struct {
+type component struct {
widget.BaseWidget
- Pos fyne.Position
- Model Modeler
- CurrentOverTime []float64
- ANode *Node
- BNode *Node
- CurrentRange chart.Range
- Image *canvas.Image
+ modeler
+ pos fyne.Position
+ currentOverTime []float64
+ nodes [2]int
+ currentRange chart.Range
+ chart *canvas.Image
labels []*widget.Label
entries []*widget.Entry
}
-func NewComponent(settings io.Reader, r chart.Range, nodes []*Node, update func()) *Component {
- var c Component
+func newComponent(settings io.Reader, r chart.Range, update func()) *component {
+ var c component
var t string
var a int
var b int
_, err := fmt.Fscanf(settings, "%f %f %s %d %d\n",
- &c.Pos.X, &c.Pos.Y, &t, &a, &b,
+ &c.pos.X, &c.pos.Y, &t, &a, &b,
)
if err != nil {
return nil
@@ -39,25 +38,23 @@ func NewComponent(settings io.Reader, r chart.Range, nodes []*Node, update func(
if a <= 0 || b <= 0 {
log.Fatal("unconnected components in the circuit")
}
- c.ANode = nodes[a-1]
- nodes[a-1].AComponents = append(nodes[a-1].AComponents, &c)
- c.BNode = nodes[b-1]
- nodes[b-1].BComponents = append(nodes[b-1].BComponents, &c)
- switch t {
- case "resistor":
- c.Model = &Resistor{100.0}
- case "capacitor":
- c.Model = &Capacitor{0.000001}
- case "inductor":
- c.Model = &Inductor{0.000001}
- case "diode":
- c.Model = &Diode{}
- case "power":
- c.Model = &Power{Current: 1.0, Frequency: 1000.0}
+ c.nodes[0] = a - 1
+ c.nodes[1] = b - 1
+ c.setupModeler(t, update)
+ c.currentRange = r
+ c.currentOverTime = make([]float64, iterations)
+ for i := range c.currentOverTime {
+ c.currentOverTime[i] = 0
}
+ c.renderChart()
+ return &c
+}
+
+func (c *component) setupModeler(name string, update func()) {
+ c.modeler = newModeler(name)
c.entries = make([]*widget.Entry, 0)
c.labels = make([]*widget.Label, 0)
- for k, v := range c.Model.Parameters() {
+ for k, v := range c.parameters() {
e := widget.NewEntry()
e.TextStyle.Monospace = true
e.SetPlaceHolder(k)
@@ -66,9 +63,9 @@ func NewComponent(settings io.Reader, r chart.Range, nodes []*Node, update func(
var v float64
_, err := fmt.Sscanf(s+"\n", "%f\n", &v)
if err != nil {
- e.SetText(fmt.Sprintf("%f", c.Model.Parameters()[k]))
+ e.SetText(fmt.Sprintf("%f", c.parameters()[k]))
} else {
- c.Model.UpdateParameter(k, v)
+ c.updateParameter(k, v)
update()
}
}
@@ -77,36 +74,29 @@ func NewComponent(settings io.Reader, r chart.Range, nodes []*Node, update func(
l.TextStyle.Monospace = true
c.labels = append(c.labels, l)
}
- c.CurrentRange = r
- c.CurrentOverTime = make([]float64, 1000)
- for i := range c.CurrentOverTime {
- c.CurrentOverTime[i] = 0
- }
- c.renderChart()
- return &c
}
-func (c *Component) renderChart() {
+func (c *component) renderChart() {
graph := chart.Chart{
- Width: 140,
- Height: 60,
+ Width: chartWidth,
+ Height: chartHeight,
ColorPalette: &componentColorPalette{},
XAxis: chart.HideXAxis(),
YAxis: chart.YAxis{
Style: chart.Hidden(),
- Range: c.CurrentRange,
+ Range: c.currentRange,
},
Series: []chart.Series{
chart.ContinuousSeries{
- XValues: chart.LinearRange(0, 999),
- YValues: c.CurrentOverTime,
+ XValues: chart.LinearRange(0, float64(iterations)-1),
+ YValues: c.currentOverTime,
},
},
}
writer := &chart.ImageWriter{}
graph.Render(chart.PNG, writer)
img, _ := writer.Image()
- c.Image = canvas.NewImageFromImage(img)
+ c.chart = canvas.NewImageFromImage(img)
}
type componentColorPalette struct{}
@@ -118,7 +108,12 @@ func (*componentColorPalette) BackgroundStrokeColor() drawing.Color {
return drawing.ColorTransparent
}
func (*componentColorPalette) CanvasColor() drawing.Color {
- return drawing.Color{R: 191, G: 254, B: 247, A: 128}
+ return drawing.Color{
+ R: currentCanvasR,
+ G: currentCanvasG,
+ B: currentCanvasB,
+ A: currentCanvasA,
+ }
}
func (*componentColorPalette) CanvasStrokeColor() drawing.Color {
return drawing.ColorTransparent
@@ -130,25 +125,25 @@ func (*componentColorPalette) TextColor() drawing.Color {
return drawing.ColorTransparent
}
func (*componentColorPalette) GetSeriesColor(index int) drawing.Color {
- return drawing.Color{R: 3, G: 247, B: 198, A: 255}
+ return drawing.Color{R: currentR, G: currentG, B: currentB, A: currentA}
}
-func (c *Component) CreateRenderer() fyne.WidgetRenderer { return c }
-func (c *Component) Layout(s fyne.Size) {
+func (c *component) CreateRenderer() fyne.WidgetRenderer { return c }
+func (c *component) Layout(s fyne.Size) {
pos := fyne.NewPos(0, 0)
for i, e := range c.entries {
- c.labels[i].Resize(fyne.NewSize(140, c.labels[i].MinSize().Height))
+ c.labels[i].Resize(fyne.NewSize(chartWidth, c.labels[i].MinSize().Height))
c.labels[i].Move(pos)
pos.Y += c.labels[i].MinSize().Height
- e.Resize(fyne.NewSize(140, e.MinSize().Height))
+ e.Resize(fyne.NewSize(chartWidth, e.MinSize().Height))
e.Move(pos)
pos.Y += e.MinSize().Height
}
- c.Image.Resize(fyne.NewSize(140, 60))
- c.Image.Move(pos)
+ c.chart.Resize(fyne.NewSize(chartWidth, chartHeight))
+ c.chart.Move(pos)
}
-func (c *Component) MinSize() fyne.Size {
- res := fyne.NewSize(140, 60)
+func (c *component) MinSize() fyne.Size {
+ res := fyne.NewSize(chartWidth, chartHeight)
for _, e := range c.entries {
res.Height += e.MinSize().Height
}
@@ -157,7 +152,7 @@ func (c *Component) MinSize() fyne.Size {
}
return res
}
-func (c *Component) Refresh() {
+func (c *component) Refresh() {
c.renderChart()
for _, e := range c.entries {
e.Refresh()
@@ -166,13 +161,13 @@ func (c *Component) Refresh() {
l.Refresh()
}
}
-func (c *Component) Destroy() {}
-func (c *Component) Objects() []fyne.CanvasObject {
+func (c *component) Destroy() {}
+func (c *component) Objects() []fyne.CanvasObject {
res := []fyne.CanvasObject{}
for i, e := range c.entries {
res = append(res, c.labels[i])
res = append(res, e)
}
- res = append(res, c.Image)
+ res = append(res, c.chart)
return res
}
diff --git a/cirsim/models.go b/cirsim/models.go
index f4a73e9..70710d7 100644
--- a/cirsim/models.go
+++ b/cirsim/models.go
@@ -1,102 +1,144 @@
package cirsim
-import "math"
+import (
+ "log"
+ "math"
+)
-type Modeler interface {
- ModelConductance(time, delta, voltage, current float64) float64
- ModelCurrent(time, delta, voltage, current float64) float64
- Parameters() map[string]float64
- UpdateParameter(name string, value float64)
+type modeler interface {
+ conductance(time, delta, voltage, current float64) float64
+ current(time, delta, voltage, current float64) float64
+ parameters() map[string]float64
+ updateParameter(name string, value float64)
}
-type Capacitor struct {
- Capacitance float64
+func newModeler(name string) modeler {
+ switch name {
+ case "resistor":
+ return newResistor()
+ case "capacitor":
+ return newCapacitor()
+ case "inductor":
+ return newInductor()
+ case "diode":
+ return newDiode()
+ case "power":
+ return newPower()
+ default:
+ log.Fatal("wrong component name")
+ // compiler wants return here, but it will be never executed:
+ return nil
+ }
+}
+
+type capacitor struct {
+ capacitance float64
}
-func (m *Capacitor) ModelConductance(time, delta, voltage, current float64) float64 {
- return 2.0 * m.Capacitance / delta
+func newCapacitor() *capacitor {
+ return &capacitor{0.000001}
}
-func (m *Capacitor) ModelCurrent(time, delta, voltage, current float64) float64 {
- return -current - voltage*2*m.Capacitance/delta
+
+func (m *capacitor) conductance(time, delta, voltage, current float64) float64 {
+ return 2.0 * m.capacitance / delta
}
-func (m *Capacitor) Parameters() map[string]float64 {
- return map[string]float64{"Capacitance": m.Capacitance}
+func (m *capacitor) current(time, delta, voltage, current float64) float64 {
+ return -current - voltage*2*m.capacitance/delta
}
-func (m *Capacitor) UpdateParameter(name string, value float64) {
+func (m *capacitor) parameters() map[string]float64 {
+ return map[string]float64{"Capacitance": m.capacitance}
+}
+func (m *capacitor) updateParameter(name string, value float64) {
if name == "Capacitance" {
- m.Capacitance = value
+ m.capacitance = value
}
}
-type Resistor struct {
- Resistance float64
+type resistor struct {
+ resistance float64
+}
+
+func newResistor() *resistor {
+ return &resistor{100.0}
}
-func (m *Resistor) ModelConductance(time, delta, voltage, current float64) float64 {
- return 1.0 / m.Resistance
+func (m *resistor) conductance(time, delta, voltage, current float64) float64 {
+ return 1.0 / m.resistance
}
-func (m *Resistor) ModelCurrent(time, delta, voltage, current float64) float64 {
+func (m *resistor) current(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *Resistor) Parameters() map[string]float64 {
- return map[string]float64{"Resistance": m.Resistance}
+func (m *resistor) parameters() map[string]float64 {
+ return map[string]float64{"Resistance": m.resistance}
}
-func (m *Resistor) UpdateParameter(name string, value float64) {
+func (m *resistor) updateParameter(name string, value float64) {
if name == "Resistance" {
- m.Resistance = value
+ m.resistance = value
}
}
-type Inductor struct {
- Inductance float64
+type inductor struct {
+ inductance float64
}
-func (m *Inductor) ModelConductance(time, delta, voltage, current float64) float64 {
- return delta / (2.0 * m.Inductance)
+func newInductor() *inductor {
+ return &inductor{0.000001}
}
-func (m *Inductor) ModelCurrent(time, delta, voltage, current float64) float64 {
- return current + voltage*delta/(2*m.Inductance)
+
+func (m *inductor) conductance(time, delta, voltage, current float64) float64 {
+ return delta / (2.0 * m.inductance)
+}
+func (m *inductor) current(time, delta, voltage, current float64) float64 {
+ return current + voltage*delta/(2*m.inductance)
}
-func (m *Inductor) Parameters() map[string]float64 {
- return map[string]float64{"Inductance": m.Inductance}
+func (m *inductor) parameters() map[string]float64 {
+ return map[string]float64{"Inductance": m.inductance}
}
-func (m *Inductor) UpdateParameter(name string, value float64) {
+func (m *inductor) updateParameter(name string, value float64) {
if name == "Inductance" {
- m.Inductance = value
+ m.inductance = value
}
}
-type Diode struct{}
+type diode struct{}
+
+func newDiode() *diode {
+ return &diode{}
+}
-func (m *Diode) ModelConductance(time, delta, voltage, current float64) float64 {
+func (m *diode) conductance(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *Diode) ModelCurrent(time, delta, voltage, current float64) float64 {
+func (m *diode) current(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *Diode) Parameters() map[string]float64 {
+func (m *diode) parameters() map[string]float64 {
return map[string]float64{}
}
-func (m *Diode) UpdateParameter(name string, value float64) {}
+func (m *diode) updateParameter(name string, value float64) {}
+
+type power struct {
+ maxCurrent float64
+ frequency float64
+}
-type Power struct {
- Current float64
- Frequency float64
+func newPower() *power {
+ return &power{maxCurrent: 1.0, frequency: 1000.0}
}
-func (m *Power) ModelConductance(time, delta, voltage, current float64) float64 {
+func (m *power) conductance(time, delta, voltage, current float64) float64 {
return 0
}
-func (m *Power) ModelCurrent(time, delta, voltage, current float64) float64 {
- return m.Current * math.Sin(time*m.Frequency*2*math.Pi)
+func (m *power) current(time, delta, voltage, current float64) float64 {
+ return m.maxCurrent * math.Sin(time*m.frequency*2*math.Pi)
}
-func (m *Power) Parameters() map[string]float64 {
- return map[string]float64{"Current": m.Current, "Frequency": m.Frequency}
+func (m *power) parameters() map[string]float64 {
+ return map[string]float64{"Current": m.maxCurrent, "Frequency": m.frequency}
}
-func (m *Power) UpdateParameter(name string, value float64) {
+func (m *power) updateParameter(name string, value float64) {
if name == "Current" {
- m.Current = value
+ m.maxCurrent = value
} else if name == "Frequency" {
- m.Frequency = value
+ m.frequency = value
}
}
diff --git a/cirsim/node.go b/cirsim/node.go
index a9e8301..d26d78f 100644
--- a/cirsim/node.go
+++ b/cirsim/node.go
@@ -11,52 +11,50 @@ import (
"github.com/wcharczuk/go-chart/v2/drawing"
)
-type Node struct {
+type node struct {
widget.BaseWidget
- Pos fyne.Position
- VoltageOverTime []float64
- VoltageRange chart.Range
- AComponents []*Component
- BComponents []*Component
- Image *canvas.Image
+ pos fyne.Position
+ voltageOverTime []float64
+ voltageRange chart.Range
+ chart *canvas.Image
}
-func NewNode(settings io.Reader, r chart.Range) *Node {
- var n Node
- _, err := fmt.Fscanf(settings, "%f %f\n", &n.Pos.X, &n.Pos.Y)
+func newNode(settings io.Reader, r chart.Range) *node {
+ var n node
+ _, err := fmt.Fscanf(settings, "%f %f\n", &n.pos.X, &n.pos.Y)
if err != nil {
return nil
}
- n.VoltageRange = r
- n.VoltageOverTime = make([]float64, 1000)
- for i := range n.VoltageOverTime {
- n.VoltageOverTime[i] = 0
+ n.voltageRange = r
+ n.voltageOverTime = make([]float64, iterations)
+ for i := range n.voltageOverTime {
+ n.voltageOverTime[i] = 0
}
n.renderChart()
return &n
}
-func (n *Node) renderChart() {
+func (n *node) renderChart() {
graph := chart.Chart{
- Width: 140,
- Height: 60,
+ Width: chartWidth,
+ Height: chartHeight,
ColorPalette: &nodeColorPalette{},
XAxis: chart.HideXAxis(),
YAxis: chart.YAxis{
Style: chart.Hidden(),
- Range: n.VoltageRange,
+ Range: n.voltageRange,
},
Series: []chart.Series{
chart.ContinuousSeries{
- XValues: chart.LinearRange(0, 999),
- YValues: n.VoltageOverTime,
+ XValues: chart.LinearRange(0, float64(iterations)-1),
+ YValues: n.voltageOverTime,
},
},
}
writer := &chart.ImageWriter{}
graph.Render(chart.PNG, writer)
img, _ := writer.Image()
- n.Image = canvas.NewImageFromImage(img)
+ n.chart = canvas.NewImageFromImage(img)
}
type nodeColorPalette struct{}
@@ -68,7 +66,12 @@ func (*nodeColorPalette) BackgroundStrokeColor() drawing.Color {
return drawing.ColorTransparent
}
func (*nodeColorPalette) CanvasColor() drawing.Color {
- return drawing.Color{R: 249, G: 254, B: 172, A: 128}
+ return drawing.Color{
+ R: voltageCanvasR,
+ G: voltageCanvasG,
+ B: voltageCanvasB,
+ A: voltageCanvasA,
+ }
}
func (*nodeColorPalette) CanvasStrokeColor() drawing.Color {
return drawing.ColorTransparent
@@ -80,17 +83,17 @@ func (*nodeColorPalette) TextColor() drawing.Color {
return drawing.ColorTransparent
}
func (*nodeColorPalette) GetSeriesColor(index int) drawing.Color {
- return drawing.Color{R: 247, G: 239, B: 3, A: 255}
+ return drawing.Color{R: voltageR, G: voltageG, B: voltageB, A: voltageA}
}
-func (n *Node) CreateRenderer() fyne.WidgetRenderer { return n }
-func (n *Node) Layout(s fyne.Size) {
- n.Image.Resize(s)
- n.Image.Move(fyne.NewPos(0, 0))
+func (n *node) CreateRenderer() fyne.WidgetRenderer { return n }
+func (n *node) Layout(s fyne.Size) {
+ n.chart.Resize(s)
+ n.chart.Move(fyne.NewPos(0, 0))
}
-func (n *Node) MinSize() fyne.Size { return n.Image.MinSize() }
-func (n *Node) Refresh() { n.renderChart() }
-func (n *Node) Destroy() {}
-func (n *Node) Objects() []fyne.CanvasObject {
- return []fyne.CanvasObject{n.Image}
+func (n *node) MinSize() fyne.Size { return n.chart.MinSize() }
+func (n *node) Refresh() { n.renderChart() }
+func (n *node) Destroy() {}
+func (n *node) Objects() []fyne.CanvasObject {
+ return []fyne.CanvasObject{n.chart}
}
diff --git a/cirsim/simulation.go b/cirsim/simulation.go
index ca0d841..9b7234c 100644
--- a/cirsim/simulation.go
+++ b/cirsim/simulation.go
@@ -3,6 +3,7 @@ package cirsim
import (
"fmt"
"image/color"
+ "io"
"log"
"os"
@@ -15,13 +16,36 @@ import (
"gonum.org/v1/gonum/mat"
)
-type Simulation struct {
- Period float64
- Size fyne.Size
- Nodes []*Node
- Components []*Component
- VoltageRange chart.ContinuousRange
- CurrentRange chart.ContinuousRange
+const (
+ defaultPeriod float64 = 0.01
+ iterations int = 1000
+ chartWidth = 140
+ chartHeight = 60
+ currentCanvasR uint8 = 191
+ currentCanvasG = 254
+ currentCanvasB = 247
+ currentCanvasA = 128
+ currentR = 3
+ currentG = 247
+ currentB = 198
+ currentA = 255
+ voltageCanvasR = 249
+ voltageCanvasG = 254
+ voltageCanvasB = 172
+ voltageCanvasA = 128
+ voltageR = 247
+ voltageG = 239
+ voltageB = 3
+ voltageA = 255
+)
+
+type simulation struct {
+ period float64
+ size fyne.Size
+ nodes []*node
+ components []*component
+ voltageRange chart.ContinuousRange
+ currentRange chart.ContinuousRange
periodEntry *widget.Entry
voltageLabel *canvas.Text
currentLabel *canvas.Text
@@ -29,244 +53,222 @@ type Simulation struct {
func New() fyne.CanvasObject {
background := canvas.NewRectangle(color.White)
- image := canvas.NewImageFromFile("circuit.svg")
- file, err := os.Open("circuit")
+ circuit := canvas.NewImageFromFile("circuit.svg")
+ settings, err := os.Open("circuit")
if err != nil {
log.Fatal(err)
}
- var sim Simulation
- sim.Period = 0.01
- sim.VoltageRange = chart.ContinuousRange{Min: 0, Max: 0}
- sim.CurrentRange = chart.ContinuousRange{Min: 0, Max: 0}
- fmt.Fscanf(file, "%f %f\n\n", &sim.Size.Width, &sim.Size.Height)
+ var sim simulation
+ sim.period = defaultPeriod
+ sim.voltageRange = chart.ContinuousRange{Min: 0, Max: 0}
+ sim.currentRange = chart.ContinuousRange{Min: 0, Max: 0}
+ fmt.Fscanf(settings, "%f %f\n\n", &sim.size.Width, &sim.size.Height)
+ cont := container.New(&sim, sim.newPanel(settings), background, circuit)
+ sim.addNodes(cont, settings)
+ sim.addComponents(cont, settings)
+ settings.Close()
+ sim.update()
+ return cont
+}
+
+func (sim *simulation) newPanel(settings io.Reader) *fyne.Container {
sim.voltageLabel = canvas.NewText(
fmt.Sprintf(" %e < voltage < %e ",
- sim.VoltageRange.Min, sim.VoltageRange.Max),
- color.RGBA{R: 247, G: 239, B: 3, A: 255},
+ sim.voltageRange.Min, sim.voltageRange.Max),
+ color.RGBA{R: voltageR, G: voltageG, B: voltageB, A: voltageA},
)
sim.voltageLabel.TextStyle.Monospace = true
sim.currentLabel = canvas.NewText(
fmt.Sprintf(" %e < current < %e ",
- sim.CurrentRange.Min, sim.CurrentRange.Max),
- color.RGBA{R: 3, G: 247, B: 198, A: 255},
+ sim.currentRange.Min, sim.currentRange.Max),
+ color.RGBA{R: currentR, G: currentG, B: currentB, A: currentA},
)
sim.currentLabel.TextStyle.Monospace = true
periodLabel := widget.NewLabel("Period")
periodLabel.TextStyle.Monospace = true
sim.periodEntry = widget.NewEntry()
sim.periodEntry.TextStyle.Monospace = true
- sim.periodEntry.SetPlaceHolder("default: 0.01s")
+ sim.periodEntry.SetPlaceHolder(fmt.Sprintf("default: %fs", defaultPeriod))
sim.periodEntry.OnSubmitted = sim.updatePeriod
- c := container.New(&sim,
- container.NewHBox(
- sim.voltageLabel,
- sim.currentLabel,
- layout.NewSpacer(),
- periodLabel,
- container.New(&entryLayout{}, sim.periodEntry),
- ),
- background,
- image,
+ return container.NewHBox(
+ sim.voltageLabel,
+ sim.currentLabel,
+ layout.NewSpacer(),
+ periodLabel,
+ container.New(&entryLayout{}, sim.periodEntry),
)
- for n := NewNode(file, &sim.VoltageRange); n != nil; n = NewNode(file, &sim.VoltageRange) {
- sim.Nodes = append(sim.Nodes, n)
- c.Add(n)
- }
- for component := NewComponent(file, &sim.CurrentRange, sim.Nodes, sim.simulate); component != nil; component = NewComponent(file, &sim.CurrentRange, sim.Nodes, sim.simulate) {
- sim.Components = append(sim.Components, component)
- c.Add(component)
- }
- file.Close()
- sim.simulate()
- return c
}
-func (sim *Simulation) simulate() {
- for _, n := range sim.Nodes {
- for i := range n.VoltageOverTime {
- n.VoltageOverTime[i] = 0
- }
+func (sim *simulation) addNodes(cont *fyne.Container, settings io.Reader) {
+ n := newNode(settings, &sim.voltageRange)
+ for n != nil {
+ sim.nodes = append(sim.nodes, n)
+ cont.Add(n)
+ n = newNode(settings, &sim.voltageRange)
}
- for _, c := range sim.Components {
- for i := range c.CurrentOverTime {
- c.CurrentOverTime[i] = 0
- }
+}
+
+func (sim *simulation) addComponents(cont *fyne.Container, settings io.Reader) {
+ c := newComponent(settings, &sim.currentRange, sim.update)
+ for c != nil {
+ sim.components = append(sim.components, c)
+ cont.Add(c)
+ c = newComponent(settings, &sim.currentRange, sim.update)
}
- maxv := 0.0
- minv := 0.0
- maxc := 0.0
- minc := 0.0
- for i := 0; i != 1000; i++ {
- N := len(sim.Nodes)
- for refine := 0; refine != 10; refine++ {
- m := mat.NewDense(N+1, N, nil)
- v := mat.NewVecDense(N+1, nil)
- for j, n := range sim.Nodes {
- for _, c := range sim.Components {
- if c.ANode == n {
- v.SetVec(j, v.AtVec(j)-c.Model.ModelCurrent(
- float64(i)*sim.Period/1000, sim.Period/1000,
- c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- d := 0
- for k, v := range sim.Nodes {
- if c.BNode == v {
- d = k
- break
- }
- }
- m.Set(j, d, m.At(j, d)-
- c.Model.ModelConductance(
- float64(i)*sim.Period/1000, sim.Period/1000,
- c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- m.Set(j, j, m.At(j, j)+
- c.Model.ModelConductance(
- float64(i)*sim.Period/1000, sim.Period/1000,
- c.BNode.VoltageOverTime[i]-n.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- } else if c.BNode == n {
- v.SetVec(j, v.AtVec(j)+c.Model.ModelCurrent(
- float64(i)*sim.Period/1000, sim.Period/1000,
- n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- d := 0
- for k, v := range sim.Nodes {
- if c.ANode == v {
- d = k
- break
- }
- }
- m.Set(j, d, m.At(j, d)-
- c.Model.ModelConductance(
- float64(i)*sim.Period/1000, sim.Period/1000,
- n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- m.Set(j, j, m.At(j, j)+
- c.Model.ModelConductance(
- float64(i)*sim.Period/1000, sim.Period/1000,
- n.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
- c.CurrentOverTime[i]))
- }
- }
- }
- r := make([]float64, N)
- for j := 0; j != N-1; j++ {
- r[j] = 0
- }
- r[N-1] = 1
- m.SetRow(N, r)
- v.SetVec(N, 0)
- res := mat.NewVecDense(N, nil)
- res.SolveVec(m, v)
- for j, n := range sim.Nodes {
- n.VoltageOverTime[i] = res.AtVec(j)
- }
- for _, c := range sim.Components {
- c.CurrentOverTime[i] = (c.ANode.VoltageOverTime[i]-c.BNode.VoltageOverTime[i])*
- c.Model.ModelConductance(
- float64(i)*sim.Period/1000, sim.Period/1000,
- c.BNode.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
- c.CurrentOverTime[i]) +
- c.Model.ModelCurrent(
- float64(i)*sim.Period/1000, sim.Period/1000,
- c.BNode.VoltageOverTime[i]-c.ANode.VoltageOverTime[i],
- c.CurrentOverTime[i])
- }
- }
- if i == 999 {
- break
- }
- for _, n := range sim.Nodes {
- n.VoltageOverTime[i+1] = n.VoltageOverTime[i]
- if n.VoltageOverTime[i] > maxv {
- maxv = n.VoltageOverTime[i]
- } else if n.VoltageOverTime[i] < minv {
- minv = n.VoltageOverTime[i]
+}
+
+func (sim *simulation) update() {
+ sim.simulate()
+ sim.voltageRange.Max = 0
+ sim.voltageRange.Min = 0
+ for _, n := range sim.nodes {
+ for _, v := range n.voltageOverTime {
+ if v > sim.voltageRange.Max {
+ sim.voltageRange.Max = v
+ } else if v < sim.voltageRange.Min {
+ sim.voltageRange.Min = v
}
}
- for _, c := range sim.Components {
- c.CurrentOverTime[i+1] = c.CurrentOverTime[i]
- if c.CurrentOverTime[i] > maxc {
- maxc = c.CurrentOverTime[i]
- } else if c.CurrentOverTime[i] < minc {
- minc = c.CurrentOverTime[i]
+ }
+ sim.currentRange.Max = sim.components[0].currentOverTime[0]
+ sim.currentRange.Min = sim.components[0].currentOverTime[0]
+ for _, comp := range sim.components {
+ for _, c := range comp.currentOverTime {
+ if c > sim.currentRange.Max {
+ sim.currentRange.Max = c
+ } else if c < sim.currentRange.Min {
+ sim.currentRange.Min = c
}
}
}
- sim.VoltageRange.Max = maxv
- sim.VoltageRange.Min = minv
- sim.CurrentRange.Max = maxc
- sim.CurrentRange.Min = minc
sim.voltageLabel.Text = fmt.Sprintf(" %e < voltage < %e ",
- sim.VoltageRange.Min, sim.VoltageRange.Max)
+ sim.voltageRange.Min, sim.voltageRange.Max)
sim.currentLabel.Text = fmt.Sprintf(" %e < current < %e ",
- sim.CurrentRange.Min, sim.CurrentRange.Max)
+ sim.currentRange.Min, sim.currentRange.Max)
sim.voltageLabel.Refresh()
sim.currentLabel.Refresh()
- for _, n := range sim.Nodes {
+ for _, n := range sim.nodes {
n.Refresh()
}
- for _, c := range sim.Components {
+ for _, c := range sim.components {
c.Refresh()
}
}
-func (sim *Simulation) updatePeriod(period string) {
- _, err := fmt.Sscanf(period+"\n", "%f\n", &sim.Period)
- if err != nil {
- sim.periodEntry.SetText(fmt.Sprintf("%f", sim.Period))
- } else {
- sim.simulate()
+func (sim *simulation) simulate() {
+ sim.nullify()
+ N := len(sim.nodes)
+ delta := sim.period / float64(iterations)
+ for i := 0; i != iterations; i++ {
+ // fill conductances and currents:
+ time := float64(i) * sim.period / float64(iterations)
+ conductances := mat.NewDense(N+1, N, nil)
+ currents := mat.NewVecDense(N+1, nil)
+ for _, c := range sim.components {
+ voltage := sim.nodes[c.nodes[1]].voltageOverTime[i] -
+ sim.nodes[c.nodes[0]].voltageOverTime[i]
+ current := c.current(time, delta, voltage, c.currentOverTime[i])
+ cond := c.conductance(time, delta, voltage, c.currentOverTime[i])
+ currents.SetVec(c.nodes[1], currents.AtVec(c.nodes[1])-current)
+ currents.SetVec(c.nodes[0], currents.AtVec(c.nodes[0])+current)
+ conductances.Set(c.nodes[1], c.nodes[1],
+ conductances.At(c.nodes[1], c.nodes[1])+cond)
+ conductances.Set(c.nodes[0], c.nodes[0],
+ conductances.At(c.nodes[0], c.nodes[0])+cond)
+ conductances.Set(c.nodes[1], c.nodes[0],
+ conductances.At(c.nodes[1], c.nodes[0])-cond)
+ conductances.Set(c.nodes[0], c.nodes[1],
+ conductances.At(c.nodes[0], c.nodes[1])-cond)
+ }
+ // set up additional row to determine ground node:
+ groundNodeRow := make([]float64, N)
+ groundNodeRow[0] = 1
+ for j := 1; j != N; j++ {
+ groundNodeRow[j] = 0
+ }
+ conductances.SetRow(N, groundNodeRow)
+ currents.SetVec(N, 0)
+ // solve the equation:
+ voltages := mat.NewVecDense(N, nil)
+ voltages.SolveVec(conductances, currents)
+ // save results:
+ for j, n := range sim.nodes {
+ n.voltageOverTime[i] = voltages.AtVec(j)
+ }
+ for _, c := range sim.components {
+ voltage := voltages.AtVec(c.nodes[1]) - voltages.AtVec(c.nodes[0])
+ current := c.current(time, delta, voltage, c.currentOverTime[i])
+ cond := c.conductance(time, delta, voltage, c.currentOverTime[i])
+ c.currentOverTime[i] = voltage*cond + current
+ }
}
}
-func (l *Simulation) MinSize(objects []fyne.CanvasObject) fyne.Size {
- s := fyne.NewSize(0, 0)
- for _, i := range objects {
- switch o := i.(type) {
- case *canvas.Image:
- s = o.MinSize()
- default:
+func (sim *simulation) nullify() {
+ for _, n := range sim.nodes {
+ for i := range n.voltageOverTime {
+ n.voltageOverTime[i] = 0
}
}
- return s
+ for _, c := range sim.components {
+ for i := range c.currentOverTime {
+ c.currentOverTime[i] = 0
+ }
+ }
+}
+
+func (sim *simulation) updatePeriod(period string) {
+ _, err := fmt.Sscanf(period+"\n", "%f\n", &sim.period)
+ if err != nil {
+ sim.periodEntry.SetText(fmt.Sprintf("%f", sim.period))
+ } else {
+ sim.update()
+ }
}
-func (l *Simulation) Layout(obs []fyne.CanvasObject, size fyne.Size) {
+func (l *simulation) MinSize(objects []fyne.CanvasObject) fyne.Size {
+ return objects[2].MinSize()
+}
+func (l *simulation) Layout(obs []fyne.CanvasObject, size fyne.Size) {
var p fyne.Position
var s fyne.Size
- panel_height := obs[0].MinSize().Height
- H := size.Height - panel_height
+ panelHeight := obs[0].MinSize().Height
+ H := size.Height - panelHeight
W := size.Width
- h := l.Size.Height
- w := l.Size.Width
+ h := l.size.Height
+ w := l.size.Width
var scale float32
if H/W > h/w {
- p = fyne.NewPos(0, (H-W*h/w)/2+panel_height)
+ p = fyne.NewPos(0, (H-W*h/w)/2+panelHeight)
s = fyne.NewSize(W, W*h/w)
scale = W / w
} else {
- p = fyne.NewPos((W-H*w/h)/2, panel_height)
+ p = fyne.NewPos((W-H*w/h)/2, panelHeight)
s = fyne.NewSize(H*w/h, H)
scale = H / h
}
- obs[0].Resize(fyne.NewSize(W, panel_height))
+ obs[0].Resize(fyne.NewSize(W, panelHeight))
obs[0].Move(fyne.NewPos(0, 0))
obs[1].Resize(size)
- obs[1].Move(fyne.NewPos(0, panel_height))
+ obs[1].Move(fyne.NewPos(0, panelHeight))
obs[2].Resize(s)
obs[2].Move(p)
- for i, n := range l.Nodes {
- obs[3+i].Resize(fyne.NewSize(140, 60))
- shift := fyne.NewPos(p.X+n.Pos.X*scale-70, p.Y+n.Pos.Y*scale-30)
+ for i, n := range l.nodes {
+ obs[3+i].Resize(fyne.NewSize(chartWidth, chartHeight))
+ shift := fyne.NewPos(
+ p.X+n.pos.X*scale-chartWidth/2.0,
+ p.Y+n.pos.Y*scale-chartHeight/2.0,
+ )
obs[3+i].Move(shift)
}
- for i, c := range l.Components {
- ms := obs[3+len(l.Nodes)+i].MinSize()
- obs[3+len(l.Nodes)+i].Resize(ms)
- shift := fyne.NewPos(p.X+c.Pos.X*scale-70, p.Y+c.Pos.Y*scale-ms.Height+30)
- obs[3+len(l.Nodes)+i].Move(shift)
+ for i, c := range l.components {
+ ms := obs[3+len(l.nodes)+i].MinSize()
+ obs[3+len(l.nodes)+i].Resize(ms)
+ shift := fyne.NewPos(
+ p.X+c.pos.X*scale-chartWidth/2.0,
+ p.Y+c.pos.Y*scale-ms.Height+chartHeight/2.0,
+ )
+ obs[3+len(l.nodes)+i].Move(shift)
}
}
@@ -274,7 +276,7 @@ type entryLayout struct{}
func (*entryLayout) MinSize(objects []fyne.CanvasObject) fyne.Size {
return fyne.NewSize(
- objects[0].MinSize().Width*4,
+ objects[0].MinSize().Width*6,
objects[0].MinSize().Height,
)
}