Exemplo n.º 1
0
    def setUp(self):

        self.layers = {}
        self.layers['BASE'] = ConvLayer(8, 16, 28, 3)
        self.layers['POOL'] = PoolingLayer(16, 28, 2)
        self.layers['LR'] = LocalRegionLayer(16, 28, nreg=3, sreg=1)

        self.batch_size = 4

        self.cost = Cost(mac_op=1,
                         mem_hier=(200, 6, 2, 1),
                         noc_hop=50,
                         idl_unit=50)

        self.none_cstr = SchedulingConstraint()
        self.cstr = SchedulingConstraint(topofm=1, topbat=self.batch_size)

        self.resource = Resource(
            proc_region=NodeRegion(origin=PhyDim2(0, 0),
                                   dim=PhyDim2(4, 4),
                                   type=NodeRegion.PROC),
            dram_region=NodeRegion(origin=PhyDim2(0, 0),
                                   dim=PhyDim2(4, 1),
                                   type=NodeRegion.DRAM),
            src_data_region=NodeRegion(origin=PhyDim2(0, 0),
                                       dim=PhyDim2(4, 1),
                                       type=NodeRegion.DRAM),
            dst_data_region=NodeRegion(origin=PhyDim2(0, 0),
                                       dim=PhyDim2(4, 1),
                                       type=NodeRegion.DRAM),
            dim_array=PhyDim2(16, 16),
            size_gbuf=65536,
            size_regf=64,
            array_bus_width=float('inf'),
            dram_bandwidth=float('inf'),
            no_time_mux=False)

        self.options = Option(partition_hybrid=True,
                              partition_batch=True,
                              partition_ifmaps=True,
                              ntops=10)

        self.ifmap_layouts = {}
        part = PartitionScheme(order=(pe.INPP, pe.BATP, pe.OUTP, pe.OFMP),
                               pdims=((1, 2), (2, 1), (1, 2), (2, 1)))
        for wlkey in self.layers:
            input_layer = self.layers[wlkey].input_layer()
            self.ifmap_layouts[wlkey] = DataLayout(
                frngs=(FmapRange((0, 0, 0, 0),
                                 FmapPosition(b=self.batch_size,
                                              n=input_layer.nofm,
                                              h=input_layer.hofm,
                                              w=input_layer.wofm)), ),
                regions=(self.resource.src_data_region, ),
                parts=(part.projection(self.resource.src_data_region,
                                       appl2frng=True), ))

        self.sched_seq = (2, 0, 1)
Exemplo n.º 2
0
class TestPartitionScheme(unittest.TestCase):
    ''' Tests for PartitionScheme. '''
    def setUp(self):
        self.ps1 = PartitionScheme(order=[pe.BATP, pe.OUTP, pe.OFMP, pe.INPP],
                                   pdims=[(2, 3), (3, 1), (1, 5), (5, 2)])
        self.ps2 = PartitionScheme(order=list(range(pe.NUM)),
                                   pdims=[(2, 2), (5, 5), (3, 3), (1, 1)])

        self.nr1 = NodeRegion(origin=PhyDim2(0, 0),
                              dim=self.ps1.dim(),
                              type=NodeRegion.PROC)
        self.nr2 = NodeRegion(origin=PhyDim2(0, 0),
                              dim=self.ps2.dim(),
                              type=NodeRegion.PROC)

    def test_invalid_order(self):
        ''' Invalid order. '''
        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*order.*'):
            _ = PartitionScheme(order=list(range(pe.NUM - 1)),
                                pdims=[(1, 1)] * pe.NUM)

        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*order.*'):
            _ = PartitionScheme(order=[0] + list(range(2, pe.NUM)),
                                pdims=[(1, 1)] * pe.NUM)

        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*order.*'):
            _ = PartitionScheme(order=[1] + list(range(pe.NUM)),
                                pdims=[(1, 1)] * pe.NUM)

        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*order.*'):
            _ = PartitionScheme(order=list(range(4, 4 + pe.NUM)),
                                pdims=[(1, 1)] * pe.NUM)

    def test_invalid_pdims(self):
        ''' Invalid pdims. '''
        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*pdims.*'):
            _ = PartitionScheme(order=list(range(pe.NUM)),
                                pdims=[(1, 1)] * (pe.NUM - 1))

        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*pdims.*'):
            _ = PartitionScheme(order=list(range(pe.NUM)),
                                pdims=[(1, 1), (1, 1), (2, 1, 1), (1, 1)])

    def test_dim(self):
        ''' Get dim. '''
        self.assertEqual(self.ps1.dim(0), PhyDim2(2, 3))
        self.assertEqual(self.ps1.dim(1), PhyDim2(3, 1))
        self.assertEqual(self.ps1.dim(2), PhyDim2(1, 5))
        self.assertEqual(self.ps1.dim(3), PhyDim2(5, 2))

        self.assertEqual(self.ps2.dim(0), PhyDim2(2, 2))
        self.assertEqual(self.ps2.dim(1), PhyDim2(5, 5))
        self.assertEqual(self.ps2.dim(2), PhyDim2(3, 3))
        self.assertEqual(self.ps2.dim(3), PhyDim2(1, 1))

        self.assertEqual(self.ps1.dim(0, 1, 2),
                         PhyDim2(2, 3) * PhyDim2(3, 1) * PhyDim2(1, 5))
        self.assertEqual(
            self.ps1.dim(),
            PhyDim2(2, 3) * PhyDim2(3, 1) * PhyDim2(1, 5) * PhyDim2(5, 2))

        self.assertEqual(self.ps1.dim(0, 1, 2), self.ps1.dim(1, 2, 0))

    def test_dim_invalid_index(self):
        ''' Get dim invalid index. '''
        with self.assertRaises(IndexError):
            _ = self.ps1.dim(pe.NUM + 1)

        with self.assertRaises(IndexError):
            _ = self.ps1.dim(0, 1, pe.NUM)

    def test_size(self):
        ''' Get size. '''
        for l in range(1, pe.NUM):
            for args in itertools.combinations(range(pe.NUM), l):
                self.assertEqual(
                    self.ps1.dim(*args).size(), self.ps1.size(*args))

    def test_size_invalid_index(self):
        ''' Get size invalid index. '''
        with self.assertRaises(IndexError):
            _ = self.ps1.size(pe.NUM + 1)

        with self.assertRaises(IndexError):
            _ = self.ps1.size(0, 1, pe.NUM)

    def test_gen_pidx(self):
        ''' Generate pidx. '''
        for ps in [self.ps1, self.ps2]:

            pidx_list = list(ps.gen_pidx())

            # Num. of pidx == size.
            self.assertEqual(len(pidx_list), ps.size())
            self.assertEqual(len(set(pidx_list)), ps.size())

            for i, idx_list in enumerate(zip(*pidx_list)):
                cnt = collections.Counter(idx_list)
                # Num. of different pidx == size.
                self.assertEqual(len(cnt), ps.size(i))
                # Num. of repeated pidx == other sizes.
                for c in cnt.values():
                    self.assertEqual(c, ps.size() // ps.size(i))

    def test_coordinate(self):
        ''' Get coordinate. '''
        for ps, nr in zip([self.ps1, self.ps2], [self.nr1, self.nr2]):

            coord_list = [ps.coordinate(nr, pidx) for pidx in ps.gen_pidx()]

            self.assertEqual(len(coord_list), ps.size())
            self.assertEqual(len(set(coord_list)), ps.size())

            for coord in coord_list:
                self.assertGreaterEqual(coord.h, 0)
                self.assertGreaterEqual(coord.w, 0)
                self.assertLess(coord.h, ps.dim().h)
                self.assertLess(coord.w, ps.dim().w)

        pidx = [PhyDim2(0, 0)] * pe.NUM
        pidx[pe.OUTP] = PhyDim2(1, 1)

        self.assertEqual(self.ps1.coordinate(self.nr1, pidx),
                         self.ps1.dim(pe.OFMP, pe.INPP) * PhyDim2(1, 1))

        self.assertEqual(
            self.ps2.coordinate(self.nr2, pidx),
            self.ps2.dim(pe.OFMP, pe.BATP, pe.INPP) * PhyDim2(1, 1))

    def test_fmap_range(self):
        ''' Get fmap_range. '''
        fr1 = FmapRange(FmapPosition(b=0, n=0, h=0, w=0),
                        FmapPosition(b=8, n=64, h=28, w=28))
        # Small ranges.
        fr2 = FmapRange(FmapPosition(b=0, n=0, h=0, w=0),
                        FmapPosition(b=1, n=1, h=1, w=1))
        # Irregular values.
        fr3 = FmapRange(FmapPosition(b=2, n=4, h=2, w=6),
                        FmapPosition(b=5, n=11, h=13, w=13))

        ps = self.ps2

        # No overlap.
        for fr in [fr1, fr2, fr3]:
            pfr_list = [ps.fmap_range(fr, pidx) for pidx in ps.gen_pidx()]
            for idx, pfr in enumerate(pfr_list):
                for jdx in range(idx):
                    self.assertEqual(pfr_list[jdx].overlap_size(pfr), 0)

        pidx = (PhyDim2(1, 0), PhyDim2(4, 3), PhyDim2(0, 2), PhyDim2(0, 0))

        self.assertEqual(
            ps.fmap_range(fr1, pidx),
            FmapRange(FmapPosition(b=1, n=32, h=22, w=16),
                      FmapPosition(b=2, n=48, h=28, w=22)))
        self.assertEqual(
            ps.fmap_range(fr2, pidx),
            FmapRange(FmapPosition(b=0, n=0, h=0, w=0),
                      FmapPosition(b=0, n=0, h=1, w=0)))
        self.assertEqual(
            ps.fmap_range(fr3, pidx),
            FmapRange(FmapPosition(b=2, n=7, h=10, w=10),
                      FmapPosition(b=3, n=9, h=13, w=11)))

    def test_is_appl2frng(self):
        ''' Get is_applicable_to_fmap_range. '''
        self.assertFalse(self.ps1.is_applicable_to_fmap_range())
        self.assertTrue(self.ps2.is_applicable_to_fmap_range())

    def test_part_layer(self):
        ''' Get part_layer. '''
        batch_size = 16

        layer = ConvLayer(32, 128, 28, 3)
        p_layer, p_batch_size, p_occ = self.ps1.part_layer(layer, batch_size)
        self.assertGreaterEqual(p_layer.hofm * self.ps1.dim(pe.OFMP).h,
                                layer.hofm, 'part_layer: Conv: hofm')
        self.assertGreaterEqual(p_layer.wofm * self.ps1.dim(pe.OFMP).w,
                                layer.wofm, 'part_layer: Conv: wofm')
        self.assertGreaterEqual(p_layer.nofm * self.ps1.size(pe.OUTP),
                                layer.nofm, 'part_layer: Conv: nofm')
        self.assertGreaterEqual(p_layer.nifm * self.ps1.size(pe.INPP),
                                layer.nifm, 'part_layer: Conv: nifm')
        self.assertGreaterEqual(p_batch_size * self.ps1.size(pe.BATP), 16,
                                'part_layer: Conv: batch_size')
        self.assertAlmostEqual(
            p_occ, 1. * (32 * 128 * 28 * 28 * 16) /
            (4 * 22 * 10 * 28 * 4 * self.ps1.size()))

        layer = PoolingLayer(128, 112, 2)
        p_layer, p_batch_size, p_occ = self.ps2.part_layer(layer, batch_size)
        self.assertGreaterEqual(p_layer.hofm * self.ps2.dim(pe.OFMP).h,
                                layer.hofm, 'part_layer: Pooling: hofm')
        self.assertGreaterEqual(p_layer.wofm * self.ps2.dim(pe.OFMP).w,
                                layer.wofm, 'part_layer: Pooling: wofm')
        self.assertGreaterEqual(p_layer.nofm * self.ps2.size(pe.OUTP),
                                layer.nofm, 'part_layer: Pooling: nofm')
        self.assertGreaterEqual(p_layer.nifm, p_layer.nofm,
                                'part_layer: Pooling: nifm')
        self.assertGreaterEqual(p_batch_size * self.ps2.size(pe.BATP), 16,
                                'part_layer: Pooling: batch_size')
        self.assertAlmostEqual(
            p_occ,
            1. * (128 * 112 * 112 * 16) / (32 * 23 * 23 * 2 * self.ps2.size()))

    def test_part_layer_invalid_inpart(self):
        ''' Get part_layer invalid INPP. '''
        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*input.*'):
            _ = self.ps1.part_layer(
                PoolingLayer(self.ps1.size(pe.OUTP), self.ps1.size(pe.OFMP),
                             2), self.ps1.size(pe.BATP))

    def test_part_layer_invalid_type(self):
        ''' Get part_layer invalid type. '''
        class _Layer(Layer):
            def input_layer(self):
                return self

            def ops_per_neuron(self):
                return 0

            @staticmethod
            def data_loops():
                return None

        layer = _Layer(self.ps1.size(pe.OUTP), self.ps1.size(pe.OFMP))
        self.assertEqual(layer.total_ops(), 0)
        self.assertIsNone(_Layer.data_loops())

        with self.assertRaisesRegex(TypeError, 'PartitionScheme: .*layer.*'):
            _ = self.ps1.part_layer(layer, self.ps1.size(pe.BATP))

    def test_part_neighbor_dist(self):
        ''' Get part_neighbor_dist. '''
        for ps, nr in zip([self.ps1, self.ps2], [self.nr1, self.nr2]):

            for idx in range(pe.NUM):
                nbr_dist = ps.part_neighbor_dist(nr, ps.order[idx])
                dim_below = ps.dim(*ps.order[idx + 1:]) if idx + 1 < pe.NUM \
                        else PhyDim2(1, 1)
                dim_cur = ps.dim(ps.order[idx])

                if dim_cur.h == 1:
                    self.assertTrue(math.isinf(nbr_dist.h))
                else:
                    self.assertEqual(nbr_dist.h, dim_below.h)

                if dim_cur.w == 1:
                    self.assertTrue(math.isinf(nbr_dist.w))
                else:
                    self.assertEqual(nbr_dist.w, dim_below.w)

    def test_part_neighbor_dist_inv(self):
        ''' Get part_neighbor_dist invalid arg. '''
        dist = self.ps1.part_neighbor_dist(self.nr1, pe.NUM)
        self.assertTrue(all(math.isnan(d) for d in dist))

    def test_projection(self):
        ''' Get projection. '''
        def _make_region(dim):
            return NodeRegion(origin=PhyDim2(0, 0),
                              dim=PhyDim2(*dim),
                              type=NodeRegion.DRAM)

        # Shrink.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (1, 1)))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink multiple.
        proj_part = part.projection(_make_region((2, 2)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 1))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 2))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink non-dividable.
        proj_part = part.projection(_make_region((3, 54)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (2, 45))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        # For height, 3 // 2 = 1.
        # For width, 54 // 5 = 10, 10 // 3 = 3.
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 3))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink with INPP.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (4, 4)))
        proj_part = part.projection(_make_region((4, 30)), appl2frng=True)
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (2, 2))

        # Shrink all.
        proj_part = part.projection(_make_region((1, 1)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (1, 1))

        # Extend.
        part = PartitionScheme(order=(pe.INPP, pe.BATP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (1, 1), (1, 1)))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Extend non-dividable.
        proj_part = part.projection(_make_region((5, 40)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        # For height, 5 // 2 = 2.
        # For width, 40 // (3 * 5) == 2.
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Extend with INPP.
        part = PartitionScheme(order=(pe.INPP, pe.BATP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (1, 1), (4, 4)))
        proj_part = part.projection(_make_region((4, 30)), appl2frng=True)
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Both shrink and extend.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (1, 1)))
        proj_part = part.projection(_make_region((16, 16)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (16, 15))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (8, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

    def test_projection_empty_region(self):
        ''' Get projection with empty region. '''
        with self.assertRaisesRegex(ValueError, 'PartitionScheme: .*region.*'):
            _ = self.ps1.projection(
                NodeRegion(origin=PhyDim2(0, 0),
                           dim=PhyDim2(0, 0),
                           type=NodeRegion.DRAM))

    def test_repr(self):
        ''' __repr__. '''
        # pylint: disable=eval-used
        self.assertEqual(eval(repr(self.ps1)), self.ps1)
        self.assertEqual(eval(repr(self.ps2)), self.ps2)
Exemplo n.º 3
0
    def test_projection(self):
        ''' Get projection. '''
        def _make_region(dim):
            return NodeRegion(origin=PhyDim2(0, 0),
                              dim=PhyDim2(*dim),
                              type=NodeRegion.DRAM)

        # Shrink.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (1, 1)))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink multiple.
        proj_part = part.projection(_make_region((2, 2)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 1))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 2))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink non-dividable.
        proj_part = part.projection(_make_region((3, 54)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (2, 45))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        # For height, 3 // 2 = 1.
        # For width, 54 // 5 = 10, 10 // 3 = 3.
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 3))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Shrink with INPP.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (4, 4)))
        proj_part = part.projection(_make_region((4, 30)), appl2frng=True)
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (1, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (2, 2))

        # Shrink all.
        proj_part = part.projection(_make_region((1, 1)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (1, 1))

        # Extend.
        part = PartitionScheme(order=(pe.INPP, pe.BATP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (1, 1), (1, 1)))
        proj_part = part.projection(_make_region((4, 30)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Extend non-dividable.
        proj_part = part.projection(_make_region((5, 40)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        # For height, 5 // 2 = 2.
        # For width, 40 // (3 * 5) == 2.
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Extend with INPP.
        part = PartitionScheme(order=(pe.INPP, pe.BATP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (1, 1), (4, 4)))
        proj_part = part.projection(_make_region((4, 30)), appl2frng=True)
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (4, 30))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (2, 2))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))

        # Both shrink and extend.
        part = PartitionScheme(order=(pe.BATP, pe.INPP, pe.OUTP, pe.OFMP),
                               pdims=((2, 3), (1, 5), (4, 4), (1, 1)))
        proj_part = part.projection(_make_region((16, 16)))
        self.assertTupleEqual(proj_part.order, part.order)
        self.assertTupleEqual(proj_part.dim(), (16, 15))
        self.assertTupleEqual(proj_part.dim(pe.OUTP), (2, 3))
        self.assertTupleEqual(proj_part.dim(pe.OFMP), (1, 5))
        self.assertTupleEqual(proj_part.dim(pe.BATP), (8, 1))
        self.assertTupleEqual(proj_part.dim(pe.INPP), (1, 1))