Skip to content

Commit

Permalink
constructor for window opening
Browse files Browse the repository at this point in the history
  • Loading branch information
shieldo committed Jul 18, 2023
1 parent e50bd7e commit 7681389
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 11 deletions.
1 change: 0 additions & 1 deletion src/core/controls/time_control.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// This module provides structs to model time controls

use crate::simulation_time::{SimulationTimeIteration, HOURS_IN_DAY};
use itertools::sorted;

/// An object to model a time-only control with on/off (not modulating) operation
pub struct OnOffTimeControl {
Expand Down
24 changes: 24 additions & 0 deletions src/core/space_heat_demand/building_element.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::core::space_heat_demand::zone::NamedBuildingElement;
use crate::core::units::average_monthly_to_annual;
use crate::external_conditions::{CalculatedDirectDiffuseTotalIrradiance, ExternalConditions};
use crate::input::{BuildingElement, MassDistributionClass};
Expand Down Expand Up @@ -420,13 +421,36 @@ pub fn h_ri_for(_: &BuildingElement) -> f64 {
H_RI
}

pub fn mid_height_for(element: &BuildingElement) -> Option<f64> {
match element {
BuildingElement::Transparent {
base_height,
height,
..
} => Some(base_height + height / 2.0),
_ => None,
}
}

pub fn orientation_for(element: &BuildingElement) -> Option<f64> {
match element {
BuildingElement::Transparent { orientation, .. } => Some(*orientation),
_ => None,
}
}

#[derive(Debug, PartialEq)]
enum HeatFlowDirection {
Horizontal,
Upwards,
Downwards,
}

pub fn element_from_named(named_element: &NamedBuildingElement) -> &BuildingElement {
let NamedBuildingElement { element, .. } = named_element;
element
}

// Thermal properties of ground from BS EN ISO 13370:2017 Table 7
// Use values for clay or silt (same as BR 443 and SAP 10)
const THERMAL_CONDUCTIVITY_OF_GROUND: f64 = 1.5; // in W/(m.K)
Expand Down
255 changes: 251 additions & 4 deletions src/core/space_heat_demand/ventilation_element.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
use crate::core::controls::time_control::SetpointTimeControl;
use crate::core::energy_supply::energy_supply::EnergySupply;
use crate::core::space_heat_demand::building_element::{
area_for_building_element_input, element_from_named, mid_height_for, orientation_for,
};
use crate::core::space_heat_demand::zone::NamedBuildingElement;
use crate::core::units::{LITRES_PER_CUBIC_METRE, SECONDS_PER_HOUR, WATTS_PER_KILOWATT};
use crate::external_conditions::ExternalConditions;
use crate::input::{
EnergySupplyInput, InfiltrationBuildType, InfiltrationShelterType, InfiltrationTestType,
BuildingElement, EnergySupplyInput, InfiltrationBuildType, InfiltrationShelterType,
InfiltrationTestType,
};
use crate::simulation_time::SimulationTimeIterator;
use std::collections::HashSet;

fn air_change_rate_to_flow_rate(air_change_rate: f64, zone_volume: f64) -> f64 {
air_change_rate * zone_volume / SECONDS_PER_HOUR as f64
Expand Down Expand Up @@ -503,9 +510,249 @@ impl VentilationElement for WholeHouseExtractVentilation {

//tbc
pub struct NaturalVentilation;
//tbc:
pub struct WindowOpeningForCooling {
external_conditions: ExternalConditions, // incomplete struct

pub struct WindowOpeningForCooling<'a> {
window_area_equivalent: f64,
external_conditions: ExternalConditions,
openings: Option<Vec<&'a BuildingElement>>, // actually only meaningfully contains BuildingElement::Transparent
control: &'a SetpointTimeControl,
natural_ventilation: Option<NaturalVentilation>,
a_b: Option<f64>,
a_w: Option<f64>,
opening_height_diff: f64,
opening_area_ratio: Option<f64>,
cross_vent: bool,
stack_vent: bool,
}

impl<'a> WindowOpeningForCooling<'a> {
/// Arguments:
/// `window_area_equivalent` - maximum equivalent area of all openings in the relevant zone
/// `external_conditions` - reference to ExternalConditions object
/// `named_openings` - list of openings to be considered
/// `control` - reference to control object (must implement setpnt function)
/// `natural_ventilation` - reference to NaturalVentilation object, if building is naturally ventilated
pub fn new(
window_area_equivalent: f64,
external_conditions: ExternalConditions,
named_openings: &'a Vec<NamedBuildingElement>,
control: &'a SetpointTimeControl,
natural_ventilation: Option<NaturalVentilation>,
) -> Self {
let openings = named_openings
.iter()
.map(element_from_named)
.collect::<Vec<&BuildingElement>>();
// Assign equivalent areas to each window/group in proportion to actual area
let opening_area_total = openings
.iter()
.map(|element| area_for_building_element_input(element))
.sum::<f64>();
let opening_area_equiv_total_ratio = window_area_equivalent / opening_area_total;

// Find orientation of largest window
// Find height of highest and lowest windows
let mut largest_op = &openings[0];
let mut highest_op = &openings[0];
let mut lowest_op = &openings[0];
for op in openings[1..].iter() {
if area_for_building_element_input(op) > area_for_building_element_input(largest_op) {
largest_op = op;
}
if mid_height_for(op) > mid_height_for(highest_op) {
highest_op = op;
}
if mid_height_for(op) < mid_height_for(lowest_op) {
lowest_op = op;
}
}
let largest_op_orientation = orientation_for(largest_op).unwrap();
let op_height_threshold =
(mid_height_for(highest_op).unwrap() - mid_height_for(lowest_op).unwrap()) / 2.0;

let mut openings_same_side = vec![];
let mut openings_opp_side = vec![];
let mut openings_low = vec![];
let mut openings_high = vec![];
for op in named_openings.iter() {
// Determine orientation of other windows relative to largest
let mut op_rel_orientation =
(orientation_for(element_from_named(op)).unwrap() - largest_op_orientation).abs();
if op_rel_orientation > 360.0 {
op_rel_orientation -= 360.0;
}
// Group windows into same, opposite and adjacent sides
if op_rel_orientation <= 45.0 {
openings_same_side.push(op);
} else if op_rel_orientation >= 135.0 {
openings_opp_side.push(op);
}
// Else opening is on adjacent side, so ignore

// Assign windows to high and low groups based on which they are closest to
if mid_height_for(element_from_named(op)).unwrap() < op_height_threshold {
openings_low.push(op);
} else {
openings_high.push(op);
}
}

let cross_vent = openings_opp_side.len() > 0;

let mut a_b = None;
let mut a_w = None;
let mut stack_vent = false;
let mut opening_height_diff = 0.0;
let mut opening_area_ratio = None;
let mut openings_for_struct = None;

if cross_vent {
let unique_openings_same_side = openings_same_side.iter().collect::<HashSet<_>>();
let unique_openings_opp_side = openings_opp_side.iter().collect::<HashSet<_>>();
let unique_openings_high = openings_high.iter().collect::<HashSet<_>>();
let unique_openings_low = openings_low.iter().collect::<HashSet<_>>();
let openings_same_side_high = unique_openings_same_side
.intersection(&unique_openings_high)
.collect::<Vec<_>>();
let openings_same_side_low = unique_openings_same_side
.intersection(&unique_openings_low)
.collect::<Vec<_>>();
let openings_opp_side_high = unique_openings_opp_side
.intersection(&unique_openings_high)
.collect::<Vec<_>>();
let openings_opp_side_low = unique_openings_same_side
.intersection(&unique_openings_low)
.collect::<Vec<_>>();

// Calculate high and low opening areas on same and opposite sides of building
let a1 = opening_area_equiv_total_ratio
* openings_same_side_high
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();
let a2 = opening_area_equiv_total_ratio
* openings_same_side_low
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();
let a3 = opening_area_equiv_total_ratio
* openings_opp_side_high
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();
let a4 = opening_area_equiv_total_ratio
* openings_opp_side_low
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();
a_w = Some((1.0 / ((1.0 / ((a1 + a3).powi(2))) + 1.0 / ((a2 + a4).powi(2)))).sqrt());
if a2 + a4 == 0.0 {
a_b = Some(0.0);
opening_height_diff = 0.0;
} else {
a_b =
Some((1.0 / ((1.0 / ((a1 + a3).powi(2))) + 1.0 / ((a2 + a4).powi(2)))).sqrt());

// Calculate area-weighted average height of windows in high and low groups
let opening_mid_height_ave_upper = (openings_same_side_high
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>()
+ openings_opp_side_high
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>())
/ (openings_same_side_high
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>()
+ openings_opp_side_high
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>());
let opening_mid_height_ave_lower = (openings_same_side_low
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>()
+ openings_opp_side_low
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>())
/ (openings_same_side_low
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>()
+ openings_opp_side_low
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>());
opening_height_diff = opening_mid_height_ave_upper - opening_mid_height_ave_lower;
}
} else {
if openings_high.len() > 1 && openings_low.len() > 1 {
stack_vent = true;
let opening_area_upper = openings_high
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();
let opening_area_lower = openings_low
.iter()
.map(|nel| area_for_building_element_input(element_from_named(nel)))
.sum::<f64>();

// Calculate opening area ratio
opening_area_ratio = Some(opening_area_upper / opening_area_lower);

let opening_mid_height_ave_upper = openings_high
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>()
/ opening_area_upper;
let opening_mid_height_ave_lower = openings_low
.iter()
.map(|nel| {
let el = element_from_named(nel);
mid_height_for(el).unwrap() * area_for_building_element_input(el)
})
.sum::<f64>()
/ opening_area_lower;
// Calculate opening height difference
opening_height_diff = opening_mid_height_ave_upper - opening_mid_height_ave_lower;
} else {
stack_vent = false;
openings_for_struct = Some(openings);
}
}

Self {
window_area_equivalent,
external_conditions,
openings: openings_for_struct,
control,
natural_ventilation,
a_b,
a_w,
opening_height_diff,
opening_area_ratio,
cross_vent,
stack_vent,
}
}
}

/// Calculate the supply temperature of the air flow element
Expand Down
32 changes: 26 additions & 6 deletions src/core/space_heat_demand/zone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::simulation_time::{SimulationTime, SimulationTimeIterator};
use nalgebra::{matrix, DMatrix};
use serde::Deserialize;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

// Convective fractions
// (default values from BS EN ISO 52016-1:2017, Table B.11)
Expand All @@ -28,13 +29,13 @@ const F_SOL_C: f64 = 0.1;
// (default value from BS EN ISO 52016-1:2017, Table B.17)
const K_M_INT: f64 = 10000.0; // J / (m2.K)

pub struct Zone {
pub struct Zone<'a> {
useful_area: f64,
volume: f64,
building_elements: Vec<NamedBuildingElement>,
thermal_bridging: ThermalBridging,
vent_elements: Vec<Box<dyn VentilationElement>>,
vent_cool_extra: Option<WindowOpeningForCooling>,
vent_cool_extra: Option<WindowOpeningForCooling<'a>>,
tb_heat_trans_coeff: f64,
/// total area of all building elements associated with this zone, in m2
area_el_total: f64,
Expand Down Expand Up @@ -66,7 +67,7 @@ pub struct Zone {
external_conditions: ExternalConditions,
}

impl Zone {
impl<'a> Zone<'a> {
/// Construct a Zone object
///
/// ## Arguments
Expand Down Expand Up @@ -379,7 +380,10 @@ pub fn space_heat_cool_demand(
// and need to use interpolation to work out additional ventilation required
// (just like calc for heat_cool_load_unrestricted below)
let mut h_ve_cool_extra = 0.0;
// if vent_cool_extra.is_some() && temp_operative_free > temp_setpnt_cool_vent
if vent_cool_extra.is_some() && temp_operative_free > temp_setpnt_cool_vent.unwrap() {
// Calculate node and internal air temperatures with maximum additional ventilation
// finish
}

(0.0, 0.0, 0.0)
}
Expand Down Expand Up @@ -894,8 +898,24 @@ fn temp_operative(

#[derive(Clone)]
pub struct NamedBuildingElement {
name: String,
element: BuildingElement,
pub name: String,
pub element: BuildingElement,
}

// equality and hashing based on name for identity

impl PartialEq for NamedBuildingElement {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}

impl Eq for NamedBuildingElement {}

impl Hash for NamedBuildingElement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}

struct HeatBalanceAirNode {
Expand Down

0 comments on commit 7681389

Please sign in to comment.