friendly / matlib

Matrix Functions for Teaching and Learning Linear Algebra and Multivariate Statistics
http://friendly.github.io/matlib/
65 stars 16 forks source link

latexMatrix arithmetic: trivial simplifications? #58

Closed friendly closed 2 months ago

friendly commented 2 months ago

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:

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?

john-d-fox commented 2 months ago

OK -- I've now moved the rbind.latexMatrix(), cbind.latexMatrix(), and [.latexMatrix() methods and examples to R/latexMatrix.R.

I hope that you didn't end up losing work.

philchalmers commented 2 months ago

Great, thanks John. I've uploaded a dev/addPartitions.R function to add partitioning lines as a complimentary helper. Not sure if more flexibly should be added, such as \multicolumn{.} and \cline{.} for partial partitioning, but it's a start.

john-d-fox commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

@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 commented 2 months ago

Actually, I wasn't suggesting using the partitions in further operations (as you say, that doesn't necessarily make sense), just that your approach of modifying the elements of a matrix would interfere with, e.g., adding, multiplying, etc., matrices.

Also, after further thought, the plan I proposed is too elaborate. I think that I can do what's needed more simply.

On 2024-08-29 10:20 a.m., Phil Chalmers wrote:

Caution: External email.

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.

— Reply to this email directly, view it on GitHub <https://github.com/ friendly/matlib/issues/58#issuecomment-2317843548>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ ADLSAQU6D5532X36FRKJ35TZT4U2VAVCNFSM6AAAAABM42L7RCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJXHA2DGNJUHA>. You are receiving this because you were mentioned.Message ID: <friendly/ @.***>

friendly commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

You might want to hold off on partitioned matrices for now since this is in flux.

I just added dev/partition.R with a partition() function that alters just the printed representation of the object, and is, e.g., used as

X <- latexMatrix(nrow=5, ncol=6) partition(X, rows=c(2, 4), columns=c(3, 5))

\begin{pmatrix} \begin{array}{c c c | c c | c} x{11} & x{12} & x{13} & x{14} & x{15} & x{16}\ x{21} & x{22} & x{23} & x{24} & x{25} & x{26}\ \hline x{31} & x{32} & x{33} & x{34} & x{35} & x{36}\ x{41} & x{42} & x{43} & x{44} & x{45} & x{46}\ \hline x{51} & x{52} & x{53} & x{54} & x{55} & x{56}\ \end{array} \end{pmatrix}

If this proves sound (and it would be a good idea to try it out to test it some more), then I would add it to R/latexMatrix.R

On 2024-08-29 11:45 a.m., Michael Friendly wrote:

Caution: External email.

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.png (view on web) <https://github.com/user-attachments/assets/ ecd4fe9e-1d4c-49f8-9088-5f77dce5fa80>

— Reply to this email directly, view it on GitHub <https://github.com/ friendly/matlib/issues/58#issuecomment-2318192330>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ ADLSAQWTMBV4WYAS26TEVX3ZT46ZNAVCNFSM6AAAAABM42L7RCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJYGE4TEMZTGA>. You are receiving this because you were mentioned.Message ID: <friendly/ @.***>

john-d-fox commented 2 months ago

I think that adding the partition information to the object is more elaborate than necessary. The partition() function I just added in dev/partition.R simply modifies the $matrix slot of the "latexMatrix" object and so affects only the printed representation of the object.

On 2024-08-29 11:47 a.m., Phil Chalmers wrote:

Caution: External email.

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.

— Reply to this email directly, view it on GitHub <https://github.com/ friendly/matlib/issues/58#issuecomment-2318198664>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ ADLSAQS6O7PV2YWQWGG5CIDZT47CXAVCNFSM6AAAAABM42L7RCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMJYGE4TQNRWGQ>. You are receiving this because you were mentioned.Message ID: <friendly/ @.***>

friendly commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

I don't think that we should always make 1 x 1 matrices into "scalars" but in this case I think that it makes sense to return the contents of the single element as a scalar (i.e., one-element R character vector).

On 2024-08-30 5:47 p.m., Michael Friendly wrote:

Caution: External email.

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"?

— Reply to this email directly, view it on GitHub <https://github.com/ friendly/matlib/issues/58#issuecomment-2322382905>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/ ADLSAQULY4ZWCZ5MI52GHYDZUDR7RAVCNFSM6AAAAABM42L7RCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMRSGM4DEOJQGU>. You are receiving this because you were mentioned.Message ID: <friendly/ @.***>

philchalmers commented 2 months ago

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 commented 2 months ago

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

friendly commented 2 months ago

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:

john-d-fox commented 2 months ago

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

(1) I can make the deparse.levelargument 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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

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 commented 2 months ago

No disagreement on that, though by the same token why not use the getBody() logic to get the scalar element?

Perhaps instead of A[,] with a drop logical a A[,,body=TRUE] could be used?

--- Sent from my mobile phone. Please excuse any tpyos or unusual autocorrect-ships

On Sat, Aug 31, 2024, 12:00 PM John Fox @.***> wrote:

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]?

— Reply to this email directly, view it on GitHub https://github.com/friendly/matlib/issues/58#issuecomment-2322944659, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAJXEYDFP6GOVZ6STIPG4A3ZUHSCFAVCNFSM6AAAAABM42L7RCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMRSHE2DINRVHE . You are receiving this because you commented.Message ID: @.***>

friendly commented 2 months ago

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 commented 2 months ago

"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 commented 2 months ago

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 commented 2 months ago

Closing now...