Today's guest article is by Wesley Hamilton, a STEM Outreach engineer here at MathWorks. Wesley's roles involve popularizing the use of MATLAB and Simulink for younger folk, especially towards... read more >>

]]>Sudoku is a popular puzzle in which puzzlers fill in a 9x9 grid with the digits 1-9 such that a number of conditions are met: each 3x3 subgrid, row, and column must contain all digits. While Sudoku is usually referred to as a logic puzzle, it is actually possible to recast these puzzles as a (mathematical) constrainted optimization problem, wherein the optimal solution gets interpreted as the inputs for a completed Sudoku puzzle. What's crazy about this formulation though is that the function we optimize will be the zero function, so that any input is optimal; all of the legwork is in satisfying the optimization problem's constraints. Interested? Read on to learn more!

The presentation and code in this blog are adapted from a workshop developed by Dr. Tim Chartier, based on code developed in collaboration with Dr. Amy Langville of the College of Charleston and her students.

Table of Contents

Rules and History of Sudoku

Solving Sudoku Puzzles, part 1

Linear Programming

Sudoku as a Linear Program

Box constraints

Row constraints

Column constraints

Subgrid constraints

Solving Sudoku Puzzles, part 2

Ordering of decision variables

Box constraints

Row constraints

Column constraints

Subgrid constraints

The givens

Further directions

Just the code

Solving Sudoku Puzzles, part 1

Linear Programming

Sudoku as a Linear Program

Box constraints

Row constraints

Column constraints

Subgrid constraints

Solving Sudoku Puzzles, part 2

Ordering of decision variables

Box constraints

Row constraints

Column constraints

Subgrid constraints

The givens

Further directions

Just the code

The classic version of Sudoku most people are familiar with consists of a 9x9 grid divided into nine 3x3 subgrids. The grid will start with some boxes filled in with the digits 1-9, and the goal of the puzzle is to fill in each box with one of the 9 digits in a way that the following three conditions are satisfied:

- Every row of the grid contains each of the digits 1-9, and each digit only appears once,
- Every column of the grid contains each of the digits 1-9, and each digit only appears once,
- Every 3x3 subgrid contains each of the digits 1-9, and each digit only appears once.

Here is an example of a 9x9 Sudoku puzzle, retrieved from websudoku.com on May 3, 2023.

One variation of the puzzle is the 4x4 version, in which a 4x4 grid is split into four 2x2 subgrids and the three conditions are modified so that only the digits 1-4 are used. This version is often used as an easier version than the 9x9, and makes visualizing what happens in the computational side of things a bit more tractable as well.

Here is an example of a 4x4 Sudoku puzzle, generated by Dr. Tim Chartier, MoMath's 2022–2023 Distinguished Visiting Professor for the Public Dissemination of Mathematics.

Sudoku was popularized in Japan in the late 1980s, before grabbing worldwide attention in the early 2000s. Unbeknownst to many, very early versions of Sudoku appeared in France in the late 1800s as puzzle-variations of magic squares; see the Wikipedia page and its references for more information.

The next section walks through the basics of solving Sudoku puzzles. If you're already very familiar with Sudoku, feel free to skip down to the text right after the completed 4x4 puzzle.

So how does one solve a Sudoku puzzle? In practice, we iteratively apply the three conditions to identify which numbers go in which boxes until all boxes are filled in. In harder puzzles we may be forced to make a choice between two equally likely entries; in these situations we'll often try writing in one of the options and seeing what other numbers can get filled in based on that, and then either run into a contradiction, or solve the board.

Let's see this process in action with Tim's 4x4 puzzle. For clarity, the three conditions we'll be iteratively checking are:

- Every row of the grid contains each of the digits 1-4, and each digit only appears once,
- Every column of the grid contains each of the digits 1-4, and each digit only appears once,
- Every 2x2 subgrid contains each of the digits 1-4, and each digit only appears once.

First we'll focus on the upper-right 2x2 subgrid and identify where we may be able to fill in a number, in either the red circle or the blue square.

Applying condition 3., we know that the digits in these empty grid boxes have to be 2 and 3. So which digit goes where? By condition 2., each column of the grid can only contain each digit exactly once, and the fourth column (containing the blue square) already contains a 2, so it is not possible to write the digit 2 in the blue square (similarly, by condition 1. the row with the blue square already has the digit 2 somewhere in the row). Thus, the digit 2 goes in the square with the red circle.

Applying condition 3. to the same subgrid, we conclude the remaining grid box must contain the digit 3.

Continuing in this way we'll eventually be able to fill in every box; for example, the next move might be writing in the digit 4 in the only remaining box in the second row. Feel free to try solving this 4x4 on your own, and check your solution against ours:

Awesome! But what if we want to have MATLAB solve the puzzle for us? An initial approach might be to code (in some way) these three constraints and have our computer iterate through each row, column, and subgrid writing in digits one-by-one. What happens if there are two digits that might work in a grid box? Are there other logical steps that might occur in a harder puzzle that we can't anticipate?

Instead of taking this approach, we'll take a very different, and possibly cleverer, approach making use of some deep and powerful computational mathematics known as Linear Programming.

Linear programming is, simply put, an optimization problem in which the objective function and constraints are all linear. Let's break down what each of these pieces mean:

- an optimization problem is a problem in which one seeks the maximum (or minimum) of an objective function, i.e. find a value for x that maximizes a given function $ f(x) $;
- an objective function is the function we want to maximize or minimize. We could just call it a function, but often in optimization problems multiple functions come in to play, including functions that add constraints to the problem, in addition to the function we're optimizing;
- a linear objective function is an objective function that is linear, i.e. takes the form $ f(x) = a_1 x_1 + \cdots + a_n x_n $, for a vector $ x = \pmatrix{x_1 \cr \vdots \cr x_n} $. Writing $ a = \pmatrix{a_1 \cr \vdots \cr a_n} $, this function can also be expressed $ f(x) = a^t x $, where the t superscript means we're taking the matrix transpose of a vector.
- a constraint is a rule for which values of x are feasible, i.e. x must be non-negative, or if x is a vector then all of the components of x must be non-negative;
- a linear constraint is a constraint that's written in the form of a linear equation. For example, if we're working with a vector $ x = \pmatrix{x_1 \cr x_2 \cr x_3} $, then a linear constraint might look like $ x_1 - x_2 + 3 x_3 \leq 0 $. If we write $ b=\pmatrix{1 \cr -1 \cr 3} $, then this linear constraint can also be written $ b^t x \leq 0 $, where the t superscript means we're taking the matrix transpose of a vector.

Linear constraints can actually come in two forms: linear inequalities ($ Ax\leq b $) and linear equalities ($ A_{eq} x = b_{eq} $). Both kinds of constraints The kinds of linear programs we'll work with here can all be expressed in the following form:

find a vector x that maximizes $ f(x) = a^t x $ subject to $ Ax \leq b $, $ A_{eq} x = b_{eq} $, and $ x\geq 0 $,

where $ a, b, b_{eq} $ are given vectors, and $ A, A_{eq} $ are given matrices encoding the linear constraints. Note that if $ x = \pmatrix{x_1 \cr \vdots \cr x_n} $, then the notation $ x\geq 0 $ means $ x_i \geq 0 $ for $ i=1, ..., n $. The components $ x_i $ of the vector x are often called decision variables, since these are the values the linear program needs to decide, or find values for.

In this post we'll be using MATLAB's intlinprog function, which you can treat as a black box linear program solver that finds integer solutions to a linear program. This solver automatically selects which algorithm to use and works to find a solution, so the only work we have to do is write down the linear program in a way it can understand. We'll do that next.

Keep in mind that we're using a matrix multiplication formulation to express the constraints. For background on matrix multiplication and basic linear algebra concepts, check out our interactive courseware module on Matrix Methods of Linear Algebra.

To set up a Sudoku puzzle as a linear program, we need to identify a few things:

- What are the variables that we're solving for?
- What are the constraints we want to impose?
- What is the function we want to maximize?

Why not identify the function before the constraints? Well, turns out we don't actually care what the function is, which will be clear by the end of this section.

So what should the variables be? For a 4x4 grid we have 16 total boxes to fill in, and an initial approach might be to use variables $ x_1, ..., x_{16} $ where each $ x_i $ should take a value between 1 and 4. While reasonable, there's actually a computationally easier approach.

Instead of using the variables to tell us which digit is in each box, let's use variables to tell us whether or not a digit is in a box. By this we mean using variables $ x_{ijk} $, where the pair $ (i,j) $ tells us which box we're looking at, and k specifies which digit we care about. If $ x_{ijk} = 0 $, then digit k should not be written in box $ (i,j) $, while if $ x_{ijk} = 1 $, then digit k belongs in box $ (i,j) $.

As an example, consider the 4x4 grid we considered earlier (with some of the box indeces added in the top-left corners for clarity):

To number the grid boxes, $ (1,1) $ will be the top-left box, $ (2,1) $ will be the box beneath it, and $ (1,2) $ will be the box to the right of $ (1,1) $. In other words, the first index i in $ (i,j) $ counts how many rows down we go, and the index j counts how many columns to the right we go.

So with this notation, we have the digit 4 in the $ (1,4) $ box, the digit 1 in the $ (2,3) $ box, and the digit 2 in the $ (3,4) $ box at the start (among others). In particular, the digits $ 1,2,3 $ do not appear in the $ (1,4) $ box. Using our variables to encode this information, we'd have $ x_{1,4,4}=1 $ and $ x_{1,4,1} = x_{1,4,2} = x_{1,4,3} = 0 $. At the start of the puzzle we did not know which digit went in the $ (1,3) $ box, so at the start $ x_{1,3,k} $ is not specified for $ k=1,...,4 $. Using the rules of Sudoku, we identified that a 2 belongs in that box, so by using the constraints we concluded that $ x_{1,3,2} = 1 $ and $ x_{1,3,1}=x_{1,3,3} = x_{1,3,4} = 0 $. Now that we have our variables specified, we're in a position to specify the constraints.

So how do we encode the Sudoku constraints into this framework? Keep in mind there are three constraints from the original puzzle which we'll need to encode in turn. There's actually a fourth constraint we need to encode, as discussed just above, in which each box can only have a single digit written in it.

The first constraint we need to encode is that each box can only have one digit written in it. So, for the $ (i,j) $ box, as soon as the digit 1 is written in it (for example), we get $ x_{i,j,1} = 1 $ and $ x_{i,j,2} = x_{i,j,3} = x_{i,j,4} = 0 $. The trick here is that each variable should take the value of either 0 or 1 (and nothing else), so if we sum them up and set the sum equal to 1, that ensures only one of the variables can take the value 1. Written out explicitly, for each of the boxes $ (i,j) $ we require:

$ x_{i,j,1} + x_{i,j,2} + x_{i,j,3} + x_{i,j,4} = 1 $.

The first of the puzzle constraints is that each row of the grid has each of the digits, and that each digit only appears once. Since we're looking at the rows, we're going to be focusing on sets of variables $ x_{i,1,k}, x_{i,2,k}, x_{i,3,k}, x_{i,4,k} $ for $ i = 1,...,4 $ and $ k=1,...,4 $. The trick here is the same as above, in that each variable should take the value of either 0 or 1 (and nothing else), so if we sum them up then, in a solved puzzle, we should get exactly 1:

$ x_{i,1,k} + x_{i,2,k} + x_{i,3,k} + x_{i,4,k} = 1 $.

As soon as one of the $ x_{i,j,k} $ takes the value 1, the rest are forced to be 0. This ensures that, in the ith row, the digit k only appears once. This constraint should hold for every possible row i and possible digit k.

The column constraint is set up exactly the same way as the row constraint, except here we're summing over the row indeces instead of the column indeces:

$ x_{1,j,k} + x_{2,j,k} + x_{3,j,k} + x_{4,j,k} = 1 $,

and this equality should hold for every possible column j and digit k.

The last of the Sudoku puzzle constraints to implement is the subgrid rule, in which each of the four digits is present in each of the 2x2 subgrids. As a specific example to guide our work, consider the subgrid in the top-left consisting of the boxes $ (1,1), (1,2), (2,1), (2,2) $. The subgrid constraint specifies that each digit k appears just once among these four boxes, so we'll actually have four constraints for this subgrid:

$ x_{1,1,k} + x_{1,2,k} + x_{2,1,k} + x_{2,2,k} = 1 $, $ k=1,...,4 $.

There are four of these subgrids though, so we also need to incorporate those constraints:

$ x_{1,3,k} + x_{1,4,k} + x_{2,3,k} + x_{2,4,k} = 1 $, $ k=1,...,4 $,

$ x_{3,1,k} + x_{3,2,k} + x_{4,1,k} + x_{4,2,k} = 1 $, $ k=1,...,4 $,

$ x_{3,3,k} + x_{3,4,k} + x_{4,3,k} + x_{4,4,k} = 1 $, $ k=1,...,4 $.

That's a lot! Good thing we don't have to work with all of these equations by hand and have MATLAB to do all the heavy lifting for us!

The last thing we need to do is specify what our objective function is, the thing we're actually minimizing. The thing is, though, we've already encoded everything we want to keep track of through the variables and constraints, so there isn't any need for a specific function. This means that, for our purposes, we're free to set the function to be $ f(x) = 0 $. In this case, any vector x maximizes the function, so all of the work goes into finding a vector x that satisfies the constraints. If this still seems weird, you're not alone; if it helps, you're welcome to just have faith that we'll get a solution using this weird function, that's perfectly fine.

To summarize our work in this section, we're solving the following linear program:

find a vector $ x = \pmatrix{x_{1,1,1}\cr x_{1,1,2}\cr \vdots \cr x_{4,4,4}} $ that maximizes the function $ f(x) = 0 $, subject to the constraints

- $ x_{i,j,1} + x_{i,j,2} + x_{i,j,3} + x_{i,j,4} = 1 $ for $ i = 1,...,4 $ and $ j=1,...,4 $, (box constraint)
- $ x_{i,1,k} + x_{i,2,k} + x_{i,3,k} + x_{i,4,k} = 1 $ for $ i = 1,...,4 $ and $ k=1,...,4 $, (row constraint)
- $ x_{1,j,k} + x_{2,j,k} + x_{3,j,k} + x_{4,j,k} = 1 $ for $ j = 1,...,4 $ and $ k=1,...,4 $, (column constraint)
- $ x_{1,1,k} + x_{1,2,k} + x_{2,1,k} + x_{2,2,k} = 1 $ for $ k=1,...,4 $, (top-left subgrid constraint)
- $ x_{1,3,k} + x_{1,4,k} + x_{2,3,k} + x_{2,4,k} = 1 $ for $ k=1,...,4 $, (top-right subgrid constraint)
- $ x_{3,1,k} + x_{3,2,k} + x_{4,1,k} + x_{4,2,k} = 1 $ for $ k=1,...,4 $, (bottom-left subgrid constraint)
- $ x_{3,3,k} + x_{3,4,k} + x_{4,3,k} + x_{4,4,k} = 1 $ for $ k=1,...,4 $. (bottom-right subgrid constraint)

Note that all of this work was just for the 4x4 version of Sudoku; how would we modify everything for the original 9x9 version? Take a minute to think about it, and then read on for a solution... Afterwards we'll implement everything in MATLAB and actually solve some puzzles.

For the 9x9 version, the straightforward changes are that the sums should include 9 terms everywhere, and the indeces $ i,j,k $ should each range from 1 to 9 everywhere. The more-involved modification is that there will be nine subgrid constraints instead of four, since the 9x9 grid contains nine 3x3 subgrids. The top-left 3x3 subgrid will have the constraint

$ x_{1,1,k} + x_{1,2,k} + x_{1,3,k} + x_{2,1,k} + x_{2,2,k} + x_{2,3,k} + x_{3,1,k} + x_{3,2,k} + x_{3,3,k} = 1 $, for $ k = 1, ..., 9 $.

The other subgrid constraints can be written out in a similar (slightly cumbersome) way.

In this section, we'll encode the constraints we described above and actually solve a Sudoku puzzle using linear programming.

There's one other set of constraints that we need to impose, that are independent of our framework and the rules of Sudoku. Remember that a Sudoku puzzle starts with some of the grid boxes filled in, which we can specify by a matrix G specifying the row number, column number, and digit of each of the initial entries. As a concrete example, for our 4x4 example we have $ G = \pmatrix{1 & 4 & 4 \cr 2 & 1 & 2\cr 2 & 3 & 1\cr 3 & 1 & 3 \cr 3 & 4 & 2} $; the first row is $ (1\, 4\, 4) $ since we start with the digit 4 in the first row and fourth column, etc.

The first thing to do is specify the size of the Sudoku grid where working with:

n=4; % for 4 by 4 sudoku problem

%n=9; % for 9 by 9 sudoku problem

Keep in mind that the vector of variables, specifying which digits appear where in the grid, will have $ n^3 $ entries: $ n\times n $ entries corresponding to each box in the grid, and another n for the n possible digits. One thing we need to be careful about is what order our decision variables appear in our vector. Three (plus one) reasonable options are:

- Order the variables by incrementing the digit index first, then the column index, then the row index: $ x_{1,1,1}, x_{1,1,2}, ..., x_{1,1,4}, x_{1,2,1}, x_{1,2,2}, ... $ This enumeration will list all the digit design variables first before going to a new box.
- Order the variables by incrementing the row index first, then the column index, then the digit index: $ x_{1,1,1}, x_{2,1,1}, ..., x_{4,1,1}, x_{1,2,1}, x_{2,2,1}, ... $ This enumeration will iterate through the entire grid by going down columns then across rows, before increment the digit index and iterating through again.
- Order the variables by incrementing the column index first, then the row index, then the digit index: $ x_{1,1,1}, x_{1,2,1}, ..., x_{1,4,1}, x_{2,1,1}, x_{2,2,1}, ... $ This is similar to the previous possible enumeration, except we traverse the grid along rows first, then along columns, then along digits.
- Order the variables randomly.

The fourth option isn't actually reasonable, since you'll have significantly more bookkeeping to manage (it is, technically, possible to implement though!). Here we'll take option two, so that every set of $ n^2 $ components of the decision vector x correspond to the same digit enumerated along columns and then rows.

Namely, for our implementation here, x(1) tells us whether or not digit 1 is in the $ (1,1) $ box, x(n^2+1) tells us if the digit 2 is in the $ (1,1) $ box, etc. Likewise, x(2) tells us if the digit 1 is in the $ (2,1) $ box, x(3) tells us if the digit 1 is in the $ (3,1) $ box, x(n^2+2) tells us if the digit 2 is in the $ (2,1) $ box, etc.

Extra Challenge: implement this Sudoku solver using the other two (reasonable) methods of ordering the design variables. Extra bonus points for implementing the random ordering approach.

Next let's encode the constraints as a matrix $ A_{eq} $, so that in our linear program we'll have $ A_{eq} x = b $ as our constraint. The matrix $ A_{eq} $ should be a $ C\times n^3 $ matrix, where C is the number of constraints. The vector b will be a vector of 1s, being the right-hand side of each of our constraints.

What is C in this case? The constraints we identified are:

- for each of the $ n^2 $ grid boxes we have a single constraint, totalling $ n^2 $ constraints,
- for each of the n rows, and each of the n digits, we have a constraint, totalling $ n^2 $ constraints,
- for each of the n columns, and each of the n digits, we have a constraint, totalling $ n^2 $ constraints,
- for each of the n subgrids, we have n constraints telling us each digit must appear in that subgrid, totalling $ n^2 $ constraints,
- we start with $ |G| = \# $of rows of G entries already filled in, totalling $ |G| $ constraints.

Summing these numbers up, we get $ C = 4n^2 + G $. In our approach, we'll define $ A_{eq} $ with $ C = 4n^2 $ first and fill in the first four constraints in the above bulleted list. Once finished, we'll add $ |G| $ more rows to $ A_{eq} $ and incorporate the givens. So let's start by initializing $ A_{eq} $:

Aeq=zeros(4*n^2, n^3);

Here's a sketch of the matrix we'll be filling in during the rest of this section:

At the top we've indicated how the decision variables are ordered: the first $ n^2 $ columns correspond to whether or not digit 1 is written in the boxes $ (1,1), (2,1), ..., (n,n) $, the next $ n^2 $ columns correspond to digit 2, and so on, exactly as we specified when we decided to use option 2 for ordering above. The rows are split based on the constraints we're encoding as well as the given entries of the initial Sudoku puzzle.

In the next few subsections we'll iterate through this matrix and write-in each of constraints. The subsections showcase what the matrix will look like (i.e. where the 0's and 1's go), before displaying the MATLAB code that accomplishes this (with plenty of comments).

%% Fill in Aeq for the n^2 constraints requiring that each element

% of the matrix must contain an integer 1:n

for i=1:n % for each row (of the Sudoku grid)

for j=1:n % for each column (of the Sudoku grid)

for k=1:n % increment through each digit

% the (i,j) grid box is associated to row (i-1)*n+j of A_eq

% incrementing the digit k comes with n^2 entries along the array

% though the box index (i,j) stays the same as we incorporate all

% digits

Aeq((i-1)*n+j,(i-1)*n+j + (k-1)*n^2)=1;

end

end

end

% if we had ordered the design variables so that we increment through

% digits first, we could replace this code with

%

% for i=1:n^2

% Aeq(i,(i-1)*n+1:i*n)=ones(1,n);

% end

%% Fill in Aeq for the n^2 constraints requiring that each row

% can have only 1 of each integer 1:n

for k=1:n % for each digit

for i=1:n % fix the row of the Sudoku grid

for j=1:n % increment the column indeces we're summing up

% note that we've already added the n^2 box constraints

% so updating A_eq starts at the n^2 row

Aeq(n^2+i+(k-1)*n,1+(i-1)+(j-1)*n+(k-1)*n^2)=1;

end

end

end

% if we had ordered the design variables so that we increment through

% rows first, we could replace this code with

%

% for i=1:n^2

% Aeq(n^2+i,(i-1)*n+1:i*n)=ones(1,n);

% end

%% Fill in Aeq for the n^2 constraints requiring that each column

% can have only 1 of each integer 1:n

for i=1:n^2 % quick trick since the column constraints correspond to rows of adjacent 1s

% we've added the n^2 box constraints and n^2 row constraints

% so these constraints start on row 2*n^2

% since our variable indexing goes along columns first,

% when incorporating these constraints

Aeq(2*n^2 + i,(i-1)*n+1:i*n)=ones(1,n);

end

%% Fill in Aeq for the n^2 constraints requiring that each submatrix

% can have only 1 of each integer 1:n

% note that n is a square, so sqrt(n) is an integer

% m and l are indeces for the subgrids - m=1, l=1 is the top-left subgrid

for m=1:sqrt(n)

for l=1:sqrt(n) % for the (m,l) subgrid

for j=1:n

for k=1:sqrt(n)

% added n^2 box, n^2 row, and n^2 column constraints

% the subgrid constraints should be added starting at row 3*n^2

Aeq(3*n^2+(m-1)*sqrt(n)*n+(l-1)*n+j,(j-1)*n^2+(l-1)*sqrt(n)+...

(m-1)*sqrt(n)*n+(k-1)*n+1:...

(j-1)*n^2+(l-1)*sqrt(n)+(m-1)*sqrt(n)*n+(k-1)*n+sqrt(n))=1;

end

end

end

end

Next, we need to incorporate the information that some of the entries are already filled in. We'll do this by specifying the givens in the form of the matrix mentioned at the start of this section, and then writing this information into $ A_{eq} $ in newly added rows below the information we've already incorporated.

For this example, we'll use the same matrix G for our favourite 4x4 example:

% If any values of the Sudoku matrix are given, add these as

% constraints

% Enter each given value in matrix as triplet (row, col, integer)

% Example for 4-by-4 sudoku

Givens=[1 4 4;

2 1 2;

2 3 1;

3 1 3;

3 4 2];

With this matrix specified, let's now append rows to $ A_{eq} $ and incorporate these constraints specifying our given digits:

% turn these given elements into their appropriate position in the x vector

% of decision variables.

g=size(Givens,1);

for i=1:g % for each of the givens

% we've added in 4*n^2 constraints already, so the first of the givens

% should be added at row 4*n^2 + 1, etc.

% the column entry to write the given constraint is at

% rowId + colId*n + digitId*n^2

% based on how we've organized the decision variables

Aeq(4*n^2+i,Givens(i,1)+(Givens(i,2)-1)*n+(Givens(i,3)-1)*n^2)=1;

end

This concludes writing out $ A_{eq} $. The final matrix should resemble this sketch:

Note the 1 at the matrix element corresponding to the given $ (2,1,2) $.

Since all of the constraints are set-up to equal 1, the vector b in $ A_{eq}x = b $ should be a vector of 1s:

beq=ones(4*n^2+g,1);

With the variables and constraints in place, we're almost ready to have MATLAB generate our solution. Looking at the documentation for intlinprog, we need to explicitly specify some other pieces:

- intcon, or the variables in x that should be integers. We want all of the components of x to be integers, so this should be the array 1:numVariables,
- numVariables, or the length of the variable x ($ n^3 $ in this case),
- lb, the vector of lower bounds for each of the variables. Since each $ x_{ijk} $ is 0 or 1, lb will be a vector of 0s,
- ub, the vector of upper bounds for each of the variables. Since each $ x_{ijk} $ is 0 or 1, ub will be a vector of 1s.

Let's go ahead and specify each of these variables:

%% run matlab's "intlinprog" command

numVariables = size(Aeq,2);

intcon = 1:numVariables;

lb = zeros(1,numVariables);

ub = ones(1,numVariables);

With everything in place, let's generate a solution to our 4x4 puzzle.

[x,fval,exitflag,output]=intlinprog(zeros(n^3,1),intcon,[],[],Aeq,beq,lb,ub);

% note the two arguments of []

% these inputs correspond to inequality constraints

% since we're not using inequality constraints, we leave these inputs empty

exitflag;

output;

x

For our 4x4 example, x is a vector with $ 4^3 = 64 $ entries, each of which is 0 or 1. While this is a solution to the linear program, it's not yet helpful for our purposes; what would be nice is if we could convert this vector into a grid of digits 1 to 4, corresponding to a Sudoku solution. Let's do that now and see the result:

%% turn matlab's outputed solution vector x into sudoku terms

S=zeros(n,n); %initialize the grid of solutions

for k=1:n % for each digit

% find the entries in the solutions vector x corresponding to digit k

% that have a value of 1 (i.e., the digit should be written in that box)

subx=find(x((k-1)*n^2+1:k*n^2));

for j=1:n % loop through

row=mod(subx(j),n);

if row==0

row=n;

end

S(row,j)=k;

end

end

% Sudoku Matrix S

S

Compare this to the solution we generated by hand earlier, and see that MATLAB succeeded in finding the same solution!

Next up is to try this with your own puzzles. The code has been copied below in one section for your convenience, with the sample 9x9 puzzle shown at the start encoded in the given matrix. Check that MATLAB can solve that as well, and try your own!

Interested in exploring some other interesting questions related to this post? Here are some further directions to get you started:

- What happens if we don't add any givens to the linear program, i.e. we start with an empty Sudoku board? We should end up with a solution - is it unique? How might we interpret this solution? (The solver may take a bit longer to find the solution, but it will find a solution.)
- How might you impose other constraints to transform the Sudoku puzzle into something possibly more difficult? Two options might be: (a) requiring that each diagonal of the Sudoku grid contains each of the digits 1-9 once and only once, and (b) requiring that the middle boxes of each subgrid form their own subgrid requiring each of the digits 1-9 once and only once.
- Pick another variant of Sudoku and implement those constraints, such as if the subgrids are shaped differently.

%n=4; % for 4 by 4 sudoku problem

n=9; % for 9 by 9 sudoku problem

Aeq=zeros(4*n^2, n^3);

%% Fill in Aeq for the n^2 constraints requiring that each element

% of the matrix must contain an integer 1:n

for i=1:n

for j=1:n

for k=1:n

Aeq((i-1)*n+j,(i-1)*n+j + (k-1)*n^2)=1;

end

end

end

%% Fill in Aeq for the n^2 constraints requiring that each row

% can have only 1 of each integer 1:n

for k=1:n

for i=1:n

for j=1:n

Aeq(n^2+i+(k-1)*n,1+(i-1)+(j-1)*n+(k-1)*n^2)=1;

end

end

end

%% Fill in Aeq for the n^2 constraints requiring that each column

% can have only 1 of each integer 1:n

for i=1:n^2

Aeq(2*n^2+i,(i-1)*n+1:i*n)=ones(1,n);

end

%% Fill in Aeq for the n^2 constraints requiring that each submatrix

% can have only 1 of each integer 1:n

for m=1:sqrt(n)

for l=1:sqrt(n)

for j=1:n

for k=1:sqrt(n)

Aeq(3*n^2+(m-1)*sqrt(n)*n+(l-1)*n+j,(j-1)*n^2+(l-1)*sqrt(n)+...

(m-1)*sqrt(n)*n+(k-1)*n+1:...

(j-1)*n^2+(l-1)*sqrt(n)+(m-1)*sqrt(n)*n+(k-1)*n+sqrt(n))=1;

end

end

end

end

% If any values of the Sudoku matrix are given, add these as

% constraints

% Enter each given value in matrix as triplet (row, col, integer)

% Example for 9-by-9 sudoku

Givens=[1 1 4;

1 2 9;

1 6 5;

1 9 1;

2 2 5;

2 7 7;

2 8 2;

2 9 9;

3 3 2;

3 6 8;

3 8 4;

3 9 5;

4 1 2;

4 3 8;

4 5 4;

4 6 3;

5 2 7;

5 4 8;

5 6 6;

5 8 3;

6 4 5;

6 5 9;

6 7 1;

6 9 8;

7 1 7;

7 2 8;

7 4 6;

7 7 4;

8 1 3;

8 2 4;

8 3 9;

8 8 6;

9 1 5;

9 4 3;

9 8 1;

9 9 7];

% turn these given elements into their appropriate position in the x vector

% of decision variables.

g=size(Givens,1);

for i=1:g

Aeq(4*n^2+i,Givens(i,1)+(Givens(i,2)-1)*n+(Givens(i,3)-1)*n^2)=1;

end

% beq=rhs vector (3n^2 by 1) for equality contraints

beq=ones(4*n^2+g,1);

%% run matlab's "intlinprog" command

numVariables = size(Aeq,2);

intcon = 1:numVariables;

lb = zeros(1,numVariables);

ub = ones(1,numVariables);

[x,fval,exitflag,output]=intlinprog(zeros(n^3,1),intcon,[],[],Aeq,beq,lb,ub);

exitflag;

output;

%% turn matlab's outputed solution vector x into sudoku terms

S=zeros(n,n);

for k=1:n

subx=find(x((k-1)*n^2+1:k*n^2));

for j=1:n

row=mod(subx(j),n);

if row==0

row=n;

end

S(row,j)=k;

end

end

% Sudoku Matrix S

S

Earlier this year I wrote about solving word ladders with MATLAB. There was a lot of interest in that post, so I thought I'd share my investigations regarding another word-based app. In this script,... read more >>

]]>Earlier this year I wrote about solving word ladders with MATLAB. There was a lot of interest in that post, so I thought I'd share my investigations regarding another word-based app. In this script, I'm trying to create a puzzle modeled on the NY Times Spelling Bee game. Here is the premise: you are given seven letters, and your job is to find all possible words you can make with those seven letters. You can use letters more than once, but they must be four or more letters long, and they must all include the special center letter. Every Spelling Bee puzzle has at least one "pangram", which is a word that uses all seven letters.

As an example, suppose you were given this puzzle.

From this puzzle you could create PORE, ADORE, ADOPT, as well as the pangram OPERATED. But you couldn't create POD (too short) or PAPER (doesn't include the center letter "O").

Here I'm not so much interested in solving the puzzle. I want to write a script that can create a Spelling Bee puzzle.

We need some ground truth for all the legal words. Here's Google's 10,000 word English dictionary. It's small, but good enough for our purposes here.

url = 'https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english.txt';

wordList = webread(url);

Split the word list into strings.

words = split(string(wordList));

words = lower(words);

The Spelling Bee puzzle avoids the letter S so that it never has to deal with plurals. Let's use logical indexing to keep only the words that don't contain an S.

keep = ~words.contains("s");

words = words(keep);

Of these, keep only the words that are four letters or longer.

keep = words.strlength >= 4;

words = words(keep);

I like how these string commands are easy to read and understand! Just like for us humans, code that communicates clearly keeps its job the longest. We don't want to employ fussy, high-maintenance code.

How many words do we end up with?

fprintf("The adjusted dictionary now contains %d words\n",length(words))

We've thrown out about half the words in our dictionary.

We'll be converting from ASCII representation to alphabet number a few times, so let's invest in a little anonymous helper function. It'll make the code below a little easier to read.

% An anonymous function to translate ASCII values to A=1, B=2, ... Z=26.

ascii2letter = @(chars) abs(lower(chars))-96;

Try it out

ascii2letter('ABCXYZ')

Looks good!

Build a 26-column sparse matrix to represent unique letter usage for each word. Each word in the dictionary gets one row. Every letter that appears in the word gets a 1.

dictionaryLength = numel(words);

a = sparse(dictionaryLength,26);

for i = 1:dictionaryLength

uniqueLetters = unique(words(i).char);

a(i,ascii2letter(uniqueLetters)) = 1;

end

nnz(a)/numel(a)

At 22% full, this matrix isn't terribly sparse as sparse matrices go. But it's a fun excuse to play with SPARSE, so let's carry on. Here's a SPY plot of the matrix.

spy(a)

set(gca, ...

XTick=1:26, ...

XTickLabel=num2cell('A':'Z'), ...

XTickLabelRotation=0, ...

XAxisLocation="top", ...

PlotBoxAspectRatio=[1 1 1])

To make much out of this, we need to zoom in. Let's see how the word LABEL is encoded.

ix = 1002;

% In case you're wondering, I like to use "ix" as short for "index"

words(ix)

full(a(ix,:))

spy(a(1001:1010,:))

xline([1 2 5 12])

yline(2,Color="red")

set(gca, ...

XTick=1:26, ...

XTickLabel=num2cell('A':'Z'), ...

XTickLabelRotation=0, ...

XAxisLocation="top", ...

YTick=1:10, ...

YTickLabels=words(1001:1010))

Note that even though the L appears twice in the word, it only has a one in the matrix.

Just for fun, let's do a histogram of the letters. This can be a useful sanity check that we're on the right track.

allLetters = ascii2letter(char(join(words,'')));

histogram(allLetters)

set(gca, ...

XTick=1:26, ...

XTickLabel=num2cell('A':'Z'), ...

XTickLabelRotation=0)

As expected, E is the most commonly appearing letter. Q is the least, but for the fact that we completely wiped out S.

How many words are composed of exactly seven distinct letters? That is, how many are potential pangrams for a game?

% Summing letter-use across the rows

% Use FIND to build an index to all the words that use exactly seven letters

ix7LetterWords = find(sum(a,2)==7);

length(ix7LetterWords)

Let's look at a few of these seven-letter words.

num7LetterWords = numel(ix7LetterWords);

ix = ix7LetterWords(1:10);

disp(words(ix(1:10)))

Pick a good one as the "seed" pangram for our game.

ix = ix(9);

disp(words(ix))

How many words can be spelled from those same 7 letters? Find all the words that use nothing but the letters in the seed word

lettersInSeedWord = a(ix,:);

% Find all the words that have none of the letters from the seed word.

% We're making sure that all contributions of letters other than

% the seed letters sum to zero.

ixWordsFromSeedWord = find(sum(a(:,~lettersInSeedWord),2) == 0);

fprintf('%d words use only the seven letters appearing in the seed word.\n',numel(ixWordsFromSeedWord))

Which letters appear most frequently?

bar(sum(a(ixWordsFromSeedWord,:)))

set(gca, ...

XTick=1:26, ...

XTickLabel=num2cell('A':'Z'), ...

XTickLabelRotation=0)

We need to designate one letter as the "center letter" that must appear in every word. Let's pick the letter that appears least frequently.

vals = full(sum(a(ixWordsFromSeedWord,:)));

vals(vals==0) = inf;

[~,ixlet] = min(vals);

centerLetter = char(ixlet+96);

Remove all the words that don't have this letter

ixRequired = find(a(:,ixlet));

ix2a = intersect(ixWordsFromSeedWord,ixRequired);

This is the original seed word:

fprintf("%s\n", words(ix))

Here are the seven unique letters:

sevenLetters = unique(char(words(ix)));

fprintf("%s\n", sevenLetters);

The required center letter is:

fprintf("%s\n", upper(centerLetter));

Here are the pangrams

ix3 = find(sum(a(ix2a,:),2)==7);

for i = 1:numel(ix3)

fprintf("%s\n",words(ix2a(ix3(i))));

end

Here is the complete list of words

for i = 1:numel(ix2a)

fprintf("%s\n",words(ix2a(i)));

end

Some of those are little odd, but we have our dictionary to thank for that.

sixLetters = setdiff(sevenLetters,centerLetter);

sixLetters = sixLetters(randperm(6));

Now for the graphics! Make the plot

% The perimeter hex cells are centered from pi/6 to 11*pi/6 radians

t = 2*pi*(0:5)/6 + 2*pi/12;

r = 1.08*sqrt(3);

x = r*cos(t);

y = r*sin(t);

cla

hexplot(0,0,upper(centerLetter),"#F7DA21")

hold on

for i = 1:numel(x)

hexplot(x(i),y(i),upper(sixLetters(i)),"#E6E6E6")

end

hold off

axis equal

axis off

And there you have it! It's easy to see how valuable a good algorithmically generated game can be. Once you've worked out the logic, you just need to run it once every day and your puzzle is ready to go. Compare this with the labor that goes into a crossword puzzle. Although who knows? Maybe crossword puzzles will be yet another of those things that AI proves to be adept at.

function hexplot(x,y,letter,color)

t = linspace(0,2*pi,7);

patch(cos(t)+x,sin(t)+y,"white", ...

EdgeColor="none", ...

FaceColor=color)

text(x,y,letter, ...

FontSize=24, ...

FontWeight="bold", ...

HorizontalAlignment="center", ...

VerticalAlignment="middle")

end

Today's guest article is by Adam Danz whose name you might recognize from the MATLAB Central community. Adam is a developer on the MATLAB Graphics and Charting team. About 20 years ago Adam... read more >>

]]>Today's guest article is by Adam Danz whose name you might recognize from the MATLAB Central community. Adam is a developer on the MATLAB Graphics and Charting team. About 20 years ago Adam memorized pi to a measly 200 decimal places which is 1/50 of the digits he'll visualize today using MATLAB.

Today is pi day, the most popular irrational number at any party, transcendental to all number systems, with an infinite number of decimal places.

But how many decimal places are needed? NASA's Jet Propulsion Laboratory uses 15 decimal places of pi for interplanetary navigation. With 15 decimals of pi, the earth's circumference can be calculated from its 12756 km (7926 mi) diameter with an error the size of a molecule. The figure below shows the increase in precision (decrease in error) of calculating the earth's circumference as more digits of pi are included in the calculation c=πd.

.

The value of pi in MATLAB, like most other technical computing environments, loses precision after the 15th decimal place due to limitations of floating point arithmetic. To get more than 15 decimal places of pi we can set variable precision that converts symbolic expressions to floating-point numbers, provided by the Symbolic Math Toolbox.

Since we're being irrational, let's get 10,000 decimal places of pi.

% Requires the Symbolic Math toolbox

symExpression = sym(pi); % Symbolic expression; e.g. sym(pi), exp(sym(1)), sym(1/23)

nDecimalPlaces = 10000; % Number of decimal places

decimalChar = char(vpa(mod(abs(symExpression),1),nDecimalPlaces+3))

To use each decimal digit independently, convert the character array to a numeric vector.

decvec = decimalChar(3:end-3) - '0'

10,000 decimals of pi are efficiently visualized as a single Patch object. Each digit is assigned an angular coordinate evenly distributed within 10 wedges, one wedge for each value 0 to 9. Lines segments connect adjacent digits starting with 1-to-4, 4-to-1, 1-to-5, 5-to-9, 9-to-2 and so on, moving slightly counterclockwise within each wedge. Each line segment is colored according to its length.

% Assign each digit an angular coordinate based on its value 0:9

nDecimals = numel(decvec);

theta = ((0:36:324)+(0:36/nDecimals:36)')*pi/180;

ang = theta(sub2ind(size(theta),1:nDecimals,decvec+1));

% Compute the length of each line segment; used to set color

[x,y] = pol2cart(ang,1);

[~,~,d] = uniquetol(hypot(diff(x),diff(y)));

alpha = min(0.85,max(0.006, 1000/nDecimals));

% Plot line segments using the edge property of a single Patch object

fig = figure(Color='k');

fig.Position(3:4) = 600;

movegui(fig)

ax = axes(fig);

p = patch(ax,XData=x,YData=y,FaceColor='none',EdgeAlpha=alpha);

set(p,'FaceVertexCData', [d;nan], 'EdgeColor','Flat')

colormap(ax,jet(10))

axis equal off

% Add pi character

text(0,0.05,'\pi', ...

HorizontalAlignment='Center', ...

FontUnits='normalized', ...

FontSize = 0.2, ...

Color = 'k');

% wedge lines

th = linspace(0,2*pi,1000);

gaps = 0 : pi/5 : 2*pi;

isgap = any(abs(th-gaps') < pi/120);

th(isgap) = nan;

radius = 1.08;

[segx,segy] = pol2cart(th,radius);

hold on

plot(segx,segy,'-',LineWidth=1,Color=[.8 .8 .8])

% wedge labels

midAng = gaps(1:end-1) + pi/10;

tradius = radius + .08;

[tx,ty] = pol2cart(midAng,tradius);

text(tx, ty, string(0:9), ...,

FontUnits='normalized',...

FontSize=0.05, ...

Color=[.8 .8 .8], ...

HorizontalAlignment='center',...

VerticalAlignment='middle');

To animate the visualization, each line segment was plotted individually and a frame was captured every 200 iterations using getframe, written to a gif file using imwrite with a delay of 0.2 sec.

We can use this visualization to investigate patterns in several approximations of pi by replacing the symExpression variable with sym(22/7), sym(333/106), sym(355/113), and sym(103993)/sym(33102).

- Use the vector of pi digits, decvec, to create a different visual representation of pi.
- Visualize different symbolic expressions by replacing the value in symExpression.
- What would digits of e look like? exp(sym(1))
- How about √2 ? sqrt(sym(2))
- Check out sym(1/9973), sym(1/23), sym(1/42), sym(1/49), sym(1/31), sym(1/61), sym(2/11)

I'd love to see your creations in the comments below!

]]>This cartoon sums up my feelings about testing. Testing is great. When you have working tests, you feel clean and warm. The sun shines a little brighter and the birds sing a song just for you. But... read more >>

]]>Testing is great. When you have working tests, you feel clean and warm. The sun shines a little brighter and the birds sing a song just for you. But jeez, getting those tests up and working is such a pain. Writing tests is a nuisance, and running tests consistently (and capturing the results) is tedious. If only we could decrease that hassle while retaining the value, what a world it would be!

Hold that thought, my friends, because I want to show you something glorious. A bright new world where the birds sing a song just for you. If you have MATLAB code in a GitHub repo (and if you don't, I'll wait five minutes for you to put some there), then it's almost trivial to set up automated MATLAB testing. No fiddling with licenses and installs. It just works, and I can practically guarantee that it's easier than you think it is.

For this, I'll be showing you the tests I set up for my Animator repo on GitHub (and linked to from the File Exchange). Animator makes it easy to create little animations like the one shown here. In case you're wondering, you're looking at the three types of trochoid curves here: the cycloid, the curtate trochoid, and the prolate trochoid. But that's not what I came here to talk about.

The Animator tool builds animations by reaching into the Editor and manipulating the script that makes the image. So I have some finicky file parsing code that I'd like to protect with some tests.

There are some very fancy ways to set up tests, but I'm going to tell you the absolute easiest rock-bottom way to get started. Let's imagine you have a function called times2.m. It multiplies the input by 2. Here it is.

function y = times2(x) % Return the input multiplied by two. y = 2*x; end

Nice, eh? Let's say that's your whole toolbox, right there. Make a script called testTimes2.m and put this code in it.

testTimes2.m --------- %% Test 1 assert(isequal(times2(3),6)) %% Test 2 assert(isequal(times2(6),12))

The assert command is a good friend to have. You just give it an expression, and if it evaluates as true, it's completely silent. But if it's false, it throws an error and fails the test.

Now you go to your GitHub repo and create a new Actions workflow. You'll see a number of suggested items, but choose the option at the top of the page: "Set up a workflow yourself."

This will open up a web editor for the file main.yml. Stick the following text in there.

name: Run Tests on GitHub-Hosted Runner on: [push] jobs: my-job: name: Run MATLAB Tests and Generate Artifacts runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v3 - name: Set up MATLAB uses: matlab-actions/setup-matlab@v1 - name: Run tests and generate artifacts uses: matlab-actions/run-tests@v1

You can learn more about this code from the documentation on the matlab-actions repo. The bottom line is that the Continuous Integration team at MathWorks created some actions that automatically setup and run MATLAB tests. The code shown above is generic. You don't need to edit it. It will look around for any files that either begin or end with the magic sentinel string "test" (case insensitive). My Animator repo has a file called smokeTest.m that matches this criterion. I also have some MATLAB unit tests (in animatorTests.m), which you don't need to worry about for now. But those are automatically detected as well.

If we return to our imaginary times2 repo, once you save main.yml, every time you push any file to the repo, testTimes2.m will run, and those two assertions will make sure times2 is doing the right thing. And you'll always be testing on the latest version of MATLAB on the latest version of Ubuntu. Niiice.

One last thing. If you've turned on testing, you should tell the world. You can do that with a badge in your repo's README.md. Once again, it could hardly be easier. Stick this text at the top of your README. Naturally, you'll need to substitute the name and address of your own repo.

[![Animator CI/CD](https://github.com/gulley/Animator/actions/workflows/main.yml/badge.svg)] (https://github.com/gulley/Animator/actions/workflows/main.yml)

With zero configuration, this text will not only let your visitors know that you have tests. It will tell them whether the most recent test passed or failed.

That's all there is to it!

1. Make a simple test.

2. Create an action workflow with the text provided above

3. OPTIONAL. Add a badge for your README with the text provided above.

There are, of course, lots more bells and whistles to this process. As I said, you're getting the bargain basement version of testing. Once you get it working, you'll probably develop a taste for more sophisticated testing: MATLAB unit tests, project-defined testing files, filters that will only test certain kinds of changes. But that can wait.

For now, just enjoy that clean, warm feeling.

]]>MATLAB Mobile allows you to collect information from your device sensors and perform cool experiments with the acquired data. For this post, I would like to welcome Luiz Zaniolo, our development... read more >>

]]>MATLAB Mobile allows you to collect information from your device sensors and perform cool experiments with the acquired data. For this post, I would like to welcome Luiz Zaniolo, our development manager to talk about one such experiment.

While I wrote this blog post, all credit for the experiment must go to my son, Giancarlo, a Carnegie Mellon University student (and adventure sports enthusiast) who wanted to perform some tests with his BMX bicycle.

His objective: determine how much time the bike spent in the air in a jump.

His means: he used an interesting technique to attach his cell phone to the bike and turned on the accelerometer in MATLAB Mobile, started logging data, and proceed to ride the bicycle and jump over a small ramp.

The log file was automatically uploaded to MATLAB Drive after the data collection ended.

Quick primer for those who are not aware: MATLAB Mobile can acquire data from your device sensors. You can analyze the data directly in MATLAB Mobile, MATLAB Online or on a MATLAB session on your desktop. You can even log sensor data when you are offline.

To start analyzing the data, my son logged in to MATLAB Online on his laptop, loaded the .mat file and plotted the acceleration results in the X, Y, and Z axes.

load jumpKink.mat

xAcceleration = Acceleration.X;

yAcceleration = Acceleration.Y;

zAcceleration = Acceleration.Z;

rawTimestamp = Acceleration.Timestamp;

timestamp = rawTimestamp - rawTimestamp(1);

allAxis = [xAcceleration, yAcceleration, zAcceleration];

figure; plot(timestamp, allAxis)

title('X, Y, and Z acceleration values')

xlabel('Time')

ylabel('Acceleration (m/s^2)')

legend('X', 'Y', 'Z')

Since the measurement axis on a smartphone is defined as shown in the picture below, the most appropriate axis to measure gravity acceleration is Y.

Plotting the acceleration on the Y axes gives us more clarity on the 3 different states: stopped, riding, and jumping.

figure; plot(timestamp, yAcceleration)

title('Y acceleration values')

xlabel('Time')

ylabel('Acceleration (m/s^2)')

legend('Y')

By differentiating the acceleration file, he was able to identify the jerk (rate of change of acceleration), which should be small when the bike is either stopped or in the air.

difYAcceleration = diff(yAcceleration);

timestampdif = timestamp;

timestampdif(end)=[]; % remove last element to match with difYAcceleration array

figure; plot(timestampdif, difYAcceleration)

title('Differential of Y')

xlabel('Time')

ylabel('Jerk (m/s^3)')

legend('Y')

He then applied his algorithm to detect when the bicycle was in the air. The algorithm implements a low-pass filter in the differential signal by averaging the time measurement with 2 neighbors on each side and outputs an array of the current state in time.

% mode 0: stopped

% mode 1: riding

% mode 2: in air

modeArray = zeros(length(yAcceleration), 1);

filteredDifArray = zeros(length(difYAcceleration), 1);

modeArray(1) = 0;

airCounter = 0;

threshold = 16;

for i = 3 : (length(difYAcceleration) - 2)

sumOfPoints = abs(difYAcceleration(i-2)) + abs(difYAcceleration(i-1)) + ...

abs(difYAcceleration(i)) + abs(difYAcceleration(i+1)) + abs(difYAcceleration(i+2));

filteredDifArray(i) = sumOfPoints / 5;

% if it passes the threshold (moving on bumpy enough ground)

if sumOfPoints > threshold

% if in "air" for less than 0.25 secs before jump lands, read over all "air"s with 1s

if airCounter < 25

for j = i : -1 : (i - airCounter)

modeArray(j) = 1;

end

end

modeArray(i) = 1;

airCounter = 0;

elseif sumOfPoints < threshold

% if it doesnt pass the threshold (either still or in air)

if modeArray(i - 1) == 0 || length(yAcceleration) - i < 300

%if its stopped it stays stopped

modeArray(i) = 0;

%if its still for too long it counts it as stopped, reads over all "air"s with 0s

elseif airCounter >= 300

for j = i: -1: (i - airCounter)

modeArray(j) = 0;

end

else

modeArray(i) = 2;

end

airCounter = airCounter + 1;

end

end

Finally, plotting the mode array on top of the Y acceleration shows the detections of different modes: stopped, riding, and jumping.

modeArray = modeArray .* 20; % amplify to show better in graph scale

figure; plot(timestamp, yAcceleration)

hold on

plot(timestamp, modeArray, 'r')

hold off

title('Jump Detection Algorithm')

xlabel('Time')

ylabel('Acceleration (m/s^2)')

legend('Y', 'modeArray')

And since he had the mode array, he counted how many samples were reported as "in air" to display the total air time of 0.44 seconds.

numOfAirSamples = sum (modeArray == 40);

airTime = numOfAirSamples * 0.01 % the acquisition rate was 100 samples per second

Even though this is a simple example of signal processing, my son described it as something that he enjoyed doing. The intermediate plots helped him understand better what needed to be done to accomplish his task. Although if he's planning to execute more reckless jumps to improve his air time, his father needs to have a talk with him.

Have you used your phone to collect sensor data for experiments like this? If you did, we would love to learn more. Please share them with us by adding a comment below.

]]>MATLAB is good at math, of course, but it can also let you have some fun with words. Go to the Gaming > Word Games category of the File Exchange and you can find a variety of different word... read more >>

]]>MATLAB is good at math, of course, but it can also let you have some fun with words. Go to the Gaming > Word Games category of the File Exchange and you can find a variety of different word games.

I like laddergrams, and since I couldn't find any on the File Exchange, I thought I'd have some fun and write about them here. Laddergrams (also called Word Ladders) are a word game invented by Lewis Carroll in which you change one word into another by means of intermediate words that differ by exactly one character. He introduced the puzzle with this word pair: HEAD to TAIL.

So the challenge is this: how can you transform HEAD to TAIL one letter at a time? Here is his answer from 1879.

Can we write some MATLAB code that will find a solution to this problem?

This problem gives us a chance to play with the graph object in MATLAB. As is frequently the case, once you set up the problem correctly, it's a breeze to calculate laddergrams for any pair of words. We'll start by reading a list of English words into a string.

url = "https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english.txt";

wordList = string(webread(url));

words = wordList.splitlines;

Extract all the words that are exactly 4 letters long.

keep = words.matches(lettersPattern(4));

words = words(keep);

numWords = length(words)

Let's alphabetize the list.

words = sort(words);

We want to build a graph that contains all these four-letter words. Each node is a word, and each edge connects two words that can be "bridged" in one laddergram step. Let's build the adjacency matrix.

Two words are connected by an edge if they differ by one and only one letter location. So HEAD and HEAL are connected. HEAD and HEED are connected. But HEAD and HERE are not, since you need to change two letters to get from one to the next.

adj = zeros(numWords);

for i = 1:numWords

for j = i:numWords

% Three letters must be exact matches

if sum(char(words(i))==char(words(j)))==3

% The matrix is symmetric (the graph is undirected), so we

% touch two locations in the adjacency matrix.

adj(i,j) = 1;

adj(j,i) = 1;

end

end

end

spy(adj)

The adjacency matrix has some fascinating structure! By marking the break between first letters, we can see what's going on a little better. We'll use the diff command to see where the first letter of each word changes.

ix = find(diff(char(words.extract(1)))~=0);

for i = 1:length(ix)

xline(ix(i))

yline(ix(i))

end

title("Word Connectivity Adjacency Plot")

Around index 800, you can see a very narrow strip that corresponds to Q. Only three 4-letter words in this dictionary start with Q: QUAD, QUIT, and QUIZ

Let's throw the alphabet on the plot's Y axis to make this more clear.

alphabet = ('a':'z')';

set(gca,YTick=ix, ...

YTickLabel=cellstr(alphabet))

Make the graph object.

g = graph(adj,words);

Calculate the distances between all the words. This is where the magic happens. This one command is incredible: effectively distances is solving every single potential word ladder. That's the beauty of tapping into the excellent libraries that are just waiting to be used in MATLAB. Any code I would come up with to solve this problem would take a long time to write and longer to run. Instead, BOOM! It's all done.

d = distances(g);

Some word pairs are relatively inaccessible, so they show up as infinitely far apart.

[word1ix,word2ix] = find(d==Inf);

Here are two words you will never be able to connect via laddergram, at least with this dictionary.

i = 10;

words([word1ix(i) word2ix(i)])

Or, as they say in Maine: You can't get there from here.

Let's zero out all those infinite edges so they don't confuse the rest of our calculations.

d(d==Inf) = 0;

And now a histogram to look at the numbers.

maxLadderLen = max(d(:))

histogram(d(:),1:maxLadderLen)

xlabel("Length of the Laddergram")

ylabel("Number of Word Pairs")

Six is the most common number of intermediate steps.

What are the longest possible word ladders?

[word1ix,word2ix] = find(d==max(d(:)));

length(word1ix)

i = 9;

p = shortestpath(g,word1ix(i),word2ix(i));

for j = 1:length(p)

fprintf("%s\n",words(p(j)))

end

There is a multi-way tie for longest laddergram (20 steps), but this champion word pair goes from TOWN to DRUM. As always, everything depends on the dictionary. A different dictionary can give vastly different results.

Finally, we can solve the problem that Lewis Carroll posed back in 1879.

p = shortestpath(g,"head","tail")'

Our words are different, but even with MATLAB on our side, we can't do better than Carroll did almost 150 years ago. But in the bargain, we've solved every single laddergram that can be represented in this dictionary.

numLaddergrams = nnz(d)/2

All 405,591 of them! I just love graph algorithms.

]]>Seasonal greetings of the Greater Solstice Metropolitan Area! As I write this, the solstitial sun sets on the shortest day of my northern hemispheric year. Winter is here. So naturally, my mind... read more >>

]]>Seasonal greetings of the Greater Solstice Metropolitan Area! As I write this, the solstitial sun sets on the shortest day of my northern hemispheric year. Winter is here. So naturally, my mind turns to cookies.

The inspiration for this winter daydream comes from some code written by Eric Ludlam. Eric, in addition to being in charge of the MATLAB graphics team, is an inveterate tinkerer. He loves making fun graphics code. You may remember some of his pumpkin-inspired creations that I described in this space last year. Or you may recall his many entries from the latest Mini Hack (and the one before that).

He's back, this time with code for seasonal cookies and snowflakes. If you want to look at his code, you can find it in his Digital Art with MATLAB repo. In fact, let's go look at it now.

We can start by cloning our own copy in MATLAB. You can do this directly from the Current Folder Browser. Select "Manage Files..." from the Source Control context menu.

Enter the Repository Path (https://github.com/zappo2/digital-art-with-matlab), and the files come right down.

Eric made some cookie code using blobby implicit surfaces as described by Jim Blinn. We can use Eric's template as cookie-cutter (heh) to stamp out other designs. Shown below is the code from gingerbreadperson.m, only I've substituted my own design at the top: a candy cane.

% Create a gingerbread person cookie

K = [' xxx '

' xxxxx '

' xxx xxx '

' xxx xxx '

'xxx xxx '

'xxx xxx '

'xxx xxx '

' xxx '

' xxx '

' xxx '

' xxx '

' xxx '

' xxx '];

% Convert K into array of blob centers

[y,x]=find(~isspace(K));

% Scale and offset centers into 3D cookie space

C=[x*5,y*5]+5; C(:,3)=10;

% Compute a volume using a short implementation of Blinn's blobs

nx=size(K,2)*5+10; ny=size(K,1)*5+10; nz=20; % Size of volume

[y,x,z]=ndgrid(1:ny,1:nx,1:nz);

vol=zeros(ny,nx,nz);

for i=1:size(C,1)

vol=vol+exp(-.05*((C(i,1)-x).^2 + (C(i,2)-y).^2 + (C(i,3)-z).^2));

end

% Compute isosurface from the blobs

S=isosurface(vol,.3);

% Draw the isosurface using patch

newplot

patch(S,'FaceColor','#a56c3c','EdgeColor','none'); % cookie

axis equal ij off

lighting gouraud

camlight

material([.6 .9 .3 ])

It's not the most shapely candy cane, but it will do. Let's add some icing.

newplot

patch(S,'FaceColor','interp','EdgeColor','none','FaceVertexCData',S.vertices(:,3));

colormap(validatecolor({ '#a56c3c' '#f09090' }, 'multiple'));

clim([10 20]);

axis equal ij off

lighting gouraud

camlight

material([.6 .9 .3 ])

Obviously, it's easy to draw with X's into the variable K to make any cookie shape that pleases you. What happens if we make it random?

% Create a gingerbread blob

K = double(rand(10,10)>0.5);

K(K==0) = char('*');

K = char(K);

% Convert K into array of blob centers

[y,x]=find(~isspace(K));

% Scale and offset centers into 3D cookie space

C=[x*5,y*5]+5; C(:,3)=10;

% Compute a volume using a short implementation of Blinn's blobs

nx=size(K,2)*5+10; ny=size(K,1)*5+10; nz=20; % Size of volume

[y,x,z]=ndgrid(1:ny,1:nx,1:nz);

vol=zeros(ny,nx,nz);

for i=1:size(C,1)

vol=vol+exp(-.05*((C(i,1)-x).^2 + (C(i,2)-y).^2 + (C(i,3)-z).^2));

end

% Compute isosurface from the blobs

newplot

S=isosurface(vol,.3);

patch(S,'FaceColor','interp','EdgeColor','none','FaceVertexCData',S.vertices(:,3));

colormap(validatecolor({ '#a56c3c' '#8090f0' }, 'multiple'));

clim([10 20]);

axis equal ij off

lighting gouraud

camlight

material([.6 .9 .3 ])

I like my random cookies, and I'm now inclined to try some in the kitchen.

I'll close with Eric's snowflake code. Here's the default.

snowflake

axis off

But, just as with the cookies (and as with real-life snowflakes), we can opt for a random flake.

steps = randi(10,1,randi(8)+2)

snowflake(steps)

axis off

Happy holidays, and thanks for the cookies, Eric!

]]>As the days get shorter and drearier here in New England, I get hungry for sunlight. This makes me somewhat obsessed with when the sun sets. But unlike our ancient ancestors, I don't need to worry... read more >>

]]>As the days get shorter and drearier here in New England, I get hungry for sunlight. This makes me somewhat obsessed with when the sun sets. But unlike our ancient ancestors, I don't need to worry that the sun will never return. I have something they didn't have: MATLAB. To cope with the shortening days, I can calculate exactly when the days will start getting longer. And for me, the days will start getting longer on December 10th.

You're probably aware that the shortest day is December 21st. But the earliest sunset is on December 10th (at my latitude). And since I never wake up before dawn, then the day as I experience it is already getting longer by December 11th. It's a subtle point, but it helps carry me through the twilight of the waning year (I've written about this phenomenon before).

Using Francois Beauducel's "Sunrise" contribution on the File Exchange, I can easily make a plot of the sunset time where I live (north latitude 42 degrees).

% Where: Natick, Massachusetts.

lat = 42.3;

lon = -71.4;

% When: this year.

% List all the days of the year

d = datetime(2022,1,1:365);

% SUNRISE uses the DATENUM format

[~,sset_datenum] = sunrise(lat,lon,0,[],datenum(d));

sset = datetime(sset_datenum,ConvertFrom="datenum");

t = hour(sset) + minute(sset)/60 + second(sset)/3600;

plot(d,t,LineWidth=3)

grid on

box on

ylim([15 20])

ylabel("Time of Sunset")

[~,ix] = min(t);

line(sset(ix),t(ix), ...

Marker="o", ...

MarkerSize=15, ...

LineWidth=2, ...

Color="red")

text(sset(ix),t(ix), ...

sprintf("Earliest Sunset \n%s ",sset(ix)), ...

Color="red", ...

VerticalAlignment="bottom", ...

HorizontalAlignment="right")

setTimeYTick(gca)

Note that all times are Standard Time. We're not worrying about any Daylight Saving Time nonsense. I like to call the day with the earliest sunset Crepusculus (after crepusculum, the Latin word for dusk). It's an important milestone, so it deserves a name!

Let's take that same data and use it to animate the sunset time using the face of a clock. Here we're visualizing the hour hand at the time of sunset. You can see a little asymmetry here. The days get shorter more quickly than they get longer.

For this animation, I used a File Exchange contribution of my own called Animator.

Now let's plot the span from latest to earliest sunset for several north latitudes.

clf

hold on

latList = 0:10:50;

crepusculusTimes = zeros(size(latList));

crepusculusDates = NaT(size(latList));

colorMap = flipud(parula(10));

for n = 1:length(latList)

d = datetime(2022,1,1:365);

lat = latList(n);

lon = -71;

[~,sset] = sunrise_dt(lat,lon,d);

t = hour(sset)+minute(sset)/60+second(sset)/3600;

[~,ixEarliest] = min(t);

[~,ixLatest] = max(t);

sset = sset(ixLatest:ixEarliest);

crepusculusTimes(n) = t(ixEarliest);

crepusculusDates(n) = d(ixEarliest);

plot(d,t,LineWidth=3,Color=colorMap(n,:))

grid on

hold on

box on

end

legend(string(latList + " deg"),Location="southeast")

title("Sunset Times at Various North Latitudes")

ylabel("Time of Sunset")

setTimeYTick(gca)

hold off

As expected, day length varies considerably as you get farther away from the equator.

legend off

set(findobj(gcf,"Type","line"),LineWidth=1)

h = line(crepusculusDates,crepusculusTimes,LineWidth=2,Color="red",Marker="o");

legend(h,"Crepusculus",Location="southeast")

title("Date of the Earliest Sunset Varies with Latitude")

Crepusculus depends on your latitude! The earliest sunset skews earlier in the year as you get closer to the equator.

Once again, let's display some of this information on the face of a clock. This time, instead of an animation, we'll do a static clock face where the color of the hour hand depends on the month of the year.

d = datetime(2022,6,1:214);

lat = 42.3;

lon = -71.4;

[~,sset] = sunrise_dt(lat,lon,d);

t = hour(sset)+minute(sset)/60+second(sset)/3600;

[~,ixEarliest] = min(t);

[~,ixLatest] = max(t);

d = d(ixLatest:ixEarliest);

t = t(ixLatest:ixEarliest);

sset = sset(ixLatest:ixEarliest);

clf

drawClockFace

gammaHr = pi/2 - 2*pi*mod(t,12)/12;

% colorMap = prism(7);

colorMap = get(gca,"ColorOrder");

colorIxList = [1 1 1 1 1 1 2 3 4 5 6 7];

monthList = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

monthList = monthList(unique(month(d)));

h = zeros(12,1);

nMonths = length(unique(month(d)));

r = 0.8;

for i = 1:length(gammaHr)

colorIx = colorIxList(month(d(i)));

% colorIx = colorIxList(i);

color = colorMap(colorIx,:);

h(colorIx) = line([0 r*cos(gammaHr(i))],[0 r*sin(gammaHr(i))], ...

Color=color, ...

LineWidth=2);

end

r = 1.1;

x = r*cos(gammaHr(1));

y = r*sin(gammaHr(1));

line([0 x],[0 y],Color=0.5*[1 1 1])

sset.Format = "dd-MMM HH:mm";

text(x,y,sprintf("Latest Sunset \n%s ", string(sset(1))), ...

Color=0.5*[1 1 1], ...

VerticalAlignment="top", ...

HorizontalAlignment = "right")

x = r*cos(gammaHr(end));

y = r*sin(gammaHr(end));

line([0 x],[0 y],Color=0.5*[1 1 1])

sset.Format = "dd-MMM HH:mm";

text(x,y,sprintf(" Earliest Sunset \n %s ", string(sset(end))), ...

Color=0.5*[1 1 1], ...

VerticalAlignment="top", ...

HorizontalAlignment = "left")

axis equal

axis off

monthList = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

monthList = monthList(unique(month(d)));

h(h==0) = [];

legend(h,monthList, ...

Location="NortheastOutside")

This graph shows roughly half the year, from the latest sunset in June to the earliest sunset in December.

Finally, here's an animation of the sunset visualization shown above as it changes with latitude. Once again, we see that equatorial days don't vary much in length.

Which would you rather have: the steady day length of the tropics, or gloriously long days of summer at the expense of short winter days?

function setTimeYTick(ax)

h = hours(get(ax,"YTick"))-hours(12);

h.Format = "hh:mm";

hStr = replace(string(h), textBoundary+"0", "") + " pm";

set(gca,YTickLabels=hStr, ...

YDir="reverse")

end

Any cricketers out there? If you like cricket and coding in MATLAB (and who doesn't?), then allow me to direct your attention to an entirely cricket-themed Cody problem group created by quizmaster... read more >>

]]>The Cody First XI – MATLAB Problems for Cricket Fans

As a fan of the New Zealand Black Caps, Matt has been keenly following the T20 World Cup going on in Australia. Unfortunately for Matt, the Black Caps were eliminated by Pakistan in the semifinals. I'm not much of a cricketer myself, but here's another result that caught my eye.

I will note that, if your name is Ned, you can't help but cheer for Netherlands during international sporting events.

But what does this have to do with Cody? Matt was inspired by the World Cup to make this latest sports-themed problem group. He was even kind enough to put me in a problem description here.

It's a nifty problem dealing with MATLAB tables. The title is a mouthful, but the function you write to solve it is whosyourbunny. *Who's your bunny?* Like I said, cricket is not my first language, so I had to look it up. A bunny is a batter who is consistently dismissed by a particular bowler. The problem description portrays me as a skilled bowler, so naturally I had to solve it right away. This let me be the first solver, bringing me, after all these years, my first Leader badge. Huzzah! (My answer has since been surpassed...)

I want to thank Matt and Ben Placek and Renee Bach for all the work they did creating problems for the recent tenth anniversary contest. You can find dozens of new and fascinating problems in these groups. The contest may be over, but these problems are still ready for you answers.

Week 1: Matrices and Arrays

Week 2: Plotting and Visualization

Week 3: Programming Constructs

... and everyone's favorite: Week 4: Are You Smarter Than a MathWorker?

But of course, if cricket is your sport, then you know where to go!

]]>Today, once again, I am delighted to introduce guest blogger Dave Bulkin. You may remember that Dave blogged in this space last year about our first Mini Hack (Mini Hack Contest Analysis). Dave is a... read more >>

]]>Today, once again, I am delighted to introduce guest blogger Dave Bulkin. You may remember that Dave blogged in this space last year about our first Mini Hack (Mini Hack Contest Analysis). Dave is a developer on the MATLAB Graphics and Charting Team. In the post below, Dave not only examines the code behind the leading entries, but he also introduces you to some new visualization techniques.

For reference, follow this link to the Mini Hack Gallery.

by Dave Bulkin

Thanks to the Mini Hack organizers and all the contributors for another great set of beautiful and inspiring visualizations! And thanks to Ned for letting me return to his blog to share some analyses about the content.

Last year’s analysis showed that the axis function was unquestionably the most popular function, the only one used in the majority of submissions. This was unsurprising: axis is a four-character “Swiss army knife” of functionality. The axis function remained on top in 2022, and still was used in most entries, but the chart below shows that trig functions made their way up in the ranks this year.

To make this chart I used one of our standalone visualizations: parallelplot. Parallel coordinates plots are often used with more than two variables but can be handy for a quick visualization of how things change in two different conditions. I used a trick to label the values: parallelplotprovides a grouping variable which is intended to visualize how groups of lines change across multiple coordinates, but I used one group for each function to serve as a label. My code to call parallelplotlooked something like this (I also tweaked the Colorproperty a bit to get distinct colors for the last 5 functions:

parallelplot(tbl, ...

CoordinateVariables = ["prob21" "prob22"], ...

GroupVariable = 'Function', ...

CoordinateTickLabels = ["2021" "2022"], ...

LineWidth = 2, ...

DataNormalization = "none");

When I looked at the Mini Hack data last year I tried to drill into all the ways that contest entries used axis, this year I thought it would be interesting to look at how the various colormaps were used. Here, I didn’t rely on static analysis to pick out function calls because colormaps can be used as a function (clr = lines(4);) or as a string (colormap('hot')), so instead I just searched the entries for all occurrences of all of our colormaps. The contains function makes short work of this kind of task. I started with a bar plot of the data but it was a little bland, so I decided to try to make a chart where each bar’s face showed the colormap:

One approach to making these bars would be to use patch, setting linearly spaced vertices along the edges of the bars and using the FaceVertexCData property to specify the color for each vertex. However, bars are rectangles, and it’s quite easy to make colorful rectangles with the image command, so I took that approach. My code looked something like this:

spacemult = 1.3; % used for adding space between the bars

for i = 1:numel(cmaps)

x = [0 mu(i)];

y = [i i] * spacemult;

n = ...; % n was normally 255, but a lower value for prism, flag, and lines

c = reshape(feval(cmaps(i), n), 1, n, 3);

offset = diff(x)/n; % An offset because image takes center coordinates

image(x + [offset -offset]/2, y, c);

rectangle('Position',[0 y(1)-.5 x(2) 1])

end

And here’s a version of the plot that looks at the change in colormap popularity across years. hsv, turbo, bone and cool were less popular, while copper, colorcube, lines and prism were more popular:

It’s all well and good to talk about which functions (and colormaps) were used the most, but if you want to have a successful Mini Hack entry, you probably want to know which functions led to the most votes. To investigate I computed the average number of votes for entries that contained each function. Because some functions were used sparsely, I limited this analysis to functions that were used in at least 20 entries, and to simplify the presentation I included both years in my analysis and didn’t try to compare. This was by no means a perfect analysis, I found that popular submissions (and their remixes) tended to dominate the scores. But if you’re looking to win in Mini Hack, you might consider making an image with some gamma random numbers, a two-dimensional inverse Fourier transform, and a pink colormap…

One of the coolest features of this year’s Mini Hack, was the ability to depend on File Exchange submissions. For my final analysis I wanted to use a File Exchange chart that uses the MATLAB ChartContainer base class. ChartContainer provides a framework for developing standalone visualizations like parallelplot above – these visualizations aren’t like traditional MATLAB graphics that can be combined in an axes, but instead are charts designed for a special purpose that works independently from other charts. You can read more about standalone visualizations in the “More About” section at the bottom of the parallelplot documentation page.

The MATLAB Charting Team has published a few example charts on File Exchange (you can find these by searching for the chartcontainer tag). One of our examples produces Euler diagrams. These charts have been controversial internally – in part because many folks call them Venn diagrams which are slightly different. However, the deeper challenge with this visualization is that proportional circular Euler diagrams with more than two categories don’t necessarily have a solution, so error (potentially qualitative error) tends to be present in these charts. Thus, we’ve puzzled about whether we should include this kind of visualization in a product where the math…works. Nonetheless we had an ambitious intern last year who implemented an algorithm published in IEEE Transactions on Visualization and Computer Graphics, and it makes for a neat chart that highlights the co-occurrence of function use in Mini Hack entries. The diagram below shows how light is often used in entries with surf, but not with scatter, and colormap is used with surf and scatter, but not so much with plot.

]]>