tbd 3.1.1 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/pull_request.yml +16 -0
- data/LICENSE.md +1 -1
- data/lib/measures/tbd/LICENSE.md +1 -1
- data/lib/measures/tbd/README.md +8 -0
- data/lib/measures/tbd/measure.rb +44 -2
- data/lib/measures/tbd/measure.xml +33 -24
- data/lib/measures/tbd/resources/geo.rb +56 -16
- data/lib/measures/tbd/resources/oslog.rb +17 -7
- data/lib/measures/tbd/resources/psi.rb +113 -7
- data/lib/measures/tbd/resources/tbd.rb +1 -1
- data/lib/measures/tbd/resources/ua.rb +36 -69
- data/lib/measures/tbd/resources/utils.rb +86 -28
- data/lib/measures/tbd/tests/tbd_tests.rb +117 -87
- data/lib/tbd/geo.rb +56 -16
- data/lib/tbd/psi.rb +113 -7
- data/lib/tbd/ua.rb +36 -69
- data/lib/tbd/version.rb +2 -2
- data/lib/tbd.rb +1 -1
- metadata +3 -3
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # MIT License
         | 
| 2 2 | 
             
            #
         | 
| 3 | 
            -
            # Copyright (c) 2020- | 
| 3 | 
            +
            # Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
         | 
| 4 4 | 
             
            #
         | 
| 5 5 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 6 | 
             
            # of this software and associated documentation files (the "Software"), to deal
         | 
| @@ -36,187 +36,217 @@ class TBDTest < Minitest::Test | |
| 36 36 | 
             
              # end
         | 
| 37 37 |  | 
| 38 38 | 
             
              def test_number_of_arguments_and_argument_names
         | 
| 39 | 
            -
                # create an instance of the measure
         | 
| 40 39 | 
             
                measure = TBDMeasure.new
         | 
| 41 | 
            -
                # make an empty model
         | 
| 42 40 | 
             
                model = OpenStudio::Model::Model.new
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                # get arguments and test that they are what we are expecting
         | 
| 45 41 | 
             
                arguments = measure.arguments(model)
         | 
| 46 | 
            -
                assert_equal( | 
| 42 | 
            +
                assert_equal(15, arguments.size)
         | 
| 47 43 | 
             
              end
         | 
| 48 44 |  | 
| 49 45 | 
             
              def test_no_load_tbd_json
         | 
| 50 | 
            -
                # create an instance of the measure
         | 
| 51 46 | 
             
                measure = TBDMeasure.new
         | 
| 52 47 |  | 
| 53 | 
            -
                # Output  | 
| 54 | 
            -
                seed_dir = File.join(__dir__,  | 
| 48 | 
            +
                # Output directories.
         | 
| 49 | 
            +
                seed_dir = File.join(__dir__, "output/no_load_tbd_json/")
         | 
| 55 50 | 
             
                FileUtils.mkdir_p(seed_dir)
         | 
| 56 | 
            -
                seed_path = File.join(seed_dir,  | 
| 51 | 
            +
                seed_path = File.join(seed_dir, "in.osm")
         | 
| 57 52 |  | 
| 58 | 
            -
                #  | 
| 53 | 
            +
                # Create runner with empty OSW, and example test model.
         | 
| 59 54 | 
             
                osw = OpenStudio::WorkflowJSON.new
         | 
| 60 55 | 
             
                osw.setSeedFile(seed_path)
         | 
| 61 56 | 
             
                runner = OpenStudio::Measure::OSRunner.new(osw)
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                # create example test model
         | 
| 64 57 | 
             
                model = OpenStudio::Model::exampleModel
         | 
| 65 58 | 
             
                model.save(seed_path, true)
         | 
| 66 59 |  | 
| 67 | 
            -
                #  | 
| 60 | 
            +
                # Get measure arguments.
         | 
| 68 61 | 
             
                arguments = measure.arguments(model)
         | 
| 69 62 | 
             
                argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
         | 
| 70 63 |  | 
| 71 | 
            -
                #  | 
| 72 | 
            -
                 | 
| 73 | 
            -
                 | 
| 74 | 
            -
                 | 
| 75 | 
            -
                 | 
| 76 | 
            -
                 | 
| 77 | 
            -
                 | 
| 78 | 
            -
             | 
| 79 | 
            -
                #  | 
| 80 | 
            -
             | 
| 81 | 
            -
                # populate argument with specified hash value if specified
         | 
| 64 | 
            +
                # Hash of argument values (defaults from measure.rb for other arguments).
         | 
| 65 | 
            +
                argh                   = {}
         | 
| 66 | 
            +
                argh["option"        ] = "efficient (BETBG)"
         | 
| 67 | 
            +
                argh["write_tbd_json"] = true
         | 
| 68 | 
            +
                argh["gen_UA_report" ] = true
         | 
| 69 | 
            +
                argh["wall_option"   ] = "ALL wall constructions"
         | 
| 70 | 
            +
                argh["wall_ut"       ] = 0.5
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                # Populate arguments with specified hash value if specified.
         | 
| 82 73 | 
             
                arguments.each do |arg|
         | 
| 83 74 | 
             
                  temp_arg_var = arg.clone
         | 
| 84 | 
            -
                   | 
| 85 | 
            -
                  assert(temp_arg_var.setValue(args_hash[arg.name])) if gotit
         | 
| 75 | 
            +
                  assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
         | 
| 86 76 | 
             
                  argument_map[arg.name] = temp_arg_var
         | 
| 87 77 | 
             
                end
         | 
| 88 78 |  | 
| 89 | 
            -
                #  | 
| 79 | 
            +
                # Run the measure and assert that it ran correctly.
         | 
| 90 80 | 
             
                Dir.chdir(seed_dir)
         | 
| 91 81 | 
             
                measure.run(model, runner, argument_map)
         | 
| 92 82 | 
             
                result = runner.result
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                # show the output
         | 
| 95 83 | 
             
                show_output(result)
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                # assert that it ran correctly
         | 
| 98 | 
            -
                assert_equal('Success', result.value.valueName)
         | 
| 84 | 
            +
                assert_equal("Success", result.value.valueName)
         | 
| 99 85 | 
             
                assert(result.warnings.empty?)
         | 
| 86 | 
            +
                assert(result.errors.empty?)
         | 
| 100 87 |  | 
| 101 | 
            -
                #  | 
| 102 | 
            -
                 | 
| 103 | 
            -
                 | 
| 88 | 
            +
                # Save the model to test output directory.
         | 
| 89 | 
            +
                output_path = File.join(seed_dir, "out.osm")
         | 
| 90 | 
            +
                model.save(output_path, true)
         | 
| 104 91 | 
             
              end
         | 
| 105 92 |  | 
| 106 93 | 
             
              def test_load_tbd_json
         | 
| 107 | 
            -
                # create an instance of the measure
         | 
| 108 94 | 
             
                measure = TBDMeasure.new
         | 
| 109 95 |  | 
| 110 | 
            -
                # Output  | 
| 96 | 
            +
                # Output directories.
         | 
| 111 97 | 
             
                seed_dir = File.join(__dir__, "output/load_tbd_json/")
         | 
| 112 98 | 
             
                FileUtils.mkdir_p(seed_dir)
         | 
| 113 99 | 
             
                seed_path = File.join(seed_dir, "in.osm")
         | 
| 114 100 |  | 
| 115 | 
            -
                #  | 
| 101 | 
            +
                # Create runner with empty OSW, and example test model.
         | 
| 116 102 | 
             
                osw = OpenStudio::WorkflowJSON.new
         | 
| 117 103 | 
             
                osw.setSeedFile(seed_path)
         | 
| 118 104 | 
             
                runner = OpenStudio::Measure::OSRunner.new(osw)
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                # create example test model
         | 
| 121 105 | 
             
                model = OpenStudio::Model::exampleModel
         | 
| 122 106 | 
             
                model.save(seed_path, true)
         | 
| 123 107 |  | 
| 124 | 
            -
                #  | 
| 108 | 
            +
                # Copy tdb.json next to seed.
         | 
| 125 109 | 
             
                origin_pth = File.join(__dir__, "tbd_full_PSI.json")
         | 
| 126 110 | 
             
                target_pth = File.join(seed_dir, "tbd.json")
         | 
| 127 111 | 
             
                FileUtils.cp(origin_pth, target_pth)
         | 
| 128 112 |  | 
| 129 | 
            -
                #  | 
| 113 | 
            +
                # Get measure arguments.
         | 
| 130 114 | 
             
                arguments = measure.arguments(model)
         | 
| 131 115 | 
             
                argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
         | 
| 132 116 |  | 
| 133 | 
            -
                #  | 
| 134 | 
            -
                 | 
| 135 | 
            -
                 | 
| 136 | 
            -
                # using defaults values from measure.rb for other arguments
         | 
| 117 | 
            +
                # Hash of argument values (defaults from measure.rb for other arguments).
         | 
| 118 | 
            +
                argh                   = {}
         | 
| 119 | 
            +
                argh["load_tbd_json" ] = true
         | 
| 137 120 |  | 
| 138 | 
            -
                #  | 
| 121 | 
            +
                # Populate arguments with specified hash value if specified.
         | 
| 139 122 | 
             
                arguments.each do |arg|
         | 
| 140 123 | 
             
                  temp_arg_var = arg.clone
         | 
| 141 | 
            -
                  if  | 
| 142 | 
            -
                    assert(temp_arg_var.setValue(args_hash[arg.name]))
         | 
| 143 | 
            -
                  end
         | 
| 124 | 
            +
                  assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
         | 
| 144 125 | 
             
                  argument_map[arg.name] = temp_arg_var
         | 
| 145 126 | 
             
                end
         | 
| 146 127 |  | 
| 147 | 
            -
                #  | 
| 128 | 
            +
                # Run the measure and assert that it ran correctly.
         | 
| 148 129 | 
             
                Dir.chdir(seed_dir)
         | 
| 149 130 | 
             
                measure.run(model, runner, argument_map)
         | 
| 150 131 | 
             
                result = runner.result
         | 
| 151 | 
            -
             | 
| 152 | 
            -
                # show the output
         | 
| 153 132 | 
             
                show_output(result)
         | 
| 154 | 
            -
             | 
| 155 | 
            -
                # assert that it ran correctly
         | 
| 156 | 
            -
                assert_equal('Success', result.value.valueName)
         | 
| 133 | 
            +
                assert_equal("Success", result.value.valueName)
         | 
| 157 134 | 
             
                assert(result.warnings.empty?)
         | 
| 135 | 
            +
                assert(result.errors.empty?)
         | 
| 158 136 |  | 
| 159 | 
            -
                #  | 
| 160 | 
            -
                 | 
| 161 | 
            -
                 | 
| 137 | 
            +
                # Save the model to test output directory.
         | 
| 138 | 
            +
                output_path = File.join(seed_dir, "out.osm")
         | 
| 139 | 
            +
                model.save(output_path, true)
         | 
| 162 140 | 
             
              end
         | 
| 163 141 |  | 
| 164 142 | 
             
              def test_load_tbd_json_error
         | 
| 165 | 
            -
                # create an instance of the measure
         | 
| 166 143 | 
             
                measure = TBDMeasure.new
         | 
| 167 144 |  | 
| 168 | 
            -
                # Output  | 
| 145 | 
            +
                # Output directories.
         | 
| 169 146 | 
             
                seed_dir = File.join(__dir__, "output/load_tbd_json_error/")
         | 
| 170 147 | 
             
                FileUtils.mkdir_p(seed_dir)
         | 
| 171 148 | 
             
                seed_path = File.join(seed_dir, "in.osm")
         | 
| 172 149 |  | 
| 173 | 
            -
                #  | 
| 150 | 
            +
                # Create runner with empty OSW, and example test model.
         | 
| 174 151 | 
             
                osw = OpenStudio::WorkflowJSON.new
         | 
| 175 152 | 
             
                osw.setSeedFile(seed_path)
         | 
| 176 153 | 
             
                runner = OpenStudio::Measure::OSRunner.new(osw)
         | 
| 177 | 
            -
             | 
| 178 | 
            -
                # create example test model
         | 
| 179 154 | 
             
                model = OpenStudio::Model::exampleModel
         | 
| 180 155 | 
             
                model.save(seed_path, true)
         | 
| 181 156 |  | 
| 182 | 
            -
                #  | 
| 157 | 
            +
                # POSTULATED USER ERROR: Do not copy tdb.json next to seed.
         | 
| 183 158 |  | 
| 184 | 
            -
                #  | 
| 159 | 
            +
                # Get measure arguments.
         | 
| 185 160 | 
             
                arguments = measure.arguments(model)
         | 
| 186 161 | 
             
                argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
         | 
| 187 162 |  | 
| 188 | 
            -
                #  | 
| 189 | 
            -
                 | 
| 190 | 
            -
                 | 
| 191 | 
            -
                # using defaults values from measure.rb for other arguments
         | 
| 163 | 
            +
                # Hash of argument values (defaults from measure.rb for other arguments).
         | 
| 164 | 
            +
                argh                   = {}
         | 
| 165 | 
            +
                argh["load_tbd_json" ] = true
         | 
| 192 166 |  | 
| 193 | 
            -
                #  | 
| 167 | 
            +
                # Populate argument with specified hash value if specified.
         | 
| 194 168 | 
             
                arguments.each do |arg|
         | 
| 195 169 | 
             
                  temp_arg_var = arg.clone
         | 
| 196 | 
            -
                  if  | 
| 197 | 
            -
                    assert(temp_arg_var.setValue(args_hash[arg.name]))
         | 
| 198 | 
            -
                  end
         | 
| 170 | 
            +
                  assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
         | 
| 199 171 | 
             
                  argument_map[arg.name] = temp_arg_var
         | 
| 200 172 | 
             
                end
         | 
| 201 173 |  | 
| 202 | 
            -
                #  | 
| 174 | 
            +
                # Run the measure, assert that it did not run correctly.
         | 
| 203 175 | 
             
                Dir.chdir(seed_dir)
         | 
| 204 176 | 
             
                measure.run(model, runner, argument_map)
         | 
| 205 177 | 
             
                result = runner.result
         | 
| 206 | 
            -
             | 
| 207 | 
            -
                # show the output
         | 
| 208 178 | 
             
                show_output(result)
         | 
| 179 | 
            +
                assert_equal("Fail", result.value.valueName)
         | 
| 209 180 |  | 
| 210 | 
            -
                # assert that it ran correctly
         | 
| 211 | 
            -
                assert_equal('Fail', result.value.valueName)
         | 
| 212 | 
            -
                assert(result.errors.size == 1)
         | 
| 213 181 | 
             
                assert(result.warnings.size == 1)
         | 
| 214 | 
            -
                 | 
| 215 | 
            -
                 | 
| 216 | 
            -
                assert( | 
| 182 | 
            +
                message = result.warnings[0].logMessage
         | 
| 183 | 
            +
                puts message
         | 
| 184 | 
            +
                assert(message.include?("Can't find 'tbd.json' - simulation halted"))
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                assert(result.errors.size == 1)
         | 
| 187 | 
            +
                message = result.errors[0].logMessage
         | 
| 188 | 
            +
                puts message
         | 
| 189 | 
            +
                assert(message.include?("Halting all TBD processes, "))
         | 
| 190 | 
            +
                assert(message.include?("and halting OpenStudio - see 'tbd.out.json'"))
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                # Save the model to test output directory.
         | 
| 193 | 
            +
                output_path = File.join(seed_dir, "out.osm")
         | 
| 194 | 
            +
                model.save(output_path, true)
         | 
| 195 | 
            +
              end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
              def test_tbd_kiva_massless_error
         | 
| 198 | 
            +
                measure = TBDMeasure.new
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                # Output directories.
         | 
| 201 | 
            +
                seed_dir = File.join(__dir__, "output/tbd_kiva_massless_error/")
         | 
| 202 | 
            +
                FileUtils.mkdir_p(seed_dir)
         | 
| 203 | 
            +
                seed_path = File.join(seed_dir, "in.osm")
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                # Create runner with empty OSW, and example test model.
         | 
| 206 | 
            +
                osw = OpenStudio::WorkflowJSON.new
         | 
| 207 | 
            +
                osw.setSeedFile(seed_path)
         | 
| 208 | 
            +
                runner = OpenStudio::Measure::OSRunner.new(osw)
         | 
| 209 | 
            +
                model = OpenStudio::Model::exampleModel
         | 
| 210 | 
            +
                model.save(seed_path, true)
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                # Copy tdb.json next to seed.
         | 
| 213 | 
            +
                origin_pth = File.join(__dir__, "tbd_full_PSI.json")
         | 
| 214 | 
            +
                target_pth = File.join(seed_dir, "tbd.json")
         | 
| 215 | 
            +
                FileUtils.cp(origin_pth, target_pth)
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                # Get measure arguments.
         | 
| 218 | 
            +
                arguments = measure.arguments(model)
         | 
| 219 | 
            +
                argument_map = OpenStudio::Measure.convertOSArgumentVectorToMap(arguments)
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                # Hash of argument values (defaults from measure.rb for other arguments).
         | 
| 222 | 
            +
                argh                   = {}
         | 
| 223 | 
            +
                argh["gen_kiva_force"] = true
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                # POSTULATED USER ERROR : Slab on grade construction holds a massless layer.
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                # Populate argument with specified hash value if specified.
         | 
| 228 | 
            +
                arguments.each do |arg|
         | 
| 229 | 
            +
                  temp_arg_var = arg.clone
         | 
| 230 | 
            +
                  assert(temp_arg_var.setValue(argh[arg.name])) if argh.key?(arg.name)
         | 
| 231 | 
            +
                  argument_map[arg.name] = temp_arg_var
         | 
| 232 | 
            +
                end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                # Run the measure, assert that it did not run correctly.
         | 
| 235 | 
            +
                Dir.chdir(seed_dir)
         | 
| 236 | 
            +
                measure.run(model, runner, argument_map)
         | 
| 237 | 
            +
                result = runner.result
         | 
| 238 | 
            +
                show_output(result)
         | 
| 239 | 
            +
                assert_equal("Fail", result.value.valueName)
         | 
| 240 | 
            +
                assert(result.warnings.empty?)
         | 
| 241 | 
            +
                assert(result.errors.size == 4)
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                result.errors.each do |error|
         | 
| 244 | 
            +
                  assert(error.logMessage.include?("KIVA requires standard materials ("))
         | 
| 245 | 
            +
                end
         | 
| 217 246 |  | 
| 218 | 
            -
                #  | 
| 219 | 
            -
                # | 
| 220 | 
            -
                 | 
| 247 | 
            +
                # Save the model to test output directory. There should be neither instance
         | 
| 248 | 
            +
                # of KIVA objects nor TBD derated materials/constructions.
         | 
| 249 | 
            +
                output_path = File.join(seed_dir, "out.osm")
         | 
| 250 | 
            +
                model.save(output_path, true)
         | 
| 221 251 | 
             
              end
         | 
| 222 252 | 
             
            end
         | 
    
        data/lib/tbd/geo.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # MIT License
         | 
| 2 2 | 
             
            #
         | 
| 3 | 
            -
            # Copyright (c) 2020- | 
| 3 | 
            +
            # Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
         | 
| 4 4 | 
             
            #
         | 
| 5 5 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 6 | 
             
            # of this software and associated documentation files (the "Software"), to deal
         | 
| @@ -22,24 +22,27 @@ | |
| 22 22 |  | 
| 23 23 | 
             
            module TBD
         | 
| 24 24 | 
             
              ##
         | 
| 25 | 
            -
              # Check for matching Topolys vertex pairs between edges | 
| 25 | 
            +
              # Check for matching Topolys vertex pairs between edges.
         | 
| 26 26 | 
             
              #
         | 
| 27 27 | 
             
              # @param e1 [Hash] first edge
         | 
| 28 28 | 
             
              # @param e2 [Hash] second edge
         | 
| 29 | 
            +
              # @param tol [Float] user-set tolerance (> TOL) in m
         | 
| 29 30 | 
             
              #
         | 
| 30 31 | 
             
              # @return [Bool] true if edges share vertex pairs
         | 
| 31 32 | 
             
              # @return [Bool] false if invalid input
         | 
| 32 | 
            -
              def matches?(e1 = {}, e2 = {})
         | 
| 33 | 
            +
              def matches?(e1 = {}, e2 = {}, tol = TOL)
         | 
| 33 34 | 
             
                mth = "TBD::#{__callee__}"
         | 
| 34 35 | 
             
                cl  = Topolys::Point3D
         | 
| 35 36 | 
             
                a   = false
         | 
| 36 37 |  | 
| 37 38 | 
             
                return mismatch("e1", e1, Hash, mth, DBG, a)        unless e1.is_a?(Hash)
         | 
| 38 39 | 
             
                return mismatch("e2", e2, Hash, mth, DBG, a)        unless e2.is_a?(Hash)
         | 
| 40 | 
            +
             | 
| 39 41 | 
             
                return hashkey("e1", e1, :v0, mth, DBG, a)          unless e1.key?(:v0)
         | 
| 40 42 | 
             
                return hashkey("e1", e1, :v1, mth, DBG, a)          unless e1.key?(:v1)
         | 
| 41 43 | 
             
                return hashkey("e2", e2, :v0, mth, DBG, a)          unless e2.key?(:v0)
         | 
| 42 44 | 
             
                return hashkey("e2", e2, :v1, mth, DBG, a)          unless e2.key?(:v1)
         | 
| 45 | 
            +
             | 
| 43 46 | 
             
                return mismatch("e1 :v0", e1[:v0], cl, mth, DBG, a) unless e1[:v0].is_a?(cl)
         | 
| 44 47 | 
             
                return mismatch("e1 :v1", e1[:v1], cl, mth, DBG, a) unless e1[:v1].is_a?(cl)
         | 
| 45 48 | 
             
                return mismatch("e2 :v0", e2[:v0], cl, mth, DBG, a) unless e2[:v0].is_a?(cl)
         | 
| @@ -51,26 +54,29 @@ module TBD | |
| 51 54 | 
             
                return zero("e1", mth, DBG, a) if e1_vector.magnitude < TOL
         | 
| 52 55 | 
             
                return zero("e2", mth, DBG, a) if e2_vector.magnitude < TOL
         | 
| 53 56 |  | 
| 57 | 
            +
                return mismatch("e1", e1, Hash, mth, DBG, a)       unless tol.is_a?(Numeric)
         | 
| 58 | 
            +
                return zero("tol", mth, DBG, a)                        if tol < TOL
         | 
| 59 | 
            +
             | 
| 54 60 | 
             
                return true if
         | 
| 55 61 | 
             
                (
         | 
| 56 62 | 
             
                  (
         | 
| 57 | 
            -
                    ( (e1[:v0].x - e2[:v0].x).abs <  | 
| 58 | 
            -
                      (e1[:v0].y - e2[:v0].y).abs <  | 
| 59 | 
            -
                      (e1[:v0].z - e2[:v0].z).abs <  | 
| 63 | 
            +
                    ( (e1[:v0].x - e2[:v0].x).abs < tol &&
         | 
| 64 | 
            +
                      (e1[:v0].y - e2[:v0].y).abs < tol &&
         | 
| 65 | 
            +
                      (e1[:v0].z - e2[:v0].z).abs < tol
         | 
| 60 66 | 
             
                    ) ||
         | 
| 61 | 
            -
                    ( (e1[:v0].x - e2[:v1].x).abs <  | 
| 62 | 
            -
                      (e1[:v0].y - e2[:v1].y).abs <  | 
| 63 | 
            -
                      (e1[:v0].z - e2[:v1].z).abs <  | 
| 67 | 
            +
                    ( (e1[:v0].x - e2[:v1].x).abs < tol &&
         | 
| 68 | 
            +
                      (e1[:v0].y - e2[:v1].y).abs < tol &&
         | 
| 69 | 
            +
                      (e1[:v0].z - e2[:v1].z).abs < tol
         | 
| 64 70 | 
             
                    )
         | 
| 65 71 | 
             
                  ) &&
         | 
| 66 72 | 
             
                  (
         | 
| 67 | 
            -
                    ( (e1[:v1].x - e2[:v0].x).abs <  | 
| 68 | 
            -
                      (e1[:v1].y - e2[:v0].y).abs <  | 
| 69 | 
            -
                      (e1[:v1].z - e2[:v0].z).abs <  | 
| 73 | 
            +
                    ( (e1[:v1].x - e2[:v0].x).abs < tol &&
         | 
| 74 | 
            +
                      (e1[:v1].y - e2[:v0].y).abs < tol &&
         | 
| 75 | 
            +
                      (e1[:v1].z - e2[:v0].z).abs < tol
         | 
| 70 76 | 
             
                    ) ||
         | 
| 71 | 
            -
                    ( (e1[:v1].x - e2[:v1].x).abs <  | 
| 72 | 
            -
                      (e1[:v1].y - e2[:v1].y).abs <  | 
| 73 | 
            -
                      (e1[:v1].z - e2[:v1].z).abs <  | 
| 77 | 
            +
                    ( (e1[:v1].x - e2[:v1].x).abs < tol &&
         | 
| 78 | 
            +
                      (e1[:v1].y - e2[:v1].y).abs < tol &&
         | 
| 79 | 
            +
                      (e1[:v1].z - e2[:v1].z).abs < tol
         | 
| 74 80 | 
             
                    )
         | 
| 75 81 | 
             
                  )
         | 
| 76 82 | 
             
                )
         | 
| @@ -354,6 +360,7 @@ module TBD | |
| 354 360 | 
             
                  next                                                          unless valid
         | 
| 355 361 | 
             
                  vec      = s.vertices
         | 
| 356 362 | 
             
                  area     = s.grossArea
         | 
| 363 | 
            +
                  mult     = s.multiplier
         | 
| 357 364 | 
             
                  typ      = s.subSurfaceType.downcase
         | 
| 358 365 | 
             
                  type     = :skylight
         | 
| 359 366 | 
             
                  type     = :window       if typ.include?("window" )
         | 
| @@ -442,6 +449,7 @@ module TBD | |
| 442 449 | 
             
                          n:        n,
         | 
| 443 450 | 
             
                          gross:    s.grossArea,
         | 
| 444 451 | 
             
                          area:     area,
         | 
| 452 | 
            +
                          mult:     mult,
         | 
| 445 453 | 
             
                          type:     type,
         | 
| 446 454 | 
             
                          u:        u,
         | 
| 447 455 | 
             
                          unhinged: unhinged }
         | 
| @@ -478,7 +486,9 @@ module TBD | |
| 478 486 | 
             
                end
         | 
| 479 487 |  | 
| 480 488 | 
             
                subarea = 0
         | 
| 481 | 
            -
             | 
| 489 | 
            +
             | 
| 490 | 
            +
                subs.values.each { |sub| subarea += sub[:area] * sub[:mult] }
         | 
| 491 | 
            +
             | 
| 482 492 | 
             
                surf[:net] = surf[:gross] - subarea
         | 
| 483 493 |  | 
| 484 494 | 
             
                # Tranform final Point 3D sets, and store.
         | 
| @@ -604,6 +614,36 @@ module TBD | |
| 604 614 | 
             
                return mismatch("floors", floors, cl2, mth, DBG, a) unless floors.is_a?(cl2)
         | 
| 605 615 | 
             
                return mismatch("edges", edges, cl2, mth, DBG, a)   unless edges.is_a?(cl2)
         | 
| 606 616 |  | 
| 617 | 
            +
                kva = true
         | 
| 618 | 
            +
             | 
| 619 | 
            +
                # Pre-validate foundation-facing constructions.
         | 
| 620 | 
            +
                model.getSurfaces.each do |s|
         | 
| 621 | 
            +
                  id = s.nameString
         | 
| 622 | 
            +
                  construction = s.construction
         | 
| 623 | 
            +
                  next unless s.outsideBoundaryCondition.downcase == "foundation"
         | 
| 624 | 
            +
             | 
| 625 | 
            +
                  if construction.empty?
         | 
| 626 | 
            +
                    log(ERR, "Invalid construction for KIVA (see #{id})")
         | 
| 627 | 
            +
                    kva = false if kva
         | 
| 628 | 
            +
                  else
         | 
| 629 | 
            +
                    construction = construction.get.to_LayeredConstruction
         | 
| 630 | 
            +
             | 
| 631 | 
            +
                    if construction.empty?
         | 
| 632 | 
            +
                      log(ERR, "KIVA requires layered constructions (see #{id})")
         | 
| 633 | 
            +
                      kva = false if kva
         | 
| 634 | 
            +
                    else
         | 
| 635 | 
            +
                      construction = construction.get
         | 
| 636 | 
            +
             | 
| 637 | 
            +
                      unless standardOpaqueLayers?(construction)
         | 
| 638 | 
            +
                        log(ERR, "KIVA requires standard materials (see #{id})")
         | 
| 639 | 
            +
                        kva = false if kva
         | 
| 640 | 
            +
                      end
         | 
| 641 | 
            +
                    end
         | 
| 642 | 
            +
                  end
         | 
| 643 | 
            +
                end
         | 
| 644 | 
            +
             | 
| 645 | 
            +
                return a unless kva
         | 
| 646 | 
            +
             | 
| 607 647 | 
             
                # Strictly relying on Kiva's total exposed perimeter approach.
         | 
| 608 648 | 
             
                arg = "TotalExposedPerimeter"
         | 
| 609 649 | 
             
                kiva = true
         | 
    
        data/lib/tbd/psi.rb
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # MIT License
         | 
| 2 2 | 
             
            #
         | 
| 3 | 
            -
            # Copyright (c) 2020- | 
| 3 | 
            +
            # Copyright (c) 2020-2023 Denis Bourgeois & Dan Macumber
         | 
| 4 4 | 
             
            #
         | 
| 5 5 | 
             
            # Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 6 6 | 
             
            # of this software and associated documentation files (the "Software"), to deal
         | 
| @@ -913,6 +913,7 @@ module TBD | |
| 913 913 | 
             
                return mismatch("argh", argh, Hash, mth, DBG, tbd) unless argh.is_a?(Hash)
         | 
| 914 914 |  | 
| 915 915 | 
             
                argh                 = {}                       if argh.empty?
         | 
| 916 | 
            +
                argh[:sub_tol      ] = TBD::TOL             unless argh.key?(:sub_tol      )
         | 
| 916 917 | 
             
                argh[:option       ] = ""                   unless argh.key?(:option       )
         | 
| 917 918 | 
             
                argh[:io_path      ] = nil                  unless argh.key?(:io_path      )
         | 
| 918 919 | 
             
                argh[:schema_path  ] = nil                  unless argh.key?(:schema_path  )
         | 
| @@ -1174,9 +1175,6 @@ module TBD | |
| 1174 1175 | 
             
                        farthest_V = origin_point_V if farther
         | 
| 1175 1176 | 
             
                      end
         | 
| 1176 1177 |  | 
| 1177 | 
            -
                      puts "ADDITION!!"                      if id == "ADDITION"
         | 
| 1178 | 
            -
                      puts "#{reference_V} vs #{farthest_V}" if id == "ADDITION"
         | 
| 1179 | 
            -
             | 
| 1180 1178 | 
             
                      angle = reference_V.angle(farthest_V)
         | 
| 1181 1179 | 
             
                      invalid("#{id} polar angle", mth, 0, ERROR, 0) if angle.nil?
         | 
| 1182 1180 | 
             
                      angle = 0                                      if angle.nil?
         | 
| @@ -1771,13 +1769,120 @@ module TBD | |
| 1771 1769 | 
             
                  end
         | 
| 1772 1770 | 
             
                end
         | 
| 1773 1771 |  | 
| 1772 | 
            +
                # Fetch edge multipliers for subsurfaces, if applicable.
         | 
| 1773 | 
            +
                edges.values.each do |edge|
         | 
| 1774 | 
            +
                  next     if edge.key?(:mult)                    # skip if already assigned
         | 
| 1775 | 
            +
                  next unless edge.key?(:surfaces)
         | 
| 1776 | 
            +
                  next unless edge.key?(:psi)
         | 
| 1777 | 
            +
                  ok = false
         | 
| 1778 | 
            +
             | 
| 1779 | 
            +
                  edge[:psi].keys.each do |k|
         | 
| 1780 | 
            +
                    break if ok
         | 
| 1781 | 
            +
             | 
| 1782 | 
            +
                    jamb = k.to_s.include?("jamb")
         | 
| 1783 | 
            +
                    sill = k.to_s.include?("sill")
         | 
| 1784 | 
            +
                    head = k.to_s.include?("head")
         | 
| 1785 | 
            +
                    ok   = jamb || sill || head
         | 
| 1786 | 
            +
                  end
         | 
| 1787 | 
            +
             | 
| 1788 | 
            +
                  next unless ok     # if OK, edge links subsurface(s) ... yet which one(s)?
         | 
| 1789 | 
            +
             | 
| 1790 | 
            +
                  edge[:surfaces].each do |id, surface|
         | 
| 1791 | 
            +
                    next unless tbd[:surfaces].key?(id)    # look up parent (opaque) surface
         | 
| 1792 | 
            +
             | 
| 1793 | 
            +
                    [:windows, :doors, :skylights].each do |subtypes|
         | 
| 1794 | 
            +
                      next unless tbd[:surfaces][id].key?(subtypes)
         | 
| 1795 | 
            +
             | 
| 1796 | 
            +
                      tbd[:surfaces][id][subtypes].each do |nom, sub|
         | 
| 1797 | 
            +
                        next unless edge[:surfaces].key?(nom)
         | 
| 1798 | 
            +
                        next unless sub[:mult] > 1
         | 
| 1799 | 
            +
             | 
| 1800 | 
            +
                        # An edge may be tagged with (potentially conflicting) multipliers.
         | 
| 1801 | 
            +
                        # This is only possible if the edge links 2 subsurfaces, e.g. a
         | 
| 1802 | 
            +
                        # shared jamb between window & door. By default, TBD tags common
         | 
| 1803 | 
            +
                        # subsurface edges as (mild) "transitions" (i.e. PSI 0 W/K.m), so
         | 
| 1804 | 
            +
                        # there would be no point in assigning an edge multiplier. Users
         | 
| 1805 | 
            +
                        # can however reset an edge type via a TBD JSON input file (e.g.
         | 
| 1806 | 
            +
                        # "joint" instead of "transition"). It would be a very odd choice,
         | 
| 1807 | 
            +
                        # but TBD doesn't prohibit it. If linked subsurfaces have different
         | 
| 1808 | 
            +
                        # multipliers (e.g. 2 vs 3), TBD tracks the highest value.
         | 
| 1809 | 
            +
                        edge[:mult] = sub[:mult] unless edge.key?(:mult)
         | 
| 1810 | 
            +
                        edge[:mult] = sub[:mult]     if sub[:mult] > edge[:mult]
         | 
| 1811 | 
            +
                      end
         | 
| 1812 | 
            +
                    end
         | 
| 1813 | 
            +
                  end
         | 
| 1814 | 
            +
                end
         | 
| 1815 | 
            +
             | 
| 1816 | 
            +
                # Unless a user has set the thermal bridge type of an individual edge via
         | 
| 1817 | 
            +
                # JSON input, reset any subsurface's head, sill or jamb edges as (mild)
         | 
| 1818 | 
            +
                # transitions when in close proximity to another subsurface edge. Both
         | 
| 1819 | 
            +
                # edges' origin and terminal vertices must be in close proximity. Edges
         | 
| 1820 | 
            +
                # of unhinged subsurfaces are ignored.
         | 
| 1821 | 
            +
                edges.each do |id, edge|
         | 
| 1822 | 
            +
                  nb    = 0                            # linked subsurfaces (i.e. "holes")
         | 
| 1823 | 
            +
                  match = false
         | 
| 1824 | 
            +
                  next if edge.key?(:io_type)          # skip if set in JSON
         | 
| 1825 | 
            +
                  next unless edge.key?(:v0)
         | 
| 1826 | 
            +
                  next unless edge.key?(:v1)
         | 
| 1827 | 
            +
                  next unless edge.key?(:psi)
         | 
| 1828 | 
            +
                  next unless edge.key?(:surfaces)
         | 
| 1829 | 
            +
             | 
| 1830 | 
            +
                  edge[:surfaces].keys.each do |identifier|
         | 
| 1831 | 
            +
                    break    if match
         | 
| 1832 | 
            +
                    next unless holes.key?(identifier)
         | 
| 1833 | 
            +
             | 
| 1834 | 
            +
                    if holes[identifier].attributes.key?(:unhinged)
         | 
| 1835 | 
            +
                      nb = 0 if holes[identifier].attributes[:unhinged]
         | 
| 1836 | 
            +
                      break  if holes[identifier].attributes[:unhinged]
         | 
| 1837 | 
            +
                    end
         | 
| 1838 | 
            +
             | 
| 1839 | 
            +
                    nb += 1
         | 
| 1840 | 
            +
                    match = true if nb > 1
         | 
| 1841 | 
            +
                  end
         | 
| 1842 | 
            +
             | 
| 1843 | 
            +
                  if nb == 1 # linking 1x subsurface, search for 1x other.
         | 
| 1844 | 
            +
                    e1 = { v0: edge[:v0].point, v1: edge[:v1].point }
         | 
| 1845 | 
            +
             | 
| 1846 | 
            +
                    edges.each do |nom, e|
         | 
| 1847 | 
            +
                      nb = 0
         | 
| 1848 | 
            +
                      break    if match
         | 
| 1849 | 
            +
                      next     if nom == id
         | 
| 1850 | 
            +
                      next     if e.key?(:io_type)
         | 
| 1851 | 
            +
                      next unless e.key?(:psi)
         | 
| 1852 | 
            +
                      next unless e.key?(:surfaces)
         | 
| 1853 | 
            +
             | 
| 1854 | 
            +
                      e[:surfaces].keys.each do |identifier|
         | 
| 1855 | 
            +
                        next unless holes.key?(identifier)
         | 
| 1856 | 
            +
             | 
| 1857 | 
            +
                        if holes[identifier].attributes.key?(:unhinged)
         | 
| 1858 | 
            +
                          nb = 0 if holes[identifier].attributes[:unhinged]
         | 
| 1859 | 
            +
                          break  if holes[identifier].attributes[:unhinged]
         | 
| 1860 | 
            +
                        end
         | 
| 1861 | 
            +
             | 
| 1862 | 
            +
                        nb += 1
         | 
| 1863 | 
            +
                      end
         | 
| 1864 | 
            +
             | 
| 1865 | 
            +
                      next unless nb == 1 # only process edge if linking 1x subsurface
         | 
| 1866 | 
            +
             | 
| 1867 | 
            +
                      e2 = { v0: e[:v0].point, v1: e[:v1].point }
         | 
| 1868 | 
            +
                      match = matches?(e1, e2, argh[:sub_tol])
         | 
| 1869 | 
            +
                    end
         | 
| 1870 | 
            +
                  end
         | 
| 1871 | 
            +
             | 
| 1872 | 
            +
                  next unless match
         | 
| 1873 | 
            +
             | 
| 1874 | 
            +
                  edge[:psi] = { transition: 0.000 }
         | 
| 1875 | 
            +
                  edge[:set] = json[:io][:building][:psi]
         | 
| 1876 | 
            +
                end
         | 
| 1877 | 
            +
             | 
| 1774 1878 | 
             
                # Loop through each edge and assign heat loss to linked surfaces.
         | 
| 1775 1879 | 
             
                edges.each do |identifier, edge|
         | 
| 1776 1880 | 
             
                  next unless  edge.key?(:psi)
         | 
| 1777 1881 | 
             
                  rsi        = 0
         | 
| 1778 | 
            -
                  max        = edge[:psi].values.max
         | 
| 1779 | 
            -
                  type       = edge[:psi].key(max)
         | 
| 1882 | 
            +
                  max        = edge[:psi   ].values.max
         | 
| 1883 | 
            +
                  type       = edge[:psi   ].key(max)
         | 
| 1780 1884 | 
             
                  length     = edge[:length]
         | 
| 1885 | 
            +
                  length    *= edge[:mult  ] if edge.key?(:mult)
         | 
| 1781 1886 | 
             
                  bridge     = { psi: max, type: type, length: length }
         | 
| 1782 1887 | 
             
                  deratables = {}
         | 
| 1783 1888 | 
             
                  apertures  = {}
         | 
| @@ -1869,7 +1974,7 @@ module TBD | |
| 1869 1974 | 
             
                # ... first 'uprate' targeted insulation layers (see ua.rb) before derating.
         | 
| 1870 1975 | 
             
                # Check for new argh keys [:wall_uo], [:roof_uo] and/or [:floor_uo].
         | 
| 1871 1976 | 
             
                up = argh[:uprate_walls] || argh[:uprate_roofs] || argh[:uprate_floors]
         | 
| 1872 | 
            -
                uprate(model, tbd[:surfaces], argh) | 
| 1977 | 
            +
                uprate(model, tbd[:surfaces], argh) if up
         | 
| 1873 1978 |  | 
| 1874 1979 | 
             
                # Derated (cloned) constructions are unique to each deratable surface.
         | 
| 1875 1980 | 
             
                # Unique construction names are prefixed with the surface name,
         | 
| @@ -1975,6 +2080,7 @@ module TBD | |
| 1975 2080 | 
             
                  set        = e[:set]
         | 
| 1976 2081 | 
             
                  t          = e[:psi].key(v)
         | 
| 1977 2082 | 
             
                  l          = e[:length]
         | 
| 2083 | 
            +
                  l         *= e[:mult] if e.key?(:mult)
         | 
| 1978 2084 | 
             
                  edge       = { psi: set, type: t, length: l, surfaces: e[:surfaces].keys }
         | 
| 1979 2085 | 
             
                  edge[:v0x] = e[:v0].point.x
         | 
| 1980 2086 | 
             
                  edge[:v0y] = e[:v0].point.y
         |