diff --git a/DESCRIPTION b/DESCRIPTION
index 50a35e7..d1c4454 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -8,4 +8,7 @@ Description: Room squares in R.
 License: MIT + file LICENSE
 Encoding: UTF-8
 Roxygen: list(markdown = TRUE)
-RoxygenNote: 7.0.0
+RoxygenNote: 7.3.1
+Imports:
+  purrr,
+  tidyr
diff --git a/R/latin.R b/R/latin.R
new file mode 100644
index 0000000..6440f62
--- /dev/null
+++ b/R/latin.R
@@ -0,0 +1,43 @@
+#' Does a row satisfy the latin constraint?
+#'
+#' @param R A Room square
+#' @param i A row index
+#'
+#' @return True if and only if row i of R satisfies the latin constraint.
+is_row_latin_i <- function(R, i) {
+  R <- R |> tidyr::pivot_longer(first:second)
+  u <- R[R$row == i, "value"]$value
+  u <- u[!is.na(u)]
+  length(u) == length(unique(u))
+}
+
+#' Does a column satisfy the latin constraint?
+#'
+#' @param R A Room square
+#' @param i A column index
+#'
+#' @return True if and only if column i of R satisfies the latin constraint.
+is_col_latin_i <- function(R, i) {
+  R <- R |> tidyr::pivot_longer(first:second)
+  u <- R[R$col == i, "value"]$value
+  u <- u[!is.na(u)]
+  length(u) == length(unique(u))
+}
+
+#' Is a Room square row latin?
+#'
+#' @param R A Room square
+#'
+#' @return True if and only if R is row latin.
+is_row_latin <- function(R) {
+  all(purrr::map_lgl(1:max(R$row), is_row_latin_i, R = R))
+}
+
+#' Is A Room square column latin?
+#'
+#' @param R A Room square
+#'
+#' @return True if and only if R is column latin.
+is_col_latin <- function(R) {
+  all(purrr::map_lgl(1:max(R$col), is_col_latin_i, R = R))
+}
\ No newline at end of file
diff --git a/R/remove-both.R b/R/remove-both.R
new file mode 100644
index 0000000..686cfb9
--- /dev/null
+++ b/R/remove-both.R
@@ -0,0 +1,17 @@
+#' Remove both elements of a pair from a list
+#'
+#' @param X A list
+#' @param p A pair
+#'
+#' @return The list X with both elements of p removed (if they exist).
+remove_both <- function(X, p) {
+  m1 <- match(p[1], X)
+  if(!is.na(m1)) {
+    X <- X[-m1]
+  }
+  m2 <- match(p[2], X)
+  if(!is.na(m2)) {
+    X <- X[-m2]
+  }
+  return(X)
+}
\ No newline at end of file
diff --git a/man/is_col_latin.Rd b/man/is_col_latin.Rd
new file mode 100644
index 0000000..4bb0a30
--- /dev/null
+++ b/man/is_col_latin.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/latin.R
+\name{is_col_latin}
+\alias{is_col_latin}
+\title{Is A Room square column latin?}
+\usage{
+is_col_latin(R)
+}
+\arguments{
+\item{R}{A Room square}
+}
+\value{
+True if and only if R is column latin.
+}
+\description{
+Is A Room square column latin?
+}
diff --git a/man/is_col_latin_i.Rd b/man/is_col_latin_i.Rd
new file mode 100644
index 0000000..fe93a05
--- /dev/null
+++ b/man/is_col_latin_i.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/latin.R
+\name{is_col_latin_i}
+\alias{is_col_latin_i}
+\title{Does a column satisfy the latin constraint?}
+\usage{
+is_col_latin_i(R, i)
+}
+\arguments{
+\item{R}{A Room square}
+
+\item{i}{A column index}
+}
+\value{
+True if and only if column i of R satisfies the latin constraint.
+}
+\description{
+Does a column satisfy the latin constraint?
+}
diff --git a/man/is_row_latin.Rd b/man/is_row_latin.Rd
new file mode 100644
index 0000000..c76e9a5
--- /dev/null
+++ b/man/is_row_latin.Rd
@@ -0,0 +1,17 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/latin.R
+\name{is_row_latin}
+\alias{is_row_latin}
+\title{Is a Room square row latin?}
+\usage{
+is_row_latin(R)
+}
+\arguments{
+\item{R}{A Room square}
+}
+\value{
+True if and only if R is row latin.
+}
+\description{
+Is a Room square row latin?
+}
diff --git a/man/is_row_latin_i.Rd b/man/is_row_latin_i.Rd
new file mode 100644
index 0000000..c9ed744
--- /dev/null
+++ b/man/is_row_latin_i.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/latin.R
+\name{is_row_latin_i}
+\alias{is_row_latin_i}
+\title{Does a row satisfy the latin constraint?}
+\usage{
+is_row_latin_i(R, i)
+}
+\arguments{
+\item{R}{A Room square}
+
+\item{i}{A row index}
+}
+\value{
+True if and only if row i of R satisfies the latin constraint.
+}
+\description{
+Does a row satisfy the latin constraint?
+}
diff --git a/man/remove_both.Rd b/man/remove_both.Rd
new file mode 100644
index 0000000..25f7f30
--- /dev/null
+++ b/man/remove_both.Rd
@@ -0,0 +1,19 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/remove-both.R
+\name{remove_both}
+\alias{remove_both}
+\title{Remove both elements of a pair from a list}
+\usage{
+remove_both(X, p)
+}
+\arguments{
+\item{X}{A list}
+
+\item{p}{A pair}
+}
+\value{
+The list X with both elements of p removed (if they exist).
+}
+\description{
+Remove both elements of a pair from a list
+}
diff --git a/wallis.Rproj b/wallis.Rproj
index 21a4da0..eaa6b81 100644
--- a/wallis.Rproj
+++ b/wallis.Rproj
@@ -15,3 +15,4 @@ LaTeX: pdfLaTeX
 BuildType: Package
 PackageUseDevtools: Yes
 PackageInstallArgs: --no-multiarch --with-keep.source
+PackageRoxygenize: rd,collate,namespace