PowerTAC Demo Agent Documentation

From powerwiki
Revision as of 05:16, 21 September 2011 by Nguyen Nguyen (talk | contribs) (Created page with "= Overview = This document is a guide to help developer use the PowerTAC Demo Agent - GRAILS (PDA-G) as an example to develop agent for the PowerTAC platform. This is written...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Overview

This document is a guide to help developer use the PowerTAC Demo Agent - GRAILS (PDA-G) as an example to develop agent for the PowerTAC platform.

This is written under assumption that the developer is familiar with Java and Groovy. PDA-G is developed using the GRAILS framework to take advance of its support in web-based application. Web-based user interface is used to view agent status and control agent behavior.

Advantages of using PDA-G for developing an agent:

  • Easy to add support for new messages from server
  • Easy to register of message handlers
  • Easy to add phase activation tasks
  • Easy to send message to server

The main goals are flexibility and ease of use. These would allow developer to concentrate on agent behavior without dealing with low level details in communication layer.

There are two main events that could trigger agent action:

  1. A particular event from the server
  2. Phase activation

Communication with server

Agent makes web service (WS) call to authenticate with the server using username and password. The WS call will response with URL to JMS server and agent's JMS destination name (queue name). After login, communication between server and agent is over JMS messages.

Structure

 + grails-app/
   + conf/
     - BuildConfig.groovy
     - Config.groovy
     + spring/
       - resources.groovy
   + controllers/                                                   Agent's web controllers
   + domain/                                                        Agent's domain classes
   + i18n/
   + jobs/
   + views/
   + services/                                                      Agent's services
 + src/
   + java/
   + groovy/
     + api/                                                         API classes
     + core/                                                        Core package
     + exceptions/                                                  Exceptions classes
     + infrastructure/                                              Messaging and persistence infrastructure
     + interfaces/                                                  Interfaces 
 + test/
   + unit/
   + integration/
 + web-app/


Each component of an agent is wired up using Spring allow easy replacement of different implementation in resource file grails-app/conf/spring/resources.groovy. For example, default messagePersistenceManager uses hibernate/GORM to manage persistence. Developer could implement persistence manager that store data using a different method and replace messagePersistenceManager bean with the new class. Similar concept applies to other agent components.

<syntaxhighlight lang="groovy"> beans = {

 jmsConnectionFactory(org.springframework.jms.connection.SingleConnectionFactory) {bean ->
   targetConnectionFactory = {org.apache.activemq.ActiveMQConnectionFactory cf ->
     brokerURL = 
   }
 }
 messageListenerRegistrar(org.powertac.broker.infrastructure.messaging.MessageListenerRegistrar)
 xmlMessageReceiver(org.powertac.broker.infrastructure.messaging.XMLMessageReceiver) {
   messageListenerRegistrar = messageListenerRegistrar
   messageConverter = ref('messageConverter')
 }
 messageReceiver(org.powertac.broker.infrastructure.messaging.MessageReceiver) {
   xmlMessageReceiver = xmlMessageReceiver
 }
 arrayListProcessor(org.powertac.broker.infrastructure.messaging.ArrayListProcessor) { bean ->
   messageListenerRegistrar = messageListenerRegistrar
 }
 messagePersistenceManager(org.powertac.broker.infrastructure.persistence.MessagePersistenceManager)
 marketMessageListener(org.powertac.broker.infrastructure.messaging.MarketMessageListener) { bean ->
   messagePersistenceManager = messagePersistenceManager
 }
 tariffNegotiator(org.powertac.broker.core.tariffnegotiator.DemoTariffNegotiator)

} </syntaxhighlight>

Getting started

Checking out agent source code

From github

  1. cd /your/target/checkout/directory
  2. git clone https://nnguyen@github.com/powertac/powertac-demo-agent-grails.git

Repeat Step 2 for

  1. git clone https://github.com/powertac-plugins/powertac-common.git
  2. git clone https://github.com/powertac-plugins/powertac-server-interface.git
  3. git clone https://github.com/powertac-plugins/powertac-db-stuff.git

Running agent in developement mode

Note: currently, all dependency modules have to be checkout as sub-directory under one directory.

1) Start an instance of the server (instruction on how to download server at [1])

% cd powertac-server
% grails run-app

2) Start an instance of the agent (specify server.port so it would not conflict with server if server is running on the same machine)

% cd powertac-demo-agent-grails
% grails -Dserver.port=9090 run-app

3) Browse to http://localhost:9090/powertac-demo-agent-grails and perform login for agent to server

4) Browse to http://localhost:8080/powertac-server to get to server web dashbard to perform server function (ie. start competition)

Registering to event from server

The simplest method to register to an event (message) from server is to implement MessageListenerWithAutoRegistration interface. The implementation would have a getMessages method that returns a list of interested message types and an onMessage for each of the interested message type. An example is MarketMessageListener.

Registering to phase activation

The simplest method to register with phase activation is to implement TimeslotPhaseProcessorWithAutoRegistration interface. The implementation would have a getPhases method that returns a list of interested phase and an activate method that take in a Instant object and a phase number that would be call when each of the interested phase is activated. An example is ShoutRequestService.

Sample code

MarketMessageListener

<syntaxhighlight lang="groovy"> class MarketMessageListener implements MessageListenerWithAutoRegistration {

 private static final log = LogFactory.getLog(this)
 MessagePersistenceManager messagePersistenceManager
 static final def transientClazz = [SimEnd, ArrayList, CustomerBootstrapData,
     CustomerList, ErrorCmd, SimStart, SimPause, SimResume]
 def getMessages () {
   [Object]
 }
 def onMessage (Object msg) {
   log.debug("onMessage(${msg.class.name}) - start")
   if (!transientClazz.contains(msg.class)) {
     log.debug("onMessage(${msg.class.name}) - saving...")
     messagePersistenceManager.save(msg)
   }
   log.debug("onMessage(${msg.class.name}) - end")
 }

} </syntaxhighlight>

MessagePersistenceManager

<syntaxhighlight lang="groovy"> class MessagePersistenceManager {

 private static final log = LogFactory.getLog(this)
 private long timeslotMillis
 def save (Object obj) {
   log.warn("I don't know how to save ${obj.class.name} message type yet")
 }
 def save (WeatherReport wr) {
   log.debug("save(WeatherReport) - start")
   wr.merge()
   log.debug("save(WeatherReport) - end")
 }
 def save (Orderbook ob) {
   log.debug("save(Orderbook) - start")
   def asks = ob.asks.clone()
   def bids = ob.bids.clone()
   ob.asks.clear()
   ob.bids.clear()
   def timeslot = ob.timeslot
   if (timeslot) {
     timeslot.addToOrderbooks(ob)
   }
   ob.save()
   def processedAsks = []
   asks.each { ask ->
     def dbAsk = OrderbookAsk.findById(ask.id)
     if (dbAsk) {
       ask.version = dbAsk.version
       ask = ask.merge()
     }
     processedAsks << ask
   }
   def processedBids = []
   bids.each { bid ->
     def dbBid = OrderbookBid.findById(bid.id)
     if (dbBid) {
       bid.version = dbBid.version
       bid = bid.merge()
     }
     processedBids << bid
   }
   ob.bids.addAll(processedBids)
   ob.asks.addAll(processedAsks)
   log.debug("save(Orderbook) - end")
 }
 def save (CustomerInfo ci) {
   log.debug("save(CustomerInfo) - start")
   ci.merge()
   log.debug("save(CustomerInfo) - end")
 }
 def save (BankTransaction bt) {
   log.debug("save(BankTransaction) - begin")
   bt.merge()
   log.debug("save(BankTransaction) - end")
 }
 def save (CashPosition cp) {
   log.debug("save(CashPosition) - begin")
   def dbCp = CashPosition.findByBroker(cp.broker)
   if (dbCp) {
     log.debug("save(CashPosition - found in db")
     cp.id = dbCp.id
     cp.version = dbCp.version
     cp.merge(flush: true)
   } else {
     log.debug("save(CashPosition - NOT found in db")
     cp.id = null
     cp.save(flush: true)
   }
   log.debug("save(CashPosition) - XXXX broker ${cp?.broker?.username} has ${cp?.balance}")
   log.debug("save(CashPosition) - end")
 }
 def save (ClearedTrade ct) {
   log.debug("save(ClearedTrade) - start")
   def dbCt = ClearedTrade.findById(ct.id)
   if (dbCt) {
     ct.version = dbCt.version
   }
   ct.merge()
   log.debug("save(ClearedTrade) - end")
 }
 def save (TariffSpecification ts) {
   log.debug("save(TariffSpecification) - start")
   def processedRates = []
   ts.rates.each { rate ->
     def dbRate = Rate.findById(rate.id)
     if (dbRate) {
       log.debug("save(TariffSpecification) - found [dbRate.id:${dbRate.id},dbRate.version:${dbRate.version},rate.version:${rate.version}]")
       rate.version = dbRate.version
       rate = rate.merge()
       log.debug("save(TariffSpecification) - after merge rate:${rate}")
     } else {
       log.debug("save(TariffSpecification) - not found rate:${rate.id}")
       rate.save()
     }
     processedRates << rate
   }
   log.debug("save(TariffSpecification) - there are ${ts.rates.size()} item in ts.rates")
   log.debug("save(TariffSpecification) - there are ${processedRates.size()} item in processedRates")
   ts.rates.clear()
   ts.rates.addAll(processedRates)
   if (TariffSpecification.findById(ts.id)) {
     ts.merge()
   } else {
     ts.save()
   }
   log.debug("save(TariffSpecification) - end")
 }
 def save (TariffTransaction ttx) {
   log.debug("save(TariffTransaction) - start")
   ttx.merge()
   log.debug("save(TariffTransaction) - receving ${ttx.txType} ttx for ${ttx.broker.username}")
   log.debug("save(TariffTransaction) - end")
 }
 def save (TariffStatus ts) {
   log.debug("save(TariffStatus) - start")
   ts.merge()
   log.debug("save(TariffStatus) - start")
 }


 def save (Competition competition) {
   log.debug("save(Competition) - start")
   competition.brokers?.each {
     log.debug("save(Competition) - populate broker: ${it}")
     def broker = new Broker(username: it, enabled: true)
     broker.save()
   }
   timeslotMillis = competition.timeslotLength * TimeService.MINUTE
   log.debug("save(Competition) - saving competition ${competition}:${competition.save() ? 'successful' : competition.errors}")
   log.debug("save(Competition) - end")
 }
 def save (SimStart simStart) {
   log.debug("save(SimStart) - start")
   log.debug("Saving simStart - start @ ${simStart.start}")
   simStart.save()
   log.debug("save(SimStart) - this: ${this}")
   log.debug("save(SimStart) - end")
 }
 def save (TimeslotUpdate slotUpdate) {
   log.debug("save(TimeslotUpdate) - start")
   log.debug("save(TimeslotUpdate) - received TimeslotUpdate: ${slotUpdate.id}")
   def newEnableds = []
   slotUpdate.enabled?.each {
     it.id = it.serialNumber
     it.enabled = true
     it.endInstant = it.startInstant + timeslotMillis
     log.debug("save(TimeslotUpdate) -    saving enabled timeslot ${it.id}: ${(newEnableds << it.merge()) ? 'successful' : it.errors}")
   }
   slotUpdate.enabled = newEnableds
   def newDisables = []
   slotUpdate.disabled?.each {
     def dbTimeslot = Timeslot.findBySerialNumber(it.serialNumber)
     it.id = it.serialNumber
     it.enabled = false
     it.endInstant = it.startInstant + timeslotMillis
     it.version = dbTimeslot.version
     log.debug("save(TimeslotUpdate) -    saving disabled timeslot ${it.id}: ${(newDisables << it.merge()) ? 'successful' : it.errors}")
   }
   slotUpdate.disabled = newDisables
   log.debug("save(TimeslotUpdate) - saving TimeslotUpdate ${slotUpdate.id}:${slotUpdate.save() ? 'successful' : slotUpdate.errors}")
   log.debug("save(TimeslotUpdate) - end")
 }
 def save (BalancingTransaction bt) {
   log.debug("save(BalancingTransaction) - not yet implemented")
 }
 def save (DistributionTransaction dt) {
   log.debug("save(DistributionTransaction) - not yet implemented")
 }

} </syntaxhighlight>

ShoutRequestService

<syntaxhighlight lang="groovy"> class ShoutRequestService implements TimeslotPhaseProcessorWithAutoRegistration {

 static transactional = true
 def jmsManagementService
 def getPhases() {
   [1]
 }
 void activate (Instant time, int phaseNumber) {
   Timeslot timeslot = getTimeSlot(time)
   if (timeslot) {
     def shouts = ShoutRequest.findAllActiveAtTimeslot(timeslot.serialNumber).list()
     shouts?.each { shoutRequest ->
       log.debug("activate -    shout: ${shoutRequest}")
       def shout = new Shout(shoutRequest.properties)
       shout.broker = Broker.findByUsername(ConfigurationHolder.config.powertac.username)
       shout.timeslot = getTimeSlot(time)
       jmsManagementService.send(shout)
     }
   }
 }
 def getTimeSlot(time) {
   def competition = Competition.currentCompetition()
   def timeslot = null
   if (competition) {
     def startTime = competition.simulationBaseTime
     int numTimeslots = (time.millis - startTime.millis) / (competition.timeslotLength * TimeService.MINUTE)
     log.debug("startTime: ${startTime}, currentTime: ${time}, numTimeslots: ${numTimeslots}")
     timeslot = new Timeslot(serialNumber: numTimeslots + 1)
   }
   return timeslot
 }

} </syntaxhighlight>

Reference material

./services/org/powertac/broker/GameStateService.groovy                        Service for retrieve/update GameState 
./services/org/powertac/broker/PauseActionStateService.groovy                 Service for pausing game functionality
./services/org/powertac/broker/TimeslotPhaseService.groovy                    Service for time slot phase activation
./services/org/powertac/broker/CashPositionService.groovy                     Service for retrieve/update CashPosition
./services/org/powertac/broker/ConnectionService.groovy                       Service for connection management
./services/org/powertac/broker/JmsManagementService.groovy                    Service for JMS management
./services/org/powertac/broker/AutoLoginService.groovy                        Service for auto login functionality
./services/org/powertac/broker/ShoutRequestService.groovy                     Service for requesting shout
./services/org/powertac/broker/CompetitionManagementService.groovy            Service for managing competition
./services/org/powertac/broker/TariffPublishingService.groovy                 Service for publishing tarifff to server
./jobs/org/powertac/broker/LoginJob.groovy                                    Task for auto login
./jobs/org/powertac/broker/ClockDriveJob.groovy                               Task for time slot management
./conf/UrlMappings.groovy                                                     Url mapping configuration file
./conf/BootStrap.groovy                                                       Bootstrap file
./conf/Config.groovy                                                          Project configuration file
./conf/DataSource.groovy                                                      Datasource configuration file
./conf/BuildConfig.groovy                                                     Build/dependency configuration file
./conf/QuartzConfig.groovy                                                    Timer service configuration file
./conf/spring/resources.groovy                                                Spring resource file
./domain/org/powertac/broker/ShoutRequest.groovy                              Domain class for ShoutRequest
./domain/org/powertac/broker/PauseActionState.groovy                          Domain class for PauseActionState
./domain/org/powertac/broker/GameState.groovy                                 Domain class for GameState
./controllers/org/powertac/broker/ShoutRequestController.groovy               Web controller for ShoutRequest
./controllers/org/powertac/broker/StatusController.groovy                     Web controller for conntection/login status
./controllers/org/powertac/broker/TariffPublisherController.groovy            Web controller for tarriff publishing
./controllers/org/powertac/broker/ConnectionController.groovy                 Web controller for connection management
./controllers/org/powertac/broker/GameStatusController.groovy                 Web controller for game status

FAQ

  • How to run a non-web-based agent?
 The web interface is only there to help with ease of agent development.  Agents could be developed without using the web interface. 
  • What relevant source files control the demo agent's behaviour?
 Agent behavior can be controlled with both asynchronous events and periodic events.
 * Asynchronous events come from messages from the server such as TariffSpecification, Orderbook, etc.
 * Periodic events come from registered tasks to be executed for each timeslot.
  • Any other recommendations for getting started tuning / developing an

agent?

 Kang, P. "Software Architecture of the TAC Energy Trading Broker", August 5, 2010