//----------------------------------------------------------------------------- // File: NewtonsApple.cpp // Class: None // Parent: None // Children: None // Purpose: Simulates Newton's falling apple // Author: Paul Mitiguy - April 1, 2007 //----------------------------------------------------------------------------- // The following are standard C/C++ header files. // If a filename is enclosed inside < > it means the header file is in the Include directory. // If a filename is enclosed inside " " it means the header file is in the current directory. #include // Character Types #include // Mathematical Constants #include // Variable Argument Lists #include // Standard Input/Output Functions #include // Utility Functions #include // String Operations #include // Signals (Contol-C + Unix System Calls) #include // Nonlocal Goto (For Control-C) #include // Time and Date information #include // Verify Program Assertion #include // Error Codes (Used in Unix system()) #include // Floating Point Constants #include // Implementation Constants #include // Standard Definitions #include // Exception handling (e.g., try, catch throw) //----------------------------------------------------------------------------- #include "SimTKsimbody.h" using namespace SimTK; using namespace std; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Prototypes for local functions (functions not called by code in other files) //----------------------------------------------------------------------------- bool DoRequiredTasks( void ); bool WriteStringToFile( const char outputString[], FILE *fptr ) { return fputs( outputString, fptr ) != EOF; } bool WriteStringToScreen( const char outputString[] ) { return WriteStringToFile( outputString, stdout ); } bool WriteDoubleToFile( double x, int precision, FILE *fptr ); FILE* FileOpenWithMessageIfCannotOpen( const char *filename, const char *attribute ); //----------------------------------------------------------------------------- // The executable program starts here //----------------------------------------------------------------------------- int main( int numberOfCommandLineArguments, char *arrayOfCommandLineArguments[] ) { // Default value is program failed bool programSucceeded = false; // It is a good programming practice to do little in the main function of a program. // The try-catch code in this main routine catches exceptions thrown by functions in the // try block, e.g., catching an exception that occurs when a NULL pointer is de-referenced. try { // Do the required programming tasks programSucceeded = DoRequiredTasks(); } // This catch statement handles certain types of exceptions catch( const exception &e ) { WriteStringToScreen( "\n\n Error: Programming error encountered.\n The exception thrown is: " ); WriteStringToScreen( e.what() ); } // The exception-declaration statement (...) handles any type of exception, // including C exceptions and system/application generated exceptions. // This includes exceptions such as memory protection and floating-point violations. // An ellipsis catch handler must be the last handler for its try block. catch( ... ) { WriteStringToScreen( "\n\n Error: Programming error encountered.\n An unhandled exception was thrown." ); } // Give the user a chance to see a final message before exiting the program. WriteStringToScreen( programSucceeded ? "\n\n Program succeeded. Press Enter to finish: " : "\n\n Program failed. Press Enter to finish: " ); getchar(); // Keeps the screen displayed until the user presses the Enter (or Return) key. // The value returned by the main function is the exit status of the program. // A normal program exit returns 0 (other return values usually signal an error). return programSucceeded == true ? 0 : 1; } //----------------------------------------------------------------------------- bool DoRequiredTasks( void ) { // Declare a multibody system (contains one or more force and matter sub-systems) MultibodySystem mbs; // 0. The ground's right-handed, orthogonal x,y,z unit vectors are directed with x horizontally right and y vertically upward. // 1. Create a gravity vector that is straight down (in the ground's frame) // 2. Create a uniform gravity sub-system // 3. Add the gravity sub-system to the multibody system Vec3 gravityVector( 0, -9.8, 0 ); UniformGravitySubsystem gravity( gravityVector ); mbs.addForceSubsystem( gravity ); // Create a matter sub-system (the apple) SimbodyMatterSubsystem apple; // Create the mass, center of mass, and inertia properties for the apple const Real massOfApple = 0.142; // The location of the apple's center of mass is a vector from the apple's // origin expressed in the "x, y, z"' unit vectors fixed in the apple's frame. // Example: The vector(0,0,0) locates the apple's center of mass at the apple's origin. // Example: The vector(1,0,0) locates the apple's center of mass 1 unit in the "x" direction from the apple's origin. const Vec3 appleCenterOfMassLocation( 0, 0, 0 ); // Create the apple's inertia matrix about its origin for the "x, y, z" unit vectors fixed in the apple's frame. // Note: The 3x3 inertia matrix is symmetric - so only 6 elements need to be defined. // Ixx, Iyy, Izz are moments of inertia ( diagonal terms in the matrix) // Ixy, Ixz, Iyz are products of inertia (off-diagonal terms in the matrix) // The following assumes an apple of radius 1.44 inches (3.6576 cm) with a radius of gyration of 0.91 inches (2.3114 cm) // which approximates a perfect sphere of radius 1.44 inches (3.6576 cm) const Real I = massOfApple * pow( 0.023114, 2 ); // Inertia = mass * radiusOfGyration^2 const Real Ixx = I, Iyy = I, Izz = I; const Real Ixy = 0, Ixz = 0, Iyz = 0; const Inertia appleInertiaMatrix( Ixx, Iyy, Izz, Ixy, Ixz, Iyz ); // The MassProperties class holds the mass, center of mass, and inertia properties of a rigid body. // Although the next line creates an instance of the MassProperties class for the apple, // it does not get associated with the apple until the addRigidBody method. MassProperties appleMassProperties( massOfApple, appleCenterOfMassLocation, appleInertiaMatrix ); // The apple's motion is related to ground via "mobilizers". // The "mobilizers" specify the allowable motion of the apple to the ground. // More specifically, the motion of an "outboard body" (e.g., the apple) // to its inboard body (e.g., the ground) is specified by first constructing: // 1. An "outboard frame" on the ground (which hooks to the "outboard body" - the apple ) // 2. An "inboard frame" on the apple (which hooks to the "inboard body" - the ground) // The orientation and position of the outboard frame from the ground's frame is specified below. // The outboard frame's axes are aligned with the ground's axes and its origin is coincident with the ground's origin. // In other words, for this simple problem the outboard frame and the ground frame are identical. const Transform outboardFrameTransformFromGround; // The default constructor is the identity transform // The orientation and position of the inboard frame from the apple's frame is specified below. // The inboard frame's axes are aligned with the apple's axes and its origin is coincident with the apple's origin // In other words, for this simple problem the inboard frame and the apple frame are identical. // Although the inboard frame can be constructed in a simple manner analogous to the outboardFrameTransformFromGround (above) // it is worthwhile to look at the details of the rotation matrix and position vector in the transform: // a. The rotation matrix relating the InboardFrame's x,y,z axes to the AppleFrame's x,y,z axes is specified InboardFrame_AppleFrame. // b. The position of the InboardFrame's origin from the AppleFrame origin, expressed in terms of the AppleFrame's "x, y, z" unit vectors. const Rotation inboardFrameOrientationInApple; // ( 1,0,0, 0,1,0, 0,0,1 ); const Vec3 inboardFrameOriginLocationFromAppleOrigin( 0, 0, 0 ); const Transform inboardFrameTransformFromApple( inboardFrameOrientationInApple, inboardFrameOriginLocationFromAppleOrigin ); // There are many ways that the apple can move relative to the ground. // The following allows the apple to move in "x", "y", and "z" directions. // Another option producing the same result is Mobilizer::Free Mobilizer appleToGroundMobilizer = Mobilizer::Cartesian; const BodyId appleBodyId = apple.addRigidBody( appleMassProperties, inboardFrameTransformFromApple, GroundId, outboardFrameTransformFromGround, appleToGroundMobilizer ); // Add the matter (apple) sub-system to the system. mbs.setMatterSubsystem( apple ); // Create a state for this system. // Define appropriate states for this multi-body system. // Set the initial time to 0.0 State s; mbs.realize( s ); s.setTime( 0.0 ); // Set the initial values for the configuration variables (x,y,z) //initial position values set as x=0, y=0 apple.setMobilizerQ( s, appleBodyId, 0, 0.0 ); apple.setMobilizerQ( s, appleBodyId, 1, 0.0 ); apple.setMobilizerQ( s, appleBodyId, 2, 0.0 ); // Set the initial values for the motion variables //initial velocity values set as Vx=39.073552512584908367950096008233; Vy=27.359596013944898784353122241808 apple.setMobilizerU( s, appleBodyId, 0, 39.073552512584908367950096008233 ); apple.setMobilizerU( s, appleBodyId, 1, 27.359596013944898784353122241808 ); apple.setMobilizerU( s, appleBodyId, 2, 0.0 ); // Create a study using the Runge Kutta Merson integrator (alternately use the CPodesIntegrator) RungeKuttaMerson myStudy( mbs, s ); // Set the numerical accuracy for the integrator myStudy.setAccuracy( 1.0E-7 ); // The next statement does lots of accounting myStudy.initialize(); // Open a file to record the simulation results (they are also displayed on screen) //Modified for 2-D, added x-components of location and velocity FILE *outputFile = FileOpenWithMessageIfCannotOpen( "ProjectileMotionResults.txt", "w" ); WriteStringToFile( "time xLocation yLocation xVelocity yVelocity mechanicalEnergy xDifferenceFromExact yDifferenceFromExact\n", outputFile ); WriteStringToScreen( "time xLocation yLocation xVelocity yVelocity mechanicalEnergy xDifferenceFromExact yDifferenceFromExact\n" ); // Set the numerical integration step and the time for the simulation to run const Real integrationStepDt = 0.01; // Run the simulation and print the results while( 1 ) { // Query for results to be printed Real time = s.getTime(); Real kineticEnergy = mbs.getKineticEnergy(s); Real uniformGravitationalPotentialEnergy = mbs.getPotentialEnergy(s); Real mechanicalEnergy = kineticEnergy + uniformGravitationalPotentialEnergy; // Locate the apple origin's from the ground's origin, expressed in terms of the ground's "x,y,z" unit vectors. // Extract the apple's y-location from this vector. const Vec3 appleLocation = apple.calcBodyOriginLocationInBody( s, appleBodyId, GroundId ); //Add x-location parameter Real xLocation = appleLocation[0]; Real yLocation = appleLocation[1]; // Get the apple origin's velocity in ground, expressed in terms of the ground's "x,y,z" unit vectors. // Extract the apple's y-velocity from this vector. const Vec3 appleVelocity = apple.calcBodyOriginVelocityInBody( s, appleBodyId, GroundId ); //Add x- velocity parameter Real xVelocity = appleVelocity[0]; Real yVelocity = appleVelocity[1]; // Get the apple origin's acceleration in ground, expressed in terms of the ground's "x,y,z" unit vectors. // Extract the apple's y-acceleration from this vector. // const Vec3 appleAcceleration = apple.calcBodyOriginAccelerationInBody( s, appleBodyId, GroundId ); // Real yAcceleration = appleAcceleration[1]; // Exact analytical results and difference of numerical integration results //modify yExact double yExact = yVelocity*time-4.9*time*time; double yDifferenceFromExact = yExact - yLocation; //add x-exact parameter double xExact = xVelocity*time; double xDifferenceFromExact = xExact - xLocation; // Print results to screen //modify for 2-D WriteDoubleToFile( time, 2, stdout ); WriteDoubleToFile( yLocation, 4, stdout ); WriteDoubleToFile( yVelocity, 4, stdout ); WriteDoubleToFile( xLocation, 4, stdout ); WriteDoubleToFile( xVelocity, 4, stdout ); WriteDoubleToFile( mechanicalEnergy, 7, stdout ); WriteDoubleToFile( yDifferenceFromExact, 7, stdout ); WriteDoubleToFile( xDifferenceFromExact, 7, stdout ); WriteStringToScreen( "\n" ); // Print results to file //modify for 2-D WriteDoubleToFile( time, 2, outputFile ); WriteDoubleToFile( xLocation, 4, outputFile ); WriteDoubleToFile( yLocation, 4, outputFile ); WriteDoubleToFile( xVelocity, 4, outputFile ); WriteDoubleToFile( yVelocity, 4, outputFile ); WriteDoubleToFile( mechanicalEnergy, 7, outputFile ); WriteDoubleToFile( xDifferenceFromExact, 7, outputFile ); WriteDoubleToFile( yDifferenceFromExact, 7, outputFile ); WriteStringToFile( "\n", outputFile ); // Check if the ball has reached the ground if( yLocation <= 0 & time > 0 ) break; // Increment time step myStudy.step( time + integrationStepDt ); } // Simulation completed properly return true; } //----------------------------------------------------------------------------- FILE* FileOpenWithMessageIfCannotOpen( const char *filename, const char *attribute ) { // Try to open the file FILE *Fptr1 = fopen( filename, attribute ); // If unable to open the file, issue a message if( !Fptr1 ) { WriteStringToScreen( "\n\n Unable to open the file: " ); WriteStringToScreen( filename ); WriteStringToScreen( "\n\n" ); } return Fptr1; } //----------------------------------------------------------------------------- bool WriteDoubleToFile( double x, int precision, FILE *fptr ) { // Ensure the precision (number of digits in the mantissa after the decimal point) makes sense. // Next, calculate the field width so it includes one extra space to the right of the number. if( precision < 0 || precision > 17 ) precision = 5; int fieldWidth = precision + 8; // Create the format specifier and print the number char format[20]; sprintf( format, " %%- %d.%dE", fieldWidth, precision ); return fprintf( fptr, format, x ) >= 0; }