The static integrators in OpenSees, including displacement control, arc length, and minimum unbalanced displacement norm (MUDN), are based on an incremental-iterative framework. After an initial load increment, each integrator imposes a constraint on the change in load factor at subsequent equilibrium iterations within a pseudo-time step.

Displacement control calculates the change in load factor necessary to keep the displacement at a specified DOF constant during iteration. Arc length and MUDN impose other constraints to determine the change in load factor. Load control also fits within this framework, but as a trivial case where the constraint is that the change in load factor is zero.

Although the documentation and many examples would lead you to believe load control controls the load factor, $\lambda$, you are actually controlling the pseudo-time, t. Here’s the source code to prove it.

You see variables currentLambda and deltaLambda sprinkled throughout the LoadControl class. But look at lines 127 through 130. These statements are getting, updating, and applying time in the AnalysisModel.

Sure, the time and load factor are the same for the common case of a linear time series where $\lambda(t)=t$. But, really, load control should be called pseudo-time control.

The point of all this is that imposed displacements via load control is often confused with the displacement control integrator. I mean, what’s clear about the fact that you have to define loads to use displacement control and you have to use load control to impose displacements?

Displacement control is best used when you have a spatial distribution of reference loads that you want to keep in proportion during a pushover analysis, i.e., you want to maintain a vertical distribution of lateral loads. You can control only one degree of freedom (DOF) with displacement control.

ops.timeSeries('Linear',2)

ops.pattern('Plain',2,2)

dU = 0.2
ops.integrator('DisplacementControl',3,1,dU)

To impose displacements at more than one DOF, you need to use sp constraints inside a load pattern. Imposing displacements at multiple locations is not very common in 2D analysis–you’re more likely to see it when imposing bidirectional displacements to 3D models–but it’s shown here anyway for the sake of demonstration.

The 2.1336 ratio of imposed displacements is based on linear response of the frame for the load pattern used in displacement control.

ops.timeSeries('Linear',2)

dU = 0.2
ops.pattern('Plain',2,2)
ops.sp(3,1,dU)
ops.sp(2,1,dU/2.1336) # Based on linear analysis

ops.integrator('LoadControl',1.0) # Using dt=1.0

Like reference loads, there is no unique way to define imposed reference displacements. So, we could have used 1.0 and 1.0/2.1336 for the sp command along with dU as the time step in the load control integrator.

As expected, the results are quite different because the imposed displacements do not remain proportional to the displacement control load pattern after yield.

These two approaches give the same result if you control only one DOF.

A more practical application of imposed displacements at multiple DOFs is discussed in this video. Define your displacement history in a Path time series, then march through pseudo-time with the load control, or rather pseudo-time control, integrator.