Published
Edited
Apr 20, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`## Results
Integral conducted stochastic analyses to understand the range of days for which the proposed microgrid system could provide backup power - under four (4) scenarios in which the building is occupied:

Current operations – assumes building retains its existing mechanical systems.

Chiller upgrade – assumes the chiller is upgraded to an air-cooled chiller (efficiency increases by 10%), while all other mechanical systems remain unchanged.

All-electric – assumes electrification of all propane-dependent heating loads and the installation of a heat pump.

Critical loads – assumes electrification of all propane-dependent heating loads, but during the outage event, roughly 50% of loads would be reduced or modulated (e.g., set point for cooling could rise to 75°).

Each dot in the graphs above represent one simulation, which uses randomized inputs to assess the probability distribution of various results. Hovering over a dot displays the total duration that the system can provide power, and other key data points such as the year of the historical weather file and the day of the outage.

Integral’s analysis reveals that the proposed system can provide, with 90% confidence, at least 4.5 days of uninterrupted power using the building’s existing mechanical equipment; the median is over one week. If the outage seemed like it might extend for a particularly long period of time, the building’s power could be modulated to reduce consumption by half (to “critical loads”). n this scenario, the microgrid provides backup power for at least 8 days with 90% certainty; the average outcome is nearly three weeks of backup power.</br></br></br>`
Insert cell
legend = g => {
const svg = g
.attr("transform", `translate(${width - margin.right},40)`)
.attr("font-family", "sans-serif")
.attr("font-size", 12)
.attr("text-anchor", "start");
const item = svg
.selectAll("g")
.data(["October/September", "Other Months"])
.join("g")
.attr("transform", (d, i) => `translate(0,${i * 25})`);
item.each(function(d, i) {
d3.select(this).append("text")
.attr("fill", "#c1c1c1")
.attr("x", -110)
.attr("dy", "0.35em")
.text(String);
d3.select(this).append("circle")
.attr("fill", i === 0 ? "#89cda8": "#3b99a7")
.attr("stroke-width", 0)
.attr("r", 4)
.attr("cx", -125)
.attr("cy", 0);
});
}

Insert cell
line = d3.line()
.x(d => x(d[0]))
.y(d => d[1])
Insert cell
rn = {
let r = Float64Array.from({length: 100}, d3.randomPareto(0.8));
r = r.map(d => (5 + Math.max(Math.min(d, 25), 1)));
return r;
}
Insert cell
Insert cell
html`<style>
@import url('https://fonts.googleapis.com/css?family=Open+Sans');
}

svg text {
font-family: "Open Sans";
}
.tick text {
stroke: #fff;
stroke-width: 3;
paint-order: stroke;
font-size: 12px;
}


</style>`
Insert cell
keyLookup = ({
current: "Current Operations",
chiller_upgrade: "Chiller Upgrade",
gshp: "All-Electric",
critical_only: "Critical Loads Only"
})
Insert cell
import {Select} from "@observablehq/inputs"
Insert cell
createTooltip = el => {
el
.attr("class", "tooltip")
.style("border-radius", "3px")
.style("pointer-events", "none")
.style("display", "none")
.style("position", "absolute")
.style("z-index", "1000")
.style("padding", "12px")
.style("font-weight", "regular")
.style("font-family", "Open Sans")
.style("font-size", "12px")
.style("background", "white")
.style("box-shadow", "0 0 10px rgba(0,0,0,.25)")
.style("color", "#333333")
.style("line-height", "1.6")
.style("pointer-events", "none");
}
Insert cell
color = "#3b99a7"
Insert cell
beeswarm.update(grouped[scenario])
Insert cell
radius = 4.5
Insert cell
padding = 1.5
Insert cell
function dodge(data, {radius = 1, x = d => d} = {}) {
const radius2 = radius ** 2;
const circles = data.map((d, i) => ({x: +x(d, i, data), data: d})).sort((a, b) => a.x - b.x);
const epsilon = 1e-3;
let head = null, tail = null;

// Returns true if circle ⟨x,y⟩ intersects with any circle in the queue.
function intersects(x, y) {
let a = head;
while (a) {
if (radius2 - epsilon > (a.x - x) ** 2 + (a.y - y) ** 2) {
return true;
}
a = a.next;
}
return false;
}

// Place each circle sequentially.
for (const b of circles) {

// Remove circles from the queue that can’t intersect the new circle b.
while (head && head.x < b.x - radius2) head = head.next;

// Choose the minimum non-intersecting tangent.
if (intersects(b.x, b.y = 0)) {
let a = head;
b.y = Infinity;
do {
let y1 = a.y + Math.sqrt(radius2 - (a.x - b.x) ** 2);
let y2 = a.y - Math.sqrt(radius2 - (a.x - b.x) ** 2);
if (Math.abs(y1) < Math.abs(b.y) && !intersects(b.x, y1)) b.y = y1;
if (Math.abs(y2) < Math.abs(b.y) && !intersects(b.x, y2)) b.y = y2;
a = a.next;
} while (a);
}

// Add b to the queue.
b.next = null;
if (head === null) head = tail = b;
else tail = tail.next = b;
}

const percentile = (d, p) => {
let dat = d.map(dd => dd.duration).sort((a, b) => a - b)
return d3.quantile(dat, p);
}

circles.percentile90 = percentile(data, .1);
circles.percentile50 = percentile(data, .5);
return circles;
}



Insert cell
Insert cell
Insert cell
x = d3.scaleLinear()
// .domain(d3.extent(data, d => d.duration))
.domain([0, 40])
.range([margin.left, width - margin.right]).nice()
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height/2})`)
.call(d3.axisBottom(x)
.tickSizeOuter(0)
.tickPadding(10)
.ticks(10)
.tickFormat(d3.format(".0f")))
.call(g => g.select(".tick:first-of-type text").clone()
.attr("y", -30)
.attr("x", 0)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.html("Number of Days"))
Insert cell
margin = ({top: 40, right: 15, bottom: 40, left: 15})
Insert cell
height = 800
Insert cell
width=960
Insert cell
chroma = require('chroma-js')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more