From GridLAB-D Wiki
Jump to: navigation, search

Developer Example 2 - Creating a class

This example is based loosely on the assert class in the assert module.

The purpose of an assert object is to verify that a particular property of an object meet certain criteria. This can be done by testing the property at the end of each timestep.

Note
Because we do not want our assert object to conflict with the existing assert object we will create a new class called my_assert.

Design

Before writing the code for a new class it is highly recommended that you write the design and user documentation. This will help you write what code needs to be written and nothing more or less.

Requirements and specifications are typically written in prescriptive language, with the requirements describing what is needed and the specification describing it is to be done. The User's manual describe how user's are to apply it to problem they wish to solve.

Use case

The use case is to create a very simple class that will allow users to continually check whether a property remains within specified bounds. The simulation clock will not advance until the variable is within bounds.

Requirements

(R1) Object specification 
The user shall specify the object that is to be tested.
(R2) Parent object 
If the user fails to specify the object that is to be test, the parent object shall be tested.
(R3) Globals 
If the object has no parent, a global variable shall be tested.
(R4) Property specification 
The user shall specify the double type property that is to be tested.
(R5) Property type 
If the user specified property is not a double, the initialization shall fail.
(R6) Value range 
The user shall specify the lower and upper bounds on the property that is to be tested.
(R7) Test frequency 
The target shall be tested during every sync event.

Specifications

(S1) Property specification 
The target property shall be specified by the property variable of the assert object.
(S2) Object specification 
The target object shall be specified by the target variable of the assert object.
(S3) Parent object 
If the target object is not specified, the parent object shall be used.
(S4) Globals 
If the target object is not specified and the assert object has no parent, the property variable shall refer to a global variable.
(S5) Property type 
The init function shall fail if the target object/property does not exist.
(S6) Value range 
The lower and upper properties shall specify the lower and upper bounds (inclusive).
(S7) Test frequency 
The sync function shall force solver iteration until the target property's value is between the lower and upper boundary.

User's Manual

The following is what the user's manual would look like in the wiki docs.

Figure 1 - Example wiki page
 {{TOC3}}
 [[my_assert]] - An example of creating a class
 
 == Synopsis ==
 
  class my_assert {
    [[#target|target]] "''object-name''";
    [[#property|property]] "''variable-name''";
    [[#lower|lower]] ''value'';
    [[#upper|upper]] ''value'';
  }
 
 - or -
 
  class my_assert {
    [[#property|property]] "''variable-name''";
    [[#lower|lower]] ''value'';
    [[#upper|upper]] ''value'';
  }
 
 == Description ==
 
 This example illustrates how to design, specify,
 implement, document and test a new class in GridLAB-D.
 
 === target ===
 
 The [[#target|target]] property specifies the object 
 that is to be tested.  If the target object is 
 omitted, the parent object is used.  If there is no 
 parent object a global variable is used.

 === property ===

 The [[#property|property]] name specifies the variable
 name that is to be tested.
 
 === lower ===
 
 Specifies the lower bound of the value test.  If the 
 property is strictly less than the lower bound, a 
 warning message will be displayed. If the lower bound 
 is not specified, the property will not be tested a
 gainst the lower bound.
 
 === upper ===
 
 Specifies the upper bound of the value test.  If the 
 property is strictly greater than the uppoer bound, a 
 warning message will be displayed.  If the upper 
 bound is not specific the property will not be tested 
 against the upper bound.
 
 == See also ==
 {{:Xref:Examples}}
 

Files

The following files need to be modified or created.

example/Makefile.am

Add the new class's source files to the makefile:

pkglib_LTLIBRARIES = example.la
example_la_SOURCES = main.cpp assert.cpp assert.h
example_la_LDFLAGS = -module -no-undefined -avoid-version -version-info 1:0:0
uninstall-hook:
  -rmdir $(DESTDIR)$(pkglibdir)

example/example.vcproj

Example 2 file tree in VS2005

Add the new implementation files example/assert.h and example/assert.cpp to the VS2005 project either by right-clicking on the "Add > New Items" for the solution or adding the following to the example/example.vcproj file

	<Files>
		<Filter Name="Source Files"
			Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx"
			UniqueIdentifier="{GUID}"
			>
			<File RelativePath=".\main.cpp" >
			</File>
			<File RelativePath=".\assert.cpp" >
			</File>
		</Filter>
		<Filter Name="Header Files"
			Filter="h;hpp;hxx;hm;inl;inc;xsd"
			UniqueIdentifier="{GUID}"
			>
			<File RelativePath=".\assert.h" >
			</File>
		</Filter>
		<Filter Name="Test files">
			<File RelativePath=".\autotest\test_assert.glm" >
			</File>
		</Filter>
		<File RelativePath=".\Makefile.am">
		</File>
	</Files>

For details on the required VS2005 project settings, see Example 1 VS2005 settings.

example/main.cpp

First, add the #include statement for the new class:

#define DLMAIN
#include "gridlabd.h"
#include "assert.h"
// TODO add class includes here
// TODO define globals here

Then add the new call for the new class and if it's the first class (which it is in this example) return it's CLASS pointer:

EXPORT CLASS *init(CALLBACKS *fntable, MODULE *module, int argc, char *argv[])
{
  if ( set_callback(fntable)==NULL )
  {
     errno = EINVAL;
     return NULL;
  }
  // TODO register globals here
  new my_assert(module);
   // TODO register classes here
 return NULL my_assert::oclass; // TODO return first class registered
}

Finally, add the stub for the module kill routine:

CDECL int do_kill(void*)
{
  return 0;
}

example/assert.h

Create the new class's header file and add the properties and optional functions:

/// $Id$
/// Copyright (c) 2013 Battelle Memorial Institute
/// @file assert.h
/// @{
#ifndef _MYASSERT_H
#define _MYASSERT_H
#include "gridlabd.h"
/// My assert class
class my_assert : public gld_object {
public: // published properties
  GL_ATOMIC(object,target);
  GL_STRING(char32,property);
  GL_STRING(char1024,lower);
  GL_STRING(char1024,upper);
public: // required functions
  my_assert(MODULE*);
  int create(void);
  int init(OBJECT*);
public: // optional functions
  inline TIMESTAMP presync(TIMESTAMP t) { return TS_NEVER; };
  TIMESTAMP sync(TIMESTAMP);
  inline TIMESTAMP postsync(TIMESTAMP t) { return TS_NEVER; };
public: // required members
  static CLASS *oclass;
  static my_assert *defaults;
};
#endif /// @} _MYASSERT_H

example/assert.cpp

First you must declare which functions will be exported to the core and declare the require static variables:

/// $Id$
/// Copyright (c) 2013 Battelle Memorial Institute
#include "assert.h"
EXPORT_INIT(my_assert);
EXPORT_CREATE(my_assert);
EXPORT_SYNC(my_assert);
CLASS *my_assert::oclass = NULL;
my_assert *my_assert::defaults = NULL;

Next you must write the constructor, which is responsible to creating the class when the module is loaded.

my_assert::my_assert(MODULE *module)
{
  if ( oclass!=NULL ) 
    exception("invalid attempt to create my_assert class a second time");
  oclass = gld_class::create(module,"my_assert",sizeof(my_assert),PC_AUTOLOCK|PC_OBSERVER|PC_BOTTOMUP);
  if ( oclass==NULL )
    exception("failed to register my_assert class");
  oclass->trl = TRL_UNKNOWN;
  defaults = this;
  if ( gl_publish_variable(oclass,
      PT_object,"target",get_target_offset(), PT_DESCRIPTION, "target object",
      PT_char32,"property",get_property_offset(), PT_DESCRIPTION, "target property",
      PT_char1024,"lower",get_lower_offset(), PT_DESCRIPTION, "lower test bound",
      PT_char1024,"upper",get_upper_offset(), PT_DESCRIPTION, "upper test bound",
      NULL)<1 ) 
    exception("unable to register my_assert properties");
  memset(defaults=this,0,sizeof(my_assert));
  lower = upper = NaN;
}

Next you must write the create function, which initializes an object's memory and sets the default defaults, if any:

int my_assert::create(void)
{
  memcpy(this,defaults,sizeof(*this));
  return SUCCESS;
}

Next you must write the init function, which initializes an object after the user has set their values.

int my_assert::init(OBJECT *parent)
{
  if ( target==NULL && get_parent()==NULL )
  {
    gld_global var(property);
    return var.is_valid();
  }
  else
  {
    gld_property var(target?target:parent, property);
    return var.is_valid();
  }
}

Finally, you must write the sync function (and any other optional functions), which perform the time synchronization operations:

TIMESTAMP my_assert::sync(TIMESTAMP t0)
{
  gld_property var(target?target:get_parent()->my(), property);
  if ( !var.is_valid() )
  {
    gl_error("%s: target/property is not valid", get_name());
    return TS_INVALID; // not a valid reference
  }
  else if ( !objprop.compare(TCOP_IN,lower,upper) )
  {
    gl_error("%s: target/property %s is not between %s and %s", 
      get_name(), (const char*)var.get_string(), (const char*)lower, (const char*)upper);
    return t0; // bounds broken, iteration required
  }
  else
    return TS_NEVER; // ok, no need to sync again later (unless something changes)
}

Validation tests

In general each requirement or specification should have at least one autotest associated with it.

example/autotest/test_assert.glm

A simple test file is essential to verify that the implementation works.

First load the example module, setup the clock and define a global variable to test:

module example;
clock {
  starttime 2000-01-01 0:00:00;
  stoptime 2000-01-02 0:00:00;
}
global double my_global 1.23;

Next define a custom class that uses a random variable:

class my_test {
  random my_property;
}

Then create an object using the custom class, name it, define the random variable and place an instance of the example assert object under it to test the parent reference:

object my_test {
  name MyTest;
  my_property "type:uniform(2,3); refresh:1h";
  object my_assert {
    property my_property;
    lower 2;
    upper 3;
  };
}

Finally, define to new instance of the example assert object, one to test the named object reference and one to test the global variable reference:

object my_assert {
  target MyTest;
  property my_property;
  lower 2;
  upper 3;
}

object my_assert {
  property my_global;
  lower 1;
  upper 2;
}

To create a test file that should fail, copy test_assert.glm to test_err_assert.glm and alter the uniform distribution of my_property so that it generates numbers outside the allowed range:

object my_test {
  name MyTest;
  my_property "type:uniform(2,3,4); refresh:1h";
  object my_assert {
    property my_property;
    lower 2;
    upper 3;
  };
}

You can then run the validate test

host% cd workdir/example
host% gridlabd --validate
Starting validation test in directory workdir/trunk/example'
Processing workdir/example/autotest/test_assert.glm...
Processing workdir/example/autotest/test_err_assert.glm...
Validation report:
2 models tested
2 tests succeeded
100% success rate
Total validation elapsed time: 0.2 seconds
See workdir/example/validate.txt' for details
Note 
On most system only the last "Processing" entry will remain as the progress output overwrites completed tests.

See also