MATLAB Substructure Analysis¤
Note
- This is an additional feature added to OpenSeesMatlab and is not a native OpenSees command.
- Currently, it only supports numerical sub-models created within Matlab.
- Any bugs or new request can be submitted as issues on GitHub.
matlabSubstructure lets an OpenSees model use a substructure calculated by a
MATLAB function. OpenSees treats it like an ordinary Element; when it needs the
Element force or tangent stiffness, it calls your MATLAB callback.
The MATLAB-side workflow is: write one callback, create the OpenSees interface nodes, register the Element, and run the analysis. The complete example below keeps those steps together so it can be copied directly.
Decide what MATLAB and OpenSees each own¤
Treat matlabSubstructure as a condensed boundary Element:
| OpenSees | MATLAB callback |
|---|---|
| Owns the global nodes, constraints, loading and solution algorithm | Owns the internal substructure model behind the interface |
| Supplies trial interface displacement, velocity and acceleration | Converts that motion into interface force, tangent and optional mass/damping |
| Accepts or rejects Newton trials and time steps | Produces a candidate history state from the last accepted state |
| May own ground motion and support excitation | May instead own internal ground excitation, but must not duplicate it |
The command does not automatically condense a MATLAB model. Your callback must already implement the map from interface motion to compatible interface force and tangent. It must not start another OpenSees command from inside the callback.
Complete example¤
First save this callback as mySubstructure.m somewhere on the MATLAB path:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | |
How committedState is stored and advanced¤
committedState is the previously committed trialState. It therefore has
the same MATLAB type, structure, and user-defined fields as the trialState
returned by the callback. MATLAB determines all of those fields; C++ only
stores the returned value, passes it back on later calls, commits it after a
successful step, or restores it on revert. C++ neither adds fields nor
interprets their contents.
The first value comes from initialState: the successful trialState returned
by init becomes the first committedState. After that, each successful
analysis step promotes its accepted trialState to the next
committedState. A useful mental model is that the Element owns three MATLAB
values:
1 2 3 | |
For example, suppose the last accepted state is S0. OpenSees may try several
displacements while solving the next step:
1 2 3 4 5 | |
Notice that calls 2 and 3 receive S0, not S1a or S1b. A Newton trial is
only a proposal; accumulating history from one proposal into the next would
make the result depend on the number of solver iterations. The callback should
therefore calculate each candidate from its third input and return the result
as trialState:
1 2 3 4 | |
Do not increment a persistent variable or modify shared state on every trial:
1 2 3 | |
The complete state transitions implemented by the Element are:
| Action | Third callback input | What C++ does with returned trialState |
|---|---|---|
init |
Original initialState |
Stores the returned value as both the initial working candidate and the first committed state. |
trial |
Current committedState |
Stores the returned value as the latest candidate only. Repeated trials replace this candidate. |
commit |
Latest accepted candidate | If supported successfully, lets the callback finalize the candidate; C++ then copies that candidate to committedState. If unsupported, C++ commits the candidate unchanged. |
revert |
Current committedState |
Ignores the returned state and replaces the candidate with committedState. |
revertToStart |
Original command initialState |
Ignores the returned state and resets both stored states to the original command value. |
shutdown |
Last stored committedState |
Uses the call only as a best-effort cleanup notification; returned state is ignored. |
The name committedState is exact during a trial call. During commit, the
same third argument position contains the accepted candidate so the callback
can optionally finalize it. This action-dependent meaning is why callback code
should normally branch on action before using the third input.
Use MATLAB value data such as structs, numeric arrays, cells, and tables for rollback-safe history. Be careful with handle objects stored inside the state: mutating a handle object in place can also change an earlier stored state, because the states then refer to the same object. If a handle class is needed, implement and use an explicit deep-copy operation before changing it.
Then run this script. It creates the package interface, builds a two-DOF spring, applies a unit load, performs one static step, and reads the result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | |
ok == 0 means the step succeeded. Check it before trusting the queried
results; u2 should be about 0.001. "tangentMode" is optional:
"matlab" uses the current tangent returned by MATLAB (the default), whereas
"initial" returns the Element's fixed initial stiffness whenever OpenSees
asks for its current tangent. That initial stiffness is
response.initialStiffness returned during init, or command K0 when the
field is omitted. The wrapper validates the
tag, callback, dimensions, interface rows, and tangent mode before forwarding
the command to the MEX interface.
matlabSubstructure inputs¤
The five inputs through interfacePairs are required positional arguments.
Optional settings follow as name-value pairs. Optional names are
case-insensitive but must be complete; an unknown or duplicate name, or a name
without a value, is an error.
| Input | Form | Value |
|---|---|---|
eleTag |
required positional | Element and callback-registry tag. |
callback |
required positional | Callback function handle. |
initialState |
required positional | Initial MATLAB-owned history; any MATLAB value, including []. |
initialStiffness |
required positional | Finite real N-by-N double matrix K0. |
interfacePairs |
required positional | N-by-2 double matrix of unique [nodeTag, oneBasedDOF] rows. |
"tangentMode" |
optional name-value | "matlab" (default) or "initial". |
For compatibility, a bare trailing "matlab" or "initial" is still accepted.
Prefer "tangentMode", value in new code.
Algorithms, integrators, and tangentMode¤
matlabSubstructure does not contain an algorithm or integrator whitelist. It
implements the normal OpenSees Element interfaces for resisting force, current
and initial stiffness, mass, and damping. The selected OpenSees algorithm and
integrator decide when and how those quantities are requested and assembled.
Consequently, the Element does not prevent a particular serial algorithm or
static/transient integrator from being selected, but this is not a guarantee
that every possible combination is numerically appropriate for a particular
callback model.
| Setting | Responsibility |
|---|---|
ops.algorithm(...) |
Controls equilibrium iterations and whether OpenSees requests a current or initial stiffness. |
ops.integrator(...) |
Controls the static increment or transient time integration and forms the effective system from stiffness and, when applicable, damping and mass. |
"tangentMode" |
Controls only what this Element's getTangentStiff() returns when OpenSees requests the current tangent. It does not select or restrict an algorithm/integrator. |
| Mode | Element current-tangent return | Typical effect |
|---|---|---|
"matlab" |
Latest response.tangent from the callback |
A Newton-type algorithm can use the callback's state-dependent consistent tangent. This is normally preferred for nonlinear response. |
"initial" |
Fixed Element initial stiffness | The callback force remains nonlinear, but iterations use a fixed stiffness for this Element, giving modified-Newton-like behavior that may require more iterations. |
getInitialStiff() always returns the Element initial stiffness independently
of tangentMode. Therefore, if the OpenSees algorithm itself explicitly asks
for an initial tangent, that algorithm choice takes precedence: selecting
tangentMode="matlab" cannot force such an algorithm to use the current MATLAB
tangent. Conversely, tangentMode="initial" makes even a normal current-tangent
request return the fixed initial matrix for this Element.
The callback must currently return a valid response.tangent for every
successful init/trial evaluation even when tangentMode="initial"; the
Element validates and stores the complete response before OpenSees requests a
matrix. For transient analysis, the effective matrix generally also contains
the callback's response.mass and response.damping (plus assigned Rayleigh
damping) with coefficients chosen by the integrator. tangentMode changes only
the stiffness contribution; it does not change mass, damping, force, state
commit/revert behavior, or time integration.
The implementation has automated coverage for Linear, Newton,
ModifiedNewton, KrylovNewton, and NewtonLineSearch in static examples and
for Newmark and HHT in transient examples. Other standard serial OpenSees
methods use the same Element interfaces, but should be verified for the target
model. Distributed sendSelf/recvSelf and concurrent MATLAB callbacks are not
supported.
Callback sequence during an analysis¤
OpenSees may evaluate one step as follows:
1 2 3 4 5 | |
Several trial calls can occur at the same analysis time. Thus a callback call
is not a time step. trial.dt is measured from the last committed time, and
trial.isNewTime only says that the current time differs from that committed
time; neither means the current trial will be accepted.
Choosing the interface DOFs¤
interfacePairs is an N-by-2 matrix. Each row contains
[OpenSees node tag, MATLAB-style one-based DOF]. N must equal the number of
components in response.force and both dimensions of K0 and
response.tangent.
1 | |
This maps trial.disp(1:4) to node 1 DOFs 1 and 2, followed by node 4 DOFs 1
and 2. All interface vectors use that same order.
The substructure is not restricted to two OpenSees nodes:
- If MATLAB already contains the pile, soil, far-field boundary, and ground reference, expose only the pile-head DOFs.
- If MATLAB describes relative behavior between two physical ends, expose both ends. The second end may be active or fixed in OpenSees.
For a MATLAB pile-soil model with its own ground reference, a single six-DOF pile-head node is enough:
1 2 3 4 5 | |
Do not add a fixed OpenSees ground node if the same reference is already inside MATLAB. Conversely, do not omit the second end if neither side otherwise defines a reference, or the model may have a rigid-body mechanism. Apply a given earthquake excitation in either MATLAB or OpenSees unless the formulation explicitly requires both, so it is not counted twice.
Callback contract¤
1 | |
Keep the ownership boundary clear:
| Object | Owner and purpose |
|---|---|
action |
C++ lifecycle request. |
trial |
Read-only OpenSees current kinematics and analysis context, including explicitly named committed OpenSees quantities. |
committedState |
The previously committed trialState, with the same MATLAB type and user-defined fields. MATLAB defines the fields; C++ only stores, passes, commits, and restores the value without interpreting it. |
response |
Current Element force/tangent and optional matrices returned to OpenSees. |
trialState |
Candidate MATLAB history; it is not accepted merely because the callback returned it. |
status |
Scalar; zero is success and nonzero is failure/unsupported action. |
In particular, committedState does not automatically contain OpenSees force,
displacement, or load factor. Put MATLAB-owned history such as plastic strain,
damage, or internal DOFs in committedState; read authoritative OpenSees values
from trial.
Trial input fields¤
N is the number of interface rows.
| Field | Type/shape | Meaning |
|---|---|---|
disp, vel, accel |
N-by-1 double | Current trial interface kinematics in interfacePairs order. |
committedDisp |
N-by-1 or empty | Displacement at the last successful Element commit; empty during init. |
committedForce |
N-by-1 or empty | Internal resisting force at the last successful Element commit; empty during init and excludes separately assembled inertia/damping. |
time |
scalar double | Current OpenSees Domain time. |
dt |
scalar double | Current time minus last committed time; repeated Newton calls can have the same value. |
previousCommittedTime |
scalar double | Domain time at the last successful Element commit. |
loadFactor |
scalar or empty | Factor of the only load pattern; empty with zero/multiple patterns. A transient pattern value is not a static multiplier. |
elementTag |
scalar double | Element tag. |
action |
char | Copy of the first callback argument. |
isInitial |
logical | True for init and reverttostart. |
isNewTime |
logical | Whether time differs from committed time, not whether the trial will commit. |
activeDofCount |
scalar double | N. |
interfacePairs |
N-by-2 double | Interface node/DOF definition. |
callId |
scalar double | Trial-call counter. |
committedRevision |
scalar double | Successful Element commit count. |
All fields exist for normal init/trial calls. Use isempty for optional
context. Additional fields do not break callbacks that ignore them.
Callback outputs¤
For init and trial, response must be a scalar struct of finite real
doubles:
| Field | Requirement and meaning |
|---|---|
force |
Required N-value internal resisting force. |
tangent |
Required N-by-N tangent consistent with force. |
mass |
Optional current N-by-N mass; omission means zero for this call. |
damping |
Optional current N-by-N damping; omission means zero for this call. |
initialStiffness |
Optional during init; defaults to command K0. |
initialMass |
Optional during init; defaults to init response mass. |
initialDamping |
Optional during init; defaults to init response damping. |
trialState may be any MATLAB value and should contain only the candidate
MATLAB model history. Its type and fields define the type and fields of the
future committedState: after a successful commit, C++ stores that
trialState, and a later trial call receives it as committedState. status
must be scalar zero on success. For notification actions whose response is
unused, return response = struct().
Action lifecycle¤
| Action | Third callback argument | State outcome |
|---|---|---|
init |
Command initialState |
Successful returned trialState becomes the initial committed MATLAB snapshot. |
trial |
Last committed MATLAB snapshot | Returned trialState remains a candidate until commit. |
commit |
Accepted candidate from the last successful trial | Returned state may finalize the candidate and is then committed. |
revert |
Last committed MATLAB snapshot | Returned state is ignored; C++ restores its committed snapshots. |
reverttostart |
Original initialState |
Returned state is ignored; initial snapshots are restored. |
shutdown |
Last stored committed MATLAB snapshot | Optional cleanup; returned state is ignored and trial may be empty after Domain removal. |
This action-dependent table is why the conventional argument name
committedState is exact for trial but should not be interpreted literally
for every notification. Callbacks implementing only init and trial remain
supported through C++ fallback behavior.
When mass or damping is returned, OpenSees forms
1 2 | |
Do not include the same C*v or M*a contribution in response.force. A
constitutive force that inherently depends on velocity may stay in
response.force; normally do not return it again as a damping matrix.
The returned mass and damping are OpenSees Element matrices. Transient analysis
therefore assembles M*a and C*v from the current nodal kinematics.
UniformExcitation is handled by the normal OpenSees
-M*R*groundAcceleration element-load convention. If Rayleigh damping is also
assigned to this Element, OpenSees adds the Rayleigh matrix to
response.damping; do not duplicate it in the callback.
Under UniformExcitation, trial.accel is the nodal relative acceleration
used by the transient integrator, not a request to add the ground acceleration
again. If the MATLAB substructure applies ground motion internally, do not also
apply the same excitation to it through OpenSees.
During trial, calculate only from committedState and return a candidate
trialState. Do not retain or reuse uncommitted history in persistent/global
MATLAB storage.
For a path-dependent model, always calculate history this way:
1 2 3 4 5 6 | |
Never advance a persistent/global history on every trial call. Newton can
retry the same step, so that would accumulate unaccepted history. The tangent
should be consistent with the returned force; otherwise Newton convergence may
be slow or fail. Selecting "initial" tangent mode merely makes OpenSees use a
fixed initial tangent (modified Newton); the callback must still return the
correct nonlinear force.
Static and transient models¤
For static stiffness-only behavior, return only force and tangent. For a
transient model, return mass and damping only if they physically belong to the
MATLAB substructure. Do not also put the same mass on OpenSees interface nodes.
Mass and damping must be full N-by-N matrices; use zero rows and columns for
DOFs that have none.
Omitting mass or damping in a trial means zero for that trial, not “reuse the
last value.” During init, K0 is the fallback initialStiffness, and
initialMass/initialDamping default to the mass/damping returned by init.
These initial matrices remain fixed after initialization.
Querying and recording results¤
Queries read stored C++ results and do not invoke the callback:
1 2 3 4 5 6 7 8 9 10 | |
The wrapper may return a matrix as a flattened row vector, although the
underlying OpenSees value remains N-by-N. Aliases include force, forces,
globalForce, activeForce, localForce, disp, vel, accel, stiffness,
initialMass, initialDamping, and interfacePairs. Here localForce means
interface force; this Element has no separate geometric local axes.
Performance queries include callCount, trialCallCount, commitCount,
callbackTime, lastCallbackTime, maxCallbackTime, and meanCallbackTime:
1 2 | |
They count actual MATLAB calls and reset on revertToStart().
Restrictions and cleanup¤
- Do not call
ops,opsMAT, or the underlying MEX interface inside the callback; re-entering the MEX call is unsupported. - Callbacks run only in the local MEX process and are not thread-safe.
- Distributed
sendSelf/recvSelfis not supported. - Remove active Elements first (normally with
ops.wipe()), then useops.unregisterMatlabSubstructure(eleTag)for one callback orops.clearMatlabSubstructures()for all callbacks. - Use
ops.hasMatlabSubstructure(eleTag)to test whether a tag is registered.
The Element does not select the analysis algorithm or integrator. Configure
Linear, Newton, ModifiedNewton, KrylovNewton, NewtonLineSearch,
Newmark, HHT, and other methods through the usual OpenSees commands.
Checklist before a full analysis¤
- All interface nodes already exist; each
[nodeTag, DOF]is valid and unique. - N equals the force length and every dimension of
K0and the returned tangent, mass and damping matrices. - Every response value is a finite real
doubleand uses internal resisting-force signs. - Ground reference, mass, damping and earthquake excitation each exist only once across MATLAB and OpenSees.
- The returned tangent has been compared with a small finite-difference change in force.
- The script checks
ok == 0before using results. - Cleanup is performed with
ops.wipe()before unregistering or clearing callback records.