This document (still under development) is a companion to the CQL by Example document presented on the Scid++ project site. A reviewer of the mainline document suggested this one, observing that the difficulty in visualizing complex set operations is the most significant obstacle to getting one's head around CQL. Given the fundamental relationship between CQL and sets, the author found that argument compelling... and so here we are.
We assume that the reader is unschooled in set theory and cares even less about mathematics in general. But CQL's application is in the context of the game of chess, after all, and so we shall endeavor to instill an appreciation of the power and of the esthetic beauty inherent in the relationships between sets on the board. Operations on sets are at the very heart of CQL.
Set theory is a branch of mathematics that studies, in lay terms, the collection of objects. For our purposes, we are interested in the collection of squares on a chess board. The theory of sets begins with the binary relation between an object and a set; i.e., an object either is or is not a member of the set. The set is also an object and so we can have a relation (subset) between sets.
Just as there are operations defined on numbers (addition, subtraction), set theory defines certain operations on sets. And, as operations on numbers produce a number, operations on sets likewise produce a set. Of the set theoretic operations, CQL has implemented several:
Beyond CQL's set theoretic operators, many of CQL's filters operate on sets and yield a set as a result. We should note that any set operation may yield an empty or null set, which has no members.
Since visualization is the whole point of this document, chess diagrams will be found throughout. In addition, each of the following examples includes the CQL syntax that yields the collection of sets depicted in its respective diagram.
If uncertain about the set resulting from a set operation or a set filter, add the expression to a comment filter to get a readout of the elements of the set (see the subset example below). Also see the reference manual with regard to the message filter.
The examples in this section employ CQL attack filters to generate sets that serve as arguments for the set operations demonstrated. The attack fields are marked by colored circles and the set resulting from the operation is marked by a solid disc or square. In addition to the visual depiction of those sets on the board, a textual readout is given as it is rendered by the CQL comment filter.
The following compound filter matches the position of the diagram only because the attack field of the pawn is a subset of the attack field of the knight. The sets representing those fields are assigned to their respective variables. Note that we must stipulate that the Pawn set is not empty because an empty set is a subset of every set. Thus, if there is a white pawn on the board and the subset clause is true (Pawn in Knight), the operational set is assigned the set of squares at the intersection of the two fields (Pawn & Knight).
cql() Knight = . attackedby n Pawn = . attackedby P Pawn and Pawn in Knight and OpSet = Pawn & Knight comment (Knight " " Pawn " " OpSet)
In theory, theory and practice are the same. In practice, they are not.
Dissecting the expression, the primary set generating mechanism is the CQL sub-expresssion in parens — the nested direction filters — which effectively gives us the union of the sets generated by that filter for each white pawn on the board. The filter vertical 0 7 P gives us a set including every square on every file on which there is a white pawn. Thus, the whole of the filter nest horizontal 1 vertical 0 7 P gives us every square on every file adjacent to a white pawn. That set is depicted as the alternating colored files in the diagram.
Taking the complement of that set, we have the set of all squares that are not on a file adjacent to a white pawn. Finally, at the intersection of that complementary set and the set of all squares occupied by a white pawn, we have the isolated queen pawn on d4.
The isolated pawn matching the CQL expression in the diagrammed position just happens to be an IQP. To match only those positions in which the isolated pawn is an IQP, simply extend the CQL expression to P & ~(horizontal 1 vertical 0 7 P) & d1-8, taking the intersection of the set of all isolated white pawns with the set of all squares on the d-file.
The point of the CQL sub-expression ~move to . previous is to eliminate the piece just moved from consideration among the pieces that might place the king in check. In the position depicted, move to . previous gives us the square to which the last moving piece has moved, which is g8. The complement of that set eliminates g8 from consideration. The piece designator [QRB] (line pieces, of necessity) gives us the set of squares as highlighted in the diagram, and the intersection of that set with the complementary set ([QRB] & ~move to . previous) gives us the set of highlighted squares minus g8. If any of the pieces occupying the squares of that set attack the king, then we have our discovered check — in this case by the rook at h7. In fact, the attack filter returns a set with one member, namely [h7].
Note that the CQL expression will also match positions in which the king is in double check.
Note that the CQL expressions ([rbn] attackedby P) >= 3 and ([rbn] & (horizontal 1 up 1 P)) >= 3 give the same results. However, the expression (P attacks [rbn]) >= 3 does not necessarily give the same result as it gives (effectively) the number of white pawns attacking a minor piece rather than the number of minor pieces attacked by a white pawn. For example, one could have two pieces attacked by three pawns.
The direction filters yield the sets depicted in the diagram by the colored lines emanating from the black king. The anydirection filter is the equivalent of diagonal | orthogonal, the union of the two sets depicted. For each of the three in filters, the filter matches the position only because the set of every piece designated is a subset of its respective direction set.
The first filter of the compound filter sets a lower bound on the number of white line pieces on the board by stipulating that the cardinality of the set designated is greater than some number. The last filter of the compound stipulates that there is no obstructing white pawn sitting between the line pieces and the black king. Note that the between filter yields a subset of the union of the sets depicted in the diagram.
cql() [QRB] > 3 B in diagonal k R in orthogonal k Q in anydirection k not P & between([QRB] k)
The expression employs the set intersection operator four times to narrow down the matching criteria to a very specific position of interest. The sub-expression isolatedpawns & passedpawns gives us the set of highlighted pawns depicted in the diagram, each of them isolated and passed. Then the expression P & isolatedpawns & passedpawns gives us just the highlighted squares occupied by white pawns.
We get a blockading black knight — on the set A of squares in front of (up 1) the set of squares occupied by white pawns — at the intersection of the set A and the set of all squares occupied by black knights (n & (up 1 ((P & isolatedpawns & passedpawns)))). Finally, we confine the match to blockading knights on the d-file ( & d1-8).
We must first locate the knight on a square three down from the square occupied by the king (N & down 3 k) and then take the intersection of the set of empty squares in the knight's field of attack (_ attackedby (N & down 3 k)) and the set of empty squares in the king's field of attack (_ attackedby k). The former are depicted in the diagram in salmon and red, the latter in brown and red. The intersection is depicted in red.
Note that for the Anastasia mate pattern, both squares at the intersection must be empty. That criteria is satisfied by stipulating the cardinality of the set at exactly two. (See the example in the main document.)
We should slow down for a moment and expand on a point about the CQL expression above that might not be intuitive for many. That expression lays out a nest of operations on eight sets, every set of which must be non-empty for the position to match. The innermost clause (N & down 3 k) must yield a non-empty set the effect of which cascades all the way out to the outermost operation (the intersection of the two fields of attack), which in turn must yield a set with exactly two members in order for the expression to match a position. Thus, if there is no white knight on the board (never mind that it must be on a precise square relative to the black king), the whole of the expression fails.
The challenge with this generalized Boden is in determining that the bishops attack along lines that are orthogonal to one another. The key in making that determination is that, if the lines are orthogonal, one bishop's line in the king's diagonal field will have its square lie adjacent to two squares in the other's line, else only one square. Those squares must be empty and are depicted in the diagram in salmon and red.
The clauses with direction filters ((diagonal 1 k) & _) and ((orthogonal 1 k) & _) single out the empty squares in the king's field with their respective orientations. The attack filters stipulate that those squares are in the fields of attack of their respective bishops. Then (orthogonal 1 (((diagonal 1 k) & _) attackedby B)) gives us the squares e7,f6,f8,g7 in the diagram, and at the intersection of that set with (((orthogonal 1 k) & _) attackedby B) we have the set of squares depicted in salmon with cardinality of two. Thus, we know that the bishops' lines of attack are orthogonal to one another.
Note that the reversecolor transform swaps the white/black role and flips the board. The clause (k & a-h8) places the king on the back rank. It is the pawn directly in front of the king that we want to see removed from his defense, and (p & down 1 (k & a-h8)) matches that pawn. The pawn did, in fact, recapture the rook and mate soon followed.
The Countem function returns the union of three sets: 1) the set of pieces directly attacking the center pawn, 2) the set of orthogonal line pieces with an xray attack on the pawn, and 3) the set of diagonal line pieces with an xray attack on the pawn through a friendly pawn. Those sets are all highlighted in the diagram for their respective sides.
cql() function Countem(Pawn) { A attacks Pawn | ray orthogonal (Pawn [QRBN] [QR]) | ray diagonal (Pawn P [QB]) } piece OP in Pd-e4-5 (Countem(OP) > 4) > {reversecolor Countem(OP)}
The assignment to the Chained variable captures the set of all squares occupied by a white pawn that is supported by another pawn. The set of squares given by the clause (northeast 1 P | northwest 1 P) is depicted in the diagram in salmon and red, and represents all squares on the board attacked by a white pawn (yes, we could have used an attack filter instead). Then P & (northeast 1 P | northwest 1 P) gives us the intersection of that set with the set of all white pawns on the board, depicted in red.
The base pawns are identified as those white pawns not a member of the set of Chained pawns that support some member of the set of Chained pawns. The clause (P & ~Chained) satisfies the former condition and then (P & ~Chained) attacks Chained satisfies the whole. The base pawns are highlighted in brown in the diagram.
We iterate over each of the base pawns looking for a chain anchored to that base and having a minimum length. The assignment to the Chain variable (Chained & (northeast Base | northwest Base)) captures the set of all squares occupied by a white pawn that is a member of the Chained set and that is also on a diagonal emanating from the base pawn. So long as there is nothing other than white pawns between the base pawn and every other pawn that is a member of Chain (not ((~P) & between(Base Chain))), we have our contiguous chain.
The chain depicted in the diagram with base at b2 has length of four, excluding the base pawn, the rook pawn having just moved to a3.
cql() Chained = P & (northeast 1 P | northwest 1 P) piece Base in (P & ~Chained) attacks Chained { Chain = Chained & (northeast Base | northwest Base) not ((~P) & between(Base Chain)) Chain > 3 }
We're looking for a sequence of positions whose overlap produces an intersection of two lines, one of which has been interposed such that the other may not be interposed. We employ the ray filter to determine that three critical squares are arranged along a line in the terminal position. The first is the square occupied by a defending queen which might have interposed an attack on her king. The second is the square occupied by an interfering bishop. And the third is the square on which the defending queen might have interposed the attack but for the interfering piece.
We acquire the square occupied by the attacking piece in the terminal position with the expression Mater =? a attacks K. Then the expression position positionid-2:{between(Mater K) attackedby Q} gives us the square on which the defending piece might have interposed (depicted in salmon), looking back two positions. Finally, we have the square occupied by the interfering piece with the expression parent:{move to . from ~K previous}.
This document is Copyright (c) 2019-2024 Lionel Hampton. The Chess Query Language was originally developed by Gady Costeff and Lewis Stiller. The upstream Scid vs PC project is managed by Steven Atkinson.
The diagrams appearing in this document were created with the Scid vs PC application's board setup dialog and comment editor. The piece set is included with that application and is credited on the project's site.