nonlinear constraints

Posted on
No user picture. kgrimm 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).

Replied on Mon, 08/17/2009 - 16:19
Picture of user. Ryne Joined: 07/31/2009

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

Replied on Thu, 08/20/2009 - 14:32
No user picture. kgrimm Joined: 08/04/2009

In reply to by Ryne

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

Replied on Thu, 08/20/2009 - 14:36
Picture of user. mspiegel Joined: 07/31/2009

In reply to by kgrimm

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.

Replied on Thu, 08/20/2009 - 15:20
No user picture. kgrimm Joined: 08/04/2009

In reply to by mspiegel

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.

Replied on Thu, 08/20/2009 - 15:29
Picture of user. neale Joined: 07/31/2009

In reply to by kgrimm

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

Replied on Thu, 08/20/2009 - 17:08
Picture of user. Ryne Joined: 07/31/2009

In reply to by neale

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?

Replied on Mon, 08/24/2009 - 17:01
No user picture. kgrimm Joined: 08/04/2009

In reply to by Ryne

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.

Replied on Thu, 08/20/2009 - 16:58
Picture of user. mspiegel Joined: 07/31/2009

In reply to by kgrimm

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.

Replied on Tue, 08/25/2009 - 09:01
Picture of user. Steve Joined: 07/30/2009

In reply to by mspiegel

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?

Replied on Tue, 08/25/2009 - 09:20
Picture of user. mspiegel Joined: 07/31/2009

In reply to by Steve

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.

Replied on Tue, 08/25/2009 - 13:23
Picture of user. Steve Joined: 07/30/2009

In reply to by mspiegel

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)
   )
)
Replied on Tue, 08/25/2009 - 13:28
Picture of user. mspiegel Joined: 07/31/2009

In reply to by Steve

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.

Replied on Tue, 08/25/2009 - 15:14
Picture of user. Ryne Joined: 07/31/2009

In reply to by Steve

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?

Replied on Thu, 08/27/2009 - 14:51
Picture of user. neale Joined: 07/31/2009

In reply to by mspiegel

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.

Replied on Thu, 08/27/2009 - 19:25
Picture of user. Steve Joined: 07/30/2009

In reply to by Ryne

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.

Replied on Thu, 08/27/2009 - 22:30
Picture of user. mspiegel Joined: 07/31/2009

In reply to by mspiegel

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/NameParameterOverlap.R?rev=771. I will document this function tomorrow morning.

Replied on Wed, 08/25/2010 - 04:40
No user picture. prast Joined: 08/18/2010

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.

Replied on Wed, 08/25/2010 - 18:17
Picture of user. neale Joined: 07/31/2009

In reply to by prast

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

Replied on Wed, 09/01/2010 - 09:30
No user picture. prast Joined: 08/18/2010

In reply to by neale

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"),

Replied on Wed, 09/01/2010 - 09:39
Picture of user. mspiegel Joined: 07/31/2009

In reply to by prast

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)