Dev:Example 2

|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.

my_assert - An example of creating a class == Synopsis == class my_assert { target "object-name"; property "variable-name"; lower value; upper value; } - or - class my_assert { property "variable-name"; lower value; upper value; } == Description == This example illustrates how to design, specify, implement, document and test a new class in GridLAB-D. === target === The 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 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 ==

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
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

  			      			         </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:

// TODO add class includes here // TODO define globals here
 * 1) define DLMAIN
 * 2) include "gridlabd.h"
 * 3) include "assert.h"

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 /// @{ /// 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; };
 * 1) ifndef _MYASSERT_H
 * 2) define _MYASSERT_H
 * 3) include "gridlabd.h"
 * 1) 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 EXPORT_INIT(my_assert); EXPORT_CREATE(my_assert); EXPORT_SYNC(my_assert); CLASS *my_assert::oclass = NULL; my_assert *my_assert::defaults = NULL;
 * 1) include "assert.h"

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 <tt>test_assert.glm</tt> to <tt>test_err_assert.glm</tt> and alter the uniform distribution of <tt>my_property</tt> 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.