Skip to content

Commit

Permalink
[core] Calculate and report more inertia disaggregations
Browse files Browse the repository at this point in the history
  • Loading branch information
GordStephen committed Oct 31, 2023
1 parent cee06b3 commit cbca66f
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 44 deletions.
4 changes: 2 additions & 2 deletions sinks/text/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ func (tv TextDataSink) Update(snapshot inertia.Snapshot) {

timestamp := snapshot.Time.Format(time.RFC3339)

text := fmt.Sprintf("%v: %v MWs\n", timestamp, snapshot.Total)
text := fmt.Sprintf("%v: %v MWs\n", timestamp, snapshot.Total.TotalInertia)
tv.outfile.WriteString(text)

for category, inertia := range snapshot.Categories {
text = fmt.Sprintf("\t%v\t%v MWs\n", category, inertia)
text = fmt.Sprintf("\t%v\t%v MWs\n", category, inertia.TotalInertia)
tv.outfile.WriteString(text)
}

Expand Down
18 changes: 9 additions & 9 deletions sinks/web/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ function appendInertia(data, latest) {

data.periods.timestamps.push(latest.time);
data.periods.requirement.push(latest.requirement);
data.periods.total.push(latest.total);
data.periods.total.push(latest.total.total_inertia);
data.periods.categories.forEach(category => {
category_inertia = latest.inertia[category.name];
category_inertia = latest.categories[category.name].total_inertia;
category.inertia.push(category_inertia);
});

Expand Down Expand Up @@ -216,16 +216,16 @@ function updateText(currentData) {

d3.select("#time #lastupdate").text(timestamp);

absolute = inertiaText(currentData.total);
absolute = inertiaText(currentData.total.total_inertia);

if (currentData.total > currentData.requirement) {
if (currentData.total.total_inertia > currentData.requirement) {

surplus = currentData.total - currentData.requirement;
surplus = currentData.total.total_inertia - currentData.requirement;
relative = inertiaText(surplus) + " above threshold"
color = "#EEEEEE";

} else {
shortfall = currentData.requirement - currentData.total;
shortfall = currentData.requirement - currentData.total.total_inertia;
relative = inertiaText(shortfall) + " below threshold"
color = "#FF0000";
}
Expand Down Expand Up @@ -297,9 +297,9 @@ function categoryRingData(latest, cx, cy, r) {
categories = [];
cum_angle = 0

for (const c of Object.keys(latest.inertia)) {
for (const c of Object.keys(latest.categories)) {

share = latest.inertia[c] / latest.total;
share = latest.categories[c].total_inertia / latest.total.total_inertia;
angle = share * 2 * Math.PI;

mid_x = cx + r * Math.sin(cum_angle + angle/2);
Expand Down Expand Up @@ -328,7 +328,7 @@ function categoryRingData(latest, cx, cy, r) {
"path": path,
"mid_x": mid_x,
"mid_y": mid_y,
"val": latest.inertia[c],
"val": latest.categories[c].total_inertia,
"share": share,
"arclength": r*angle
});
Expand Down
5 changes: 3 additions & 2 deletions sinks/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,10 @@ func jsonify(report inertia.Snapshot) ([]byte, error) {

response := map[string]any {
"time": report.Time.UnixMilli(),
"total": report.Total,
"requirement": report.Requirement,
"inertia": report.Categories,
"total": report.Total,
"categories": report.Categories,
"regions": report.Regions,
}

return json.Marshal(response)
Expand Down
2 changes: 1 addition & 1 deletion sources/mock/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (d *MockDataSource) Query() (inertia.Snapshot, error) {
{ d.system.Units["U4"], randBool() },
}

state := inertia.SystemState { now, 1500, units }
state := inertia.SystemState { now, 1500, units, &d.system }

return state.Inertia()

Expand Down
54 changes: 39 additions & 15 deletions system.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,41 +43,65 @@ type SystemState struct {
Time time.Time
Requirement float64
Units []UnitState
System *SystemMetadata
}

type UnitAggregation struct {
Units int `json:"units"`
TotalRating float64 `json:"total_rating"`
TotalInertia float64 `json:"total_inertia"`
}

func (agg *UnitAggregation) AddUnit(h float64, rating float64) {
agg.Units += 1
agg.TotalRating += rating
agg.TotalInertia += h * rating
}

// Snapshot is the common data structure used for reporting inertia
// levels at a point in time.
type Snapshot struct {
Time time.Time
Total float64
Requirement float64
Categories map[string]float64
Total UnitAggregation
Categories map[string]*UnitAggregation
Regions map[string]*UnitAggregation
}

// TODO: Always report all categories, even when SystemState doesn't have
// any for that category (committed or otherwise)
func (st SystemState) Inertia() (Snapshot, error) {

total_inertia := 0.0
category_inertia := make(map[string]float64)
total_inertia := UnitAggregation {}
category_inertias := make(map[string]*UnitAggregation)
region_inertias := make(map[string]*UnitAggregation)

for region, _ := range st.System.Regions {
region_inertias[region] = &UnitAggregation {}
}

for category, _ := range st.System.Categories {
category_inertias[category] = &UnitAggregation {}
}

for _, unit := range st.Units {

var unit_inertia float64
if !unit.Committed { continue }

total_inertia.AddUnit(unit.H, unit.Rating)

if unit.Committed {
unit_inertia = unit.H * unit.Rating
}
category := unit.Category.Name
category_inertias[category].AddUnit(unit.H, unit.Rating)

total_inertia += unit_inertia
category_inertia[unit.Category.Name] += unit_inertia
region := unit.Region.Name
region_inertias[region].AddUnit(unit.H, unit.Rating)

}

return Snapshot{
return Snapshot {
st.Time,
total_inertia, st.Requirement,
category_inertia,
st.Requirement,
total_inertia,
category_inertias,
region_inertias,
}, nil

}
96 changes: 81 additions & 15 deletions system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,98 @@ import (

func TestInertiaCalculation(t *testing.T) {

categories := []UnitCategory {
{ "C1", "#00FF00", 1 }, { "C2", "#0000FF", 2 },
categories := map[string]*UnitCategory {
"C1": { "C1", "#00FF00", 1 },
"C2": { "C2", "#0000FF", 2 },
}

regions := []Region { { "Region A" }, { "Region B" } }
regions := map[string]*Region {
"Region A": { "Region A" },
"Region B": { "Region B" },
}

units := map[string]UnitMetadata {
"U1": { "U1", categories["C1"], regions["Region A"], 100, 10 },
"U2": { "U2", categories["C1"], regions["Region B"], 50, 5 },
"U3": { "U3", categories["C2"], regions["Region A"], 100, 10 },
"U4": { "U4", categories["C2"], regions["Region B"], 100, 1 },
}

system := SystemMetadata { regions, categories, units }

units := []UnitState {
{ UnitMetadata {"U1", &categories[0], &regions[0], 10, 100 }, true },
{ UnitMetadata {"U2", &categories[0], &regions[1], 5, 50 }, true },
{ UnitMetadata {"U3", &categories[1], &regions[0], 10, 100 }, false },
{ UnitMetadata {"U4", &categories[1], &regions[1], 1, 100 }, true },
unitstates := []UnitState {
{ units["U1"], true },
{ units["U2"], true },
{ units["U3"], false },
{ units["U4"], true },
}

state := SystemState { time.Now(), 1000, units }
state := SystemState { time.Now(), 1000, unitstates, &system }
inertia, _ := state.Inertia()

if i := inertia.Categories["C1"]; i != 1250 {
t.Errorf("C1 inertia should be 1250 MW s; got %f", i)
inertia.Total.Test(t, 3, 250, 1350)

inertia.Categories["C1"].Test(t, 2, 150, 1250)
inertia.Categories["C2"].Test(t, 1, 100, 100)

inertia.Regions["Region A"].Test(t, 1, 100, 1000)
inertia.Regions["Region B"].Test(t, 2, 150, 350)

// Empty region

state.Units[0].Committed = false
inertia, _ = state.Inertia()

inertia.Total.Test(t, 2, 150, 350)

inertia.Categories["C1"].Test(t, 1, 50, 250)
inertia.Categories["C2"].Test(t, 1, 100, 100)

regionA, ok := inertia.Regions["Region A"]
if !ok {
t.Errorf("Results should exist even when no units are committed")
}

regionA.Test(t, 0, 0, 0)
inertia.Regions["Region B"].Test(t, 2, 150, 350)

// Empty category and region

state.Units[3].Committed = false
inertia, _ = state.Inertia()

inertia.Total.Test(t, 1, 50, 250)

c2, ok := inertia.Categories["C2"]
if !ok {
t.Errorf("Results should exist even when no units are committed")
}

inertia.Categories["C1"].Test(t, 1, 50, 250)
c2.Test(t, 0, 0, 0)

regionA, ok = inertia.Regions["Region A"]
if !ok {
t.Errorf("Results should exist even when no units are committed")
}

regionA.Test(t, 0, 0, 0)
inertia.Regions["Region B"].Test(t, 1, 50, 250)

}

func (agg UnitAggregation) Test(t *testing.T, units int, rating float64, inertia float64) {

if i := agg.Units; i != units {
t.Errorf("Result should have %d committed units; got %d", units, i)
}

if i := inertia.Categories["C2"]; i != 100 {
t.Errorf("C2 inertia should be 100 MW s; got %f", i)
if i := agg.TotalRating; i != rating {
t.Errorf("Result should have total rating of %f; got %f", rating, i)
}

if inertia.Total != 1350 {
t.Errorf("Total inertia should be 1350 MW s; got %f", inertia.Total)
if i := agg.TotalInertia; i != inertia {
t.Errorf("Result should have total inertia of %f; got %f", inertia, i)
}

}

0 comments on commit cbca66f

Please sign in to comment.