Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
814dba1
fix(s09): deliver outdoor air only through a selected Ventilation pro…
michaldengusiak Oct 21, 2026
2099572
feat(s09): OpenStudio native-input read-back harness
michaldengusiak Oct 21, 2026
7d6507c
test(s09): read-back tests, negatives, acceptance scripts and CI
michaldengusiak Oct 21, 2026
ebd9c4e
feat(s09): name the single-sided opaque optics proposal; acceptance e…
michaldengusiak Oct 21, 2026
d6d1cc5
docs(s09): PR record for the OpenStudio native-input read-back
michaldengusiak Oct 21, 2026
cfe097f
fix(s09): refuse native multiplicity and orientation SAM does not state
michaldengusiak Oct 21, 2026
6f7e6b7
docs(s09): revision 2 record, evidence and optics decision table at c…
michaldengusiak Oct 21, 2026
c1e5332
fix(s09): claim OSM/IDF multiplier consistency only against one ident…
michaldengusiak Oct 21, 2026
e0a6ea1
docs(s09): revision 3 record, evidence and assessment wording at c1e5…
michaldengusiak Oct 21, 2026
0c23287
fix(s09): identify a process by its id and creation time, and report …
michaldengusiak Oct 21, 2026
5903678
docs(s09): revision 4 record and evidence for the Build 39603021836 f…
michaldengusiak Oct 21, 2026
6a36afb
docs(s09): exclude Simulation from the writer-test command; separate …
michaldengusiak Oct 21, 2026
20f8fb6
fix(s09): split the lighting radiant proportion per S07 §5.1 (T-LIGHT…
michaldengusiak Oct 21, 2026
31af602
docs(s09): revision 5 record and evidence for the S07 §5.1 lighting s…
michaldengusiak Oct 21, 2026
225881f
fix(s09): fail closed on an invalid or plenum-dynamic Lights split be…
michaldengusiak Oct 22, 2026
8fd0ffc
docs(s09): revision 6 record and evidence for the lighting read-back …
michaldengusiak Oct 22, 2026
614a7a8
feat(s09): report the owner-accepted optics limitation for the review…
michaldengusiak Oct 22, 2026
7fccdcc
docs(s09): revision 7 record and evidence for the owner decisions at …
michaldengusiak Oct 22, 2026
fc83a6d
fix(s09): write each used opaque face from its authored SAM face (rev…
michaldengusiak Oct 22, 2026
901e592
docs(s09): revision 8 record and evidence for the opaque face-optics …
michaldengusiak Oct 22, 2026
354e532
fix(s09): keep native optical edits, merge face objects by source, re…
michaldengusiak Oct 22, 2026
806d544
docs(s09): revision 9 record and evidence for the review of 901e592a …
michaldengusiak Oct 22, 2026
78f1014
fix(s09): validate every face object before a merged result, keep sha…
michaldengusiak Oct 22, 2026
e04ff90
docs(s09): revision 10 record, evidence and layer-order proposal for …
michaldengusiak Oct 22, 2026
93ec315
docs(s09): remove trailing whitespace in the revision 10 test summaries
michaldengusiak Oct 22, 2026
37841ea
fix(s09): read opaque surface constructions innermost first and keep …
michaldengusiak Oct 22, 2026
5241bdd
fix(s09): report the interzone re-read as a warning and make its test…
michaldengusiak Oct 22, 2026
f5f207b
fix(s09): fail closed when an imported surface construction cannot ke…
michaldengusiak Oct 22, 2026
89a2216
docs(s09): revision 11 record and evidence for the opaque import orde…
michaldengusiak Oct 22, 2026
4ea2e6c
fix(s09): compare each native layer's thickness and thermal propertie…
michaldengusiak Oct 22, 2026
adcad2f
docs(s09): revision 11 per-layer consistency record and evidence at 4…
michaldengusiak Oct 22, 2026
8edf793
feat(s09): S07 §5.2 amendment U1: report unused-face opaque optics as…
michaldengusiak Oct 23, 2026
d26665b
docs(s09): revision 12 record and evidence for S07 §5.2 amendment U1 …
michaldengusiak Oct 23, 2026
39a6c90
docs(s09): acceptance pack at d26665bd (revision 12): both fixtures P…
michaldengusiak Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@
# output, which is UTF-8 without BOM and uses LF newlines. Pin them to LF so Git's autocrlf
# setting cannot rewrite them to CRLF on checkout and break the byte-stability comparison.
benchmark/**/*.json text eol=lf

# S09: the S08 signed-off equivalence fixtures are reused byte for byte (sha256 in Fixtures/SHA256SUMS.txt, CRLF as
# committed in SAM_Tas). Later rules win, so this keeps them out of the LF rule above.
benchmark/SAM.Analytical.OpenStudio.InputReadBack/Fixtures/** -text
74 changes: 74 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,51 @@ jobs:
if (-not (Test-Path $dll)) { throw "Expected schema DLL missing after build: $dll" }
Write-Host "Schema DLL present: $dll"

- name: Clone S08 evaluation core (SAM_Tas, sparse)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'

$org = '${{ env.ORG_NAME }}'
$r = 'SAM_Tas'

# The S09 read-back (benchmark/SAM.Analytical.OpenStudio.InputReadBack) reuses, from SAM_Tas, the COM-free S08
# evaluation core (project reference) and the SAM-intent builder ReadBackExpected.cs (linked source). Only those
# two folders are checked out; nothing TAS/COM is built. Same branch-candidate order as the other dependencies.
if (-not (Test-Path $r)) {
$headRef = '${{ github.head_ref }}'
$baseRef = '${{ github.base_ref }}'
$refName = '${{ github.ref_name }}'
$candidates = @()
if ($headRef) { $candidates += $headRef }
$sowRef = if ($baseRef -match '^sow/') { $baseRef } elseif ($refName -match '^sow/') { $refName } else { '' }
if ($sowRef -and $sowRef -ne $headRef) { $candidates += $sowRef }
foreach ($branchRef in @($headRef, $baseRef, $refName)) {
if ($branchRef -match '^sow/(\d{4}-Q\d)') {
$quarterRef = "sow/$($Matches[1])"
if ($candidates -notcontains $quarterRef) { $candidates += $quarterRef }
}
}
$cloned = $false
foreach ($cand in $candidates) {
$has = (git ls-remote --heads "https://github.com/$org/$r.git" $cand 2>$null | Out-String).Trim()
if ($has) {
Write-Host "Cloning $org/$r @ $cand (sparse)"
git clone --depth 1 --filter=blob:none --sparse --branch $cand "https://github.com/$org/$r.git" $r
$cloned = $true
break
}
}
if (-not $cloned) { throw "No SAM_Tas branch among: $($candidates -join ', ')" }
git -C $r sparse-checkout set benchmark/SAM.Analytical.InputEquivalence SAM_Tas/SAM.Analytical.Tas.DirectT3D.Validation/ReadBack
if ($LASTEXITCODE -ne 0) { throw "SAM_Tas sparse checkout failed" }
}

Write-Host ("SAM_Tas at " + (git -C $r rev-parse HEAD) + " (" + (git -C $r rev-parse --abbrev-ref HEAD) + ")")
foreach ($p in @('benchmark/SAM.Analytical.InputEquivalence/SAM.Analytical.InputEquivalence.csproj', 'SAM_Tas/SAM.Analytical.Tas.DirectT3D.Validation/ReadBack/ReadBackExpected.cs')) {
if (-not (Test-Path (Join-Path $r $p))) { throw "Missing from the SAM_Tas checkout: $p" }
}

- name: Restore + Rebuild in order (safe Platform handling)
shell: pwsh
run: |
Expand Down Expand Up @@ -271,6 +316,35 @@ jobs:
--logger 'console;verbosity=normal'
if ($LASTEXITCODE -ne 0) { throw "Benchmark producer tests failed" }

- name: Run S09 OpenStudio native-input read-back tests (offline)
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'

$repo = '${{ env.REPO_NAME }}'

# S09: the writer's ventilation policy and the fresh-process OSM/IDF read-back on the S08 signed-off fixtures with
# the SAM#212 Budapest EPW (from the SAM sibling). OpenStudio SDK NuGet only: no CLI, no EnergyPlus run.
$testProject = Join-Path $repo 'benchmark/SAM.Analytical.OpenStudio.InputReadBack.Tests/SAM.Analytical.OpenStudio.InputReadBack.Tests.csproj'
Write-Host "==> Testing: $testProject"
& dotnet test $testProject `
-c Release `
'/p:Platform=x64' `
'/p:SAMVersion=${{ steps.ver.outputs.samversion }}' `
--logger 'console;verbosity=normal'
if ($LASTEXITCODE -ne 0) { throw "S09 read-back tests failed" }

- name: Upload S09 failure diagnostics
# Only when a step failed: the bounded text evidence (exit code, stdout/stderr, process identities, file list) the
# S09 tests write for a failed CLI step. No native files.
if: failure()
uses: actions/upload-artifact@v4
with:
name: ${{ env.REPO_NAME }}-s09-diagnostics
path: ${{ env.REPO_NAME }}/benchmark/SAM.Analytical.OpenStudio.InputReadBack.Tests/bin/**/s09-diagnostics/*.txt
if-no-files-found: ignore
retention-days: 30

- name: Upload build folders (optional)
uses: actions/upload-artifact@v4
with:
Expand Down
45 changes: 45 additions & 0 deletions SAM_OpenStudio.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAM.Analytical.OpenStudio.B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAM.Analytical.OpenStudio.Benchmark.Tests", "benchmark\SAM.Analytical.OpenStudio.Benchmark.Tests\SAM.Analytical.OpenStudio.Benchmark.Tests.csproj", "{A845555D-DDC0-49C9-8BAD-ADEF4F5E7C6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAM.Analytical.OpenStudio.InputReadBack", "benchmark\SAM.Analytical.OpenStudio.InputReadBack\SAM.Analytical.OpenStudio.InputReadBack.csproj", "{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAM.Analytical.OpenStudio.InputReadBack.Cli", "benchmark\SAM.Analytical.OpenStudio.InputReadBack.Cli\SAM.Analytical.OpenStudio.InputReadBack.Cli.csproj", "{850D2D75-52C9-4322-B233-234F72981C39}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SAM.Analytical.OpenStudio.InputReadBack.Tests", "benchmark\SAM.Analytical.OpenStudio.InputReadBack.Tests\SAM.Analytical.OpenStudio.InputReadBack.Tests.csproj", "{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -138,6 +144,42 @@ Global
{A845555D-DDC0-49C9-8BAD-ADEF4F5E7C6F}.Release|x64.Build.0 = Release|x64
{A845555D-DDC0-49C9-8BAD-ADEF4F5E7C6F}.Release|x86.ActiveCfg = Release|x64
{A845555D-DDC0-49C9-8BAD-ADEF4F5E7C6F}.Release|x86.Build.0 = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|Any CPU.ActiveCfg = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|Any CPU.Build.0 = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|x64.ActiveCfg = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|x64.Build.0 = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|x86.ActiveCfg = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Debug|x86.Build.0 = Debug|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|Any CPU.ActiveCfg = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|Any CPU.Build.0 = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|x64.ActiveCfg = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|x64.Build.0 = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|x86.ActiveCfg = Release|x64
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F}.Release|x86.Build.0 = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|Any CPU.ActiveCfg = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|Any CPU.Build.0 = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|x64.ActiveCfg = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|x64.Build.0 = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|x86.ActiveCfg = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Debug|x86.Build.0 = Debug|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|Any CPU.ActiveCfg = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|Any CPU.Build.0 = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|x64.ActiveCfg = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|x64.Build.0 = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|x86.ActiveCfg = Release|x64
{850D2D75-52C9-4322-B233-234F72981C39}.Release|x86.Build.0 = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|Any CPU.ActiveCfg = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|Any CPU.Build.0 = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|x64.ActiveCfg = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|x64.Build.0 = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|x86.ActiveCfg = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Debug|x86.Build.0 = Debug|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|Any CPU.ActiveCfg = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|Any CPU.Build.0 = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|x64.ActiveCfg = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|x64.Build.0 = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|x86.ActiveCfg = Release|x64
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603}.Release|x86.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -151,6 +193,9 @@ Global
{45966997-BBBF-4272-B77F-86B401A1CE45} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
{E21DBAA0-8078-43F7-8A49-D9535DFD63D0} = {09EED85C-BE3C-7566-DC0E-2E8E43466740}
{A845555D-DDC0-49C9-8BAD-ADEF4F5E7C6F} = {09EED85C-BE3C-7566-DC0E-2E8E43466740}
{EBF33B94-81C3-4D70-BD25-F10C0E119B6F} = {09EED85C-BE3C-7566-DC0E-2E8E43466740}
{850D2D75-52C9-4322-B233-234F72981C39} = {09EED85C-BE3C-7566-DC0E-2E8E43466740}
{3F2CC081-C850-4FE7-BEF7-E7BFC5699603} = {09EED85C-BE3C-7566-DC0E-2E8E43466740}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4E0308FF-C8D5-4700-951A-859CB9D4FD09}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors

namespace SAM.Analytical.OpenStudio
{
/// <summary>
/// The lighting energy split of SAM S07 §5.1 (amendment L1, T-LIGHT-SPLIT). SAM
/// <c>LightingRadiantProportion</c> p is the total radiant share (long-wave plus short-wave) of the lighting gain
/// entering the room; SAM and Tas have no luminaire-return allocation. For EnergyPlus <c>Lights</c>:
/// Return Air Fraction 0, Fraction Visible p·s, Fraction Radiant p·(1 − s), convective 1 − p.
/// <para>
/// s is a declared contract assumption, not SAM data: the short-wave share of the emitter's radiant output for the
/// reference category, suspended (pendant) LED office lighting with the whole fitting in the conditioned room. It is
/// a midpoint-derived reference from cited historical ranges (IEA SHC Task 61 / EBC Annex 77 Technical Report D1,
/// Table 6; ASHRAE Handbook—Fundamentals 2017, Ch. 18, Table 3), kept at full precision.
/// </para>
/// The view coefficient (<c>LightingViewCoefficient</c>) is a comfort coefficient, never an energy fraction, and is
/// not part of this split.
/// </summary>
public static class LightingSplit
{
/// <summary>The reference total radiant share p₀ (S07 §5.1 baseline), applied only when SAM states no p.</summary>
public const double ReferenceRadiantProportion = 0.576;

/// <summary>The short-wave share s of the radiant output, 0.2415 / 0.576, never rounded.</summary>
public const double ShortWaveShare = 0.2415 / 0.576;

/// <summary>EnergyPlus Fraction Visible for a total radiant share p: p·s.</summary>
public static double FractionVisible(double radiantProportion) => radiantProportion * ShortWaveShare;

/// <summary>EnergyPlus Fraction Radiant (long-wave) for a total radiant share p: p·(1 − s).</summary>
public static double FractionRadiant(double radiantProportion) => radiantProportion * (1 - ShortWaveShare);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,26 @@ public sealed class OpenStudioImportContext
/// <summary>OpenStudio material name → SAM material created for it.</summary>
public IDictionary<string, Core.IMaterial> MaterialMap { get; } = new Dictionary<string, Core.IMaterial>();

/// <summary>
/// Source SAM material Guid → the one SAM material its SAM-written face and thickness objects were merged
/// into with the OpenStudio handles of its validated members, or a null material when they are imported one by
/// one (fewer than two, or a conflict). Only a listed member resolves to the merged material.
/// </summary>
internal IDictionary<Guid, (Core.IMaterial Material, ISet<string> Members)> OpaqueMaterialGroups { get; } = new Dictionary<Guid, (Core.IMaterial Material, ISet<string> Members)>();

/// <summary>OpenStudio construction name → SAM opaque Construction created for it.</summary>
public IDictionary<string, Construction> ConstructionMap { get; } = new Dictionary<string, Construction>();

/// <summary>
/// OpenStudio construction name → the innermost-first SAM Construction for the planar surfaces using it, when the
/// same OpenStudio construction is also used by a subsurface or shading surface (which keep the OpenStudio order
/// through <see cref="ConstructionMap"/>). Surfaces resolve here first.
/// </summary>
internal IDictionary<string, Construction> SurfaceConstructionMap { get; } = new Dictionary<string, Construction>();

/// <summary>OpenStudio construction handle → whether planar surfaces, and whether subsurfaces or shading surfaces, use it; read once from <see cref="Source"/>.</summary>
internal IDictionary<string, (bool Surface, bool Other)> ConstructionUses { get; set; }

/// <summary>OpenStudio construction name → SAM ApertureConstruction created for it.</summary>
public IDictionary<string, ApertureConstruction> ApertureConstructionMap { get; } = new Dictionary<string, ApertureConstruction>();

Expand Down
Loading
Loading