NTRT Simulator  Version: Master
 All Classes Namespaces Files Functions Variables Typedefs Friends Pages
JSONMetricsFeedbackControl.cpp
1 /*
2  * Copyright © 2012, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The NASA Tensegrity Robotics Toolkit (NTRT) v1 platform is licensed
7  * under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * http://www.apache.org/licenses/LICENSE-2.0.
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
15  * either express or implied. See the License for the specific language
16  * governing permissions and limitations under the License.
17 */
18 
28 #include "JSONMetricsFeedbackControl.h"
29 
30 
31 // Should include tgString, but compiler complains since its been
32 // included from BaseSpineModelLearning. Perhaps we should move things
33 // to a cpp over there
35 #include "core/tgBasicActuator.h"
38 #include "examples/learningSpines/tgCPGCableControl.h"
39 
40 #include "dev/dhustigschultz/BigPuppy_SpineOnly_Stats/BaseQuadModelLearning.h"
41 #include "helpers/FileHelpers.h"
42 
45 
46 #include "util/CPGEquationsFB.h"
47 #include "examples/learningSpines/tgCPGCableControl.h"
48 
49 #include "neuralNet/Neural Network v2/neuralNetwork.h"
50 
51 #include <json/json.h>
52 
53 #include <string>
54 #include <iostream>
55 #include <vector>
56 
57 //#define LOGGING
58 #define USE_KINEMATIC
59 #define PRINT_METRICS
60 
61 using namespace std;
62 
64  int tm,
65  int om,
66  int param,
67  int segnum,
68  double ct,
69  double la,
70  double ha,
71  double lp,
72  double hp,
73  double kt,
74  double kp,
75  double kv,
76  bool def,
77  double cl,
78  double lf,
79  double hf,
80  double ffMin,
81  double ffMax,
82  double afMin,
83  double afMax,
84  double pfMin,
85  double pfMax,
86  double maxH,
87  double minH) :
88 JSONQuadCPGControl::Config::Config(ss, tm, om, param, segnum, ct, la, ha,
89  lp, hp, kt, kp, kv, def, cl, lf, hf),
90 freqFeedbackMin(ffMin),
91 freqFeedbackMax(ffMax),
92 ampFeedbackMin(afMin),
93 ampFeedbackMax(afMax),
94 phaseFeedbackMin(pfMin),
95 phaseFeedbackMax(pfMax),
96 maxHeight(maxH),
97 minHeight(minH)
98 {
99 
100 }
107  std::string args,
108  std::string resourcePath) :
109 JSONQuadCPGControl(config, args, resourcePath),
110 m_config(config)
111 {
112  // Path and filename handled by base class
113 
114 }
115 
116 JSONMetricsFeedbackControl::~JSONMetricsFeedbackControl()
117 {
118  delete nn;
119 }
120 
122 {
123  m_pCPGSys = new CPGEquationsFB(100);
124 
125  Json::Value root; // will contains the root value after parsing.
126  Json::Reader reader;
127 
128  bool parsingSuccessful = reader.parse( FileHelpers::getFileString(controlFilename.c_str()), root );
129  if ( !parsingSuccessful )
130  {
131  // report to the user the failure and their locations in the document.
132  std::cout << "Failed to parse configuration\n"
133  << reader.getFormattedErrorMessages();
134  throw std::invalid_argument("Bad filename for JSON");
135  }
136  // Get the value of the member of root named 'encoding', return 'UTF-8' if there is no
137  // such member.
138  Json::Value nodeVals = root.get("nodeVals", "UTF-8");
139  Json::Value edgeVals = root.get("edgeVals", "UTF-8");
140 
141  std::cout << nodeVals << std::endl;
142 
143  nodeVals = nodeVals.get("params", "UTF-8");
144  edgeVals = edgeVals.get("params", "UTF-8");
145 
146  array_4D edgeParams = scaleEdgeActions(edgeVals);
147  array_2D nodeParams = scaleNodeActions(nodeVals);
148 
149  setupCPGs(subject, nodeParams, edgeParams);
150 
151  Json::Value feedbackParams = root.get("feedbackVals", "UTF-8");
152  feedbackParams = feedbackParams.get("params", "UTF-8");
153 
154  // Setup neural network
155  m_config.numStates = feedbackParams.get("numStates", "UTF-8").asInt();
156  m_config.numActions = feedbackParams.get("numActions", "UTF-8").asInt();
157  //m_config.numHidden = feedbackParams.get("numHidden", "UTF-8").asInt();
158 
159  std::string nnFile = controlFilePath + feedbackParams.get("neuralFilename", "UTF-8").asString();
160 
161  nn = new neuralNetwork(m_config.numStates, m_config.numStates*2, m_config.numActions);
162 
163  nn->loadWeights(nnFile.c_str());
164 
165  initConditions = subject.getSegmentCOM(m_config.segmentNumber);
166  for (int i = 0; i < initConditions.size(); i++)
167  {
168  std::cout << initConditions[i] << " ";
169  }
170  std::cout << std::endl;
171 #ifdef LOGGING // Conditional compile for data logging
172  m_dataObserver.onSetup(subject);
173 #endif
174 
175 #if (0) // Conditional Compile for debug info
176  std::cout << *m_pCPGSys << std::endl;
177 #endif
178  m_updateTime = 0.0;
179  m_totalTime = 0.0; //For metrics.
180  bogus = false;
181 
182  metrics.clear();
183 
184  //Getting the center of mass of the entire structure.
185  std::vector<double> structureCOM = subject.getCOM(m_config.segmentNumber);
186 
187  for(std::size_t i=0; i<3; i++)
188  {
189  metrics.push_back(structureCOM[i]);
190  }
191 
192  //"metrics" is a new section of the controller's JSON file that is
193  //added in the getNewFile function in evolution_job_master.py
194  Json::Value prevMetrics = root.get("metrics", Json::nullValue);
195 
196  Json::Value subMetrics;
197  subMetrics["initial COM x"] = metrics[0];
198  subMetrics["initial COM y"] = metrics[1];
199  subMetrics["initial COM z"] = metrics[2];
200 
201  prevMetrics.append(subMetrics);
202  root["metrics"] = prevMetrics;
203 
204  ofstream payloadLog;
205  payloadLog.open(controlFilename.c_str(),ofstream::out);
206 
207  payloadLog << root << std::endl;
208 
209 #ifdef PRINT_METRICS
210  //Just so we know how many vector rows we need:
211  std::vector<tgSpringCableActuator* > tmpStrings = subject.find<tgSpringCableActuator> ("spine ");
212  //Add the rows:
213  for(std::size_t i=0; i<tmpStrings.size(); i++){
214  m_muscleTensionsAll.push_back(vector <double> ());
215  m_muscleLengthsAll.push_back(vector <double> ());
216  }
217 #endif
218 }
219 
221 {
222  m_updateTime += dt;
223  m_totalTime += dt;
224  if (m_updateTime >= m_config.controlTime)
225  {
226 #if (1)
227  std::vector<double> desComs = getFeedback(subject);
228 
229 #else
230  std::size_t numControllers = subject.getNumberofMuslces() * 3;
231 
232  double descendingCommand = 0.0;
233  std::vector<double> desComs (numControllers, descendingCommand);
234 #endif
235  try
236  {
237  m_pCPGSys->update(desComs, m_updateTime);
238  }
239  catch (std::runtime_error& e)
240  {
241  // Stops the trial immediately, lets teardown know it broke
242  bogus = true;
243  throw (e);
244  }
245 
246 #ifdef LOGGING // Conditional compile for data logging
247  m_dataObserver.onStep(subject, m_updateTime);
248 #endif
249  notifyStep(m_updateTime);
250  m_updateTime = 0;
251  }
252 
253  double currentHeight = subject.getSegmentCOM(m_config.segmentNumber)[1];
254 
256  if (currentHeight > m_config.maxHeight || currentHeight < m_config.minHeight)
257  {
259  bogus = true;
260  throw std::runtime_error("Height out of range");
261  }
262 
263  //every 100 steps, get the COM, tensions, and lengths of active muscles and store them in 2D vectors:
264 #ifdef PRINT_METRICS
265  static int count = 0;
266  if(count > 100) {
267  m_timeStep.push_back(m_totalTime);
268 
269  //Getting the center of mass of the entire structure:
270  std::vector<double> structureCOM = subject.getCOM(m_config.segmentNumber);
271  m_quadCOM.push_back(structureCOM);
272 
273  std::vector<tgSpringCableActuator* > tmpStrings = subject.find<tgSpringCableActuator> ("spine ");
274 
275  //The tensions of all active muscles during this timestep
276  m_tensions.clear();
277  for(std::size_t i=0; i<tmpStrings.size(); i++)
278  {
279  double tension = tmpStrings[i]->getTension();
280  m_muscleTensionsAll.at(i).push_back(tension);
281  }
282 
283  //The lengths of all active muscles during this timestep
284  for(std::size_t i=0; i<tmpStrings.size(); i++)
285  {
286  double length = tmpStrings[i]->getCurrentLength();
287  m_muscleLengthsAll.at(i).push_back(length);
288  }
289 
290  count = 0;
291  }
292  else {
293  count++;
294  }
295 #endif
296 }
297 
299 {
300  scores.clear();
301  metrics.clear();
302  // @todo - check to make sure we ran for the right amount of time
303 
304  std::vector<double> finalConditions = subject.getSegmentCOM(m_config.segmentNumber);
305 
306  const double newX = finalConditions[0];
307  const double newZ = finalConditions[2];
308  const double oldX = initConditions[0];
309  const double oldZ = initConditions[2];
310 
311  const double distanceMoved = sqrt((newX-oldX) * (newX-oldX) +
312  (newZ-oldZ) * (newZ-oldZ));
313 
314  if (bogus)
315  {
316  scores.push_back(-1.0);
317  }
318  else
319  {
320  scores.push_back(distanceMoved);
321  }
322 
325  double totalEnergySpent=0;
326 
327  std::vector<tgSpringCableActuator* > tmpStrings = subject.find<tgSpringCableActuator> ("spine ");
328 
329  for(std::size_t i=0; i<tmpStrings.size(); i++)
330  {
331  tgSpringCableActuator::SpringCableActuatorHistory stringHist = tmpStrings[i]->getHistory();
332 
333  for(std::size_t j=1; j<stringHist.tensionHistory.size(); j++)
334  {
335  const double previousTension = stringHist.tensionHistory[j-1];
336  const double previousLength = stringHist.restLengths[j-1];
337  const double currentLength = stringHist.restLengths[j];
338  //TODO: examine this assumption - free spinning motor may require more power
339  double motorSpeed = (currentLength-previousLength);
340  if(motorSpeed > 0) // Vestigial code
341  motorSpeed = 0;
342  const double workDone = previousTension * motorSpeed;
343  totalEnergySpent += workDone;
344  }
345  }
346 
347  scores.push_back(totalEnergySpent);
348 
349  //Getting the center of mass of the entire structure.
350  std::vector<double> structureCOM = subject.getCOM(m_config.segmentNumber);
351 
352  for(std::size_t i=0; i<3; i++)
353  {
354  metrics.push_back(structureCOM[i]);
355  }
356 
357  std::cout << "Dist travelled " << scores[0] << std::endl;
358 
359  Json::Value root; // will contain the root value after parsing.
360  Json::Reader reader;
361 
362  bool parsingSuccessful = reader.parse( FileHelpers::getFileString(controlFilename.c_str()), root );
363  if ( !parsingSuccessful )
364  {
365  // report to the user the failure and their locations in the document.
366  std::cout << "Failed to parse configuration\n"
367  << reader.getFormattedErrorMessages();
368  throw std::invalid_argument("Bad filename for JSON");
369  }
370 
371  Json::Value prevScores = root.get("scores", Json::nullValue);
372  Json::Value prevMetrics = root.get("metrics", Json::nullValue);
373 
374  Json::Value subScores;
375  subScores["distance"] = scores[0];
376  subScores["energy"] = scores[1];
377 
378  Json::Value subMetrics;
379  subMetrics["final COM x"] = metrics[0];
380  subMetrics["final COM y"] = metrics[1];
381  subMetrics["final COM z"] = metrics[2];
382 
383  prevScores.append(subScores);
384  prevMetrics.append(subMetrics);
385 
386  root["scores"] = prevScores;
387  root["metrics"] = prevMetrics;
388 
389  ofstream payloadLog;
390  payloadLog.open(controlFilename.c_str(),ofstream::out);
391 
392  payloadLog << root << std::endl;
393 
394 #ifdef PRINT_METRICS
395  printMetrics(subject);
396 #endif
397 
398  delete m_pCPGSys;
399  m_pCPGSys = NULL;
400 
401  for(size_t i = 0; i < m_spineControllers.size(); i++)
402  {
403  delete m_spineControllers[i];
404  }
405  m_spineControllers.clear();
406 }
407 
408 void JSONMetricsFeedbackControl::setupCPGs(BaseQuadModelLearning& subject, array_2D nodeActions, array_4D edgeActions)
409 {
410 
411  std::vector <tgSpringCableActuator*> spineMuscles = subject.find<tgSpringCableActuator> ("spine ");
412 
413  CPGEquationsFB& m_CPGFBSys = *(tgCast::cast<CPGEquations, CPGEquationsFB>(m_pCPGSys));
414 
415  for (std::size_t i = 0; i < spineMuscles.size(); i++)
416  {
417 
418  tgPIDController::Config config(20000.0, 0.0, 5.0, true); // Non backdrivable
419  tgCPGCableControl* pStringControl = new tgCPGCableControl(config);
420 
421  spineMuscles[i]->attach(pStringControl);
422 
423  // First assign node numbers
424  pStringControl->assignNodeNumberFB(m_CPGFBSys, nodeActions);
425 
426  m_spineControllers.push_back(pStringControl);
427  }
428 
429  // Then determine connectivity and setup string
430  for (std::size_t i = 0; i < m_spineControllers.size(); i++)
431  {
432  tgCPGActuatorControl * const pStringInfo = m_spineControllers[i];
433  assert(pStringInfo != NULL);
434  pStringInfo->setConnectivity(m_spineControllers, edgeActions);
435 
436  //String will own this pointer
437  tgImpedanceController* p_ipc = new tgImpedanceController( m_config.tension,
438  m_config.kPosition,
439  m_config.kVelocity);
440  if (m_config.useDefault)
441  {
442  pStringInfo->setupControl(*p_ipc);
443  }
444  else
445  {
446  pStringInfo->setupControl(*p_ipc, m_config.controlLength);
447  }
448  }
449 
450 }
451 
452 array_2D JSONMetricsFeedbackControl::scaleNodeActions (Json::Value actions)
453 {
454  std::size_t numControllers = actions.size();
455  std::size_t numActions = actions[0].size();
456 
457  array_2D nodeActions(boost::extents[numControllers][numActions]);
458 
459  array_2D limits(boost::extents[2][numActions]);
460 
461  // Check if we need to update limits
462  assert(numActions == 5);
463 
464  limits[0][0] = m_config.lowFreq;
465  limits[1][0] = m_config.highFreq;
466  limits[0][1] = m_config.lowAmp;
467  limits[1][1] = m_config.highAmp;
468  limits[0][2] = m_config.freqFeedbackMin;
469  limits[1][2] = m_config.freqFeedbackMax;
470  limits[0][3] = m_config.ampFeedbackMin;
471  limits[1][3] = m_config.ampFeedbackMax;
472  limits[0][4] = m_config.phaseFeedbackMin;
473  limits[1][4] = m_config.phaseFeedbackMax;
474 
475  Json::Value::iterator nodeIt = actions.begin();
476 
477  // This one is square
478  for( std::size_t i = 0; i < numControllers; i++)
479  {
480  Json::Value nodeParam = *nodeIt;
481  for( std::size_t j = 0; j < numActions; j++)
482  {
483  nodeActions[i][j] = ( (nodeParam.get(j, 0.0)).asDouble() *
484  (limits[1][j] - limits[0][j])) + limits[0][j];
485  }
486  nodeIt++;
487  }
488 
489  return nodeActions;
490 }
491 
492 std::vector<double> JSONMetricsFeedbackControl::getFeedback(BaseQuadModelLearning& subject)
493 {
494  // Placeholder
495  std::vector<double> feedback;
496 
497  const std::vector<tgSpringCableActuator*>& spineCables = subject.find<tgSpringCableActuator> ("spine ");
498 
499  double *inputs = new double[m_config.numStates];
500 
501  std::size_t n = spineCables.size();
502  for(std::size_t i = 0; i != n; i++)
503  {
504  std::vector< std::vector<double> > actions;
505 
506  const tgSpringCableActuator& cable = *(spineCables[i]);
507  std::vector<double > state = getCableState(cable);
508 
509  // Rescale to 0 to 1 (consider doing this inside getState
510  for (std::size_t i = 0; i < state.size(); i++)
511  {
512  inputs[i]=state[i] / 2.0 + 0.5;
513  }
514 
515  double *output = nn->feedForwardPattern(inputs);
516  vector<double> tmpAct;
517  for(int j=0;j<m_config.numActions;j++)
518  {
519  tmpAct.push_back(output[j]);
520  }
521  actions.push_back(tmpAct);
522 
523  std::vector<double> cableFeedback = transformFeedbackActions(actions);
524 
525  feedback.insert(feedback.end(), cableFeedback.begin(), cableFeedback.end());
526  }
527 
528 
529  return feedback;
530 }
531 
532 std::vector<double> JSONMetricsFeedbackControl::getCableState(const tgSpringCableActuator& cable)
533 {
534  // For each string, scale value from -1 to 1 based on initial length or max tension of motor
535 
536  std::vector<double> state;
537 
538  // Scale length by starting length
539  const double startLength = cable.getStartLength();
540  state.push_back((cable.getCurrentLength() - startLength) / startLength);
541 
542  const double maxTension = cable.getConfig().maxTens;
543  state.push_back((cable.getTension() - maxTension / 2.0) / maxTension);
544 
545  return state;
546 }
547 
548 std::vector<double> JSONMetricsFeedbackControl::transformFeedbackActions(std::vector< std::vector<double> >& actions)
549 {
550  // Placeholder
551  std::vector<double> feedback;
552 
553  // Leave in place for generalization later
554  const std::size_t numControllers = 1;
555  const std::size_t numActions = m_config.numActions;
556 
557  assert( actions.size() == numControllers);
558  assert( actions[0].size() == numActions);
559 
560  // Scale values back to -1 to +1
561  for( std::size_t i = 0; i < numControllers; i++)
562  {
563  for( std::size_t j = 0; j < numActions; j++)
564  {
565  feedback.push_back(actions[i][j] * 2.0 - 1.0);
566  }
567  }
568 
569  return feedback;
570 }
571 
572 //A function for printing lengths and tensions that were gathered while the controller was running:
573 void JSONMetricsFeedbackControl::printMetrics(BaseQuadModelLearning& subject){
574 
575  std::vector<tgSpringCableActuator* > tmpStrings = subject.find<tgSpringCableActuator> ("spine ");
576  //Printing the tags for each muscle in order, so we know which tensions and lengths correspond to each muscle
577  for(std::size_t i=0; i<tmpStrings.size(); i++)
578  {
579  std::string delim = " ";
580  std::string muscle = tmpStrings[i]->getTagStr(delim);
581  cout << muscle << endl;
582  }
583  cout << endl;
584 
585  //Printing the timesteps for which metrics were gathered:
586  for (int i = 0; i < m_timeStep.size(); ++i) {
587  cout << m_timeStep[i] << ",";
588  }
589  cout << endl;
590 
591  //Printing the tensions of each muscle:
592  for (int i = 0; i < m_muscleTensionsAll.size(); i++){
593  for(int j = 0; j < m_muscleTensionsAll[i].size(); j++){
594  cout << m_muscleTensionsAll[i][j] << ",";
595  }
596  cout << endl;
597  }
598 
599  //Printing the lengths of each muscle:
600  for (int i = 0; i < m_muscleLengthsAll.size(); i++){
601  for(int j = 0; j < m_muscleLengthsAll[i].size(); j++){
602  cout << m_muscleLengthsAll[i][j] << ",";
603  }
604  cout << endl;
605  }
606 }
Contains the definition of class ImpedanceControl. $Id$.
void update(std::vector< double > &descCom, double dt)
virtual void onSetup(BaseQuadModelLearning &subject)
virtual const double getTension() const
virtual const double getStartLength() const
virtual void onStep(BaseQuadModelLearning &subject, double dt)
void setConnectivity(const std::vector< tgCPGActuatorControl * > &allStrings, array_4D edgeParams)
virtual void onTeardown(BaseQuadModelLearning &subject)
Definition of the tgCPGStringControl observer class.
A class to read a learning configuration from a .ini file.
Contains the definition of abstract base class tgSpringCableActuator. Assumes that the string is line...
A series of functions to assist with file input/output.
Contains the definition of class AnnealEvolution. Adapting NeuroEvolution to do Simulated Annealing...
Contains the definition of class tgBasicActuator.
const Config & getConfig() const
std::vector< T * > find(const tgTagSearch &tagSearch)
Definition: tgModel.h:128
JSONMetricsFeedbackControl(JSONMetricsFeedbackControl::Config config, std::string args, std::string resourcePath="")
virtual void onStep(tgModel &model, double dt)
virtual array_4D scaleEdgeActions(Json::Value edgeParam)
Definition of class CPGEquationsFB.
virtual void onSetup(tgModel &model)
virtual const double getCurrentLength() const
Config(int ss, int tm, int om, int param, int segnum=6, double ct=0.1, double la=0, double ha=30, double lp=-1 *M_PI, double hp=M_PI, double kt=0.0, double kp=1000.0, double kv=100.0, bool def=true, double cl=10.0, double lf=0.0, double hf=30.0, double ffMin=0.0, double ffMax=0.0, double afMin=0.0, double afMax=0.0, double pfMin=0.0, double pfMax=0.0, double maxH=60.0, double minH=1.0)
void assignNodeNumberFB(CPGEquationsFB &CPGSys, array_2D nodeParams)