1 /* -------------------------------------------------------------------------- *
2 * OpenSim: testModelInterface.cpp *
3 * -------------------------------------------------------------------------- *
4 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
5 * See http://opensim.stanford.edu and the NOTICE file for more information. *
6 * OpenSim is developed at Stanford University and supported by the US *
7 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
8 * through the Warrior Web program. *
9 * *
10 * Copyright (c) 2005-2017 Stanford University and the Authors *
11 * *
12 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
13 * not use this file except in compliance with the License. You may obtain a *
14 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
15 * *
16 * Unless required by applicable law or agreed to in writing, software *
17 * distributed under the License is distributed on an "AS IS" BASIS, *
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
19 * See the License for the specific language governing permissions and *
20 * limitations under the License. *
21 * -------------------------------------------------------------------------- */
22
23 #include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
24 #include <OpenSim/Simulation/Model/Model.h>
25 #include <OpenSim/Simulation/Model/PhysicalOffsetFrame.h>
26 #include <OpenSim/Simulation/SimbodyEngine/PinJoint.h>
27 #include <OpenSim/Simulation/Manager/Manager.h>
28 #include <OpenSim/Common/LoadOpenSimLibrary.h>
29
30 using namespace OpenSim;
31 using namespace std;
32
33 void testModelFinalizePropertiesAndConnections();
34 void testModelTopologyErrors();
35
main()36 int main() {
37 LoadOpenSimLibrary("osimActuators");
38
39 SimTK_START_TEST("testModelInterface");
40 SimTK_SUBTEST(testModelFinalizePropertiesAndConnections);
41 SimTK_SUBTEST(testModelTopologyErrors);
42 SimTK_END_TEST();
43 }
44
45
testModelFinalizePropertiesAndConnections()46 void testModelFinalizePropertiesAndConnections()
47 {
48 Model model("arm26.osim");
49
50 model.printSubcomponentInfo();
51
52 // all subcomponents are accounted for since Model constructor invokes
53 // finalizeFromProperties().
54 ASSERT(model.countNumComponents() > 0);
55
56 // model must be up-to-date with its properties
57 ASSERT(model.isObjectUpToDateWithProperties());
58
59 // get writable access to Components contained in the model's Set
60 auto& muscles = model.updMuscles();
61 // to make edits, for example, muscles[0].upd_min_control() = 0.02;
62 ASSERT(!model.isObjectUpToDateWithProperties());
63
64 model.finalizeFromProperties();
65 ASSERT(model.isObjectUpToDateWithProperties());
66
67 // get writable access to another Set for the purpose of editing
68 auto& bodies = model.updBodySet();
69 // for example, bodies[1].upd_mass() = 0.05;
70 ASSERT(!model.isObjectUpToDateWithProperties());
71
72 model.finalizeFromProperties();
73 ASSERT(model.isObjectUpToDateWithProperties());
74
75 // make an edit through model's ComponentList access
76 for (auto& body : model.updComponentList<Body>()) {
77 body.upd_mass_center() = SimTK::Vec3(0);
78 break;
79 }
80 ASSERT(!model.isObjectUpToDateWithProperties());
81
82 SimTK::State dummy_state;
83
84 ASSERT_THROW(ComponentHasNoSystem, model.getSystem());
85 ASSERT_THROW(ComponentHasNoSystem, model.realizeDynamics(dummy_state));
86 ASSERT_THROW(ComponentHasNoSystem,
87 model.computeStateVariableDerivatives(dummy_state));
88 // should not be able to create a Manager either
89 ASSERT_THROW(ComponentHasNoSystem,
90 Manager manager(model));
91
92 // get a valid System and corresponding state
93 SimTK::State state = model.initSystem();
94 Manager manager(model);
95 state.setTime(0.0);
96
97 // this should invalidate the System
98 model.finalizeFromProperties();
99
100 // verify that finalizeFromProperties() wipes out the underlying System
101 ASSERT_THROW(ComponentHasNoSystem, model.getSystem());
102
103 // should not be able to "trick" the manager into integrating a model
104 // given a stale but compatible state
105 ASSERT_THROW(ComponentHasNoSystem, manager.initialize(state));
106
107 // once again, get a valid System and corresponding state
108 state = model.initSystem();
109
110 // Test for the effects of calling finalizeConnections() on the model
111 // after initSystem() has been called.
112 // In this case, there are no changes to the connections to be finalized.
113 model.finalizeConnections();
114
115 // verify that finalizeConnections() does not wipe out the underlying
116 // System when there are no changes to the connections
117 auto& sys = model.getSystem();
118
119 auto elbowInHumerus = new PhysicalOffsetFrame("elbow_in_humerus",
120 model.getComponent<Body>("./bodyset/r_humerus"),
121 SimTK::Transform(SimTK::Vec3(0, -0.33, 0)) );
122
123 model.addComponent(elbowInHumerus);
124
125 // establish the new offset frame as part of the model but not
126 // used by any joints, constraints or forces
127 state = model.initSystem();
128
129 // update the elbow Joint and connect its socket to the new frame
130 Joint& elbow = model.updComponent<Joint>("./jointset/r_elbow");
131 elbow.connectSocket_parent_frame(*elbowInHumerus);
132
133 // satisfy the new connections in the model
134 model.finalizeConnections();
135
136 // now finalizing the connections will invalidate the System because
137 // a Component (the elbow Joint and its connection) was updated
138 ASSERT_THROW(ComponentHasNoSystem, model.getSystem());
139
140 // verify the new connection was made
141 ASSERT(model.getComponent<Joint>("./jointset/r_elbow")
142 .getParentFrame().getName() == "elbow_in_humerus");
143 }
144
testModelTopologyErrors()145 void testModelTopologyErrors()
146 {
147 Model model("arm26.osim");
148 model.initSystem();
149
150 // connect the shoulder joint from torso to ground instead of r_humerus
151 // this is an invalid tree since the underlying PhysicalFrame in both
152 // cases is ground.
153 Joint& shoulder = model.updComponent<Joint>("./jointset/r_shoulder");
154 const PhysicalFrame& shoulderOnHumerus = shoulder.getChildFrame();
155 shoulder.connectSocket_child_frame(model.getGround());
156
157 // both shoulder joint frames have Ground as the base frame
158 ASSERT_THROW( JointFramesHaveSameBaseFrame, model.initSystem() );
159
160 // restore the previous frame connected to the shoulder
161 shoulder.connectSocket_child_frame(shoulderOnHumerus);
162 model.initSystem();
163
164 // create and offset for the elbow joint in the humerus
165 auto elbowInHumerus = new PhysicalOffsetFrame("elbow_in_humerus",
166 model.getComponent<Body>("./bodyset/r_humerus"),
167 SimTK::Transform(SimTK::Vec3(0, -0.33, 0)));
168
169 model.addComponent(elbowInHumerus);
170
171 // update the elbow to connect to the elbow in the humerus frame
172 Joint& elbow = model.updComponent<Joint>("./jointset/r_elbow");
173 const PhysicalFrame& elbowOnUlna = elbow.getChildFrame();
174 elbow.connectSocket_child_frame(*elbowInHumerus);
175
176 // both elbow joint frames have the humerus Body as the base frame
177 ASSERT_THROW(JointFramesHaveSameBaseFrame, model.initSystem());
178
179 // restore the ulna frame connected to the elbow
180 elbow.connectSocket_child_frame(elbowOnUlna);
181 model.initSystem();
182
183 // introduce two offsets to simulate user error in defining the elbow
184 // in the ulna by accidentally offsetting the wrong frame.
185 auto elbowOnUlnaMistake = new PhysicalOffsetFrame("elbow_in_ulna",
186 *elbowInHumerus,
187 SimTK::Transform(SimTK::Vec3(0.1, -0.22, 0.03)) );
188 model.addComponent(elbowOnUlnaMistake);
189 elbow.connectSocket_child_frame(*elbowOnUlnaMistake);
190
191 // both elbow joint frames still have the humerus Body as the base frame
192 ASSERT_THROW(JointFramesHaveSameBaseFrame, model.initSystem());
193
194
195 Model degenerate;
196 auto frame1 = new PhysicalOffsetFrame("frame1", degenerate.getGround(),
197 SimTK::Transform(SimTK::Vec3(0, -0.1, 0)));
198 auto frame2 = new PhysicalOffsetFrame("frame2", degenerate.getGround(),
199 SimTK::Transform(SimTK::Vec3(0, 0.2, 0)));
200
201 degenerate.addComponent(frame1);
202 degenerate.addComponent(frame2);
203
204 auto joint1 = new PinJoint();
205 joint1->setName("joint1");
206 joint1->connectSocket_parent_frame(*frame1);
207 // intentional mistake to connect to the same frame
208 joint1->connectSocket_child_frame(*frame1);
209
210 // Expose infinite recursion in the case that Joint explicitly invokes
211 // finalizeConnections on its parent and child frames AND the Joint itself
212 // is a subcomponent of either the parent or child frame. This test is why
213 // the Joint cannot resolve whether two frames have the same base frame.
214 frame1->addComponent(joint1);
215
216 // Parent and child frames of a Joint cannot be the same frame
217 ASSERT_THROW(JointFramesAreTheSame,
218 joint1->finalizeConnections(degenerate));
219
220 // Joint should also throw JointFramesAreTheSame before
221 // Model throws JointFramesHaveSameBaseFrame
222 ASSERT_THROW(JointFramesAreTheSame,
223 joint1->finalizeConnections(degenerate));
224
225 // now test with child as frame2 (offset) that shares the same base
226 joint1->connectSocket_child_frame(*frame2);
227
228 ASSERT_THROW(JointFramesHaveSameBaseFrame, degenerate.initSystem());
229 }
230