nonlinear constraints

22 replies [Last post]
kgrimm's picture
Offline
Joined: 08/04/2009

I was wondering if nonlinear constraints were available and if so, if additional parameters can be incorporated into the nonlinear constraint. An example involves nonlinear growth curves (Browne, 1993).

prast's picture
Offline
Joined: 08/18/2010
Does anyone has a solution to

Does anyone has a solution to Kevin's question in the meantime? Defining a structured loading matrix according to a nonlinear target function is straightforward in Mplus see e.g. Grimm and Ram (2009). I sense that it is technically also possible to define nonlinear constraints in openMx - but is it also practically feasible?

Lit.:
Grimm, K., & Ram, N. (2009). Nonlinear growth models in Mplus and SAS. Structural Equation Modeling, 16, 676 - 701.

neale's picture
Offline
Joined: 07/31/2009
I believe it should be

I believe it should be straightforward to devise a general script in OpenMx, much as Jack & I did in the case of fitting non-linear growth curves to data from twins* using classic Mx. By general, I mean a script that could be fitted to datasets with differing numbers of occasions, without altering the body of the model specification. There are several ways to attack this problem, of which the mxConstraint() function might seem the most verbally similar to the forum topic. However, I think it is more efficient to use mxAlgebra() to specify the growth curve function for the predicted means, and to specify the factor loadings as the partial derivatives of the function for the factor loadings. For example, the guts of it for a logistic LGC model might look like this:

# Matrices for parameters & some constants
mxMatrix("Unit", name="u", nrow = noccasions, ncol = 1)
mxMatrix("Full", name="t", nrow = noccasions, ncol = 1, free=F, values=c(1:noccasions))
mxMatrix("Full", name="a", nrow=1, ncol=1, free=TRUE, values=.1)
mxMatrix("Full", name="i", nrow=1, ncol=1, free=TRUE, values=.1)
mxMatrix("Full", name="r", nrow=1, ncol=1, free=TRUE, values=.1)

# Logistic function for means
mxAlgebra(i %x% u + (a-i) %x% (\exp(-(t-u) %x% r), name="expMean")
#dJ/da - asymptote factor loadings
mxAlgebra((i %x% u-(\exp(-(t-u) %x% r)).(((a * i) %x% U)/J)) / J, name="K");
#dJ/di - intercept factor loadings
mxAlgebra( ( (a %x% u) - (u-(\exp(-(t-u) %x% r))). (((a * i) %x% U)/J) ) /J, name="L" )
#dJ/dr - rate factor loadings
mxAlgebra( ( ((a-i) %x% (t-u)). (\exp(-(t-u) %x% r)). (((a * i) %x% U)/J) ) / J, name="M")

# combined factor loading matrix
mxAlgebra(cbind(K,L,M), name="F")

-- except without all the typos and errors that this quick draft probably contains :). Must implement this properly soon... but HTH for now.

*Neale M.C., McArdle JJ: Structured latent growth curves for twin data. Twin Res 2000; 3:165-177 http://www.vipbg.vcu.edu/vipbg/Articles/TwinRes-structured-2000.pdf

prast's picture
Offline
Joined: 08/18/2010
Thank you Michael, it works

Thank you Michael, it works perfectly!
I just needed to change some minor things:

# Matrices for parameters & some constants
mxMatrix("Unit", name="u", nrow = noccasions, ncol = 1),
mxMatrix("Full", name="t", nrow = noccasions, ncol = 1,
free=FALSE, values=c(1:noccasions)),
mxMatrix("Full", name="a", nrow=1, ncol=1, free=TRUE, values=15, label="alpha"),
mxMatrix("Full", name="i", nrow=1, ncol=1, free=TRUE, values=3, label="beta", lbound=0),
mxMatrix("Full", name="r", nrow=1, ncol=1, free=TRUE, values=.5, label="gamma"),
## Logistic function:
mxAlgebra(i %x% u + (a-i) %x% exp(-(t-u) %x% r), name="J"),
## Derivatives (cf. Neale & McArdle):
#dJ/da - asymptote factor loadings: Dim 7X1
mxAlgebra(((i %x% u-(exp(-(t-u) %x% r))/(((a * i) %x% u)/J)) / J), name="K"),
#dJ/di - intercept factor loadings: Dim 7X1
mxAlgebra((( (a %x% u) - (u-(exp(-(t-u) %x% r)))/(((a * i) %x% u)/J) ) /J), name="L" ),
#dJ/dr - rate factor loadings: Dim 7X1
mxAlgebra(( ((a-i) %x% (t-u))/(exp(-(t-u) %x% r))/(((a * i) %x% u)/J) ) / J, name="W"),
# combined factor loading matrix: Dimension = obs X parameters
mxAlgebra(cbind(K,L,W), name="LM"),
### logistic function for means
mxAlgebra(t(i %x% u + (a-i) %x% exp(-(t-u) %x% r)), name="expMean"),

mspiegel's picture
Offline
Joined: 07/31/2009
Try separating out common

Try separating out common subexpressions into their own algebras. OpenMx currently does not perform common subexpression elimination, which means these expressions are evaluated N times if they appear N times. One example would be: mxAlgebra( ((a * i) %x% u) / J, name = "subexpression1"). And then you could use 'subexpression1' in the other three algebras. The same with exp(-(t-u) %x% r)

Ryne's picture
Offline
Joined: 07/31/2009
Hey Kevin, They can, using

Hey Kevin,

They can, using the mxConstraint function, but that function only works on mxAlgebra and mxMatrix objects.

If you want to constrain individual parameters, you'll have to put those parameters in additional matrices. For instance, consider a RAM model with matrices A, S and F (which you could create using either paths or matrices, and using whatever objective function you like). You have two parameters "lambda1" and "lambda2" in your A matrix, and you want "lambda1" to be greater than "lambda2".

You'll have to create two new matrices, each a 1x1 and containing one free parameter (let's call them "L1" and "L2"). Label those free parameters "lambda1" and "lambda2". Then use mxConstraint to define the relationship between "L1" and "L2". As long as you include "L1", "L2" and your MxConstraint object in your model, you'll constrain those parameters. Here's a quick mock-up of an example.

model <- mxModel("testing nonlinear constraints",
    mxData(whatever....),
    mxMatrix(whatever....name="A"), #has parameters lambda1 and lambda2 in labels argument
    mxMatrix(whatever....name="S"),
    mxMatrix(whatever....name="F"),
    mxMatrix(whatever....name="M"),
    mxMatrix(Full, nrow=1, ncol=1, free=T, values=1, label="lambda1", name="L1"),
    mxMatrix(Full, nrow=1, ncol=1, free=T, values=1, label="lambda2", name="L2"),
    mxConstraint("L1", ">", "L2"),
    mxRAMObjective("A","S","F","M")
)

kgrimm's picture
Offline
Joined: 08/04/2009
Thanks Ryne. In the

Thanks Ryne. In the mxConstraint command, is it possible to write out "complex" nonlinear functions? A simple case would be an exponential curve (loading1 = 1 - exp(-L1 * 5)).
Thanks again,
k

mspiegel's picture
Offline
Joined: 07/31/2009
The constraint (loading1 = 1

The constraint (loading1 = 1 - exp(-L1 * 5)) can be expressed. The left-hand side and right-hand side of the mxConstraint() function are both mxAlgebra() expressions. As a consequence of this fact, both the left-hand side and right-hand side must evaluate to matrices. In order to express scalar constraints, use algebras that evaluate to 1x1 matrices. The exp() function is supported by the mxAlgebra() function. To see a full list of the matrix operators and functions available, type ?mxAlgebra in R.

kgrimm's picture
Offline
Joined: 08/04/2009
Thanks. I'm still not sure if

Thanks. I'm still not sure if I understand some of the intricacies of what you're describing. So, if I want to force a factor loading, say in the "A" matrix in RAM notation, to be equal to "1-exp(L1*5)" where L1 is a parameter to be estimated. Would it be necessary to create a 1x1 matrix with an element labeled the same as the factor loading in the "A" matrix I'm trying to constrain and refer to this 1x1 matrix in mxConstraint? Thanks again.

mspiegel's picture
Offline
Joined: 07/31/2009
One way to implement what

One way to implement what you've described is to add some MxMatrices and one MxAlgebra to your model. One of the MxMatrix objects, let's call it "leftHandSide", will be a 1x1 matrix that either has a fixed value (the factor loading from the 'A' matrix) or has a free parameter (with the same name as the factor loading from the 'A' matrix).

Then create another MxMatrix object, let's call it "l1matrix", that is a 1x1 matrix that contains the free parameter L1. Next we need a MxMatrix object, call it "one" that contains only the value 1. And a MxMatrix object, call it "five" that contains only the value 5.

Finally, the MxAlgebra statement, let's call it "rightHandSide", has the expression:
one - exp(l1matrix * five). Sorry for all the complications but algebras only work on matrix operations, not scalar operations.

Now you can add mxConstraint("leftHandSide", "=", "rightHandSide") to your model.

Steve's picture
Offline
Joined: 07/30/2009
This is a question for Mike.

This is a question for Mike. I'm unsure of the exact state of the namespaces, but at one point we had discussed allowing a free parameter to be a result of an algebra that evaluated to a 1x1 matrix.

If so, Kevin could create a 1x1 matrix called beta

mxMatrix("Full", 1, 1, values=0.2, free=T, name="beta")

and 4 algebras

mxAlgebra(1-exp(beta*1), name="lambda1")
mxAlgebra(1-exp(beta*2), name="lambda2")
mxAlgebra(1-exp(beta*3), name="lambda3")
mxAlgebra(1-exp(beta*4), name="lambda4")

and then add labels for the elements of the target matrix (or paths) as "lambda1", "lambda2", ... etc.

Is that legal as currently implemented?

mspiegel's picture
Offline
Joined: 07/31/2009
We have implemented algebra

We have implemented algebra substitution and matrix substitution, whereby the result of a 1x1 algebra or matrix could be substituted into a single element of another matrix. Your example isn't using algebra or matrix substitution (because there's only one MxMatrix expression and it has only free values, where a fixed value is needed to be the target of a substitution). The example is almost valid OpenMx, except that we don't have constant substitution. We talked about implementing constant substitution (where a constant scalar or matrix was automatically converted into a MxMatrix), and free parameter substitution (where a free parameter was converted into a 1x1 matrix), it's on the TODO list.

To remember the differences between substitutions: the target of algebra substitution and matrix substitution occurs inside of MATRICES, and the target of constant substitution and free parameter substitution occurs inside of ALGEBRAS.

Steve's picture
Offline
Joined: 07/30/2009
Maybe I should write out the

Maybe I should write out the whole section of code, because the way I'm reading your reply, this should work:

mxModel("foo",
   mxMatrix("Full", 1, 1, values=0.2, free=T, name="beta")
   mxAlgebra(1-exp(beta*1), name="lambda1")
   mxAlgebra(1-exp(beta*2), name="lambda2")
   mxAlgebra(1-exp(beta*3), name="lambda3")
   mxMatrix("Full", 4, 4, byrow=TRUE,
      values=c(0,0,0,.2,0,0,0,.2,0,0,0,.2,0,0,0,0),
      free=c(F,F,F,T,F,F,F,T,F,F,F,T,F,F,F,F),
      labels=c(NA,NA,NA,"lambda1,NA,NA,NA,"lambda2",NA,NA,NA,"lambda3",NA,NA,NA,NA)
   )
)

Ryne's picture
Offline
Joined: 07/31/2009
How

How about:

mxModel("foo",
#Specify A, S and F Matrices, with parameters lambda1-lambda5
   mxMatrix(..., name="A"),
   mxMatrix(..., name="S"),
   mxMatrix(..., name="F"),
   mxMatrix(..., name="M"),
#Put the lambda parameters in a matrix,
#thus constraining them to be equal to the parameters in the A matrix
   mxMatrix("Full", 5, 1, free=T, values=0.2,
      labels=c("lambda1", "lambda2", "lambda3", "lambda4", "lambda5", name="loadings"),
#Begin trickery. 
#Put "beta" in a 5 x 1 matrix five times. 
#Giving a matrix the same name as the only free parameter in it is almost certainly bad
   mxMatrix("Full", 5, 1, free=T, values=0.2, labels="beta", name="beta"),
#Create matrices of constants 1:5 and 1 five times.
   mxMatrix("Full", 5, 1, F, 1:5, NA, name="oneToFive"),
   mxMatrix("Full", 5, 1, F, 1, NA, name="one"),
#Do the algebra
   mxAlgebra(one-exp(beta*oneToFive, name="constraints"),
#Make the constraint
   mxConstraint("loadings", =, "constraints"),
#Edited: needs an objective function
   mxRAMObjective("A", "S", "F", "M")
)

Howzit?

Steve's picture
Offline
Joined: 07/30/2009
An interesting way of

An interesting way of circumventing the current limitations of mxAlgebra.

But, beta, the matrix, and beta, the parameter.... Oww! My namespace hurts.

Seriously, if we are ever to allow parameters to appear directly in algebras, this type of willful namespace abuse should throw an error, and soon.

I like the idea of using definition variables to express the exact time lag since the inception of the experiment. Not only is that useful, but in the multilevel context, it has been shown to lead to increased parameter precision.

mspiegel's picture
Offline
Joined: 07/31/2009
Oops, I didn't notice that

Oops, I didn't notice that 'beta' was used as a matrix name and free parameter name in the example. Yes, this should be disallowed. I'll fix it (maybe open a ticket and then fix it).

mspiegel's picture
Offline
Joined: 07/31/2009
OK, revision 771 in the

OK, revision 771 in the repository ensures that free parameters and named entities cannot have overlapping names. I also added a new script to the test suite that shows off the "check to make sure that errors occur" function, omxCheckError(). You can see an example of it here: http://openmx.psyc.virginia.edu/dev/browser/trunk/models/passing/NamePar.... I will document this function tomorrow morning.

mspiegel's picture
Offline
Joined: 07/31/2009
Yeah, that looks correct to

Yeah, that looks correct to me.

neale's picture
Offline
Joined: 07/31/2009
And for continuous time, or

And for continuous time, or datasets where there are extensive missing values (say each person measured only on 3 of 100 possible occasions) we might want to use definition variables in place of the 1, 2, 3 etc. to generate lambda loadings specific to that individual. Hopefully that would work as well.

mspiegel's picture
Offline
Joined: 07/31/2009
No the "1" and the "3"

No the "1" and the "3" located in mxAlgebra(1-exp(beta*3), name="lambda3") are constants, and are (currently) not allowed in MxAlgebra statements. Only MxMatrices or other MxAlebra statements are allowed as operands.

neale's picture
Offline
Joined: 07/31/2009
It sort of looks like that to

It sort of looks like that to me, but Michael S will know for sure. This makes me wonder whether we need a

as.mxMatrix()

function, which could be applied to mxPaths or mxMatrix elements which were identified by label="" arguments. Thus the explicit creation of matrices specifically for the purposes of constraints would not be necessary, simplifying Michael's mock-up example to:

model <- mxModel("testing nonlinear constraints",
mxData(whatever....),
mxMatrix(whatever....name="A"), #has parameters lambda1 and lambda2 in labels argument
mxMatrix(whatever....name="S"),
mxMatrix(whatever....name="F"),
mxMatrix(whatever....name="M"),
mxConstraint(as.mxMatrix(lambda1), ">", as.mxMatrix(lambda2)),
mxRAMObjective("A","S","F","M")
)

Ryne's picture
Offline
Joined: 07/31/2009
Unfortunately, "lambda1"

Unfortunately, "lambda1" isn't a named entity. There can be multiple "lambda1"s in the model (to enforce equality constraints, for instance), which can't occur with named entities. Using as.mxMatrix("lambda1") would look for an MxModel, MxAlgebra or other object, not an element in an MxMatrix object. The closest thing we have is mxMatrix itself.

Kevin, it is possible to specify an entire set of constraints in a single mxConstraint object. If all of your loadings get the same transformation, they all go in the same matrix.


mxMatrix(whatever...name="A") #has parameters lambda1-4
....
mxMatrix("Full", 1, 4, TRUE, values=c(1,2,3,4), labels=c("lambda1", "lambda2", "lambda3", "lambda4"), name="alpha")
mxMatrix("Full", 1, 4, TRUE, values=c(1,2,3,4), labels=c("l1", "l2", "l3", "l4"), name="beta")
mxConstraint(alpha, "=", 1-exp(beta*5))
....

Do I understand your model right, Kevin?

kgrimm's picture
Offline
Joined: 08/04/2009
Hi Ryne. So, the loadings

Hi Ryne. So, the loadings would have similar constraints - the only thing that would change is the number (in this case 5) based on the timing of the measurement occasions. For example, I would want the first factor loading to equal "1-exp(beta*1)", the second loading to equal "1-exp(beta*2)", etc.