//----------------------------------------------------------------------------- // File: InertiaPropertiesOfPDBMolecules.cpp // Class: None // Parent: None // Children: None // Purpose: Calculates the mass, center of mass, and inertia properties of // molecules that are downloaded from www.pdb.orb // Author: Paul Mitiguy - April 24, 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" #include "ChemicalElement.h" using namespace SimTK; using namespace std; //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Prototypes for local functions (functions not called by code in other files) //----------------------------------------------------------------------------- bool DoRequiredTasks( void ); bool GetStringFromFile( char inputString[], unsigned long maxSizeOfString, FILE *fptr ) { return fgets( inputString, maxSizeOfString, fptr ) != NULL; } bool GetStringFromKeyboard( char inputString[], unsigned long maxSizeOfString ) { return GetStringFromFile( inputString, maxSizeOfString, stdin ); } bool WriteStringToFile( const char outputString[], FILE *fptr ) { return fputs( outputString, fptr ) != EOF; } bool WriteStringToScreen( const char outputString[] ) { return WriteStringToFile( outputString, stdout ); } bool WriteIntegerToFile( int x, FILE *fptr ) { return fprintf( fptr, "%d", x) >= 0; } bool WriteDoubleToFile( double x, int precision, FILE *fptr ); bool WriteVec3ToFile( const Vec3 &v, int precision, FILE *fptr ); bool WriteMat33ToFile( const Mat33 &m, int precision, FILE *fptr ); FILE* FileOpenWithMessageIfCannotOpen( const char *filename, const char *attribute ); const char* ConvertStringToDouble( const char *s, double &returnValue, double defaultValue ); // Returns the number of molecules in the multibody system. Note: Check the status of successReadingPdb before continuing. int AddMoleculesFromPdbFileToSimbodyMatterSubsystem( SimbodyMatterSubsystem &allTheMolecules, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ); int CalculateMassPropertiesOfPdbMolecule( MassProperties &massProperties, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ); int SetMobilizerStatesFromPdbFile( SimbodyMatterSubsystem &allTheMolecules, State &state, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ); // Adds Bounding Box to mbs int AddBoundingBoxToSimbodyMatterSubsystem( SimbodyMatterSubsystem &allTheMolecules ); // Sets the decorative geometry for all molecules to spheres void DecorateAllTheMoleculesWithSpheres( SimbodyMatterSubsystem &allTheMolecules, VTKReporter &viewer ); // Calculates the bounding box for the origins of all the bodies in the matter subsystem. // Returns 6 real numbers in the following order: minX, maxX, minY, maxY, minZ, maxZ Vec6 CalculateBoundingBoxOfBodyOriginsUsingGroundXYZDirections( const SimbodyMatterSubsystem &allTheBodies, const State &state ); // Takes a single line from a PDB file and parses it. Returns its ChemicalElement (if there is one). const ChemicalElement* GetChemicalElementAndPositionFromPDBInputLine( const char *inputLine, char selectedPdbChain, bool includeHETAM, Vec3 &atomPositionFromLaboratoryOrigin, bool &successReadingPdb ); // Takes a single line from a PDB file and parses it looking for the atom's x,y,z positions from the laboratory origin Vec3 GetAtomPositionFromPdbLine( const char *inputLine, bool &successReadingPdb ); //----------------------------------------------------------------------------- // 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 ) { // Name of .pdb file entered by user char nameOfPdbFile[400]; // Prompt the user to enter the name of the .pdb file (from the keyboard) WriteStringToScreen( "\n This program calculates the mass distribution properties of a .pdb file" ); WriteStringToScreen("\n\n Enter the full path to the .pdb file: " ); GetStringFromKeyboard( nameOfPdbFile, 400 ); char *returnCharacterFound = strchr( nameOfPdbFile, '\n'); if( returnCharacterFound ) *returnCharacterFound = '\0'; // Calculate the mass, center of mass, and inertia properties about the laboratory origin bool includeHETAM = true; char selectedPdbChain = '\0'; // To read just Chain A, set this to 'A'. To read all chains, set it to '\0'; bool successReadingPdb = false; MassProperties pdbMassProperties; int numberOfAtomsInCalculation = CalculateMassPropertiesOfPdbMolecule( pdbMassProperties, nameOfPdbFile, selectedPdbChain, includeHETAM, successReadingPdb ); // Check if successful on reading the file if( successReadingPdb == false || numberOfAtomsInCalculation == 0 ) { WriteStringToScreen( "\n\n Unable to properly calculate mass distribution properties" ); return false; } // Open a file for writing (return false if cannot open file) const char *outputFileName = "2CDT.txt"; FILE *fptr = FileOpenWithMessageIfCannotOpen( outputFileName, "w" ); if( !fptr ) return false; // Get each of the relevant pieces for writing to output file const Real totalMassInAMU = pdbMassProperties.getMass(); const Vec3& centerOfMassLocationFromLaboratoryOrigin = pdbMassProperties.getMassCenter(); const Inertia& inertiaMatrixAboutLaboratoryOrigin = pdbMassProperties.getInertia(); // Use shift theorem to calculate the inertia matrix about the molecules mass center const Inertia inertiaMatrixAboutSystemCM = inertiaMatrixAboutLaboratoryOrigin.shiftToMassCenter( centerOfMassLocationFromLaboratoryOrigin, totalMassInAMU ); // Write the results to the file. int precision = 5; WriteStringToFile( "\n\n Number of relevant atoms in .pdb file: ", fptr ); WriteIntegerToFile( numberOfAtomsInCalculation, fptr ); WriteStringToFile( "\n\n System mass (in AMU):\n", fptr ); WriteDoubleToFile( totalMassInAMU, precision, fptr ); WriteStringToFile( "\n\n System center of mass position from point O (in Angstroms):\n", fptr ); WriteVec3ToFile( centerOfMassLocationFromLaboratoryOrigin, precision, fptr ); WriteStringToFile( "\n\n System inertia matrix about laboratory origin (in AMU * Angstrom^2):\n", fptr ); const Mat33 inertiaMatrixAboutLaboratoryOriginMat33 = inertiaMatrixAboutLaboratoryOrigin.toMat33(); WriteMat33ToFile( inertiaMatrixAboutLaboratoryOriginMat33, precision, fptr ); WriteStringToFile( "\n\n System inertia matrix about system's center of mass (in AMU * Angstrom^2):\n", fptr ); WriteMat33ToFile( inertiaMatrixAboutSystemCM.toMat33(), precision, fptr ); // Declare a multibody system (which holds the matter sub-system - containing all the molecules) MultibodySystem mbs; SimbodyMatterSubsystem allTheMolecules; mbs.setMatterSubsystem( allTheMolecules ); // Next, we are going to add all these molecules to a Simbody simulation AddMoleculesFromPdbFileToSimbodyMatterSubsystem( allTheMolecules, nameOfPdbFile, selectedPdbChain, includeHETAM, successReadingPdb ); // Add Bounding Box to a Simbody Simulation AddBoundingBoxToSimbodyMatterSubsystem( allTheMolecules); // Create a state for this system, realize its state (set aside room for its Qs and Us) and set its time to 0.0 State state; mbs.realize( state ); state.setTime( 0.0 ); // Set the initial values for each molecule's x, y, z position by re-parsing the file SetMobilizerStatesFromPdbFile( allTheMolecules, state, nameOfPdbFile, selectedPdbChain, includeHETAM, successReadingPdb ); // Create a study using the Runge Kutta Merson integrator (alternately use the CPodesIntegrator) RungeKuttaMerson myStudy( mbs, state ); // The next statement does lots of accounting myStudy.initialize(); // Calculate the bounding box of this set of molecules Vec6 boundingBox = CalculateBoundingBoxOfBodyOriginsUsingGroundXYZDirections( allTheMolecules, state ); Real xWidth = boundingBox[1] - boundingBox[0]; Real yWidth = boundingBox[3] - boundingBox[2]; Real zWidth = boundingBox[5] - boundingBox[4]; Real xCenter = (boundingBox[1] + boundingBox[0])/2.0; Real yCenter = (boundingBox[3] + boundingBox[2])/2.0; Real zCenter = (boundingBox[5] + boundingBox[4])/2.0; printf("xmin %g xmax %g ymin %g ymax %g zmin %g zmax %g\n",boundingBox[0],boundingBox[1],boundingBox[2],boundingBox[3],boundingBox[4],boundingBox[5]); printf("xCenter: %g yCenter: %g zCenter: %g\n",xCenter,yCenter,zCenter); Real volume = xWidth * yWidth * zWidth; WriteStringToFile( "\n\n Bounding box volume:\n", fptr ); WriteDoubleToFile( volume, precision, fptr ); // Calculations performed successfully WriteStringToScreen( "\n\n Calculations were successfuly performed.\n Results are in the file: " ); WriteStringToScreen( outputFileName ); //Visualize results with VTKReporter (be patient) VTKReporter viewer( mbs ); DecorateAllTheMoleculesWithSpheres( allTheMolecules, viewer ); // Visualize the Bounding Box with VTKReporter // Set the Initial Values for the Bounding Box Coordinates //Assuming again that the bodyId for the molecules are sequentially orderer // The Box Id willl be 1 after that BodyId boundingBoxBodyId( (numberOfAtomsInCalculation+1) ); //View the results viewer.report( state ); return true; } //----------------------------------------------------------------------------- int AddMoleculesFromPdbFileToSimbodyMatterSubsystem( SimbodyMatterSubsystem &allTheMolecules, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ) { // Open the .pdb file for reading FILE *pdbFile = FileOpenWithMessageIfCannotOpen( nameOfPdbFile, "r" ); if( !pdbFile ) {successReadingPdb = false; return 0;} // Default is that there are no errors in reading PDB file successReadingPdb = true; // Number of atoms included in this calculation int numberOfAtomsInCalculation = 0; // Space for line in .pdb file char inputLine[2000]; // Keep getting strings from the file until there are no more while( GetStringFromFile( inputLine, 2000, pdbFile ) ) { // Get the chemical element for this line (if there is one) // Also, get this atom's position (from the designated columns from the pdb) Vec3 atomPositionFromLaboratoryOrigin; const ChemicalElement *chemElement = GetChemicalElementAndPositionFromPDBInputLine( inputLine, selectedPdbChain, includeHETAM, atomPositionFromLaboratoryOrigin, successReadingPdb ); // It may be that this was supposed to be a chemical element, but the data is not in the correct fields if( successReadingPdb == false ) break; // It may be that this line of the .pdb does not contain ATOM or HETAM information if( !chemElement ) continue; // At this point, we have found a valid atom to include in the mass calculations ++numberOfAtomsInCalculation; // To add a particle (or rigid body), must create a MassProperties class for it Real moleculeMass = chemElement->getMass(); const Vec3 moleculeCenterOfMassLocationFromMoleculeOrigin( 0, 0, 0 ); const Inertia moleculeInertiaMatrixAboutMoleculeOrigin( 0, 0, 0, 0, 0, 0 ); MassProperties moleculeMassProperties( moleculeMass, moleculeCenterOfMassLocationFromMoleculeOrigin, moleculeInertiaMatrixAboutMoleculeOrigin ); // To add a particle (or rigid body), must inform how it is connected const Transform outboardFrameTransformFromGround; // Default constructor is the identity transform const Transform inboardFrameTransformFromMolecule; // Default constructor is the identity transform Mobilizer moleculeToGroundMobilizer = Mobilizer::Cartesian(); // Add this to the matter subsystem (does not get directly added to the multibody system) allTheMolecules.addRigidBody( moleculeMassProperties, inboardFrameTransformFromMolecule, GroundId, outboardFrameTransformFromGround, moleculeToGroundMobilizer ); } // Close the pdb file before returning (setting it to NULL is good practice) fclose( pdbFile ); pdbFile = NULL; // Was able to add all particles return numberOfAtomsInCalculation; } //----------------------------------------------------------------------------- int AddBoundingBoxToSimbodyMatterSubsystem( SimbodyMatterSubsystem &allTheMolecules ) { // To add a particle (or rigid body), must create a MassProperties class for it const Real massOfBox = 0.00000; const Vec3 boxCenterOfMassLocation( 0, 0, 0 ); const Inertia boxInertiaMatrix( 0, 0, 0, 0, 0, 0 ); MassProperties boxMassProperties( massOfBox, boxCenterOfMassLocation, boxInertiaMatrix ); // To add a particle (or rigid body), must inform how it is connected const Transform outboardFrameTransformFromGround; // Default constructor is the identity transform const Transform inboardFrameTransformFromBox; // Default constructor is the identity transform Mobilizer boxToGroundMobilizer = Mobilizer::Cartesian(); // Add this to the matter subsystem (does not get directly added to the multibody system) allTheMolecules.addRigidBody( boxMassProperties, inboardFrameTransformFromBox, GroundId, outboardFrameTransformFromGround, boxToGroundMobilizer ); return 1; } //----------------------------------------------------------------------------- int CalculateMassPropertiesOfPdbMolecule( MassProperties &pdbMassProperties, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ) { // MIMIC THE FUNCTIONALITY IN: AddMoleculesFromPdbFileToSimbodyMatterSubsystem FILE *pdbFile = FileOpenWithMessageIfCannotOpen( nameOfPdbFile, "r" ); if( !pdbFile ) {successReadingPdb = false; return 0;} // Default is that there are no errors in reading PDB file successReadingPdb = true; // Number of atoms included in this calculation int numberOfAtomsInCalculation = 0; // Space for line in .pdb file char inputLine[2000]; // Define Mass Variable Names Real totalMassOfAtomsInAMU = 0.0; Vec3 centerOfMassLocationFromLaboratoryOrigin(0,0,0); Inertia inertiaMatrixAboutLaboratoryOrigin(0,0,0,0,0,0); // Keep getting strings from the file until there are no more while( GetStringFromFile( inputLine, 2000, pdbFile ) ) { // Get the chemical element for this line (if there is one) // Also, get this atom's position (from the designated columns from the pdb) Vec3 atomPositionFromLaboratoryOrigin; const ChemicalElement *chemElement = GetChemicalElementAndPositionFromPDBInputLine( inputLine, selectedPdbChain, includeHETAM, atomPositionFromLaboratoryOrigin, successReadingPdb ); // It may be that this was supposed to be a chemical element, but the data is not in the correct fields if( successReadingPdb == false ) break; // It may be that this line of the .pdb does not contain ATOM or HETAM information if( !chemElement ) continue; // At this point, we have found a valid atom to include in the mass calculations ++numberOfAtomsInCalculation; // Calculate Mass Properties Real moleculeMass = chemElement->getMass(); Inertia moleculeInertia(atomPositionFromLaboratoryOrigin,moleculeMass); totalMassOfAtomsInAMU += moleculeMass; centerOfMassLocationFromLaboratoryOrigin += (moleculeMass*atomPositionFromLaboratoryOrigin); inertiaMatrixAboutLaboratoryOrigin += moleculeInertia; } // Divide Mass and Position Sum by total Mass centerOfMassLocationFromLaboratoryOrigin = centerOfMassLocationFromLaboratoryOrigin/totalMassOfAtomsInAMU; // Close the pdb file before returning (setting it to NULL is good practice) fclose( pdbFile ); pdbFile = NULL; // The MassProperties class is a suitcase for mass, center of mass, and inertia pdbMassProperties.setMassProperties( totalMassOfAtomsInAMU, centerOfMassLocationFromLaboratoryOrigin, inertiaMatrixAboutLaboratoryOrigin ); // Return number of atoms in .pdb file return numberOfAtomsInCalculation; } //----------------------------------------------------------------------------- int SetMobilizerStatesFromPdbFile( SimbodyMatterSubsystem &allTheMolecules, State &state, const char *nameOfPdbFile, char selectedPdbChain, bool includeHETAM, bool &successReadingPdb ) { // MIMIC THE FUNCTIONALITY IN: AddMoleculesFromPdbFileToSimbodyMatterSubsystem // Open the .pdb file for reading FILE *pdbFile = FileOpenWithMessageIfCannotOpen( nameOfPdbFile, "r" ); if( !pdbFile ) {successReadingPdb = false; return 0;} // Default is that there are no errors in reading PDB file successReadingPdb = true; // Number of atoms included in this calculation int numberOfAtomsInCalculation = 0; // Space for line in .pdb file char inputLine[2000]; // Keep getting strings from the file until there are no more while( GetStringFromFile( inputLine, 2000, pdbFile ) ) { // Get the chemical element for this line (if there is one) // Also, get this atom's position (from the designated columns from the pdb) Vec3 atomPositionFromLaboratoryOrigin; const ChemicalElement *chemElement = GetChemicalElementAndPositionFromPDBInputLine( inputLine, selectedPdbChain, includeHETAM, atomPositionFromLaboratoryOrigin, successReadingPdb ); // It may be that this was supposed to be a chemical element, but the data is not in the correct fields if( successReadingPdb == false ) break; // It may be that this line of the .pdb does not contain ATOM or HETAM information if( !chemElement ) continue; // At this point, we have found a valid atom to include in the mass calculations ++numberOfAtomsInCalculation; // The number of atoms in the calculation should never be more than all the molecules int totalNumberOfMolecules = allTheMolecules.getNBodies(); if( numberOfAtomsInCalculation > totalNumberOfMolecules ) { successReadingPdb = false; break; } // Assuming the bodies are sequentially numbered, the next line should create the proper Id BodyId moleculeId( numberOfAtomsInCalculation ); // Set the initial values for the configuration variables (x,y,z) Real x = atomPositionFromLaboratoryOrigin[0]; Real y = atomPositionFromLaboratoryOrigin[1]; Real z = atomPositionFromLaboratoryOrigin[2]; allTheMolecules.setMobilizerQ( state, moleculeId, 0, x ); allTheMolecules.setMobilizerQ( state, moleculeId, 1, y ); allTheMolecules.setMobilizerQ( state, moleculeId, 2, z ); // Set the initial values for the motion variables (vx, vy, vz) Real vx = 0; Real vy = 0; Real vz = 0; allTheMolecules.setMobilizerU( state, moleculeId, 0, vx ); allTheMolecules.setMobilizerU( state, moleculeId, 1, vy ); allTheMolecules.setMobilizerU( state, moleculeId, 2, vz ); } // Close the pdb file before returning (setting it to NULL is good practice) fclose( pdbFile ); pdbFile = NULL; // Was able to add all particles return numberOfAtomsInCalculation; } //----------------------------------------------------------------------------- void DecorateAllTheMoleculesWithSpheres( SimbodyMatterSubsystem &allTheMolecules, VTKReporter &viewer ) { // Number of atoms included in this calculation int numberOfMolecules = allTheMolecules.getNBodies(); // Decorate each molecule with a sphere for( int i=1; i xMax) xMax = xLocation; if( (i==1) || ((yLocation < yMin) && (yLocation != 0)) ) yMin = yLocation; if( yLocation > yMax) yMax = yLocation; if( (i==1) || ((zLocation < zMin) && (zLocation != 0)) ) zMin = zLocation; if( zLocation > zMax) zMax = zLocation; } // Return the bounding box as 6 numbers return Vec6( xMin, xMax, yMin, yMax, zMin, zMax ); } //----------------------------------------------------------------------------- const char* ConvertStringToDouble( const char *s, double &returnValue, double defaultValue ) { // Default return value (in case the string is not a valid number) returnValue = defaultValue; // Check if s is a NULL string or "abc" or "123huh" or "(&junk#" or if( s != NULL ) { // Use the standard math function strtod to parse the number char *pointerToCharacterAfterNumber = NULL; double x = strtod( s, &pointerToCharacterAfterNumber ); // Ensure the number was not too large (overflow), such as 1.0E+999 or -1.0E-999 if( errno==ERANGE && x!=0.0 ) return NULL; // Ensure the character after the number is a space or '\0', not 'a' or 'z' or ... char characterAfterNumber = pointerToCharacterAfterNumber ? *pointerToCharacterAfterNumber : 'z'; if( characterAfterNumber == '\0' || isspace( characterAfterNumber ) ) { returnValue = x; return pointerToCharacterAfterNumber; } } return NULL; } //----------------------------------------------------------------------------- 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; } //----------------------------------------------------------------------------- bool WriteVec3ToFile( const Vec3 &v, int precision, FILE *fptr ) { for( unsigned int i=0; i<3; i++ ) { double vi = v(i); // or v[i]; if( !WriteDoubleToFile( vi, precision, fptr ) ) return false; } return true; } //----------------------------------------------------------------------------- bool WriteMat33ToFile( const Mat33 &m, int precision, FILE *fptr ) { for( unsigned int i=0; i<3; i++ ) { // Row3 &vi = m[i]; // m[i] returns the row and m(i) returns the column // if( !WriteVec3ToFile( vi, precision, fptr ) ) return false; for( unsigned int j=0; j<3; j++ ) { const Real mij = m[i][j]; if( !WriteDoubleToFile( mij, precision, fptr ) ) return false; } if( i<=1 && !WriteStringToFile( "\n", fptr ) ) return false; } return true; }