haifengl / smile

Statistical Machine Intelligence & Learning Engine
https://haifengl.github.io
Other
5.97k stars 1.13k forks source link

InnerProduct of vectors created with cas.Vars not being simplified #762

Closed danielrmeyer closed 2 months ago

danielrmeyer commented 3 months ago

I have been experimenting with the cas package. I am working with this code snippet.

import smile.cas._

val x = Var("x")
val v = Vars(x, 2 * x, x ** 2)
val innerProduct = InnerProduct(v, v)
innerProduct.simplify

val v1 = Vars(0.0, 1.0, 3.0)
InnerProduct(v1, v1).simplify

val v2 = VectorVal(Array(0.0, 1.0, 3.0))
InnerProduct(v2, v2).simplify

The results I get are:

val x: smile.cas.Var = x
val v: smile.cas.Vars = [x, 2.0 * x, x ** 2.0]
val innerProduct: smile.cas.InnerProduct = <[x, 2.0 * x, x ** 2.0], [x, 2.0 * x, x ** 2.0]>

val res0: smile.cas.Scalar = <[x, 2.0 * x, x ** 2.0], [x, 2.0 * x, x ** 2.0]>

val v1: smile.cas.Vars = [0.0, 1.0, 3.0]

val res1: smile.cas.Scalar = <[0.0, 1.0, 3.0], [0.0, 1.0, 3.0]>

val v2: smile.cas.VectorVal = [0.0, 1.0, 3.0]

val res2: smile.cas.Scalar = 10.0

Expected: InnerProduct(v, v) should fully expand and reduce on .simplify

Got val innerProduct: smile.cas.InnerProduct = <[x, 2.0 * x, x * 2.0], [x, 2.0 x, x ** 2.0]>

danielrmeyer commented 3 months ago

Here is the version I am using: libraryDependencies += "com.github.haifengl" %% "smile-scala" % "3.1.0"

haifengl commented 3 months ago

simplify method expects an env input that provides variable values. Try

innerProduct.simplify(("x", Val(1)))

It will simplify to the value 6.0.

danielrmeyer commented 3 months ago

The problem seem to be here: https://github.com/haifengl/smile/blob/master/scala/src/main/scala/smile/cas/Vector.scala#L326

The issue is the pattern match does not check for a case(Vars(a*), Vars(b*)) => ?

Adding in the following case works for me: case(Vars(a*), Vars(b*)) => a.zip(b).map{ case(i, j) => i * j}.reduce(_ + _)

haifengl commented 3 months ago

What you propose is eager evaluation, which is not the purpose of simpify. Overall, cas prefer lazy evaluation.

danielrmeyer commented 3 months ago

It seems to me that simplify should go as far as possible to evaluate the expression but remain lazy in the sense that it only evaluates as far as it needs to (or can). The current result from simplify of val innerProduct: smile.cas.InnerProduct = <[x, 2.0 * x, x ** 2.0], [x, 2.0 * x, x ** 2.0]> is just telling me back that I am asking for an InnerProduct; however, the result I really would like to see is val innerProduct: smile.cas.Scalar = 5.0 * (x ** 2.0) + x ** 4.0 (with integer exponents rather than floats though). That result would still be lazy in the sense that we can carry the Scalar expression around until we are ready to substitute an actual value. Waiting to get a numerical value to carry out the algebraic operations does not seem right to me. I do like the idea of not carrying out any simplification until simplify is actually called though keeping in the spirit with lazy evaluation, but when I ask it to simplify my inner product I would like it to apply what rules it can to get to the scalar expression.

haifengl commented 3 months ago

I would call it "expand", not "simplify". It will cause the number of items in a formula significantly increased in some cases (e.g., quadratic).