Building a plugin that creates a non-native object in GMAT

Development of custom GMAT plugins

Building a plugin that creates a non-native object in GMAT

Postby marchand » Wed Jun 26, 2013 4:25 pm

I built a plugin, call it libMyPlugin, that extends GmatBase and calls out to the CreateObject method, and modified the GMAT base source as follows:

  • Edit gmatdefs.h and add "USER_DEFINED_OBJECT" near the end of the ObjectType enum, just before UNKNOWN_OBJECT.
  • Edit foundation/GmatBase.cpp and add "MyUserDefinedObject" to the GmatBase::OBJECT_TYPE_STRING enum, just before "UnknownObject".
The plugin builds just fine in VS2010, no errors. So, I added the path to libMyPlugin to the list of plugins to load at startup and GMAT seems to recognize it as a plugin, no complaints or errors. However, when I try to add a Create line to my script, say:

Code: Select all
Create MyUserDefinedObject myObj;


I get this error:

Code: Select all
1: **** ERROR **** Interpreter Exception: Cannot create an object "myObj". The "MyUserDefinedObject" is an unknown object type or invalid object name or dimension in line:
   " 246: Create MyUserDefinedObject myObj;"


The plugin framework alone is not sufficient to define a completely new object, that is an object whose type is not consistent with any of the pre-defined types in GMAT. Apparently some changes are required to the GMAT source to effect that, and it seems that simply modifying gmatdefs.h, and synchronizing that with foundation/GmatBase.cpp, is not enough. Things always get "stuck" at the interpreter, as it's processing the list of objects.

Can you offer some guidance on what other changes would be necessary to GMAT Base in order for a user to be able to create a completely new object? Something that does not conform to any of the existing types in GMAT?

Thanks.
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby jjkparker » Wed Jun 26, 2013 5:45 pm

Out of curiosity, does it work if you define your resource as UNKNOWN_OBJECT ("UnknownObject")? Are there bad ramifications for doing this?
Joel J. K. Parker
Flight dynamics engineer, GMAT team
http://gmatcentral.org
jjkparker
 
Posts: 617
Joined: Thu Jan 07, 2010 9:48 pm
Location: NASA Goddard Space Flight Center, Greenbelt, MD

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Wed Jun 26, 2013 6:12 pm

The error is the same either way. I think part of the issue is in the Interpreter class, and what the types of "Creatable" objects it expects.
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby jjkparker » Wed Jun 26, 2013 6:26 pm

Did you add your type to creatables in your plugin?

Code: Select all
MyUserDefinedObjectFactory::MyUserDefinedObjectFactory() :
   Factory(Gmat::USER_DEFINED_OBJECT)
{
   if (creatables.empty())
   {
      creatables.push_back("MyUserDefinedObject");
   }
}
Joel J. K. Parker
Flight dynamics engineer, GMAT team
http://gmatcentral.org
jjkparker
 
Posts: 617
Joined: Thu Jan 07, 2010 9:48 pm
Location: NASA Goddard Space Flight Center, Greenbelt, MD

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Wed Jun 26, 2013 6:41 pm

Yes.
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Wed Jun 26, 2013 8:08 pm

I found a related example. In the Estimation plugin, there's an EventFactory, which defines an event of type "LightTimeCorrection". The Event class, within the plugin, extends GmatBase directly. Now, if I go into a GMAT script, and type:

Create LightTimeCorrection myEvent;

I get the same error:

1: **** ERROR **** Interpreter Exception: Cannot create an object "myEvent". The "LightTimeCorrection" is an unknown object type or invalid object name or dimension in line:
" 245: Create LightTimeCorrection myEvent;"
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Thu Jun 27, 2013 3:13 am

Well, as per an offline discussion, seems that user defined objects are not actually supported in GMAT. The list of creatables has to conform to a specific subset of types (e.g. SPACECRAFT, PROPAGATOR, INTERFACE, etc.).

Anyway, I did find a quick workaround. Granted, it's not elegant but it works. I used the INTERFACE type, which requires your class define an Open and Close method. So, I did that but left the methods empty, so they do absolutely nothing. That allowed me to create my object. I am able to set (outside the mission sequence) and get real and integer parameters now on that object, but I haven't quite gotten the referenced objects to work.

For example, I currently have two referenced objects, one is a libration point and another is a coordinate system. My understanding, based on the available plugin examples, is that the SetRefObject is the way to define that internally. So, I currently have something like this:

Code: Select all
   bool MyUserDefinedObject::SetRefObject(GmatBase *obj, const Gmat::ObjectType type, const std::string &name)
   {
      if (obj == NULL)
        return false;

      if (obj->IsOfType(Gmat::LIBRATION_POINT)) {
         theLibrationPoint = (LibrationPoint*)obj;
      }

      if (obj->IsOfType(Gmat::COORDINATE_SYSTEM)) {
         theCoordinateSystem = (CoordinateSystem*)obj;
      }

      return GmatBase::SetRefObject(obj, type, name);

   }


where theCoordinateSystem and theLibrationPoint are protected class variables defined in the header file:

Code: Select all
LibrationPoint *theLibrationPoint;
CoordinateSystem *theCoordinateSystem;


and initialized in the constructor. I use the VS2010 debugger and put stops on all the Set methods, SetRealParameter, SetIntegerParameter, SetRefObject, SetRefObjectName, etc. Anyway, when I execute in debug mode via VS2010, I am able to stop inside the SetReal and SetInteger methods, but it never stops inside the SetRefObject method, which is strange. I followed the available examples, like the Spacecraft class and some of the plugins, and I can't really see why this wouldn't work. The error I get in GMAT says:

Code: Select all
1: **** ERROR **** GmatBase Exception Thrown: Cannot set string parameter with ID 1: "LibrationPoint" on MyUserDefinedObject named "myObj" in line:
   " 246: myObj.LibrationPoint=Libration1;"

2: **** ERROR **** GmatBase Exception Thrown: Cannot set string parameter with ID 2: "CoordinateSystem" on MyUserDefinedObjectnamed "myObj" in line:
   " 247: myObj.CoordinateSystem=EarthMoonRLP;"


Libration1 and EarthMoonRLP are already defined in the ResourceTree and in the script.

Also, if I want to reset a parameter inside the mission sequence, which method is the correct one to use? Is it still SetRealParameter and SetIntegerParameter? I am able to set the parameters currently outside the mission sequence, but I cannot reset them once I'm inside the mission sequence.



Any ideas?
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Thu Jun 27, 2013 10:00 pm

The problem has been resolved. As per an offline conversation with Darrel, the key piece of information here is that, even though I had declared the referenced objects as being of type Gmat::OBJECT_TYPE in the enumerated parameters, they are actually scripted as strings. Thus, the name setting / getting has to be handled via the SetStringParameter and GetStringParameter methods. So, the changes now look like this. I define member variables called libPointName and coordSysName, which are strings that internally store the name of each the referenced objects I used. Then, aside from any other string parameters I need to handle, here's an example of part of the SetStringParameter method:

Code: Select all
   bool MyUserDefinedObject::SetStringParameter(const Integer id,const std::string& value)
   {

      if (IsParameterReadOnly(id))
         return false;

       bool retval = false;

      if (id == MY_LIBRATIONPOINT)    {
         libPointName = value;
         retval=true;
      } else if (id == MY_COORDINATESYSTEM) {
         coordSysName = value;
         retval=true;
      } else
      {
         retval = GmatBase::SetStringParameter(id,value);
      }

      return retval;
   }




Similarly, the SetRefObject looks like this:

Code: Select all
bool MyUserDefinedObject::SetRefObject(GmatBase *obj, const Gmat::ObjectType type, const std::string &name)
   {

      if (obj == NULL)
        return false;

      if (obj->IsOfType(Gmat::LIBRATION_POINT)) {
         if (name == libPointName) {
            theLibrationPoint = (LibrationPoint*)obj;
         }
      }


      if (obj->IsOfType(Gmat::COORDINATE_SYSTEM)) {
         if (name == coordSysName) {
            theCoordinateSystem = (CoordinateSystem*)obj;
         }

      }

      return GmatBase::SetRefObject(obj, type, name);


   }



Hope that helps someone!
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm

Re: Building a plugin that creates a non-native object in GM

Postby marchand » Sat Aug 10, 2013 1:33 am

Well that actually turned out not to fix ALL the problems. The reference object names were indeed being set as suggested in the previous post, but the actual objects themselves were never internally assigned as it turns out. That is, I confirmed that the SetRefObject method was NEVER called during either the loading of the script, the definition of the object, or the execution of the script that instantiated my custom object. I stepped into my plugin with the VS2010 debugger, and into other GMAT classes, and was able to figure out why this was. I'll share the outcome of that exploration with everyone in case someone finds it useful.

To put things into context, the object I created contains two reference objects, one is a LibrationPoint and the other a CoordinateSystem. It's helpful now to distinguish the difference between loading a script vs. executing a script in GMAT. When you load a script into GMAT, one of the methods that is invoked is the HasRefObjectTypeArray method. The existence of this method, and its returning a true value, is what indicates to GMAT that it needs to process those reference objects. Notably, this is missing from the architectural specification so you only figure this out by nosing around the GmatBase code.

Next, the interpreter invokes the GetRefObjectTypeArray method, which has no input arguments, and it must return the list of ALL reference objects types. I used the Spacecraft and LibrationPoint classes as an example, to help me define my own. My example has two reference objects, one LibrationPoint and one CoordinateSystem. So, my GetRefObjectTypeArray looks like this:

Code: Select all
const ObjectTypeArray& MyUserDefinedObject::GetRefObjectTypeArray()
{
   refObjectTypes.clear();
   refObjectTypes.push_back(Gmat::LIBRATION_POINT);
   refObjectTypes.push_back(Gmat::COORDINATE_SYSTEM);

   return refObjectTypes;
}


The attribute refObjectTypes is defined in the GmatBase class, as noted in the architectural specification.

Immediately following that, the GetRefObjectNameArray method is invoked, once for each object type you defined. According to the architectural specification,

virtual const StringArray& GetRefObjectNameArray(const Gmat::ObjectType type): Returns the reference object names used by the current object. Derived classes override this method to return the correct values.


I looked to some of the existing base classes for guidance, like Spacecraft, LibrationPoint, and OrbitPlot, and started to notice a trend. If a type is specified, a list of object names of that type is returned. If, however, the type is Gmat::UNKNOWN_TYPE the method must return the list of ALL reference object names regardless of type. That little tidbit of useful information also didn’t make it into the architectural specification, but it would be helpful to add more explicitly.

Now, when you execute the script, a few things happen. First, GMAT creates an internal clone of the object by calling it’s Clone() method. Then, the ObjectInitializer base class is called. This is the class that will eventually be responsible for calling the SetRefObject method of your custom object. There is a method in this class called InitializeObjects. It initializes objects in a specific order:

  • Coordinate Systems
  • Spacecraft and Ground Stations
  • Measurement Models
  • System Parameters
  • Parameters
  • Subscribers
  • Remaining Objects

Once the first set of objects is processed, and it’s time to process the "Remaining Objects," the InitializeAllOtherObjects method is invoked. This method has an if-statement that determines which method is called to build and initialize the reference objects.

Code: Select all
    if (obj->GetType() == Gmat::ODE_MODEL)
         {
            BuildReferences(obj);
         }
         else if (obj->GetType() == Gmat::PROP_SETUP)
         {
            BuildReferences(obj);
         }
         else
         {
            if (obj->IsOfType(Gmat::SPACE_POINT)       ||
                obj->IsOfType(Gmat::BURN)              ||
                obj->IsOfType(Gmat::HARDWARE)          ||
                obj->IsOfType("Estimator")             ||
                obj->IsOfType("Simulator")             ||
                obj->IsOfType(Gmat::EVENT_LOCATOR)     ||
                obj->IsOfType(Gmat::INTERFACE))
            {
               BuildReferencesAndInitialize(obj);
            }
         }


The only difference between the BuildReferences and BuildReferencesAndInitialize methods is the latter calls the former but also invokes the object’s Initialize method, if present.

The BuildReferences method first checks for and handles objects of type Gmat::PROP_SETUP and Gmat::ODE_MODEL. Beyond that is when other object types are processed.

The first attempt is to call the GetRefObjectName method for the object. The GMAT architectural specification says the following about this method:

virtual std::string GetRefObjectName(const Gmat::ObjectType type) const: Returns the name of a referenced object of a specified type, if the object uses that type of referenced object.



Notice that a type argument is expected, and the architectural specification suggests this should return the name of the object of the given type. Immediately this raises the question, what if my custom object has more than one reference object of the same type? The answer is that you would use the GetRefObjectNameArray method instead, which returns a list of all objects of the specified type.

Still, there is merit in noting one point. The architectural documentation explicitly suggests this method should return a string with the name of the object of the specified type. It does not, however, suggest that accommodations should be made for a Gmat::UNKNOWN_OBJECT type. Curiously, that is precisely the one and only type the ObjectInitializer queries when processing all “Remaining” objects. So, if my class has more than one reference object, which name is it supposed to return in that case?

I suppose there was a perfectly good reason for doing things this way, at some point, but it is a source of confusion to folks trying to develop their own objects. For example, following the architectural documentation, the body of my GetRefObjectName method was originally this:

Code: Select all

if (type == Gmat::LIBRATION_POINT) {
   return libPointName;
}

if (type == Gmat::COORDINATE_SYSTEM) {
   return coordSysName;
}



However, since ObjectInitializer was invoking the method with the type Gmat::UNKNOWN_TYPE nothing ever got returned, and hence my reference objects were never “set” internally. Now, keep in mind that at this point I had no reason to believe I would have any need for the GetRefObjectNameArray. My understanding was that this was only necessary if you had multiple reference objects of the same type. However, without resorting to modifying the GMAT base code, that turned out to be the only method that worked for me because when the above fails, the next thing the ObjectInitializer tries is calling the GetRefObjectNameArray method.

Apparently ObjectInitializer is not the only class that calls this method because I also noticed, via the debugger, that the method is sometimes invoked with the correct type, and sometimes – as previously described - it’s invoked with the type Gmat::UNKNOWN_OBJECT. I have deduced, from looking at the Spacecraft class, that if the method contains an if-statement with the Gmat::UNKNOWN_OBJECT type, it is expected to return the list of ALL reference object names, regardless of their type. However, it should also give the option to return a list of objects of a specific type. So, in my case, the snippet of code within this method looks like this:

Code: Select all
static StringArray fullList; 
fullList.clear();

if (type == Gmat::UNKNOWN_OBJECT) 
{
   fullList.push_back(coordSysName);
   fullList.push_back(libPointName);
   return fullList;
}
else if (type == Gmat::COORDINATE_SYSTEM)
{
   fullList.push_back(coordSysName);
   return fullList;
}
if type == Gmat::LIBRATION_POINT)
{
   fullList.push_back(libPointName);
   return fullList;
}



Once this list of reference objects is retrieved, the ObjectInitializer class iterates over the members of that list of reference objects and, for each name in the list, the method SetRefFromName is called, which ultimately calls the object's SetRefObject method.

So, to recap, the reason my object’s SetRefObject method was never invoked previously was because of a discrepancy between what the architectural specification says the GetRefObjectName method should do, vs. what it is actually currently hardwired to do in the ObjectInitializer class.

Since I don’t have multiple reference objects of the same type, I originally had no need to define the GetRefObjectNameArray method to begin with. However, that turned out to be the solution to my issue. Specifically, to add a line that checked if the type was Gmat::UNKNOWN_TYPE and, if so, have the method return a list of ALL reference objects, regardless of what their actual object type is. This seems like a kludge but it works and that seems to be how other objects are handled in GMAT as well.

I’m not sure that a fix is necessarily required, but it is certainly something that should be explicitly stated in the architectural documentation somewhere.

One other interesting note, as previously noted, the GetRefObjectName class I originally had was this:

Code: Select all
if ((type == Gmat::LIBRATION_POINT)) {
   return libPointName;
}
if (type == Gmat::COORDINATE_SYSTEM) {
   return coordSysName;
}


While stepping through with the debugger, I noticed that my CoordinateSystem object was finally being set by the SetRefObject method properly, but the LibrationPoint object was not. It turns out that this was because the line that calls the SetRefObject method in the ObjectInitializer looks like this:

Code: Select all
obj->SetRefObject(refObj, refObj->GetType(), refObj->GetName());


and GMAT apparently considers the “type” for the LibrationPoint to be Gmat::CALCULATED_POINT, and not Gmat::LIBRATION_POINT. However, other classes in GMAT call this method with the argument Gmat::LIBRATION_POINT. So, the fix was to replace any instance of:

Code: Select all
if (type == Gmat::LIBRATION_POINT)

with this
Code: Select all
if  ( (type == Gmat::CALCULATED_POINT) || (type == Gmat::LIBRATION_POINT) )


However, I don’t think this should be a permanent fix because, if you think about it, what’s to prevent a user from providing a Barycenter as a reference object where a LibrationPoint is expected? They are both of type Gmat::CALCULATED_POINT after all.

One final note, in regards to the documentation. There are some methods in GmatBase which are neither mentioned nor documented in the architectural specification. For example, HasLocalClones is an abstract method in GmatBase, so somewhere one of the derived classes has to implement it. If this method returns true, somewhere during the process of calling my script, the UpdateClonedObject and the UpdateClonedObjectParameters methods were queried if present. What are these methods supposed to do? I don’t seem to need them for anything, but I am curious to know what they’re for.
marchand
 
Posts: 86
Joined: Tue Feb 21, 2012 2:42 pm


Return to Plugin Development

Who is online

Users browsing this forum: No registered users and 2 guests