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