Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

latexMatrix arithmetic: trivial simplifications? #58

Closed
friendly opened this issue Aug 21, 2024 · 79 comments
Closed

latexMatrix arithmetic: trivial simplifications? #58

friendly opened this issue Aug 21, 2024 · 79 comments

Comments

@friendly
Copy link
Owner

In my vignette example of linear hypotheses, the following generates the C & B matrices:

C <- latexMatrix(matrix(c(0, 1, 0, 0,
                          0, 0, 1, 0), nrow=2, byrow=TRUE), 
                 matrix = "bmatrix")
B <- latexMatrix('\\beta', ncol = 3, nrow=4, 
                 comma=TRUE, prefix.col = 'y_',
                 zero.based=c(TRUE, FALSE))
C %*%B

which generates a correct result, but one that is hard to look at:

\begin{bmatrix}  
0 \cdot \beta_{0,y_{1}} + 1 \cdot \beta_{1,y_{1}} + 0 \cdot \beta_{2,y_{1}} + 0 \cdot \beta_{3,y_{1}} & 0 \cdot \beta_{0,y_{2}} + 1 \cdot \beta_{1,y_{2}} + 0 \cdot \beta_{2,y_{2}} + 0 \cdot \beta_{3,y_{2}} & 0 \cdot \beta_{0,y_{3}} + 1 \cdot \beta_{1,y_{3}} + 0 \cdot \beta_{2,y_{3}} + 0 \cdot \beta_{3,y_{3}} \\ 
0 \cdot \beta_{0,y_{1}} + 0 \cdot \beta_{1,y_{1}} + 1 \cdot \beta_{2,y_{1}} + 0 \cdot \beta_{3,y_{1}} & 0 \cdot \beta_{0,y_{2}} + 0 \cdot \beta_{1,y_{2}} + 1 \cdot \beta_{2,y_{2}} + 0 \cdot \beta_{3,y_{2}} & 0 \cdot \beta_{0,y_{3}} + 0 \cdot \beta_{1,y_{3}} + 1 \cdot \beta_{2,y_{3}} + 0 \cdot \beta_{3,y_{3}} \\ 
\end{bmatrix}

and looks like this:

image

Trivial simplification of this could:

  • remove terms multiplied by 0
  • remove multipliers of 1 \cdot

Just removing the 0 * terms:

\begin{bmatrix}  
  1 \cdot \beta_{1,y_{1}} &  1 \cdot \beta_{1,y_{2}}  &  1 \cdot \beta_{1,y_{3}} \\ 
  1 \cdot \beta_{2,y_{1}} &  1 \cdot \beta_{2,y_{2}}  &  1 \cdot \beta_{2,y_{3}} \\ 
\end{bmatrix}

Is this possible? An operator function like %*% can't take other arguments, but could it handle an option in the environment?

@philchalmers
Copy link
Collaborator

philchalmers commented Aug 22, 2024

It's certainly possible to do this. I did something similar in dev/Arith2.R for the kronecker product, and I was glad to see this idea made into John's implementation as well. Perhaps the best way to do this is to add a print(..., sparse = TRUE) to detect any zero multiplications in defined matrices and have them drop out?

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 22, 2024 via email

@friendly
Copy link
Owner Author

friendly commented Aug 22, 2024 via email

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 22, 2024 via email

@john-d-fox
Copy link
Collaborator

I had some time between appointments, so I added a simplify.latexMatrix() method to dev/simplify.R.

For Michael's original example, this produces

> simplify(C %*% B)
\begin{bmatrix}  
\beta_{1,y_{1}} & \beta_{1,y_{2}} & \beta_{1,y_{3}} \\ 
\beta_{2,y_{1}} & \beta_{2,y_{2}} & \beta_{2,y_{3}} \\ 
\end{bmatrix}

In my opinion, that's not more complicated than something like matmult(C, B, simplify=TRUE).

The code in dev/simplify.R could use more testing, and I expect that it could also be improved -- I'm not a regular expression whiz.

@friendly
Copy link
Owner Author

simplify.R is very nice. Hard to believe it was something you could do between appointments!

But I was thinking of some way, hopefully more elegant, that would avoid having to parse what latexMatrix generates. That is, the best place to simplify would be as source, where the elements
of the dot product, dot(a, b) can be recognized as in 3 cases:

  • 0 in either a[i ] or b[j-]> ignore term
  • 1 -> use the other
  • -1 -> use the other, and combine with -
    For another thing, \cdot is hard-coded throughout, and we might at sometime want to change it or allow an argument, `times = c("\cdot", "\times, "")

Just for fun, I'll try to sketch out a function that works this way.

@john-d-fox
Copy link
Collaborator

I added a matmult() function to dev/simplify.R. The rationale is that the function takes an arbitrary number of "latexMatrix" objects as arguments and multiplies them in left-to-right order. There are examples in the file. All of the following work, e.g.:

matmult(R)
matmult(R, S)
matmult(R, S, T)

assuming that R, S, and T are conformable for multiplication in the order given.

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 22, 2024 via email

@friendly
Copy link
Owner Author

I added dev/dot.R, doing the latex dot product of two vectors, either numeric or symbolic, and simplifying 0s & 1s. This avoids the need to parse the results as in simplify()

>  num <- 0:2
>  chr <- letters[1:3]
>  
>  dot(num, num)
[1] "2 \\cdot 2"
>  dot(num, chr)
[1] "b + 2 \\cdot c"
>  dot(chr, num)
[1] "b + c \\cdot 2"
>  dot(chr, chr)
[1] "a \\cdot a + b \\cdot b + c \\cdot c"

If it was OK to just simplify %*% by default (I see nothing wrong with that),
this could be used inside the loop at lines 724 - 730 of latexMatrix.R

      for (k in 1:ncol(X)){
        Z[i, j] <- paste0(Z[i, j],
                          if (k > 1) " + ",
                          parenthesize(X[i, k]),
                          " \\cdot ",
                          parenthesize(Y[k, j]))
      }

replacing this with

Z[i,j] <- dot(X[i,], y[,j]

@john-d-fox
Copy link
Collaborator

I tried this, fixing the typos in the last line: Z[i, j] <- dot(X[i, ], Y[, j])

The resulting %*% fails for the following example (the second one that I tested):

> B <- latexMatrix('\\beta', ncol = 3, nrow=4, 
+                  comma=TRUE, prefix.col = 'y_',
+                  zero.based=c(TRUE, FALSE))
> D <- diag(4)
> D[3, 3] <- 0
> D <- latexMatrix(D)
> D %*% B
\begin{pmatrix}  
\beta_{0,y_{1}} & \beta_{0,y_{2}} & \beta_{0,y_{3}} \\ 
\beta_{1,y_{1}} & \beta_{1,y_{2}} & \beta_{1,y_{3}} \\ 
                &                 &                 \\ 
\beta_{3,y_{1}} & \beta_{3,y_{2}} & \beta_{3,y_{3}} \\ 
\end{pmatrix}

Some comments:

I like your approach, which I'm sure can be made more robust. As you suggest, it's simpler than simplifying at the end. You need to test with a more diverse set of examples and deal with cases like the one above.

The reason that I originally didn't simplify by default is that I thought that it was useful to show how the matrix product is formed, given the general purpose of the matlib package. That could still be done by including the matmult() function, with optional simplification, along with the %*% operator.

You mentioned that you'd like to make the LaTeX multiplication character flexible. I think that can be done by supplying a variable in the package, say latexTimesChar set initially to "\\cdot", along with a mechanism for changing it.

@john-d-fox
Copy link
Collaborator

On second thought, a latexTimesChar variable is more complicated than an option(), and still violates functional programming. I've gone ahead and implemented a latexMultSymbol option(); if it's not set, "\\cdot" is used.

@friendly
Copy link
Owner Author

I see why your %*% example failed, where the 0 is actually desired. This calls for a simplify option, but not sure where to implement it.
That's the trouble with operators, where you can't have optional arguments.
Except, a hint:

get('%*%')(A, B, simplify=TRUE)

Or in matmult()

@john-d-fox
Copy link
Collaborator

I think that makes the issue more complicated than it needs to be. You should be able to handle this and other problems that may arise in dot() with more extensive testing.

In this case, adding the line

  if (res == "") res <- "0"

as the next-to-last command in dot() produces

> D %*% B
\begin{pmatrix}  
\beta_{0,y_{1}} & \beta_{0,y_{2}} & \beta_{0,y_{3}} \\ 
\beta_{1,y_{1}} & \beta_{1,y_{2}} & \beta_{1,y_{3}} \\ 
0               & 0               & 0               \\ 
\beta_{3,y_{1}} & \beta_{3,y_{2}} & \beta_{3,y_{3}} \\ 
\end{pmatrix}

as desired.

More generally, however, I think that there's an argument for making matmult() the place where the computations happen, which would allow for additional arguments like simplify, and then to have %*% call matmult() (rather than vice-versa) with arguments set to specific values like simplify = TRUE.

Since I spent quite a bit of time trying to make simplify() robust, I'd rather not do the same for dot(), but I expect that the latter will end up being considerably simpler than the former. That is, I think that your approach is better, though it now requires additional work to make it robust.

@john-d-fox
Copy link
Collaborator

Another issue -- handling -1s:

> F <- latexMatrix(matrix(c(-1, 0, 1, 0, 0, -1, 0, 1), ncol=4))
> F
\begin{pmatrix} 
-1 &  1 &  0 &  0 \\ 
 0 &  0 & -1 &  1 \\ 
\end{pmatrix}
> F %*% B
\begin{pmatrix}  
(-1) \cdot \beta_{0,y_{1}} + \beta_{1,y_{1}} & (-1) \cdot \beta_{0,y_{2}} + \beta_{1,y_{2}} & (-1) \cdot \beta_{0,y_{3}} + \beta_{1,y_{3}} \\ 
(-1) \cdot \beta_{2,y_{1}} + \beta_{3,y_{1}} & (-1) \cdot \beta_{2,y_{2}} + \beta_{3,y_{2}} & (-1) \cdot \beta_{2,y_{3}} + \beta_{3,y_{3}} \\ 
\end{pmatrix}

@friendly
Copy link
Owner Author

Yes, I had that on my todo list, but didn't do it in my initial draft, partly because that was a little trickier given the initial logic.

@friendly
Copy link
Owner Author

I generally agree with your earlier comments about simplify and matmult, and I think you're in a better position to decide how to handle something like dot in this mix. I'll add your '0' fix and perhaps we can both identify where/how it can be made more robust.

When would it make sense to move simplify.R to R/?

@john-d-fox
Copy link
Collaborator

I was about to propose something very similar, so I think we're on the same wavelength.

I suggest the following:

  1. You work to make dot() more robust. At this point, the only issues I've uncovered are replacing blanks with 0s, which as you note is easily fixed, and handling multiplication by -1. You can add a simplify argument, which defaults to TRUE to dot().
  2. When you're done, I rewrite matmult() so that it uses dot() to perform matrix multiplication. It too will have a simplify argument defaulting to TRUE, which is just passed to dot().
  3. I rewrite the %*%.matrixMult() method so that it calls matmult().
  4. I take another look at the other arithmetic functions and operators to see whether a similar approach to simplification is desirable and feasible.

I don't think that it's a good idea to move simplify.R to R/. The code for simplify() is kludgy and convoluted (which is why I prefer your dot()-based approach), and it should be unnecessary once the above changes are implemented.

Does all that sound reasonable?

@john-d-fox
Copy link
Collaborator

I hope that this isn't counterproductive, but for curiosity, I tried to fix dot() and came up with the following:

dot <- function(x, y) {
  if (length(x) != length(y)) stop("Vectors must have the same length")
  x <- trimws(x)
  y <- trimws(y)
  res <- ""
  for (i in 1:length(x)) {
    # ignore terms multiplied by zero
    if (x[i] == "0") next
    if (y[i] == "0") next
    xi <- if(x[i] == "1") "" else x[i]
    yi <- if(y[i] == "1") "" else y[i]
    if (x[i] == "1" && y[i] == "1") xi <- "1"
    xi <- if (xi == "-1") "-" else xi
    if (y[i] == "-1") {
      yi <- ""
      xi <- if (x[i] == "-1") "1" else paste0("-", parenthesize(xi))
    }
    times <- if(xi == "" || xi == "-" || yi == "") "" else " \\cdot "
    res <- paste0(res,
                  if (nchar(res) > 0) " + ",
                  if (y[i] != "-1" && xi != "-") parenthesize(xi) else xi,
                  times,
                  parenthesize(yi))
  }
  if (res == "") res <- "0"
  res
}

it seems to work OK.

@friendly
Copy link
Owner Author

OK, I'll work from your version of dot. It should take one simplify=T/F arg I think not separate ones for 1s & 0s.

In terms of making it 'robust', I'm not sure what to test for, other than the combinations of num and chr, now including -1, and also with simplify = FALSE

@friendly
Copy link
Owner Author

OK, I incorporated simplify = T/F in a new dev/dot.R. Seems to work for the tests I've run

>  # w/ simplify=T
>   dot(num, num)  # OK
[1] "1 + 1 + 2 \\times 2"
>   dot(num, chr)  # OK
[1] "-a + c + 2 \\times d"
>   dot(chr, num)  # OK
[1] "-a + c + d \\times 2"
>   dot(chr, chr)  # OK
[1] "a \\times a + b \\times b + c \\times c + d \\times d"

W/o simplify

>   dot(num, num, simplify = FALSE) 
[1] "(-1) \\times (-1) + 0 \\times 0 + 1 \\times 1 + 2 \\times 2"
>   dot(num, chr, simplify = FALSE)
[1] "(-1) \\times a + 0 \\times b + 1 \\times c + 2 \\times d"
>   dot(chr, num, simplify = FALSE)
[1] "a \\times (-1) + b \\times 0 + c \\times 1 + d \\times 2"
>   dot(chr, chr, simplify = FALSE)
[1] "a \\times a + b \\times b + c \\times c + d \\times d"
>   # change the mult symbol  
>   opt <- options(latexMultSymbol = "\\times") 
>   options("latexMultSymbol")
$latexMultSymbol
[1] "\\times"

>   dot(num, num)
[1] "1 + 1 + 2 \\times 2"
>   dot(num, chr)
[1] "-a + c + 2 \\times d"

@friendly
Copy link
Owner Author

Sorry; I copy/pasted those tests out of order, after I had changed latexMultSymbol to \\times

>   options(latexMultSymbol = "\\cdot")
>   
>   dot(num, num)  # OK
[1] "1 + 1 + 2 \\cdot 2"
>   dot(num, chr)  # OK
[1] "-a + c + 2 \\cdot d"
>   dot(chr, num)  # OK
[1] "-a + c + d \\cdot 2"
>   dot(chr, chr)  # OK
[1] "a \\cdot a + b \\cdot b + c \\cdot c + d \\cdot d"

@john-d-fox
Copy link
Collaborator

All this looks promising.
With respect to additional testing, you can create some matrices and test with %*% using more complex matrix expressions like A %*% (B - C), A %*% -B, (A - k*D) %*% B (where k is a scalar), A %*% B %*% E, A %*% solve(B), etc., and see whether the simplifications hold up.

@friendly
Copy link
Owner Author

Then, I'd have to incorporate dot() into latexMartrix(). I suppose I can do this from a copy of the latter in dev/.

@john-d-fox
Copy link
Collaborator

Here's what I'm using:

numericDimensions <- matlib:::numericDimensions
updateWrapper <- matlib:::updateWrapper
parenthesize <- matlib:::parenthesize

`%*%.latexMatrix` <- function(x, y){
  if (!inherits(y, "latexMatrix")){
    stop(deparse(substitute(y)),
         " is not of class 'latexMatrix'")
  }
  numericDimensions(x)
  numericDimensions(y)
  X <- getBody(x)
  Y <- getBody(y)
  dimX <- dim(X)
  dimY <- dim(Y)
  if (dimX[2] != dimY[1]){
    stop('matricies are not conformable for multiplication')
  }
  
  Z <- matrix("", nrow(X), ncol(Y))
  for (i in 1:nrow(X)){
    for (j in 1:ncol(Y)){
      for (k in 1:ncol(X)){
        Z[i, j] <- dot(X[i, ], Y[, j])
      }
    }
  }
  result <- latexMatrix(Z)
  result <- updateWrapper(result, getWrapper(x))
  result$dim <- dim(Z)
  result
}

@friendly
Copy link
Owner Author

OK -- your test code above, along with some tests is now in dev/dot-test.R.

My first test gives a strange result, which I don't see the reason for

 (A <- latexMatrix(matrix(c(1, -3, 0, 1), 2, 2)))
 (B <- latexMatrix(matrix(c(5, 3, -1, 4), 2, 2)))
 A %*% B

Gives a naked - sign n row 1, col 2.

\begin{pmatrix}  
5                & -         \\ 
(-3) \cdot 5 + 3 & -(-3) + 4 \\ 
\end{pmatrix}

We're going out to dinner shortly, so I'm probably done for the day.

@john-d-fox
Copy link
Collaborator

A simple fix is to add if (res == "-") res <- "-1" near the bottom of dot(), resulting in

\begin{pmatrix}  
5                & -1        \\ 
(-3) \cdot 5 + 3 & -(-3) + 4 \\ 
\end{pmatrix}

That's correct but clearly could be simplified further.
BTW, for strictly numeric matrices, one could always do the numeric matrix multiplication and then make the "latexMatrix" object.

@john-d-fox
Copy link
Collaborator

Here's another approach, which converts the matrices to numeric if that's possible.

Code:

`%*%.latexMatrix` <- function(x, y){
  if (!inherits(y, "latexMatrix")){
    stop(deparse(substitute(y)),
         " is not of class 'latexMatrix'")
  }
  numericDimensions(x)
  numericDimensions(y)
  X <- getBody(x)
  Y <- getBody(y)
  dimX <- dim(X)
  dimY <- dim(Y)
  if (dimX[2] != dimY[1]){
    stop('matricies are not conformable for multiplication')
  }
  
  XX <- suppressWarnings(as.numeric(X))
  YY <- suppressWarnings(as.numeric(Y))
  if (!any(is.na(XX)) && !any(is.na(YY))){
    XX <- matrix(XX, dimX[1], dimX[2])
    YY <- matrix(YY, dimY[1], dimY[2])
    Z <- XX %*% YY
  
  } else {
    
    Z <- matrix("", nrow(X), ncol(Y))
    for (i in 1:nrow(X)){
      for (j in 1:ncol(Y)){
        for (k in 1:ncol(X)){
          Z[i, j] <- dot(X[i, ], Y[, j])
        }
      }
    }
  }
  
  result <- latexMatrix(Z)
  result <- updateWrapper(result, getWrapper(x))
  result$dim <- dim(Z)
  result
}

dot <- function(x, y) {
  if (length(x) != length(y)) stop("Vectors must have the same length")
  x <- trimws(x)
  y <- trimws(y)
  res <- ""
  for (i in 1:length(x)) {
    # ignore terms multiplied by zero
    if (x[i] == "0") next
    if (y[i] == "0") next
    xi <- if(x[i] == "1") "" else x[i]
    yi <- if(y[i] == "1") "" else y[i]
    if (x[i] == "1" && y[i] == "1") xi <- "1"
    xi <- if (xi == "-1") "-" else xi
    if (y[i] == "-1") {
      yi <- ""
      xi <- if (x[i] == "-1") "1" else paste0("-", parenthesize(xi))
    }
    times <- if(xi == "" || xi == "-" || yi == "") "" else " \\cdot "
    res <- paste0(res,
                  if (nchar(res) > 0) " + ",
                  if (y[i] != "-1" && xi != "-") parenthesize(xi) else xi,
                  times,
                  parenthesize(yi))
  }
  if (res == "") res <- "0"
  if (res == "-") res <- "-1"
  res
}

Behaviour:

> C <- latexMatrix(matrix(c(0, 1, 0, 0,
+                           0, 0, 1, 0), nrow=2, byrow=TRUE), 
+                  matrix = "bmatrix")
> B <- latexMatrix('\\beta', ncol = 3, nrow=4, 
+                  comma=TRUE, prefix.col = 'y_',
+                  zero.based=c(TRUE, FALSE))
> C %*% B
\begin{bmatrix}  
\beta_{1,y_{1}} & \beta_{1,y_{2}} & \beta_{1,y_{3}} \\ 
\beta_{2,y_{1}} & \beta_{2,y_{2}} & \beta_{2,y_{3}} \\ 
\end{bmatrix}

> A <- latexMatrix(matrix(c(1, -3, 0, 1), 2, 2))
> B <- latexMatrix(matrix(c(5, 3, -1, 4), 2, 2))
> A %*% B
\begin{pmatrix}  
  5 &  -1 \\ 
-12 &   7 \\ 
\end{pmatrix}

@friendly
Copy link
Owner Author

I like this very much. I don't see any reason to stick with latex expressions that evaluate to a number when both X and Y are numeric. Needs more testing, though

@friendly
Copy link
Owner Author

Your code and a bunch of tests is now in dev/dot-test2.R.

I'm going to move dev/dot.R to R/ and finish documenting it. For now in a separate .Rd. The docs for latexMatrix are now quite unwieldly. I think that operation / operators for latex matrices would be better in a separate .Rd file.

@john-d-fox
Copy link
Collaborator

I agree that it would be best to document the arithmetic functions and operators separately.
Also, defaulting to numeric operations on numeric matrices can be done in all of the other matrix arithmetic functions and operators.
I think that it would be useful to have arithmetic functions corresponding to the other operators, like matmult() for %*% -- add() for +, etc. -- with the actual computations done in the functions, which each would have a simplify operator defaulting to TRUE. The operators would then just call the corresponding functions with default arguments. That would allow, e.g., showing details when desired but suppressing them by default. (I made a similar suggestions previously.)
I likely won't have much time today to work on this but should have plenty of time during the week.

@john-d-fox
Copy link
Collaborator

Hmm. My last emailed response doesn't seem to have made it here.

I coincidentally added the ability to show partition lines to print.LatexMethod() via hline and vline arguments. That's now in dev/print.LatexMethod.R, with a couple of examples.

I wouldn't, of course, have done that if I knew you were working on something similar! I think I've had enough for the night, so will look at what you did tomorrow.

@philchalmers
Copy link
Collaborator

Hi John,

No problem at all. I wasn't sure if we wanted to go down the LaTeX array route but I think that's more kosher for vertical lines.

Note that to behave well with Eqn() the return should be an invisible character vector, otherwise the object will be printed twice, which is why addPartition() just returned the modified object instead of trying to print. Alternatively, the object returned could have a flag added to it such as x$cat <- FALSE so that Eqn() knows not to reprint the returned object since it was already cat-ed.

@john-d-fox
Copy link
Collaborator

I took at look at your solution. I think that there's a problem with altering the elements in the "latexMatrix" object, which then couldn't be used in further computation.

On the other hand, one could add a $partition slot to the object with contents like list(h=c(2, 4), v=c(3, 5)) and which defaults to NULL. This could be done by latexMatrix() directly or by a separate function like addParition(). The @matrix slot could be modified accordingly without changing the print() method, so Eqn() should still work.

In addition, I didn't realize that one could add \hline to the beginning of the next line rather than to the end of the current line to show a partitioning line in a matrix. I should be able to greatly simplify my code doing that.

Assuming that this plan is sound, I should have time today to try to work everything out along the lines I just suggested.

@philchalmers
Copy link
Collaborator

Interesting, I hadn't considered carrying the partitions through to other operations. That makes sense for some matrix operators (addition, Hadamard, Kronecker), but for others, like multiplication, I'd have to think more clearly about what it would mean or if it's even useful as the resulting row/col dimensions change.

@friendly
Copy link
Owner Author

@john-d-fox Is it OK for me to edit the documentation for latexMatrixOperations or should I wait.

What I did before was mainly a bit of extra text in the @description + a few typos I found.
( I had also indented continued lines under @param for readability.)

#' Various Functions and Operators for \code{l"atexMatrix"} Objects
#'
#' @description
#' 
#' These operators and functions provide for LaTeX representations of
#' symbolic and numeric matrix arithmetic and computations.
#' They provide reasonable means to compose meaningful matrix equations
#' in LaTeX far easier than doing this manually matrix by matrix.
#' 
#' The following operators and functions are documented here:

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 29, 2024 via email

@friendly
Copy link
Owner Author

I added some stuff on partitioned matrices to the vignette. [ indexing and r/c-bind work nicely here.

Maybe this is enough? I like the result of addPartitions(), but not at the expense of making "latexMatrix" objects
more complicated.

> addPartitions(M, row=c(2,4), col = c(2,4))
\begin{pmatrix}  
\beta_{11}        & \beta_{12} \bigm| & \beta_{13} & \beta_{14} \bigm| & \beta_{15} & \beta_{16} \\ 
\beta_{21}        & \beta_{22} \bigm| & \beta_{23} & \beta_{24} \bigm| & \beta_{25} & \beta_{26} \\ 
\hline \beta_{31} & \beta_{32} \bigm| & \beta_{33} & \beta_{34} \bigm| & \beta_{35} & \beta_{36} \\ 
\beta_{41}        & \beta_{42} \bigm| & \beta_{43} & \beta_{44} \bigm| & \beta_{45} & \beta_{46} \\ 
\hline \beta_{51} & \beta_{52} \bigm| & \beta_{53} & \beta_{54} \bigm| & \beta_{55} & \beta_{56} \\ 
\end{pmatrix}

image

@philchalmers
Copy link
Collaborator

Ah, I see what you mean now; thanks for clarifying. Agreed, my approach would needlessly break too much, and I like the idea of adding the partitions as a separate object element. Should it make sense to apply some logic to the operations with partitions down the road we could do so.

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 29, 2024 via email

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 29, 2024 via email

@friendly
Copy link
Owner Author

Your partition() function looks great, and testifies to the elegance of this design with wrapper, body, etc.
I'll test it out some more.

@john-d-fox
Copy link
Collaborator

I did some further testing and then added partition() (now a generic function with a "latexMatrix" method) to R/latexMatrix.R; I updated the Roxygen/.Rd. markup accordingly, including an example.

I wonder whether this "issue" has gotten too long!

@philchalmers
Copy link
Collaborator

philchalmers commented Aug 30, 2024

This issue has certainly grown past its original intent... and, I honestly can't tell if it has been resolved. Does the simplify portion address this issue (if I recall there was an issue with trailing -1s, though that may have been patched)? If so, I vote to close. Many of the new functions will require a suite of testing anyway, so any remaining issues should be caught around that time.

@friendly
Copy link
Owner Author

An interesting edge-case with matrix indexing:

> A <- matrix(1:4, nrow =2) |> latexMatrix()
> A[1,1]
\begin{pmatrix}  
1 \\ 
\end{pmatrix}

This is certainly OK, in that it renders as a 1x1 pmatrix: (1). Would we ever want it otherwise, i.e., just "1"?

@john-d-fox
Copy link
Collaborator

john-d-fox commented Aug 30, 2024 via email

@philchalmers
Copy link
Collaborator

An as.vector() function would IMO be the best way to do this. We could do something like A[1,1,drop=TRUE], but that's rather against the drop convention.

@friendly
Copy link
Owner Author

I wrote sections on Partitioned matrices and Kronecker products in the vignette. Take a look, make edits or suggestions, etc.

@friendly
Copy link
Owner Author

I'm looking at the pkgdown documentation for latexMatrix, http://friendly.github.io/matlib/reference/latexMatrix.html

Aside from the fact that some generated equations aren't rendered, I note:

  • partition() doesn't appear, nor is it in the Reference index. I can't see why this is.
  • rbind() has a default deparse.level = 1 but cbind() does not

@john-d-fox
Copy link
Collaborator

My emailed comments don't seem to have made it here.

(1) I can make the deparse.level argument to the rbind() and cbind() methods consistent. The argument is ignored in any event.

(2) I can alter the indexing method so that it returns a one-element character vector when a single element is selected.

Unless someone objects, I'll go ahead and do this. I'll wait awhile in case someone else is working on latexMatrix.R.

With respect to the vignette. I think that it would be desirable to add partition lines to the final Kronecker-product example. Also, it is probably worth mentioning that partition() can show more than one horizontal and vertical partition line (or no line at all).

@john-d-fox
Copy link
Collaborator

Oh, one more thing. With respect to returning a single element as a "scalar": Should this always happen? If not, Phil seems suggest that we add an as.vector.latexMatrix() method rather than a drop argument to [.latexMatrix, but it's unclear to me what theas.vector() method would do with anything but a 1 x 1 matrix. I think it makes more sense to add drop with FALSE as the default (even though other [ methods AFAIK have TRUE as the default).

@philchalmers
Copy link
Collaborator

philchalmers commented Aug 31, 2024

As a thought, the ambiguity would be removed if bracket indexing always returned the matrix elements rather than a latexMatrix. I imagine if one is using [,] there's reason to have direct access to the elements, after which the result could be wrapped back via A[i,j] |> latexMatrix(). That way empty row/col indices have the same behaviour on the returned vectors.

@john-d-fox
Copy link
Collaborator

That approach loses the LaTeX matrix environment if it's not pmatrix. If you want the character sub-matrix, why not just use, e.g., getBody(X[1:2, 3:4]) or getBody(X)[1:2, 3:4]?

@philchalmers
Copy link
Collaborator

philchalmers commented Aug 31, 2024 via email

@friendly
Copy link
Owner Author

Too many things in this thread now!

(1) Re: deparse.level: Just make consistent with base::rbind(..., deparse.level=1)

(2) Re: indexing: Just use drop = FALSE (default). The 1x1 case was just a bit surprising at first, but it is perfectly consistent with other uses of [ that yield a latexMatrix object.

(3) Re: the Kronecker product example -- I'll add the partition lines.

(4) I won't touch latexMatrix.R

(5) Any idea about:

I'm looking at the pkgdown documentation for latexMatrix, http://friendly.github.io/matlib/reference/latexMatrix.html

* `partition()` doesn't appear, nor is it in the Reference index. I can't see why this is.

@john-d-fox
Copy link
Collaborator

"Too many things in this thread now!" Indeed. I hoped we could close the thread and open another (or other) more specific thread(s) as necessary.

With respect to:

(1) I've gone with deparse.level (no -1) in both cases since the argument is ignored. The value -1 is passed by the generic in any event. I thought that providing the default value for these methods would be potentially confusing to the user given that the argument is ignored. Feel free to change this to deparse.level=1 in both cases if you wish.

(2) Yes, I had drop=FALSE as the default, and it applied only to the 1 x 1 case, since it's necessary that drop=FALSE when a "latexMatrix" object is returned. Phil's suggestion of instead having a body argument seems ambiguous to me, inasmuch as getBody(X[1, 1]) and getBody(X)[1, 1] produce different results -- the first a 1 x 1 character matrix and the second a 1-element character vector. Which would be intended by body=TRUE? This has gotten too complicated for me. I reverted the changes here that I made locally and have just left [.latexMatrix() as-is. In the probably unusual case where one wants a single character value, just use, e.g., getBody(X)[1, 1]. Please feel free to change [.latexMatrix() to handle the 1 x 1 case more flexibly if you wish.

(3) Thank you.

(4) I committed the small change I made to the deparse.level arg.

(5) I have no idea. More generally, I often find the behaviour of Roxygen and GitHub actions mysterious.

@john-d-fox
Copy link
Collaborator

Sorry -- I accidentally closed and then reopened this issue. I do think that we should close it, but I didn't mean to preempt the decision...

@friendly
Copy link
Owner Author

Closing now...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants