Rectangle Detection
Jelly, 11 10 bytes
ṣ⁷^2\⁺€FS¬
Massive thanks to @Dennis for golfing this one down to half its original size (via undocumented features).
Try it online! Note that the triple quotes are for a multiline string.
Explanation
The basic algorithm is: return true iff every 2x2 subgrid has an even number of 1s (or, equivalently, 0s).
It's clear why an odd number of 1s can't work, since we would have one of the following:
10 01 00 00 01 10 11 11
00 00 01 10 11 11 10 01
Note that the first 4 are rotations of the same thing, and ditto for the last 4. The reflex angle can't be part of a rectangle, hence why it would be invalid.
In other words, all 2x2 subgrids must be one of the following:
00 00 11 01 10 01 10 11
00 11 00 01 10 10 01 11
which, if we look at the boundaries, can be imagined as the following "puzzle pieces":
___ ___ ___ ___
| | | | | | | | | |
| | | | | |---| |-|-|
|___| |_|_| |___| |_|_|
And try forming a non-rectangle with those puzzle pieces :) (whilst having the ends match up)
The actual implementation is thus:
ṣ⁷ Split input by newlines to give rows
^2\ Taking overlapping sets of 2 rows at a time: accumulate rows by XOR
Note that strings cast to integers automatically for bitwise operators
⁺€ Repeat the previous link (⁺), on each (€) element in the resulting array
F Flatten the array
S Sum (effectively reducing by OR)
¬ Logical negation of the result
For example, for the input
100
010
000
101
we have:
ṣ⁷: ["100", "010", "000", "101"]
^2\: [[1, 1, 0], [0, 1, 0], [1, 0, 1]] (e.g. first entry is "100" ^ "010")
^2\€: [[0, 1], [1, 1], [1, 1]] (e.g. the first entry is [1^1, 1^0] - this
gives the counts of 1s in each subgrid, mod 2)
F: [0, 1, 1, 1, 1, 1]
S: 5 (this gives the number of invalid 2x2 subgrids,
which is indeed all but the top left)
¬: 0
Ruby, 76
->s{j=!r=1
s.lines{|t|i=t.to_i(2)
j&&r&&=(j^i)%t.tr(?0,?1).to_i(2)<1
j=i}
r}
In any grid composed entirely of rectangles, each line must be identical to the line before, or have all bits flipped from 0 to 1 and vice versa.
This is easy to prove. Take a piece of paper and draw arbitrary vertical and horizontal lines all the way across it. Now colour the rectangles using only 2 colours. You will end up with a distorted checkerboard, where all the colours flip at each line.
Want to draw rectangles with lines only part way across? try deleting a segment of any of your lines. You will now need more than 2 colours to colour your design, because you will have points where 3 rectangles meet (2 corners and an edge.) Such designs are therefore irrelevant to this question.
I'm surprised answers so far haven't noticed this.
I think this algorithm should be a lot shorter in some other language.
Ungolfed in test program
f=->s{
j=!r=1 #r = truthy, j=falsy
s.lines{|t| #for each line
i=t.to_i(2) #i = value of current line, converted to a number in base 2 (binary)
j&& #if j is truthy (i.e this is not the first line)
r&&=(j^i)%t.tr(?0,?1).to_i(2)<1 #XOR i with the previous line. Take the result modulo (current line with all 0 replaced by 1)
#if the result of the XOR was all 0 or all 1, the modulo == zero (<1). Otherwise, it will be a positive number.
j=i} #j = value of current line (always truthy in ruby, even if zero)
r} #return 1 or true if all the modulo calculations were zero, else false.
#text to print after test case to check answer is as desired
T='T
'
F='F
'
#test cases
puts f['0'],T
puts f['1'],T
puts f['00
'],T
puts f['01'],T
puts f['10'],T
puts f['11
'],T
puts f['0000000'],T
puts f['1111111'],T
puts f['011100100100101100110100100100101010100011100101'],T
puts f['00
11'],T
puts f['01
10'],T
puts f['01
11'],F
puts f['00
01'],F
puts f['11
11
'],T
puts f['110
100'],F
puts f['111
000'],T
puts f['111
101
111'],F
puts f['101
010
101
'],T
puts f['1101
0010
1101
0010'],T
puts f['1101
0010
1111
0010'],F
puts f['0011
0111
0100
'],F
puts f['0011
0011
1100'],T
puts f['00000000
01111110
00000000'],F
puts f['11111111
11111111
11111111'],T
puts f['0000001111
0000001111'],T
puts f['0000001111
0000011111'],F
puts f['0000001111
1000001111'],F
puts f['1000001111
1000001111'],T
puts f['1110100110101010110100010111011101000101111
1010100100101010100100010101010101100101000
1110100110010010110101010111010101010101011
1010100100101010010101010110010101001101001
1010110110101010110111110101011101000101111'],F
Jelly, 7 bytes
ṣ⁷µ=ḢZE
This uses the same algorithm as @LevelRiverSt's Ruby answer. The actual algorithm fits in the last 4 bytes; the first 3 bytes are required to parse the input format.
Try it online!
How it works
ṣ⁷µ=ḢZE Main link. Argument: t (string)
ṣ⁷ Split t at linefeeds..
µ Begin a new, monadic link. Argument: A (list of strings)
Ḣ Pop the first string of A.
= Compare all other strings in A with the first.
= compares characters, so this yields a list of Booleans for each string.
For a truthy input, all pairs of lines now have been transformed in lists
of only 1's or only 0's. That means all columns must be equal.
Z Zip; transpose rows with columns.
E Check if all rows (former columns) are equal to each other.