예제 #1
0
def test_default_modifiers():
    """Test the auto-assigning of modifiers by face type and boundary condition."""
    vertices_parent_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    vertices_parent_wall_2 = list(reversed(vertices_parent_wall))
    vertices_wall = [[0, 1, 0], [0, 2, 0], [0, 2, 2], [0, 0, 2]]
    vertices_wall_2 = list(reversed(vertices_wall))
    vertices_floor = [[0, 0, 0], [0, 10, 0], [10, 10, 0], [10, 0, 0]]
    vertices_roof = [[10, 0, 3], [10, 10, 3], [0, 10, 3], [0, 0, 3]]

    wf = Face.from_vertices('wall_face', vertices_parent_wall)
    wa = Aperture.from_vertices('wall_window', vertices_wall)
    wf.add_aperture(wa)
    Room('TestRoom1', [wf])
    assert wa.properties.radiance.modifier == generic_exterior_window

    wf2 = Face.from_vertices('wall_face2', vertices_parent_wall_2)
    wa2 = Aperture.from_vertices('wall_window2', vertices_wall_2)
    wf2.add_aperture(wa2)
    Room('TestRoom2', [wf2])
    wa.set_adjacency(wa2)
    assert wa.properties.radiance.modifier == generic_interior_window

    ra = Aperture.from_vertices('roof_window', vertices_roof)
    assert ra.properties.radiance.modifier == generic_exterior_window
    fa = Aperture.from_vertices('floor_window', vertices_floor)
    assert fa.properties.radiance.modifier == generic_exterior_window
예제 #2
0
def test_default_constructions():
    """Test the auto-assigning of constructions by boundary condition."""
    vertices_parent_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    vertices_parent_wall_2 = list(reversed(vertices_parent_wall))
    vertices_wall = [[0, 1, 0], [0, 2, 0], [0, 2, 2], [0, 0, 2]]
    vertices_wall_2 = list(reversed(vertices_wall))
    vertices_floor = [[0, 0, 0], [0, 10, 0], [10, 10, 0], [10, 0, 0]]
    vertices_roof = [[10, 0, 3], [10, 10, 3], [0, 10, 3], [0, 0, 3]]

    wf = Face.from_vertices('wall face', vertices_parent_wall)
    wa = Aperture.from_vertices('wall window', vertices_wall)
    wf.add_aperture(wa)
    Room('Test Room 1', [wf])
    assert wa.properties.energy.construction.name == 'Generic Double Pane'

    wf2 = Face.from_vertices('wall face2', vertices_parent_wall_2)
    wa2 = Aperture.from_vertices('wall window2', vertices_wall_2)
    wf2.add_aperture(wa2)
    Room('Test Room 2', [wf2])
    wa.set_adjacency(wa2)
    assert wa.properties.energy.construction.name == 'Generic Single Pane'

    ra = Aperture.from_vertices('roof window', vertices_roof)
    assert ra.properties.energy.construction.name == 'Generic Double Pane'
    fa = Aperture.from_vertices('floor window', vertices_floor)
    assert fa.properties.energy.construction.name == 'Generic Double Pane'
예제 #3
0
def test_duplicate():
    """Test what happens to radiance properties when duplicating a Aperture."""
    verts = [
        Point3D(0, 0, 0),
        Point3D(10, 0, 0),
        Point3D(10, 0, 10),
        Point3D(0, 0, 10)
    ]
    triple_pane = Glass.from_single_transmittance('TriplePane', 0.45)
    ap_original = Aperture.from_vertices('wall_aper', Face3D(verts))
    ap_dup_1 = ap_original.duplicate()

    assert ap_original.properties.radiance.host is ap_original
    assert ap_dup_1.properties.radiance.host is ap_dup_1
    assert ap_original.properties.radiance.host is not ap_dup_1.properties.radiance.host

    assert ap_original.properties.radiance.modifier == \
        ap_dup_1.properties.radiance.modifier
    ap_dup_1.properties.radiance.modifier = triple_pane
    assert ap_original.properties.radiance.modifier != \
        ap_dup_1.properties.radiance.modifier

    ap_dup_2 = ap_dup_1.duplicate()

    assert ap_dup_1.properties.radiance.modifier == \
        ap_dup_2.properties.radiance.modifier
    ap_dup_2.properties.radiance.modifier = None
    assert ap_dup_1.properties.radiance.modifier != \
        ap_dup_2.properties.radiance.modifier
예제 #4
0
def test_to_from_dict_with_states():
    """Test the Aperture from_dict method with radiance properties."""
    ap = Aperture.from_vertices(
        'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    shd1 = StateGeometry.from_vertices(
        'wall_overhang1', [[0, 0, 10], [10, 0, 10], [10, 2, 10], [0, 2, 10]])
    shd2 = StateGeometry.from_vertices(
        'wall_overhang2', [[0, 0, 5], [10, 0, 5], [10, 2, 5], [0, 2, 5]])

    ecglass1 = Glass.from_single_transmittance('ElectrochromicState1', 0.4)
    ecglass2 = Glass.from_single_transmittance('ElectrochromicState2', 0.27)
    ecglass3 = Glass.from_single_transmittance('ElectrochromicState3', 0.14)
    ecglass4 = Glass.from_single_transmittance('ElectrochromicState4', 0.01)

    tint1 = RadianceSubFaceState(ecglass1)
    tint2 = RadianceSubFaceState(ecglass2)
    tint3 = RadianceSubFaceState(ecglass3, [shd1])
    tint4 = RadianceSubFaceState(ecglass4, [shd1.duplicate(), shd2])
    states = (tint1, tint2, tint3, tint4)

    ap.properties.radiance.dynamic_group_identifier = 'ElectrochromicWindow1'
    ap.properties.radiance.states = states
    tint4.gen_geos_from_tmtx_thickness(0.05)

    ad = ap.to_dict()
    new_aperture = Aperture.from_dict(ad)
    assert new_aperture.properties.radiance.dynamic_group_identifier == \
        ap.properties.radiance.dynamic_group_identifier
    state_ids1 = [state.modifier for state in states]
    state_ids2 = [
        state.modifier for state in new_aperture.properties.radiance.states
    ]
    assert state_ids1 == state_ids2
    assert new_aperture.to_dict() == ad
예제 #5
0
def test_writer():
    """Test the Aperture writer object."""
    vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    ap = Aperture.from_vertices('RectangleWindow', vertices)

    writers = [mod for mod in dir(ap.to) if not mod.startswith('_')]
    for writer in writers:
        assert callable(getattr(ap.to, writer))
예제 #6
0
def test_energy_properties():
    """Test the existence of the Aperture energy properties."""
    aperture = Aperture.from_vertices(
        'wall_aperture', [[0, 0, 1], [10, 0, 1], [10, 0, 2], [0, 0, 2]])
    assert hasattr(aperture.properties, 'energy')
    assert isinstance(aperture.properties.energy, ApertureEnergyProperties)
    assert isinstance(aperture.properties.energy.construction,
                      WindowConstruction)
    assert not aperture.properties.energy.is_construction_set_by_user
예제 #7
0
def test_to_from_dict():
    """Test the to/from dict of Aperture objects."""
    vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    ap = Aperture.from_vertices('RectangleWindow', vertices)

    ap_dict = ap.to_dict()
    new_ap = Aperture.from_dict(ap_dict)
    assert isinstance(new_ap, Aperture)
    assert new_ap.to_dict() == ap_dict
예제 #8
0
def test_radiance_properties():
    """Test the existence of the Aperture radiance properties."""
    aperture = Aperture.from_vertices(
        'wall_aper', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    assert hasattr(aperture.properties, 'radiance')
    assert isinstance(aperture.properties.radiance, ApertureRadianceProperties)

    assert aperture.properties.radiance.modifier == generic_exterior_window
    assert aperture.properties.radiance.modifier_blk == black
    assert not aperture.properties.radiance.is_modifier_set_on_object
예제 #9
0
def test_to_from_dict():
    """Test the Aperture from_dict method with states."""
    aperture = Aperture.from_vertices(
        'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    triple_pane = Glass.from_single_transmittance('TriplePane', 0.45)
    aperture.properties.radiance.modifier = triple_pane

    ad = aperture.to_dict()
    new_aperture = Aperture.from_dict(ad)
    assert new_aperture.properties.radiance.modifier == triple_pane
    assert new_aperture.to_dict() == ad
예제 #10
0
def test_set_modifier():
    """Test the setting of a modifier on an Aperture."""
    aperture = Aperture.from_vertices(
        'wall_aper', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    triple_pane = Glass.from_single_transmittance('TriplePane', 0.45)
    aperture.properties.radiance.modifier = triple_pane

    assert aperture.properties.radiance.modifier == triple_pane
    assert aperture.properties.radiance.is_modifier_set_on_object

    with pytest.raises(AttributeError):
        aperture.properties.radiance.modifier.r_transmittance = 0.45
예제 #11
0
def test_writer_to_rad():
    """Test the Aperture to.rad method."""
    pts = [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]
    aperture = Aperture.from_vertices('wall_aperture', pts)
    triple_pane = Glass.from_single_transmittance('TriplePane', 0.45)
    aperture.properties.radiance.modifier = triple_pane

    assert hasattr(aperture.to, 'rad')
    rad_string = aperture.to.rad(aperture)
    assert 'polygon wall_aperture' in rad_string
    assert 'TriplePane' in rad_string
    for pt in pts:
        assert ' '.join([str(float(x)) for x in pt]) in rad_string
예제 #12
0
def test_to_dict():
    """Test the Aperture to_dict method."""
    vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    ap = Aperture.from_vertices('Rectangle Window', vertices)

    ad = ap.to_dict()
    assert ad['type'] == 'Aperture'
    assert ad['name'] == 'RectangleWindow'
    assert ad['display_name'] == 'Rectangle Window'
    assert 'geometry' in ad
    assert len(ad['geometry']['boundary']) == len(vertices)
    assert 'properties' in ad
    assert ad['properties']['type'] == 'ApertureProperties'
    assert not ad['is_operable']
    assert ad['boundary_condition']['type'] == 'Outdoors'
예제 #13
0
def test_from_dict():
    """Test the Aperture from_dict method with energy properties."""
    aperture = Aperture.from_vertices(
        'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715,
                                              0.770675, 0.07, 0.8836, 0.0804,
                                              0, 0.84, 0.84, 1.0)
    gap = EnergyWindowMaterialGas('air gap', thickness=0.0127)
    triple_pane = WindowConstruction(
        'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass])
    aperture.properties.energy.construction = triple_pane

    ad = aperture.to_dict()
    new_aperture = Aperture.from_dict(ad)
    assert new_aperture.properties.energy.construction == triple_pane
    assert new_aperture.to_dict() == ad
예제 #14
0
def test_default_properties():
    """Test the auto-assigning of shade properties."""
    out_shade = Shade.from_vertices(
        'overhang', [[0, 0, 3], [1, 0, 3], [1, 1, 3], [0, 1, 3]])
    in_shade = Shade.from_vertices(
        'light_shelf', [[0, 0, 3], [-1, 0, 3], [-1, -1, 3], [0, -1, 3]])
    aperture = Aperture.from_vertices(
        'parent_aperture', [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]])

    assert out_shade.properties.radiance.modifier == generic_context
    assert in_shade.properties.radiance.modifier == generic_context

    aperture.add_outdoor_shade(out_shade)
    assert out_shade.properties.radiance.modifier == generic_exterior_shade

    aperture.add_indoor_shade(in_shade)
    assert in_shade.properties.radiance.modifier == generic_interior_shade
예제 #15
0
def test_writer_to_idf():
    """Test the Aperture to_idf method."""
    aperture = Aperture.from_vertices(
        'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715,
                                              0.770675, 0.07, 0.8836, 0.0804,
                                              0, 0.84, 0.84, 1.0)
    gap = EnergyWindowMaterialGas('air gap', thickness=0.0127)
    triple_pane = WindowConstruction(
        'TriplePane', [clear_glass, gap, clear_glass, gap, clear_glass])
    aperture.properties.energy.construction = triple_pane

    assert hasattr(aperture.to, 'idf')
    idf_string = aperture.to.idf(aperture)
    assert 'wall_window,' in idf_string
    assert 'FenestrationSurface:Detailed,' in idf_string
    assert 'TriplePane' in idf_string
예제 #16
0
def test_to_dict():
    """Test the Aperture to_dict method."""
    vertices = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    unique_id = str(py_uuid.uuid4())
    ap = Aperture.from_vertices(unique_id, vertices)
    ap.display_name = 'Rectangle Window'

    ad = ap.to_dict()
    assert ad['type'] == 'Aperture'
    assert ad['identifier'] == unique_id
    assert ad['display_name'] == 'Rectangle Window'
    assert 'geometry' in ad
    assert len(ad['geometry']['boundary']) == len(vertices)
    assert 'properties' in ad
    assert ad['properties']['type'] == 'ApertureProperties'
    assert not ad['is_operable']
    assert ad['boundary_condition']['type'] == 'Outdoors'
예제 #17
0
def test_default_properties():
    """Test the auto-assigning of shade properties."""
    shade = Shade.from_vertices('overhang',
                                [[0, 0, 3], [1, 0, 3], [1, 1, 3], [0, 1, 3]])
    aperture = Aperture.from_vertices(
        'ParentAperture', [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]])

    assert shade.properties.energy.transmittance_schedule is None
    assert shade.properties.energy.construction.solar_reflectance == 0.2
    assert shade.properties.energy.construction.visible_reflectance == 0.2
    assert not shade.properties.energy.construction.is_specular
    assert shade.properties.energy.transmittance_schedule is None

    aperture.add_outdoor_shade(shade)
    assert shade.properties.energy.construction.solar_reflectance == 0.35
    assert shade.properties.energy.construction.visible_reflectance == 0.35
    assert not shade.properties.energy.construction.is_specular
예제 #18
0
def test_set_construction():
    """Test the setting of a construction on an Aperture."""
    vertices_wall = [[0, 0, 0], [0, 10, 0], [0, 10, 3], [0, 0, 3]]
    clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715,
                                              0.770675, 0.07, 0.8836, 0.0804,
                                              0, 0.84, 0.84, 1.0)
    gap = EnergyWindowMaterialGas('air gap', thickness=0.0127)
    triple_pane = WindowConstruction(
        'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass])

    aperture = Aperture.from_vertices('wall window', vertices_wall)
    aperture.properties.energy.construction = triple_pane

    assert aperture.properties.energy.construction == triple_pane
    assert aperture.properties.energy.is_construction_set_by_user

    with pytest.raises(AttributeError):
        aperture.properties.energy.construction[0].thickness = 0.1
예제 #19
0
def test_aperture_from_vertices():
    """Test the initialization of Aperture objects from vertices."""
    pts = (Point3D(0, 0, 0), Point3D(0, 0, 3), Point3D(5, 0, 3), Point3D(5, 0, 0))
    aperture = Aperture.from_vertices('Test Window', pts)

    assert aperture.name == 'TestWindow'
    assert aperture.display_name == 'Test Window'
    assert isinstance(aperture.geometry, Face3D)
    assert len(aperture.vertices) == 4
    assert aperture.upper_left_vertices[0] == Point3D(5, 0, 3)
    assert len(aperture.triangulated_mesh3d.faces) == 2
    assert aperture.normal == Vector3D(0, 1, 0)
    assert aperture.center == Point3D(2.5, 0, 1.5)
    assert aperture.area == 15
    assert aperture.perimeter == 16
    assert isinstance(aperture.boundary_condition, Outdoors)
    assert aperture.is_operable is False
    assert not aperture.has_parent
예제 #20
0
def test_to_dict():
    """Test the Aperture to_dict method with radiance properties."""
    aperture = Aperture.from_vertices(
        'wall_aperture', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    triple_pane = Glass.from_single_transmittance('TriplePane', 0.45)

    ad = aperture.to_dict()
    assert 'properties' in ad
    assert ad['properties']['type'] == 'ApertureProperties'
    assert 'radiance' in ad['properties']
    assert ad['properties']['radiance']['type'] == 'ApertureRadianceProperties'

    aperture.properties.radiance.modifier = triple_pane
    ad = aperture.to_dict()
    assert 'modifier' in ad['properties']['radiance']
    assert ad['properties']['radiance']['modifier'] is not None
    assert ad['properties']['radiance']['modifier'][
        'identifier'] == 'TriplePane'
예제 #21
0
def test_to_dict():
    """Test the Aperture to_dict method with energy properties."""
    aperture = Aperture.from_vertices(
        'wall_window', [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]])
    clear_glass = EnergyWindowMaterialGlazing('Clear Glass', 0.005715,
                                              0.770675, 0.07, 0.8836, 0.0804,
                                              0, 0.84, 0.84, 1.0)
    gap = EnergyWindowMaterialGas('air gap', thickness=0.0127)
    triple_pane = WindowConstruction(
        'Triple Pane', [clear_glass, gap, clear_glass, gap, clear_glass])

    ad = aperture.to_dict()
    assert 'properties' in ad
    assert ad['properties']['type'] == 'ApertureProperties'
    assert 'energy' in ad['properties']
    assert ad['properties']['energy']['type'] == 'ApertureEnergyProperties'

    aperture.properties.energy.construction = triple_pane
    ad = aperture.to_dict()
    assert 'construction' in ad['properties']['energy']
    assert ad['properties']['energy']['construction'] is not None
예제 #22
0
def dmtx_group_command(
    folder,
    octree,
    rflux_sky,
    name,
    size,
    threshold,
    ambient_division,
    output_folder,
):
    """Calculate aperture groups for daylight matrix purposes.
    This command calculates view factor from apertures to sky patches (rfluxmtx). Each
    aperture is represented by a sensor grid, and the view factor for the whole aperture
    is the average of the grid. The apertures are grouped based on the threshold.
    \b
    Args:
        folder: Path to a Radiance model folder.
        octree: Path to octree file.
        rflux_sky: Path to rflux sky file.
    """
    def _index_and_min(distance_matrix):
        """Return the minimum value of the distance matrix, as well as the index [j, i] of
        the minimum value of the distance matrix."""
        min_value = min([min(sublist) for sublist in distance_matrix])
        for i, _i in enumerate(distance_matrix):
            for j, _j in enumerate(distance_matrix):
                if distance_matrix[i][j] == min_value:
                    index = [j, i]
                    break
        return min_value, index

    def _pairwise_maximum(array1, array2):
        """Return an array of the pairwise maximum of two arrays."""
        pair_array = [array1, array2]
        max_array = list(map(max, zip(*pair_array)))
        return max_array

    def _tranpose_matrix(matrix):
        """Transposes the distance matrix."""
        matrix = list(map(list, zip(*matrix)))
        return matrix

    def _rmse_from_matrix(input):
        """Calculates RMSE."""
        rmse = []
        for i, predicted in enumerate(input):
            r_list = []
            for j, observed in enumerate(input):
                error = [(p - o) for p, o in zip(predicted, observed)]
                square_error = [e**2 for e in error]
                mean_square_error = sum(square_error) / len(square_error)
                root_mean_square_error = mean_square_error**0.5
                r_list.append(root_mean_square_error)
            rmse.append(r_list)
        return rmse

    def _flatten(container):
        """Flatten an array."""
        if not isinstance(container, list):
            container = [container]
        for i in container:
            if isinstance(i, (list, tuple)):
                for j in _flatten(i):
                    yield j
            else:
                yield i

    def _agglomerative_clustering_complete(distance_matrix,
                                           ap_name,
                                           threshold=0.001):
        """Cluster apertures based on the threshold."""

        # Fill the diagonal with 9999 so a diagonal of zeros will NOT be stored as min_value.
        for i in range(len(distance_matrix)):
            distance_matrix[i][i] = 9999

        # Create starting list of aperture groups. Each aperture starts as its own group.
        ap_groups = ap_name

        # Set the number of samples and the minimum value of the distance matrix.
        n_samples = len(distance_matrix)

        # Set the minimum value of the distance matrix and find the indices of the minimum
        # value in the distance matrix.
        min_value, index = _index_and_min(distance_matrix)

        while n_samples > 1 and min_value < threshold:
            # Combine the two groups and place it at index 0, and remove item at index 1.
            ap_groups[index[0]] = [ap_groups[index[0]], ap_groups[index[1]]]
            ap_groups.pop(index[1])

            # Update the values in the distance matrix. We need the maximum values between
            # the new cluster and all the remaining apertures or clusters still in the
            # distance matrix.
            distance_matrix[index[0]] = \
                _pairwise_maximum(distance_matrix[index[0]], distance_matrix[index[1]])
            distance_matrix = _tranpose_matrix(distance_matrix)
            distance_matrix[index[0]] = \
                _pairwise_maximum(distance_matrix[index[0]], distance_matrix[index[1]])

            # Remove the values at index 1 along both axes.
            distance_matrix.pop(index[1])
            distance_matrix = _tranpose_matrix(distance_matrix)
            distance_matrix.pop(index[1])

            # Update the number of samples that are left in the distance matrix.
            n_samples -= 1
            # Update the minimum value and the indices.
            min_value, index = _index_and_min(distance_matrix)

        return ap_groups

    def _aperture_view_factor(project_folder,
                              apertures,
                              size=0.2,
                              ambient_division=1000,
                              receiver='rflux_sky.sky',
                              octree='scene.oct',
                              calc_folder='dmtx_aperture_grouping'):
        """Calculates the view factor for each aperture by sensor points."""

        # Instantiate dictionary that will store the sensor count for each aperture. We need
        # a OrderedDict so that we can split the rfluxmtx output file by each aperture
        # (sensor count) in the correct order.
        ap_dict = OrderedDict()

        meshes = []
        # Create a mesh for each aperture and add the the sensor count to dict.
        for aperture in apertures:
            ap_mesh = aperture.geometry.mesh_grid(size,
                                                  flip=True,
                                                  generate_centroids=False)
            meshes.append(ap_mesh)
            ap_dict[aperture.display_name] = {
                'sensor_count': len(ap_mesh.faces)
            }

        # Create a sensor grid from joined aperture mesh.
        grid_mesh = SensorGrid.from_mesh3d('aperture_grid',
                                           Mesh3D.join_meshes(meshes))

        # Write sensor grid to pts file.
        sensors = grid_mesh.to_file(os.path.join(project_folder, calc_folder),
                                    file_name='apertures')

        # rfluxmtx options
        rfluxOpt = RfluxmtxOptions()
        rfluxOpt.ad = ambient_division
        rfluxOpt.lw = 1.0 / float(rfluxOpt.ad)
        rfluxOpt.I = True
        rfluxOpt.h = True

        # rfluxmtx command
        rflux = Rfluxmtx()
        rflux.options = rfluxOpt
        rflux.receivers = receiver
        rflux.sensors = sensors
        rflux.octree = octree
        rflux.output = os.path.join(calc_folder, 'apertures_vf.mtx')

        # Run rfluxmtx command
        rflux.run(cwd=project_folder)

        # Get the output file of the rfluxmtx command.
        mtx_file = os.path.join(project_folder, rflux.output)

        return mtx_file, ap_dict

    try:
        model_folder = ModelFolder.from_model_folder(folder)

        apertures = []
        states = model_folder.aperture_groups_states(full=True)
        ap_group_folder = model_folder.aperture_group_folder(full=True)
        for ap_group in states.keys():
            if 'dmtx' in states[ap_group][0]:
                mtx_file = os.path.join(
                    ap_group_folder,
                    os.path.basename(states[ap_group][0]['dmtx']))
                polygon_string = parse_from_file(mtx_file)
                polygon = Polygon.from_string('\n'.join(polygon_string))
                apertures.append(
                    Aperture.from_vertices(ap_group, polygon.vertices))

        assert len(apertures) != 0, \
            'Found no valid dynamic apertures. There should at least be one aperture ' \
            'with transmittance matrix in your model.'

        # Calculate view factor.
        mtx_file, ap_dict = _aperture_view_factor(
            model_folder.folder,
            apertures,
            size=size,
            ambient_division=ambient_division,
            receiver=rflux_sky,
            octree=octree,
            calc_folder=output_folder)

        view_factor = []
        # Read view factor file, convert to one channel output, and divide by Pi.
        with open(mtx_file) as mtx_data:
            for sensor in mtx_data:
                sensor_split = sensor.strip().split()
                if len(sensor_split) % 3 == 0:
                    one_channel = sensor_split[::3]
                    convert_to_vf = lambda x: float(x) / math.pi
                    view_factor.append(list(map(convert_to_vf, one_channel)))

        ap_view_factor = []
        # Split the view factor file by the aperture sensor count.
        for aperture in ap_dict.values():
            sensor_count = aperture['sensor_count']
            ap_vf, view_factor = view_factor[:sensor_count], view_factor[
                sensor_count:]
            ap_view_factor.append(ap_vf)

        ap_view_factor_mean = []
        # Get the mean view factor per sky patch for each aperture.
        for aperture in ap_view_factor:
            ap_t = _tranpose_matrix(aperture)
            ap_view_factor_mean.append(
                [sum(sky_patch) / len(sky_patch) for sky_patch in ap_t])

        # Calculate RMSE between all combinations of averaged aperture view factors.
        rmse = _rmse_from_matrix(ap_view_factor_mean)

        ap_name = list(ap_dict.keys())
        # Cluster the apertures by the 'complete method'.
        ap_groups = _agglomerative_clustering_complete(rmse, ap_name,
                                                       threshold)

        # Flatten the groups. This will break the intercluster structure, but we do not need
        # to know that.
        ap_groups = [list(_flatten(cluster)) for cluster in ap_groups]

        # Add the aperture group to each aperture in the dictionary and write the aperture
        # group rad files.
        group_names = []
        groups_folder = os.path.join(model_folder.folder, output_folder,
                                     'groups')
        if not os.path.isdir(groups_folder):
            os.mkdir(groups_folder)
        for idx, group in enumerate(ap_groups):
            group_name = "group_{}".format(idx)
            group_file = os.path.join(groups_folder, group_name + '.rad')
            xform = []
            group_names.append({
                'identifier': group_name,
                'aperture_groups': group
            })

            for ap in group:
                xform.append(
                    "!xform ./model/aperture_group/{}..mtx.rad".format(ap))

            with open(group_file, "w") as file:
                file.write('\n'.join(xform))

        # Write aperture dictionary to json file.
        output = os.path.join(model_folder.folder, output_folder, 'groups',
                              '%s.json' % name)
        with open(output, 'w') as fp:
            json.dump(group_names, fp, indent=2)

    except Exception:
        _logger.exception("Failed to run dmtx-group command.")
        traceback.print_exc()
        sys.exit(1)
    else:
        sys.exit(0)