//! Kontroll av beregningsmotoren mot analytiske sammenhenger og mot publiserte eksempler. use enok::data::Datakilde; use enok::klima::Vaerdata; use enok::modell::lost::{Lost, Regulering}; use enok::modell::Bygning; use enok::motor::{iso52016::Systemtilskudd, simuler, Timeserie, Varmetapsbudsjett}; use enok::TIMER; /// Bygger et kunstig værdatasett med konstant utetemperatur og uten sol. fn stille_vaer(t_ute: f64) -> Vaerdata { Vaerdata { sted: "Prøve".into(), land: "NOR".into(), bredde: 60.0, lengde: 10.0, tidssone: 1.0, hoyde: 0.0, t_ute: vec![t_ute; TIMER], rf: vec![50.0; TIMER], ghi: vec![0.0; TIMER], dni: vec![0.0; TIMER], dhi: vec![0.0; TIMER], // Himmeltemperatur lik utetemperatur, slik at langbølget utstråling ikke spiller inn. ir_himmel: vec![5.67e-8 * (t_ute + 273.15f64).powi(4); TIMER], vind: vec![0.0; TIMER], } } const ENKEL: &str = r#" [bygning] navn = "Kassen" kategori = "smaahus" byggeaar = 2020 oppvarmet_bra = 100.0 oppvarmet_volum = 240.0 [[flate]] navn = "Vegger" type = "vegg" areal = 120.0 u_verdi = 0.20 masseklasse = "middels" [[flate]] navn = "Tak" type = "tak" areal = 100.0 u_verdi = 0.15 masseklasse = "middels" [[flate]] navn = "Gulv" type = "gulv" areal = 100.0 u_verdi = 0.15 masseklasse = "tung" [kuldebroer] normalisert = 0.0 [infiltrasjon] n50 = 0.0 [ventilasjon] type = "naturlig" luftmengde = 0.0 luftmengde_natt = 0.0 [drift] nattsenking = 21.0 [[oppvarming.kilde]] type = "direkte_el" andel_rom = 1.0 andel_ventilasjon = 1.0 andel_tappevann = 1.0 "#; /// Uten varmetilskudd, sol, ventilasjon og infiltrasjon skal effektbehovet i stasjonær /// tilstand være varmetapskoeffisienten ganger temperaturdifferansen. /// /// Overgangsmotstanden R_si = 0,13 m²K/W som U-verdien bygger på, gjelder mot operativ /// temperatur. Nodemodellen skiller luft- og strålingstemperatur, så sammenligningen gjøres /// med regulering på operativ temperatur. #[test] fn stasjonaert_varmetap_stemmer_med_varmetapstallet() { let b: Bygning = toml::from_str(ENKEL).unwrap(); let kilde = Datakilde::default(); let mut l = Lost::fra(&b, &kilde).unwrap(); // Slå av alle interne laster og bruk lufttemperatur som reguleringsstørrelse. l.laster.belysning = [0.0; 24]; l.laster.utstyr_effekt = [0.0; 24]; l.laster.utstyr_varme = [0.0; 24]; l.laster.personer = [0.0; 24]; l.laster.varmtvann = [0.0; 24]; l.laster.pumper = [0.0; 24]; l.regulering = Regulering::Operativ; let t_ute = 1.0; let v = stille_vaer(t_ute); let ts = simuler(&l, &v, Systemtilskudd::default()).unwrap(); let budsjett = Varmetapsbudsjett::beregn(&l); // Gulvet står mot det fri i denne prøven, så hele transmisjonstapet er mot utetemperaturen. let h = budsjett.transmisjon_w_per_k; let forventet = h * (l.settpunkt_oppvarming - t_ute); // Se bort fra innkjøringen: bruk siste halvdel av året. let siste: Vec = ts.romoppvarming[TIMER / 2..].to_vec(); let snitt = siste.iter().sum::() / siste.len() as f64; let avvik = (snitt - forventet).abs() / forventet; assert!( avvik < 0.02, "stasjonært varmetap avviker {:.2} %: beregnet {snitt:.1} W, forventet {forventet:.1} W", avvik * 100.0 ); } /// Energibalansen skal gå opp: tilført varme og varmetilskudd skal være lik varmetapet. #[test] fn aarlig_energibalanse_gaar_opp() { let b: Bygning = toml::from_str(ENKEL).unwrap(); let kilde = Datakilde::default(); let l = Lost::fra(&b, &kilde).unwrap(); let v = stille_vaer(1.0); let ts = simuler(&l, &v, Systemtilskudd::default()).unwrap(); let budsjett = Varmetapsbudsjett::beregn(&l); let tilfort: f64 = ts.romoppvarming.iter().sum::() + ts.internlast.iter().sum::(); // Varmetapet regnet av de simulerte operative temperaturene, som U-verdiene gjelder mot. let tap: f64 = ts .t_operativ .iter() .map(|t| budsjett.transmisjon_w_per_k * (t - 1.0)) .sum(); let avvik = (tilfort - tap).abs() / tap; assert!( avvik < 0.02, "energibalansen avviker {:.2} %", avvik * 100.0 ); } /// Dobbelt så stort temperaturfall skal gi dobbelt så stort varmebehov i en lineær modell. #[test] fn varmebehovet_er_lineaert_i_temperaturdifferansen() { let b: Bygning = toml::from_str(ENKEL).unwrap(); let kilde = Datakilde::default(); let mut l = Lost::fra(&b, &kilde).unwrap(); l.laster.belysning = [0.0; 24]; l.laster.utstyr_effekt = [0.0; 24]; l.laster.utstyr_varme = [0.0; 24]; l.laster.personer = [0.0; 24]; l.laster.varmtvann = [0.0; 24]; l.laster.pumper = [0.0; 24]; l.regulering = Regulering::Operativ; let en = Timeserie::sum_kwh( &simuler(&l, &stille_vaer(11.0), Systemtilskudd::default()) .unwrap() .romoppvarming, ); let to = Timeserie::sum_kwh( &simuler(&l, &stille_vaer(1.0), Systemtilskudd::default()) .unwrap() .romoppvarming, ); let forhold = to / en; assert!( (forhold - 2.0).abs() < 0.01, "forholdet skulle vært 2,0, men ble {forhold:.3}" ); } const MED_KJELLER: &str = r#" [bygning] navn = "Hus med kjeller" kategori = "smaahus" byggeaar = 1950 oppvarmet_bra = 100.0 oppvarmet_volum = 240.0 [[flate]] navn = "Yttervegg" type = "vegg" areal = 120.0 u_verdi = 1.0 masseklasse = "lett" [[flate]] navn = "Tak" type = "tak" areal = 100.0 u_verdi = 0.5 masseklasse = "lett" [[flate]] navn = "Etasjeskiller mot kjeller" type = "gulv" areal = 100.0 u_verdi = 0.8 mot = "uoppvarmet" sone = "Kjeller" masseklasse = "middels" [[sone]] navn = "Kjeller" volum = 220.0 luftveksling = 0.4 andel_distribusjonstap = 1.0 fast_effekt = 0.0 [[sone.flate]] navn = "Kjellermur" type = "vegg" areal = 90.0 u_verdi = 3.0 mot = "grunn" dybde = 1.6 over_terreng = 0.6 [[sone.flate]] navn = "Kjellergulv" type = "gulv" areal = 100.0 u_verdi = 3.5 mot = "grunn" omkrets = 40.0 dybde = 2.2 [kuldebroer] normalisert = 0.0 [infiltrasjon] n50 = 4.0 [ventilasjon] type = "naturlig" luftmengde = 1.2 [[oppvarming.kilde]] type = "el_kjel" andel_rom = 1.0 andel_tappevann = 1.0 [distribusjon] type = "radiator" roer_uoppvarmet = 1.0 "#; fn med_kjeller() -> (Lost, Vaerdata) { let b: Bygning = toml::from_str(MED_KJELLER).unwrap(); let l = Lost::fra(&b, &Datakilde::default()).unwrap(); (l, stille_vaer(-5.0)) } /// Kjellertemperaturen skal ligge mellom ute og inne, og beregnes for hver time. #[test] fn kjelleren_ligger_mellom_ute_og_inne() { let (l, v) = med_kjeller(); assert_eq!(l.soner.len(), 1); let ts = simuler(&l, &v, Systemtilskudd::default()).unwrap(); let t_kjeller = &ts.sonetemperatur[0]; let snitt = t_kjeller.iter().sum::() / t_kjeller.len() as f64; assert!( snitt > -5.0 && snitt < 21.0, "kjellertemperaturen ble {snitt:.1} °C" ); // Uten varmetilskudd holder etasjeskilleren kjelleren godt over utetemperaturen. assert!(snitt > 0.0, "kjellertemperaturen ble {snitt:.1} °C"); } /// Varme fra uisolerte rør i kjelleren skal heve kjellertemperaturen og redusere /// varmetapet gjennom etasjeskilleren, slik termografirapporter på slike hus beskriver. #[test] fn varme_ror_i_kjelleren_reduserer_romoppvarmingen() { let (l, v) = med_kjeller(); let uten = simuler(&l, &v, Systemtilskudd::default()).unwrap(); let med = simuler( &l, &v, Systemtilskudd { tappevann: 0.0, sone_fast: vec![1500.0], sone_andel_romvarme: vec![0.0], }, ) .unwrap(); let t_uten = uten.sonetemperatur[0].iter().sum::() / TIMER as f64; let t_med = med.sonetemperatur[0].iter().sum::() / TIMER as f64; assert!( t_med > t_uten + 2.0, "kjelleren ble bare {t_med:.1} mot {t_uten:.1} °C" ); let varme_uten = Timeserie::sum_kwh(&uten.romoppvarming); let varme_med = Timeserie::sum_kwh(&med.romoppvarming); assert!( varme_med < varme_uten, "romoppvarmingen økte: {varme_med:.0} mot {varme_uten:.0} kWh" ); } /// Grunnen demper varmetapet fra kjelleren: en uisolert kjellermur under terreng skal /// gi lavere varmetransport enn den samme muren over terreng. #[test] fn nedgravd_mur_taper_mindre_enn_mur_over_terreng() { let (l, _) = med_kjeller(); let sone = &l.soner[0]; // Muren er 90 m² med U = 3,0. Over terreng ville hele gitt 270 W/K. assert!( sone.h_mot_grunn < 150.0, "grunnen dempet ikke nok: {} W/K", sone.h_mot_grunn ); // Den delen som står over terreng skal fortsatt telle fullt. assert!( sone.h_mot_ute > 40.0, "delen over terreng mangler: {} W/K", sone.h_mot_ute ); }