Hvad kan Be18-parser?
Be18 gemmer et projekt som en XML-fil med endelsen .bexml eller .xml – de to har samme indhold. Be18-parser oversætter filen til C#-klasser med navngivne egenskaber, så du arbejder med fx building.HeatedFloorArea og window.U i stedet for XML-elementer og record-id'er.
Biblioteket kan:
- Læse og skrive Be18-projektfiler – en fil, der indlæses og gemmes uden ændringer, skrives tilbage byte for byte
- Vise klimaskærm, ventilation, interne varmetilskud, belysning, varmeforsyning (kedler, fjernvarme, varmepumper, solvarme og solceller), varmefordeling og varmt brugsvand som typede modeller
- Gøre referencer mellem fx vinduer og skygger til almindelige objektreferencer
- Bevare felter og typer, som biblioteket endnu ikke kender, så de skrives uændret tilbage
- Dokumentere egenskaberne med enheder og de danske betegnelser fra Be18's brugerflade
- Køre projekter gennem en lokalt installeret Be-beregningsmotor
Det skal du bruge: .NET 10 og Be-motoren
Læsning, redigering og oprettelse af projektfiler kræver kun .NET 10 SDK. Beregninger kræver desuden:
- Windows
- En installation af Be med en gyldig licens – biblioteket indeholder ikke selve beregningsmotoren, men indlæser den fra installationen
- At programmet kører med samme arkitektur som motorens DLL-fil. Be10- og Be15-motorerne er 32-bit og kræver en x86-proces
git clone https://github.com/LucasRinneToh/Be18-parser.git
dotnet build Be18-parser/Be18-parser.slnxHerefter refererer du Be18-parser.csproj fra dit eget projekt. Repositoriet har også et eksempelprojekt i mappen Examples, der viser, hvordan man indlæser, retter, opretter og beregner et projekt.
Læs og ret en Be18-fil i C#
Et projekt indlæses med Be18Document.Load. Alt andet nås via bygningen: vinduer, konstruktioner, ventilationszoner, varmeforsyning og så videre. Du retter værdierne direkte på modellerne og gemmer projektet som en ny fil:
using Be18_parser;
var document = Be18Document.Load("hus.bexml");
var building = document.Building!;
Console.WriteLine($"{building.Name}: {building.HeatedFloorArea} m²");
foreach (var window in building.TransparentConstructions)
Console.WriteLine($"{window.Name}: {window.Count} × {window.Area} m², U = {window.U} W/m²K");
// Ret værdierne direkte på modellerne, og gem projektet som en ny fil.
building.HeatingSetpoint = 21;
foreach (var construction in building.OpaqueConstructions)
construction.U *= 0.9;
document.Save("hus-revideret.bexml");Nye objekter, fx et ekstra vindue, får automatisk et ledigt record-id og skal blot tilføjes til bygningen for at blive gemt.
Hvad er en multivariat beregning?
I en klassisk følsomhedsberegning ændrer man én parameter ad gangen: Hvad sker der med energibehovet, hvis vinduerne bliver bedre? I en multivariat beregning varierer man flere parametre på én gang og regner alle kombinationerne – eller et stort udsnit af dem.
Det viser, hvilke parametre der betyder mest for netop dit projekt, og hvordan de påvirker hinanden. Fx kan virkningen af en lavere g-værdi afhænge af, hvilken vej bygningen vender. Til gengæld vokser antallet af beregninger hurtigt: Fire parametre med tre, tre, tre og fire niveauer giver 3 × 3 × 3 × 4 = 108 varianter, og dem taster man ikke ind i hånden.
Eksemplerne nedenfor varierer fire parametre:
- U-værdierne for vægge, tage og gulve, ganget med en faktor på 1,0, 0,85 eller 0,7
- Vinduestypen: 2-lags (U = 1,2 W/m²K, g = 0,60), 3-lags (U = 0,8 W/m²K, g = 0,50) eller 3-lags solafskærmende (U = 0,8 W/m²K, g = 0,35)
- Varmegenvindingen i ventilationsanlægget: 80, 85 eller 90 %
- Bygningens rotation: 0, 90, 180 eller 270 grader
Værdierne er eksempler. Brug produktdata og niveauer, der giver mening for dit projekt.
Opsæt et projekt til variantberegninger
Eksemplerne kører i en lille konsolapp, der refererer Be18-parser. Opret den ved siden af mappen Be18-parser – ikke inde i den:
dotnet new console -o Varianter
cd Varianter
dotnet add reference ../Be18-parser/Be18-parser.csprojLæg dit grundprojekt i mappen Varianter som grundprojekt.bexml. Start med et projekt, der allerede regner uden fejl i Be18, så du ved, at alle de felter, beregningsmotoren skal bruge, er udfyldt. Varianterne ændrer kun værdier i felter, der findes i forvejen.
Fælles kode: parametre og varianter
Læg koden herunder i en ny fil, Varianter.cs. Den beskriver parametrene, laver alle kombinationer og bygger hver variant ud fra en frisk kopi af grundprojektet:
using System.Globalization;
using Be18_parser;
// En vinduestype: U-værdi for hele vinduet (W/m²K) og rudens g-værdi.
public record WindowType(string Name, double U, double G);
// Én variant: én værdi for hver parameter.
public record Variant(string Id, double OpaqueUFactor, WindowType Window, double HeatRecovery, double Rotation);
public static class Variants
{
// Parametrene og deres niveauer. Værdierne er eksempler.
public static readonly double[] OpaqueUFactors = [1.0, 0.85, 0.7];
public static readonly WindowType[] WindowTypes =
[
new WindowType("2-lags", 1.2, 0.60),
new WindowType("3-lags", 0.8, 0.50),
new WindowType("3-lags solafskærmende", 0.8, 0.35),
];
public static readonly double[] HeatRecoveries = [0.80, 0.85, 0.90];
public static readonly double[] Rotations = [0, 90, 180, 270];
// Alle kombinationer: 3 × 3 × 3 × 4 = 108 varianter.
public static List<Variant> FullFactorial() =>
(from u in OpaqueUFactors
from window in WindowTypes
from heatRecovery in HeatRecoveries
from rotation in Rotations
select (u, window, heatRecovery, rotation))
.Select((c, i) => new Variant($"V{i + 1:000}", c.u, c.window, c.heatRecovery, c.rotation))
.ToList();
// Bygger varianten ud fra en frisk kopi af grundprojektet, så ændringer ikke hober sig op.
public static Be18Document Apply(string baseXml, Variant variant)
{
var document = Be18Document.Parse(baseXml);
var building = document.Building ?? throw new InvalidOperationException("Projektet har ingen bygning.");
foreach (var construction in building.OpaqueConstructions)
construction.U = Math.Round(construction.U * variant.OpaqueUFactor, 3);
// Vinduer genkendes på, at de har en glasandel. Døre uden glas beholder deres værdier.
foreach (var window in building.TransparentConstructions.Where(w => w.GlazingFraction > 0))
{
window.U = variant.Window.U;
window.GValue = variant.Window.G;
}
// Kun zoner med mekanisk ventilation har varmegenvinding.
foreach (var zone in building.VentilationZones.Where(z => z.MechanicalVentilation > 0))
zone.HeatRecoveryEfficiency = variant.HeatRecovery;
building.Rotation = variant.Rotation;
return document;
}
public const string CsvHeader = "id,u_faktor,vindue,vindue_u,vindue_g,varmegenvinding,rotation";
public static string ToCsv(Variant v) => string.Join(',',
v.Id, F(v.OpaqueUFactor), v.Window.Name, F(v.Window.U), F(v.Window.G), F(v.HeatRecovery), F(v.Rotation));
// Punktum som decimaltegn, uanset computerens sprogindstilling.
private static string F(double value) => value.ToString(CultureInfo.InvariantCulture);
}Det vigtigste er linjen med Be18Document.Parse: Hver variant starter fra den uændrede tekst i grundprojektet. Ændrede man det samme dokument igen og igen, ville fx faktoren på U-værdierne blive ganget på flere gange.
Vinduer genkendes på, at de har en glasandel, og varmegenvindingen sættes kun i zoner med mekanisk ventilation. Tilpas filtrene, hvis dit projekt kræver det – fx ud fra navnene på konstruktionerne.
Eksempel 1: Gem alle varianter som .bexml-filer
Det første eksempel kræver ingen beregningsmotor og kører på Windows, macOS og Linux. Det gemmer en .bexml-fil pr. variant og en CSV-fil med parametrene. Læg koden i Program.cs:
using Be18_parser;
// Grundprojektet indlæses én gang og bruges som udgangspunkt for alle varianter.
var baseXml = Be18Document.Load("grundprojekt.bexml").ToXml();
var variants = Variants.FullFactorial();
Directory.CreateDirectory("varianter");
var lines = new List<string> { Variants.CsvHeader };
foreach (var variant in variants)
{
Variants.Apply(baseXml, variant).Save(Path.Combine("varianter", $"{variant.Id}.bexml"));
lines.Add(Variants.ToCsv(variant));
}
File.WriteAllLines(Path.Combine("varianter", "varianter.csv"), lines);
Console.WriteLine($"Gemte {variants.Count} varianter i mappen varianter.");Kør det med dotnet run. Bagefter indeholder mappen varianter 108 projektfiler, som kan åbnes i Be18, og varianter.csv, der viser parameterværdierne for hver fil. Tal skrives med punktum som decimaltegn, så filen kan læses direkte i fx Python eller R.
Eksempel 2: Beregn alle varianter med Be-motoren
På en Windows-pc med Be installeret kan varianterne sendes direkte til beregningsmotoren uden først at blive gemt som projektfiler. Eksemplet bruger én motor til alle beregninger og gemmer nøgletallene for hver variant som XML. Læg koden i Program.cs i stedet for eksempel 1:
// Kør med: dotnet run -a x86, hvis motoren er 32-bit som Be10- og Be15-motorerne.
using Be18_parser;
using Be18_parser.Engine;
if (!OperatingSystem.IsWindows())
{
Console.WriteLine("Be-motoren kører kun på Windows.");
return;
}
var baseXml = Be18Document.Load("grundprojekt.bexml").ToXml();
var variants = Variants.FullFactorial();
Directory.CreateDirectory("resultater");
var lines = new List<string> { Variants.CsvHeader + ",ok" };
// Én motor til alle beregninger. Uden en sti findes motoren i den aktuelle mappe eller via
// registreringsdatabasen. Mangler motoren eller licensen, eller passer processens arkitektur
// ikke til motoren, kastes en BeEngineException med en forklaring.
using var engine = new BeEngine();
Console.WriteLine($"Motor: {engine.DllPath}");
foreach (var variant in variants)
{
// Varianten sendes til motoren som tekst, så der skal ikke gemmes en projektfil først.
var result = engine.GetKeyXml(Variants.Apply(baseXml, variant).ToXml());
File.WriteAllText(Path.Combine("resultater", $"{variant.Id}.xml"), result.Xml, Be18Document.TextEncoding);
lines.Add($"{Variants.ToCsv(variant)},{(result.Success ? "ja" : "nej")}");
Console.WriteLine($"{variant.Id}: {(result.Success ? "beregnet" : "fejl")}");
}
File.WriteAllLines(Path.Combine("resultater", "resultater.csv"), lines);Koden, der kører beregningsmotoren, er endnu ikke afprøvet med en Be18-installation, så kontrollér de første resultater mod Be18 selv (se Begrænsninger i Be18-parser nedenfor). Er motoren 32-bit, skal programmet køre som x86 med dotnet run -a x86, og det kræver x86-udgaven af .NET. BeEngine tjekker arkitekturen og giver en forklarende fejl, hvis den ikke passer. Findes motoren ikke automatisk, kan du give stien til DLL-filen som argument til BeEngine.
Beregningerne kører én ad gangen. BeEngine lader kaldene vente på hinanden, fordi motoren ikke vides at være trådsikker, så der er ingen gevinst ved at starte dem parallelt i samme program. Har du brug for mere end nøgletallene, giver GetResultXml de fulde resultater.
Find tallene i Be-motorens resultat
Motoren returnerer nøgletallene som XML. Den nemmeste måde at finde de tal, du vil sammenligne, er at udskrive alt i én resultatfil sammen med dets placering. Tilføj linjerne herunder sidst i Program.cs fra eksempel 2:
// Udskriv alle værdier i den første variants resultat sammen med deres placering i XML'en.
var firstResult = File.ReadAllText(Path.Combine("resultater", $"{variants[0].Id}.xml"), Be18Document.TextEncoding);
foreach (var element in System.Xml.Linq.XDocument.Parse(firstResult).Descendants())
{
var path = string.Join('/', element.AncestorsAndSelf().Reverse().Select(e => e.Name.LocalName));
foreach (var attribute in element.Attributes())
Console.WriteLine($"{path}/@{attribute.Name.LocalName} = {attribute.Value}");
if (!element.HasElements && element.Value.Length > 0)
Console.WriteLine($"{path} = {element.Value}");
}Når du kender placeringen af fx det samlede energibehov, kan du hente værdien for hver variant med XDocument og skrive den i CSV-filen ved siden af parametrene. Tjek, om tallene står med komma eller punktum, før du læser dem som tal. Så har du én tabel med input og resultater, som kan sorteres, plottes eller analyseres med regression for at se, hvilke parametre der flytter mest.
Eksempel 3: Stikprøver, når kombinationerne bliver for mange
Med flere parametre og niveauer bliver en fuld kombination hurtigt uoverskuelig: Seks parametre med fem niveauer hver giver 15.625 varianter. Et alternativ er at trække et tilfældigt udsnit, hvor de kontinuerte parametre kan tage alle værdier inden for et interval. Tilføj metoden herunder til klassen Variants i Varianter.cs:
// Tilfældige varianter. Samme seed giver de samme varianter hver gang.
public static List<Variant> RandomSample(int count, int seed = 1)
{
var random = new Random(seed);
return Enumerable.Range(1, count)
.Select(i => new Variant(
$"S{i:000}",
OpaqueUFactor: Math.Round(0.7 + 0.3 * random.NextDouble(), 3),
Window: WindowTypes[random.Next(WindowTypes.Length)],
HeatRecovery: Math.Round(0.80 + 0.10 * random.NextDouble(), 3),
Rotation: random.Next(360)))
.ToList();
}I Program.cs udskifter du blot Variants.FullFactorial() med fx Variants.RandomSample(200). Resten af koden er den samme, både når varianterne gemmes som filer, og når de beregnes. Vil du have stikprøver, der dækker intervallerne mere jævnt, kan du bruge Latin hypercube sampling – princippet er det samme.
Tilfældige stikprøver er i princippet en Monte Carlo-metode, men her er formålet at udforske designmuligheder. Bruges stikprøverne i stedet til at beskrive usikkerhed, er der tale om en Monte Carlo-analyse – se næste afsnit.
Hvad er en Monte Carlo-analyse?
En Monte Carlo-analyse bruger tilfældige stikprøver til at vise, hvor usikkert et resultat er. I stedet for at vælge mellem designmuligheder trækker man de usikre input – fx den U-værdi, der faktisk opnås på byggepladsen – fra sandsynlighedsfordelinger og regner projektet igen og igen med en ny trækning hver gang.
Resultatet er ikke ét energibehov, men en fordeling med middelværdi, spredning, fraktiler og en andel af trækningerne, der ligger over en given grænse – fx energirammen. Fordelingen viser, hvor stor margin projektet har, og hvilke usikkerheder der betyder mest for resultatet.
Eksempel 4: Monte Carlo-analyse af usikre input
Eksemplet tager udgangspunkt i grundprojektet og lader fire input afvige fra de projekterede værdier:
- U-værdierne for vægge, tage og gulve: en faktor, der er trekantfordelt mellem 0,95 og 1,20 med 1,0 som den mest sandsynlige værdi. Fordelingen er skæv opad, så der er plads til, at udførelsen bliver dårligere end projekteret
- Linjetabene: en faktor, der er trekantfordelt mellem 0,9 og 1,5 med 1,0 som den mest sandsynlige værdi
- Varmegenvindingen: en normalfordelt afvigelse fra projektets værdi med en spredning på 3 procentpoint
- SEL-værdien for ventilationen: en faktor, der er trekantfordelt mellem 0,9 og 1,3 med 1,0 som den mest sandsynlige værdi
using System.Globalization;
using Be18_parser;
// Én trækning: hvor meget hvert usikkert input afviger fra grundprojektet.
public record Sample(string Id, double OpaqueUFactor, double ThermalBridgeFactor, double HeatRecoveryChange, double SelFactor);
public static class MonteCarlo
{
// Fordelingerne er eksempler. Samme seed giver de samme trækninger hver gang.
public static List<Sample> Draw(int count, int seed = 1)
{
var random = new Random(seed);
return Enumerable.Range(1, count)
.Select(i => new Sample(
$"MC{i:0000}",
OpaqueUFactor: Triangular(random, 0.95, 1.0, 1.2),
ThermalBridgeFactor: Triangular(random, 0.9, 1.0, 1.5),
HeatRecoveryChange: Normal(random, 0, 0.03),
SelFactor: Triangular(random, 0.9, 1.0, 1.3)))
.ToList();
}
// Bygger trækningen ud fra en frisk kopi af grundprojektet.
public static Be18Document Apply(string baseXml, Sample sample)
{
var document = Be18Document.Parse(baseXml);
var building = document.Building ?? throw new InvalidOperationException("Projektet har ingen bygning.");
foreach (var construction in building.OpaqueConstructions)
construction.U = Math.Round(construction.U * sample.OpaqueUFactor, 3);
// For linjetab er U linjetabet i W/mK.
foreach (var bridge in building.ThermalBridges)
bridge.U = Math.Round(bridge.U * sample.ThermalBridgeFactor, 3);
foreach (var zone in building.VentilationZones.Where(z => z.MechanicalVentilation > 0))
{
var heatRecovery = zone.HeatRecoveryEfficiency + sample.HeatRecoveryChange;
zone.HeatRecoveryEfficiency = Math.Round(Math.Clamp(heatRecovery, 0, 0.95), 3);
zone.Sel = Math.Round(zone.Sel * sample.SelFactor, 2);
}
return document;
}
public const string CsvHeader = "id,u_faktor,linjetab_faktor,varmegenvinding_afvigelse,sel_faktor";
public static string ToCsv(Sample s) => string.Join(',',
s.Id, F(s.OpaqueUFactor), F(s.ThermalBridgeFactor), F(s.HeatRecoveryChange), F(s.SelFactor));
// Trekantfordeling mellem min og max, hvor mode er den mest sandsynlige værdi.
private static double Triangular(Random random, double min, double mode, double max)
{
var u = random.NextDouble();
var split = (mode - min) / (max - min);
return u < split
? min + Math.Sqrt(u * (max - min) * (mode - min))
: max - Math.Sqrt((1 - u) * (max - min) * (max - mode));
}
// Normalfordeling med Box-Muller-metoden.
private static double Normal(Random random, double mean, double standardDeviation)
{
var u1 = 1.0 - random.NextDouble();
var u2 = random.NextDouble();
return mean + standardDeviation * Math.Sqrt(-2 * Math.Log(u1)) * Math.Cos(2 * Math.PI * u2);
}
// Punktum som decimaltegn, uanset computerens sprogindstilling.
private static string F(double value) => value.ToString("0.####", CultureInfo.InvariantCulture);
}Kør trækningerne med Program.cs fra eksempel 2: Skift Variants.FullFactorial() ud med fx MonteCarlo.Draw(500), og brug MonteCarlo.Apply, MonteCarlo.ToCsv og MonteCarlo.CsvHeader i stedet for de tilsvarende metoder i Variants. Uden motor kan trækningerne på samme måde gemmes som filer med eksempel 1.
Hvor mange trækninger der skal til, afhænger af, hvor præcist du vil kende fordelingen – især yderpunkterne. Kør fx først 500 og derefter 1.000 trækninger. Flytter fraktilerne sig kun lidt, er antallet stort nok.
Fordelingerne er eksempler. Bedre fordelinger kan bygge på produktdata, målinger fra tidligere projekter eller tolerancer i udbudsmaterialet. Gælder spørgsmålet energirammen, så hold dig til de tekniske input: Brugen – indetemperatur, interne varmetilskud og varmtvandsforbrug – regnes med faste standardværdier i energirammeberegningen. Vil du i stedet vurdere det forventede faktiske forbrug, kan brugen også varieres.
Læs resultatet af en Monte Carlo-analyse
Når du har hentet et tal ud af resultatet for hver trækning – se afsnittet om at finde tallene i Be-motorens resultat ovenfor – kan fordelingen opsummeres. Tilføj metoden herunder til klassen MonteCarlo:
// Opsummerer ét resultat fra alle trækninger, fx energibehovet i kWh/m² pr. år.
public static void Summarize(IReadOnlyList<double> values, double limit)
{
var sorted = values.Order().ToArray();
double Percentile(double p) => sorted[(int)Math.Round(p * (sorted.Length - 1))];
var mean = sorted.Average();
var standardDeviation = Math.Sqrt(sorted.Sum(v => (v - mean) * (v - mean)) / (sorted.Length - 1));
var shareAbove = sorted.Count(v => v > limit) / (double)sorted.Length;
Console.WriteLine($"Middelværdi {mean:0.0}, spredning {standardDeviation:0.0}");
Console.WriteLine($"Fraktiler: 5 % {Percentile(0.05):0.0}, 50 % {Percentile(0.50):0.0}, 95 % {Percentile(0.95):0.0}");
Console.WriteLine($"Andel over {limit:0.0}: {shareAbove:P0}");
}Kald metoden med værdierne fra alle trækninger og den grænse, du vil sammenligne med – fx energirammen, som for boliger er 30 + 1.000/A kWh/m² pr. år, hvor A er det opvarmede etageareal. Andelen over grænsen viser, hvor stor margin projektet har, hvis de faktiske værdier afviger som antaget. Den er aldrig mere præcis end de fordelinger, du har valgt.
CSV-filen kan også vise, hvilken usikkerhed der betyder mest: Beregn fx korrelationen mellem hver inputkolonne og resultatet, eller tegn et punktdiagram for hver parameter. Det input, der hænger tættest sammen med resultatet, er det, der er mest værd at få styr på.
Gode råd til variantberegninger og Monte Carlo
Et par ting gør variantberegningerne mere pålidelige:
- Start fra et grundprojekt, der regner uden fejl i Be18, og varier kun felter, der allerede er udfyldt
- Byg hver variant ud fra en frisk kopi af grundprojektet, så ændringer ikke hober sig op
- Gem input og resultat for hver variant – også for de beregninger, der fejler, så du kan se, hvilke kombinationer motoren ikke kunne regne
- Åbn et par af de genererede filer i Be18, og sammenlign med resultaterne fra dit program, før du stoler på hele serien
- Skriv tal med punktum som decimaltegn (CultureInfo.InvariantCulture), når du gemmer CSV-filer
Begrænsninger i Be18-parser
Be18-parser er nyt, og der er et par ting, du skal kende til:
- Koden, der kører beregningsmotoren, er endnu ikke afprøvet med en Be18-installation, og filnavnet Be18Eng.dll er antaget ud fra navnene på Be10- og Be15-motorerne
- Nogle få felter er endnu ikke kortlagt til en bekræftet betydning. Det står i dokumentationen til de pågældende egenskaber
- Biblioteket bruger .NET 10. Rhino 8 og Revit 2025/2026 kører plug-ins på .NET 8, så plug-ins til de programmer kræver et ekstra target framework
- Et 64-bit program, fx et plug-in, kan ikke indlæse en 32-bit motor direkte. Det kræver en hjælpeproces, der kører som x86