diff options
-rw-r--r-- | circuit | 20 | ||||
-rw-r--r-- | circuit.svg | 1 | ||||
-rw-r--r-- | cirsim/component.go | 109 | ||||
-rw-r--r-- | cirsim/models.go | 144 | ||||
-rw-r--r-- | cirsim/node.go | 67 | ||||
-rw-r--r-- | cirsim/simulation.go | 374 |
6 files changed, 389 insertions, 326 deletions
@@ -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, ) } |