Repository: sympy/sympy

Issue:
BlockMatrix not forming a single matrix in some cases
Consider the following code:
https://github.com/sympy/sympy/blob/3d8300a569cfeed57bd36908240862ca330204b0/sympy/utilities/tests/test_wester.py#L1322-L1343

The comments are incorrect. However, the resulting matrix is
![image](https://user-images.githubusercontent.com/8114497/54477006-cf357680-4803-11e9-8e91-01c90da477be.png)

so the BlockMatrix consists of several BlockMatrices. Note that A22 is a single matrix, so something (do not) happen when merging in two dimensions.

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v143). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* matrices
  - matrices comprised of blocks in an irregular are detected in BlockMatrix and can be handled with Matrix ([#16462](https://github.com/sympy/sympy/pull/16462) by [@smichr](https://github.com/smichr))

  - Matrix creation involving matrices expands the matrices unless evaluate is False ([#16462](https://github.com/sympy/sympy/pull/16462) by [@smichr](https://github.com/smichr))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.5.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. <details><summary>Click here to see the pull request description that was parsed.</summary>

    <!-- Your title above should be a short description of what
    was changed. Do not include the issue number in the title. -->

    #### References to other Issues or PRs
    <!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
    format, e.g. "Fixes #1234". See
    https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
    write a comment on that issue linking back to this pull request once it is
    open. -->

    closes #16278 
    closes #14505 

    #### Brief description of what is fixed or changed

    There is now more checking that well formed block matrix is being formed.
    When it is not a shaped-symmetric block matrix, a keyword (strict) and method (irregular) are provided for instantiating such matrices.

    Two small modifications adding `__mod__` and `diagonal` to matrices is also included.

    #### Other comments


    #### Release Notes

    <!-- Write the release notes for this release below. See
    https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
    on how to write release notes. The bot will check your release notes
    automatically to see if they are formatted correctly. -->

    <!-- BEGIN RELEASE NOTES -->
    - matrices
        - matrices comprised of blocks in an irregular are detected in BlockMatrix and can be handled with Matrix
        - Matrix creation involving matrices expands the matrices unless evaluate is False
    <!-- END RELEASE NOTES -->


</details><p>

smichr: - [ ] fix wester test
codecov[bot]: # [Codecov](https://codecov.io/gh/sympy/sympy/pull/16462?src=pr&el=h1) Report
> Merging [#16462](https://codecov.io/gh/sympy/sympy/pull/16462?src=pr&el=desc) into [master](https://codecov.io/gh/sympy/sympy/commit/d13eb25999fb8af72c87fded526f995f5c79cb3d?src=pr&el=desc) will **increase** coverage by `0.015%`.
> The diff coverage is `n/a`.

```diff
@@             Coverage Diff              @@
##           master    #16462       +/-   ##
============================================
+ Coverage   73.62%   73.636%   +0.015%     
============================================
  Files         618       618               
  Lines      158581    158581               
  Branches    37216     37216               
============================================
+ Hits       116748    116773       +25     
+ Misses      36419     36394       -25     
  Partials     5414      5414
```


Patch:
diff --git a/sympy/core/compatibility.py b/sympy/core/compatibility.py
index b419154c2e..f70193cc5a 100644
--- a/sympy/core/compatibility.py
+++ b/sympy/core/compatibility.py
@@ -238,16 +238,17 @@ def __new__(cls, name, this_bases, d):
 
 class NotIterable:
     """
-    Use this as mixin when creating a class which is not supposed to return
-    true when iterable() is called on its instances. I.e. avoid infinite loop
-    when calling e.g. list() on the instance
+    Use this as mixin when creating a class which is not supposed to
+    return true when iterable() is called on its instances because
+    calling list() on the instance, for example, would result in
+    an infinite loop.
     """
     pass
 
 def iterable(i, exclude=(string_types, dict, NotIterable)):
     """
     Return a boolean indicating whether ``i`` is SymPy iterable.
-    True also indicates that the iterator is finite, i.e. you e.g.
+    True also indicates that the iterator is finite, e.g. you can
     call list(...) on the instance.
 
     When SymPy is working with iterables, it is almost always assuming
diff --git a/sympy/matrices/expressions/blockmatrix.py b/sympy/matrices/expressions/blockmatrix.py
index 78ce643ee3..217fd3834e 100644
--- a/sympy/matrices/expressions/blockmatrix.py
+++ b/sympy/matrices/expressions/blockmatrix.py
@@ -6,6 +6,7 @@
 from sympy.strategies import typed, exhaust, condition, do_one, unpack
 from sympy.strategies.traverse import bottom_up
 from sympy.utilities import sift
+from sympy.utilities.misc import filldedent
 
 from sympy.matrices.expressions.matexpr import MatrixExpr, ZeroMatrix, Identity
 from sympy.matrices.expressions.matmul import MatMul
@@ -20,7 +21,7 @@
 from sympy.functions.elementary.complexes import re, im
 
 class BlockMatrix(MatrixExpr):
-    """A BlockMatrix is a Matrix composed of other smaller, submatrices
+    """A BlockMatrix is a Matrix comprised of other matrices.
 
     The submatrices are stored in a SymPy Matrix object but accessed as part of
     a Matrix Expression
@@ -44,12 +45,88 @@ class BlockMatrix(MatrixExpr):
     >>> print(block_collapse(C*B))
     Matrix([[X, Z + Z*Y]])
 
+    Some matrices might be comprised of rows of blocks with
+    the matrices in each row having the same height and the
+    rows all having the same total number of columns but
+    not having the same number of columns for each matrix
+    in each row. In this case, the matrix is not a block
+    matrix and should be instantiated by Matrix.
+
+    >>> from sympy import ones, Matrix
+    >>> dat = [
+    ... [ones(3,2), ones(3,3)*2],
+    ... [ones(2,3)*3, ones(2,2)*4]]
+    ...
+    >>> BlockMatrix(dat)
+    Traceback (most recent call last):
+    ...
+    ValueError:
+    Although this matrix is comprised of blocks, the blocks do not fill
+    the matrix in a size-symmetric fashion. To create a full matrix from
+    these arguments, pass them directly to Matrix.
+    >>> Matrix(dat)
+    Matrix([
+    [1, 1, 2, 2, 2],
+    [1, 1, 2, 2, 2],
+    [1, 1, 2, 2, 2],
+    [3, 3, 3, 4, 4],
+    [3, 3, 3, 4, 4]])
+
+    See Also
+    ========
+    sympy.matrices.matrices.MatrixBase.irregular
     """
-    def __new__(cls, *args):
+    def __new__(cls, *args, **kwargs):
         from sympy.matrices.immutable import ImmutableDenseMatrix
-        args = map(sympify, args)
-        mat = ImmutableDenseMatrix(*args)
-
+        from sympy.matrices import zeros
+        from sympy.matrices.matrices import MatrixBase
+        from sympy.utilities.iterables import is_sequence
+        isMat = lambda i: getattr(i, 'is_Matrix', False)
+        if len(args) != 1 or \
+                not is_sequence(args[0]) or \
+                len(set([isMat(r) for r in args[0]])) != 1:
+            raise ValueError(filldedent('''
+                expecting a sequence of 1 or more rows
+                containing Matrices.'''))
+        rows = args[0] if args else []
+        if not isMat(rows):
+            if rows and isMat(rows[0]):
+                rows = [rows]  # rows is not list of lists or []
+            # regularity check
+            # same number of matrices in each row
+            blocky = ok = len(set([len(r) for r in rows])) == 1
+            if ok:
+                # same number of rows for each matrix in a row
+                for r in rows:
+                    ok = len(set([i.rows for i in r])) == 1
+                    if not ok:
+                        break
+                blocky = ok
+                # same number of cols for each matrix in each col
+                for c in range(len(rows[0])):
+                    ok = len(set([rows[i][c].cols
+                        for i in range(len(rows))])) == 1
+                    if not ok:
+                        break
+            if not ok:
+                # same total cols in each row
+                ok = len(set([
+                    sum([i.cols for i in r]) for r in rows])) == 1
+                if blocky and ok:
+                    raise ValueError(filldedent('''
+                        Although this matrix is comprised of blocks,
+                        the blocks do not fill the matrix in a
+                        size-symmetric fashion. To create a full matrix
+                        from these arguments, pass them directly to
+                        Matrix.'''))
+                raise ValueError(filldedent('''
+                    When there are not the same number of rows in each
+                    row's matrices or there are not the same number of
+                    total columns in each row, the matrix is not a
+                    block matrix. If this matrix is known to consist of
+                    blocks fully filling a 2-D space then see
+                    Matrix.irregular.'''))
+        mat = ImmutableDenseMatrix(rows, evaluate=False)
         obj = Basic.__new__(cls, mat)
         return obj
 
@@ -195,12 +272,13 @@ def equals(self, other):
             return True
         return super(BlockMatrix, self).equals(other)
 
+
 class BlockDiagMatrix(BlockMatrix):
     """
     A BlockDiagMatrix is a BlockMatrix with matrices only along the diagonal
 
     >>> from sympy import MatrixSymbol, BlockDiagMatrix, symbols, Identity
-    >>> n,m,l = symbols('n m l')
+    >>> n, m, l = symbols('n m l')
     >>> X = MatrixSymbol('X', n, n)
     >>> Y = MatrixSymbol('Y', m ,m)
     >>> BlockDiagMatrix(X, Y)
@@ -208,6 +286,9 @@ class BlockDiagMatrix(BlockMatrix):
     [X, 0],
     [0, Y]])
 
+    See Also
+    ========
+    sympy.matrices.common.diag
     """
     def __new__(cls, *mats):
         return Basic.__new__(BlockDiagMatrix, *mats)
diff --git a/sympy/matrices/matrices.py b/sympy/matrices/matrices.py
index 1a95618494..9e39dc121f 100644
--- a/sympy/matrices/matrices.py
+++ b/sympy/matrices/matrices.py
@@ -2211,6 +2211,49 @@ def _format_str(self, printer=None):
             return "Matrix([%s])" % self.table(printer, rowsep=',\n')
         return "Matrix([\n%s])" % self.table(printer, rowsep=',\n')
 
+    @classmethod
+    def irregular(cls, ntop, *matrices, **kwargs):
+      """Return a matrix filled by the given matrices which
+      are listed in order of appearance from left to right, top to
+      bottom as they first appear in the matrix. They must fill the
+      matrix completely.
+
+      Examples
+      ========
+
+      >>> from sympy import ones, Matrix
+      >>> Matrix.irregular(3, ones(2,1), ones(3,3)*2, ones(2,2)*3,
+      ...   ones(1,1)*4, ones(2,2)*5, ones(1,2)*6, ones(1,2)*7)
+      Matrix([
+        [1, 2, 2, 2, 3, 3],
+        [1, 2, 2, 2, 3, 3],
+        [4, 2, 2, 2, 5, 5],
+        [6, 6, 7, 7, 5, 5]])
+      """
+      from sympy.core.compatibility import as_int
+      ntop = as_int(ntop)
+      # make sure we are working with explicit matrices
+      b = [i.as_explicit() if hasattr(i, 'as_explicit') else i
+          for i in matrices]
+      q = list(range(len(b)))
+      dat = [i.rows for i in b]
+      active = [q.pop(0) for _ in range(ntop)]
+      cols = sum([b[i].cols for i in active])
+      rows = []
+      while any(dat):
+          r = []
+          for a, j in enumerate(active):
+              r.extend(b[j][-dat[j], :])
+              dat[j] -= 1
+              if dat[j] == 0 and q:
+                  active[a] = q.pop(0)
+          if len(r) != cols:
+            raise ValueError(filldedent('''
+                Matrices provided do not appear to fill
+                the space completely.'''))
+          rows.append(r)
+      return cls._new(rows)
+
     @classmethod
     def _handle_creation_inputs(cls, *args, **kwargs):
         """Return the number of rows, cols and flat matrix elements.
@@ -2253,8 +2296,14 @@ def _handle_creation_inputs(cls, *args, **kwargs):
         [0,   0],
         [1, 1/2]])
 
+        See Also
+        ========
+        irregular - filling a matrix with irregular blocks
         """
         from sympy.matrices.sparse import SparseMatrix
+        from sympy.matrices.expressions.matexpr import MatrixSymbol
+        from sympy.matrices.expressions.blockmatrix import BlockMatrix
+        from sympy.utilities.iterables import reshape
 
         flat_list = None
 
@@ -2294,46 +2343,98 @@ def _handle_creation_inputs(cls, *args, **kwargs):
             # Matrix([1, 2, 3]) or Matrix([[1, 2], [3, 4]])
             elif is_sequence(args[0]) \
                     and not isinstance(args[0], DeferredVector):
-                in_mat = []
-                ncol = set()
-                for row in args[0]:
-                    if isinstance(row, MatrixBase):
-                        in_mat.extend(row.tolist())
-                        if row.cols or row.rows:  # only pay attention if it's not 0x0
-                            ncol.add(row.cols)
+                dat = list(args[0])
+                ismat = lambda i: isinstance(i, MatrixBase) and (
+                    evaluate or
+                    isinstance(i, BlockMatrix) or
+                    isinstance(i, MatrixSymbol))
+                raw = lambda i: is_sequence(i) and not ismat(i)
+                evaluate = kwargs.get('evaluate', True)
+                if evaluate:
+                    def do(x):
+                        # make Block and Symbol explicit
+                        if isinstance(x, (list, tuple)):
+                            return type(x)([do(i) for i in x])
+                        if isinstance(x, BlockMatrix) or \
+                                isinstance(x, MatrixSymbol) and \
+                                all(_.is_Integer for _ in x.shape):
+                            return x.as_explicit()
+                        return x
+                    dat = do(dat)
+
+                if dat == [] or dat == [[]]:
+                    rows = cols = 0
+                    flat_list = []
+                elif not any(raw(i) or ismat(i) for i in dat):
+                    # a column as a list of values
+                    flat_list = [cls._sympify(i) for i in dat]
+                    rows = len(flat_list)
+                    cols = 1 if rows else 0
+                elif evaluate and all(ismat(i) for i in dat):
+                    # a column as a list of matrices
+                    ncol = set(i.cols for i in dat if any(i.shape))
+                    if ncol:
+                        if len(ncol) != 1:
+                            raise ValueError('mismatched dimensions')
+                        flat_list = [_ for i in dat for r in i.tolist() for _ in r]
+                        cols = ncol.pop()
+                        rows = len(flat_list)//cols
                     else:
-                        in_mat.append(row)
-                        try:
-                            ncol.add(len(row))
-                        except TypeError:
-                            ncol.add(-1)
-                if len(ncol) > 1:
-                    nolen = False
-                    if -1 in ncol:
-                        ncol.remove(-1)
-                        nolen = True
-                    nums = ', '.join(sorted([str(i) for i in ncol]))
-                    if nolen:
-                        nums  = 'none, ' + nums
-                    raise ValueError(
-                        "Got rows of variable lengths: %s" % nums)
-                if -1 in ncol:
-                    ncol = [1]
-                cols = ncol.pop() if ncol else 0
-                rows = len(in_mat) if cols else 0
-                if not rows:
+                        rows = cols = 0
+                        flat_list = []
+                elif evaluate and any(ismat(i) for i in dat):
+                    ncol = set()
                     flat_list = []
+                    for i in dat:
+                        if ismat(i):
+                            flat_list.extend(
+                                [k for j in i.tolist() for k in j])
+                            if any(i.shape):
+                                ncol.add(i.cols)
+                        elif raw(i):
+                            if i:
+                                ncol.add(len(i))
+                                flat_list.extend(i)
+                        else:
+                            ncol.add(1)
+                            flat_list.append(i)
+                        if len(ncol) > 1:
+                            raise ValueError('mismatched dimensions')
+                    cols = ncol.pop()
+                    rows = len(flat_list)//cols
                 else:
-                    # how many of the rows are sequences? All or none are ok.
-                    nseq = sum([is_sequence(i) for i in in_mat])
-                    if not nseq:
-                        cols = 1
-                        flat_list = [cls._sympify(i) for i in in_mat]
-                    elif nseq == rows:
-                        flat_list = [cls._sympify(in_mat[j][i]) for
-                                     j in range(rows) for i in range(cols)]
-                    else:
-                        pass  # flat_list is still None; error raised below
+                    # list of lists; each sublist is a logical row
+                    # which might consist of many rows if the values in
+                    # the row are matrices
+                    flat_list = []
+                    ncol = set()
+                    rows = cols = 0
+                    for row in dat:
+                        if not is_sequence(row) and \
+                                not getattr(row, 'is_Matrix', False):
+                            raise ValueError('expecting list of lists')
+                        if not row:
+                            continue
+                        if evaluate and all(ismat(i) for i in row):
+                            r, c, flatT = cls._handle_creation_inputs(
+                                [i.T for i in row])
+                            T = reshape(flatT, [c])
+                            flat = [T[i][j] for j in range(c) for i in range(r)]
+                            r, c = c, r
+                        else:
+                            r = 1
+                            if getattr(row, 'is_Matrix', False):
+                                c = 1
+                                flat = [row]
+                            else:
+                                c = len(row)
+                                flat = [cls._sympify(i) for i in row]
+                        ncol.add(c)
+                        if len(ncol) > 1:
+                            raise ValueError('mismatched dimensions')
+                        flat_list.extend(flat)
+                        rows += r
+                    cols = ncol.pop() if ncol else 0
 
         elif len(args) == 3:
             rows = as_int(args[0])
diff --git a/sympy/printing/pretty/pretty.py b/sympy/printing/pretty/pretty.py
index 7a3de33522..cbdad8fd9d 100644
--- a/sympy/printing/pretty/pretty.py
+++ b/sympy/printing/pretty/pretty.py
@@ -1004,6 +1004,8 @@ def _print_NDimArray(self, expr):
 
         level_str = [[]] + [[] for i in range(expr.rank())]
         shape_ranges = [list(range(i)) for i in expr.shape]
+        # leave eventual matrix elements unflattened
+        mat = lambda x: ImmutableMatrix(x, evaluate=False)
         for outer_i in itertools.product(*shape_ranges):
             level_str[-1].append(expr[outer_i])
             even = True
@@ -1013,15 +1015,17 @@ def _print_NDimArray(self, expr):
                 if even:
                     level_str[back_outer_i].append(level_str[back_outer_i+1])
                 else:
-                    level_str[back_outer_i].append(ImmutableMatrix(level_str[back_outer_i+1]))
+                    level_str[back_outer_i].append(mat(
+                        level_str[back_outer_i+1]))
                     if len(level_str[back_outer_i + 1]) == 1:
-                        level_str[back_outer_i][-1] = ImmutableMatrix([[level_str[back_outer_i][-1]]])
+                        level_str[back_outer_i][-1] = mat(
+                            [[level_str[back_outer_i][-1]]])
                 even = not even
                 level_str[back_outer_i+1] = []
 
         out_expr = level_str[0][0]
         if expr.rank() % 2 == 1:
-            out_expr = ImmutableMatrix([out_expr])
+            out_expr = mat([out_expr])
 
         return self._print(out_expr)
 


Test patch:
diff --git a/sympy/matrices/expressions/tests/test_blockmatrix.py b/sympy/matrices/expressions/tests/test_blockmatrix.py
index e21567fa1e..bc162b2824 100644
--- a/sympy/matrices/expressions/tests/test_blockmatrix.py
+++ b/sympy/matrices/expressions/tests/test_blockmatrix.py
@@ -3,7 +3,7 @@
         bc_transpose, blockcut, reblock_2x2, deblock)
 from sympy.matrices.expressions import (MatrixSymbol, Identity,
         Inverse, trace, Transpose, det)
-from sympy.matrices import Matrix, ImmutableMatrix
+from sympy.matrices import Matrix, ImmutableMatrix, ones
 from sympy.core import Tuple, symbols, Expr
 from sympy.core.compatibility import range
 from sympy.functions import transpose
diff --git a/sympy/matrices/tests/test_matrices.py b/sympy/matrices/tests/test_matrices.py
index 990a04c08d..51f260c322 100644
--- a/sympy/matrices/tests/test_matrices.py
+++ b/sympy/matrices/tests/test_matrices.py
@@ -270,6 +270,9 @@ def test_creation():
         Matrix((1, 2))[3] = 5
 
     assert Matrix() == Matrix([]) == Matrix([[]]) == Matrix(0, 0, [])
+    # anything can go into a matrix (laplace_transform uses tuples)
+    assert Matrix([[[], ()]]).tolist() == [[[], ()]]
+    assert Matrix([[[], ()]]).T.tolist() == [[[]], [()]]
 
     a = Matrix([[x, 0], [0, 0]])
     m = a
@@ -287,13 +290,9 @@ def test_creation():
 
     assert Matrix(b) == b
 
-    c = Matrix((
-        Matrix((
-            (1, 2, 3),
-            (4, 5, 6)
-        )),
-        (7, 8, 9)
-    ))
+    c23 = Matrix(2, 3, range(1, 7))
+    c13 = Matrix(1, 3, range(7, 10))
+    c = Matrix([c23, c13])
     assert c.cols == 3
     assert c.rows == 3
     assert c[:] == [1, 2, 3, 4, 5, 6, 7, 8, 9]
@@ -305,6 +304,40 @@ def test_creation():
 
     assert c is not Matrix(c)
 
+    dat = [[ones(3,2), ones(3,3)*2], [ones(2,3)*3, ones(2,2)*4]]
+    M = Matrix(dat)
+    assert M == Matrix([
+        [1, 1, 2, 2, 2],
+        [1, 1, 2, 2, 2],
+        [1, 1, 2, 2, 2],
+        [3, 3, 3, 4, 4],
+        [3, 3, 3, 4, 4]])
+    assert M.tolist() != dat
+    # keep block form if evaluate=False
+    assert Matrix(dat, evaluate=False).tolist() == dat
+    A = MatrixSymbol("A", 2, 2)
+    dat = [ones(2), A]
+    assert Matrix(dat) == Matrix([
+    [      1,       1],
+    [      1,       1],
+    [A[0, 0], A[0, 1]],
+    [A[1, 0], A[1, 1]]])
+    assert Matrix(dat, evaluate=False).tolist() == [[i] for i in dat]
+
+    # 0-dim tolerance
+    assert Matrix([ones(2), ones(0)]) == Matrix([ones(2)])
+    raises(ValueError, lambda: Matrix([ones(2), ones(0, 3)]))
+    raises(ValueError, lambda: Matrix([ones(2), ones(3, 0)]))
+
+
+def test_irregular_block():
+    assert Matrix.irregular(3, ones(2,1), ones(3,3)*2, ones(2,2)*3,
+        ones(1,1)*4, ones(2,2)*5, ones(1,2)*6, ones(1,2)*7) == Matrix([
+        [1, 2, 2, 2, 3, 3],
+        [1, 2, 2, 2, 3, 3],
+        [4, 2, 2, 2, 5, 5],
+        [6, 6, 7, 7, 5, 5]])
+
 
 def test_tolist():
     lst = [[S.One, S.Half, x*y, S.Zero], [x, y, z, x**2], [y, -S.One, z*x, 3]]
@@ -2649,7 +2682,6 @@ def test_invertible_check():
     raises(ValueError, lambda: m.inv(method="LU"))
 
 
-@XFAIL
 def test_issue_3959():
     x, y = symbols('x, y')
     e = x*y
diff --git a/sympy/utilities/tests/test_wester.py b/sympy/utilities/tests/test_wester.py
index 38e2befa55..3333922693 100644
--- a/sympy/utilities/tests/test_wester.py
+++ b/sympy/utilities/tests/test_wester.py
@@ -1317,7 +1317,6 @@ def test_P2():
                         [7, 8]])
 
 
-@XFAIL
 def test_P3():
     A = Matrix([
         [11, 12, 13, 14],
@@ -1328,20 +1327,21 @@ def test_P3():
     A11 = A[0:3, 1:4]
     A12 = A[(0, 1, 3), (2, 0, 3)]
     A21 = A
-    A221 = A[0:2, 2:4]
-    A222 = A[(3, 0), (2, 1)]
-    A22 = BlockMatrix([A221, A222])
-    B = BlockMatrix([[A11, A12],
-                    [A21, A22]])
-    # B is a matrix consisting of several matrices
-    # https://github.com/sympy/sympy/issues/16278
-    assert B == Matrix([[12, 13, 14, 13, 11, 14],
-                        [22, 23, 24, 23, 21, 24],
-                        [32, 33, 34, 43, 41, 44],
-                        [11, 12, 13, 14, 13, 14],
-                        [21, 22, 23, 24, 23, 24],
-                        [31, 32, 33, 34, 43, 42],
-                        [41, 42, 43, 44, 13, 12]])
+    A221 = -A[0:2, 2:4]
+    A222 = -A[(3, 0), (2, 1)]
+    A22 = BlockMatrix([[A221, A222]]).T
+    rows = [[-A11, A12], [A21, A22]]
+    from sympy.utilities.pytest import raises
+    raises(ValueError, lambda: BlockMatrix(rows))
+    B = Matrix(rows)
+    assert B == Matrix([
+        [-12, -13, -14, 13, 11, 14],
+        [-22, -23, -24, 23, 21, 24],
+        [-32, -33, -34, 43, 41, 44],
+        [11, 12, 13, 14, -13, -23],
+        [21, 22, 23, 24, -14, -24],
+        [31, 32, 33, 34, -43, -13],
+        [41, 42, 43, 44, -42, -12]])
 
 
 @XFAIL