NTRT Simulator  Version: Master
 All Classes Namespaces Files Functions Variables Typedefs Friends Pages
JSONQuadFeedbackControl.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 
27 #include "JSONQuadFeedbackControl.h"
28 
29 
30 // Should include tgString, but compiler complains since its been
31 // included from BaseSpineModelLearning. Perhaps we should move things
32 // to a cpp over there
34 #include "core/tgBasicActuator.h"
37 #include "examples/learningSpines/tgCPGCableControl.h"
38 
40 #include "helpers/FileHelpers.h"
41 
44 
45 #include "util/CPGEquationsFB.h"
46 #include "examples/learningSpines/tgCPGCableControl.h"
47 
48 #include "neuralNet/Neural Network v2/neuralNetwork.h"
49 
50 #include <json/json.h>
51 
52 #include <string>
53 #include <iostream>
54 #include <vector>
55 
56 //#define LOGGING
57 #define USE_KINEMATIC
58 
59 using namespace std;
60 
62  int tm,
63  int om,
64  int param,
65  int segnum,
66  double ct,
67  double la,
68  double ha,
69  double lp,
70  double hp,
71  double kt,
72  double kp,
73  double kv,
74  bool def,
75  double cl,
76  double lf,
77  double hf,
78  double ffMin,
79  double ffMax,
80  double afMin,
81  double afMax,
82  double pfMin,
83  double pfMax,
84  double maxH,
85  double minH) :
86 JSONCPGControl::Config::Config(ss, tm, om, param, segnum, ct, la, ha,
87  lp, hp, kt, kp, kv, def, cl, lf, hf),
88 freqFeedbackMin(ffMin),
89 freqFeedbackMax(ffMax),
90 ampFeedbackMin(afMin),
91 ampFeedbackMax(afMax),
92 phaseFeedbackMin(pfMin),
93 phaseFeedbackMax(pfMax),
94 maxHeight(maxH),
95 minHeight(minH)
96 {
97 
98 }
105  std::string args,
106  std::string resourcePath) :
107 JSONCPGControl(config, args, resourcePath),
108 m_config(config)
109 {
110  // Path and filename handled by base class
111 
112 }
113 
114 JSONQuadFeedbackControl::~JSONQuadFeedbackControl()
115 {
116  delete nn;
117 }
118 
120 {
121  m_pCPGSys = new CPGEquationsFB(100);
122 
123  Json::Value root; // will contains the root value after parsing.
124  Json::Reader reader;
125 
126  bool parsingSuccessful = reader.parse( FileHelpers::getFileString(controlFilename.c_str()), root );
127  if ( !parsingSuccessful )
128  {
129  // report to the user the failure and their locations in the document.
130  std::cout << "Failed to parse configuration\n"
131  << reader.getFormattedErrorMessages();
132  throw std::invalid_argument("Bad filename for JSON");
133  }
134  // Get the value of the member of root named 'encoding', return 'UTF-8' if there is no
135  // such member.
136  Json::Value nodeVals = root.get("nodeVals", "UTF-8");
137  Json::Value edgeVals = root.get("edgeVals", "UTF-8");
138 
139  std::cout << nodeVals << std::endl;
140 
141  nodeVals = nodeVals.get("params", "UTF-8");
142  edgeVals = edgeVals.get("params", "UTF-8");
143 
144  array_4D edgeParams = scaleEdgeActions(edgeVals);
145  array_2D nodeParams = scaleNodeActions(nodeVals);
146 
147  setupCPGs(subject, nodeParams, edgeParams);
148 
149  Json::Value feedbackParams = root.get("feedbackVals", "UTF-8");
150  feedbackParams = feedbackParams.get("params", "UTF-8");
151 
152  // Setup neural network
153  m_config.numStates = feedbackParams.get("numStates", "UTF-8").asInt();
154  m_config.numActions = feedbackParams.get("numActions", "UTF-8").asInt();
155  //m_config.numHidden = feedbackParams.get("numHidden", "UTF-8").asInt();
156 
157  std::string nnFile = controlFilePath + feedbackParams.get("neuralFilename", "UTF-8").asString();
158 
159  nn = new neuralNetwork(m_config.numStates, m_config.numStates*2, m_config.numActions);
160 
161  nn->loadWeights(nnFile.c_str());
162 
163  initConditions = subject.getSegmentCOM(m_config.segmentNumber);
164  for (int i = 0; i < initConditions.size(); i++)
165  {
166  std::cout << initConditions[i] << " ";
167  }
168  std::cout << std::endl;
169 #ifdef LOGGING // Conditional compile for data logging
170  m_dataObserver.onSetup(subject);
171 #endif
172 
173 #if (0) // Conditional Compile for debug info
174  std::cout << *m_pCPGSys << std::endl;
175 #endif
176  m_updateTime = 0.0;
177  bogus = false;
178 }
179 
181 {
182  m_updateTime += dt;
183  if (m_updateTime >= m_config.controlTime)
184  {
185 #if (1)
186  std::vector<double> desComs = getFeedback(subject);
187 
188 #else
189  std::size_t numControllers = subject.getNumberofMuslces() * 3;
190 
191  double descendingCommand = 0.0;
192  std::vector<double> desComs (numControllers, descendingCommand);
193 #endif
194  try
195  {
196  m_pCPGSys->update(desComs, m_updateTime);
197  }
198  catch (std::runtime_error& e)
199  {
200  // Stops the trial immediately, lets teardown know it broke
201  bogus = true;
202  throw (e);
203  }
204 
205 #ifdef LOGGING // Conditional compile for data logging
206  m_dataObserver.onStep(subject, m_updateTime);
207 #endif
208  notifyStep(m_updateTime);
209  m_updateTime = 0;
210  }
211 
212  double currentHeight = subject.getSegmentCOM(m_config.segmentNumber)[1];
213 
215  if (currentHeight > m_config.maxHeight || currentHeight < m_config.minHeight)
216  {
218  bogus = true;
219  throw std::runtime_error("Height out of range");
220  }
221 }
222 
224 {
225  scores.clear();
226  // @todo - check to make sure we ran for the right amount of time
227 
228  std::vector<double> finalConditions = subject.getSegmentCOM(m_config.segmentNumber);
229 
230  const double newX = finalConditions[0];
231  const double newZ = finalConditions[2];
232  const double oldX = initConditions[0];
233  const double oldZ = initConditions[2];
234 
235  const double distanceMoved = sqrt((newX-oldX) * (newX-oldX) +
236  (newZ-oldZ) * (newZ-oldZ));
237 
238  if (bogus)
239  {
240  scores.push_back(-1.0);
241  }
242  else
243  {
244  scores.push_back(distanceMoved);
245  }
246 
249  double totalEnergySpent=0;
250 
251  std::vector<tgSpringCableActuator* > tmpStrings = subject.find<tgSpringCableActuator> ("spine ");
252 
253  for(std::size_t i=0; i<tmpStrings.size(); i++)
254  {
255  tgSpringCableActuator::SpringCableActuatorHistory stringHist = tmpStrings[i]->getHistory();
256 
257  for(std::size_t j=1; j<stringHist.tensionHistory.size(); j++)
258  {
259  const double previousTension = stringHist.tensionHistory[j-1];
260  const double previousLength = stringHist.restLengths[j-1];
261  const double currentLength = stringHist.restLengths[j];
262  //TODO: examine this assumption - free spinning motor may require more power
263  double motorSpeed = (currentLength-previousLength);
264  if(motorSpeed > 0) // Vestigial code
265  motorSpeed = 0;
266  const double workDone = previousTension * motorSpeed;
267  totalEnergySpent += workDone;
268  }
269  }
270 
271  scores.push_back(totalEnergySpent);
272 
273  std::cout << "Dist travelled " << scores[0] << std::endl;
274 
275  Json::Value root; // will contains the root value after parsing.
276  Json::Reader reader;
277 
278  bool parsingSuccessful = reader.parse( FileHelpers::getFileString(controlFilename.c_str()), root );
279  if ( !parsingSuccessful )
280  {
281  // report to the user the failure and their locations in the document.
282  std::cout << "Failed to parse configuration\n"
283  << reader.getFormattedErrorMessages();
284  throw std::invalid_argument("Bad filename for JSON");
285  }
286 
287  Json::Value prevScores = root.get("scores", Json::nullValue);
288 
289  Json::Value subScores;
290  subScores["distance"] = scores[0];
291  subScores["energy"] = totalEnergySpent;
292 
293  prevScores.append(subScores);
294  root["scores"] = prevScores;
295 
296  ofstream payloadLog;
297  payloadLog.open(controlFilename.c_str(),ofstream::out);
298 
299  payloadLog << root << std::endl;
300 
301  delete m_pCPGSys;
302  m_pCPGSys = NULL;
303 
304  for(size_t i = 0; i < m_spineControllers.size(); i++)
305  {
306  delete m_spineControllers[i];
307  }
308  m_spineControllers.clear();
309 }
310 
311 void JSONQuadFeedbackControl::setupCPGs(BaseSpineModelLearning& subject, array_2D nodeActions, array_4D edgeActions)
312 {
313 
314  std::vector <tgSpringCableActuator*> spineMuscles = subject.find<tgSpringCableActuator> ("spine ");
315 
316  CPGEquationsFB& m_CPGFBSys = *(tgCast::cast<CPGEquations, CPGEquationsFB>(m_pCPGSys));
317 
318  for (std::size_t i = 0; i < spineMuscles.size(); i++)
319  {
320 
321  tgPIDController::Config config(20000.0, 0.0, 5.0, true); // Non backdrivable
322  tgCPGCableControl* pStringControl = new tgCPGCableControl(config);
323 
324  spineMuscles[i]->attach(pStringControl);
325 
326  // First assign node numbers
327  pStringControl->assignNodeNumberFB(m_CPGFBSys, nodeActions);
328 
329  m_spineControllers.push_back(pStringControl);
330  }
331 
332  // Then determine connectivity and setup string
333  for (std::size_t i = 0; i < m_spineControllers.size(); i++)
334  {
335  tgCPGActuatorControl * const pStringInfo = m_spineControllers[i];
336  assert(pStringInfo != NULL);
337  pStringInfo->setConnectivity(m_spineControllers, edgeActions);
338 
339  //String will own this pointer
340  tgImpedanceController* p_ipc = new tgImpedanceController( m_config.tension,
341  m_config.kPosition,
342  m_config.kVelocity);
343  if (m_config.useDefault)
344  {
345  pStringInfo->setupControl(*p_ipc);
346  }
347  else
348  {
349  pStringInfo->setupControl(*p_ipc, m_config.controlLength);
350  }
351  }
352 
353 }
354 
355 array_2D JSONQuadFeedbackControl::scaleNodeActions (Json::Value actions)
356 {
357  std::size_t numControllers = actions.size();
358  std::size_t numActions = actions[0].size();
359 
360  array_2D nodeActions(boost::extents[numControllers][numActions]);
361 
362  array_2D limits(boost::extents[2][numActions]);
363 
364  // Check if we need to update limits
365  assert(numActions == 5);
366 
367  limits[0][0] = m_config.lowFreq;
368  limits[1][0] = m_config.highFreq;
369  limits[0][1] = m_config.lowAmp;
370  limits[1][1] = m_config.highAmp;
371  limits[0][2] = m_config.freqFeedbackMin;
372  limits[1][2] = m_config.freqFeedbackMax;
373  limits[0][3] = m_config.ampFeedbackMin;
374  limits[1][3] = m_config.ampFeedbackMax;
375  limits[0][4] = m_config.phaseFeedbackMin;
376  limits[1][4] = m_config.phaseFeedbackMax;
377 
378  Json::Value::iterator nodeIt = actions.begin();
379 
380  // This one is square
381  for( std::size_t i = 0; i < numControllers; i++)
382  {
383  Json::Value nodeParam = *nodeIt;
384  for( std::size_t j = 0; j < numActions; j++)
385  {
386  nodeActions[i][j] = ( (nodeParam.get(j, 0.0)).asDouble() *
387  (limits[1][j] - limits[0][j])) + limits[0][j];
388  }
389  nodeIt++;
390  }
391 
392  return nodeActions;
393 }
394 
395 std::vector<double> JSONQuadFeedbackControl::getFeedback(BaseSpineModelLearning& subject)
396 {
397  // Placeholder
398  std::vector<double> feedback;
399 
400  const std::vector<tgSpringCableActuator*>& spineCables = subject.find<tgSpringCableActuator> ("spine ");
401 
402  double *inputs = new double[m_config.numStates];
403 
404  std::size_t n = spineCables.size();
405  for(std::size_t i = 0; i != n; i++)
406  {
407  std::vector< std::vector<double> > actions;
408 
409  const tgSpringCableActuator& cable = *(spineCables[i]);
410  std::vector<double > state = getCableState(cable);
411 
412  // Rescale to 0 to 1 (consider doing this inside getState
413  for (std::size_t i = 0; i < state.size(); i++)
414  {
415  inputs[i]=state[i] / 2.0 + 0.5;
416  }
417 
418  double *output = nn->feedForwardPattern(inputs);
419  vector<double> tmpAct;
420  for(int j=0;j<m_config.numActions;j++)
421  {
422  tmpAct.push_back(output[j]);
423  }
424  actions.push_back(tmpAct);
425 
426  std::vector<double> cableFeedback = transformFeedbackActions(actions);
427 
428  feedback.insert(feedback.end(), cableFeedback.begin(), cableFeedback.end());
429  }
430 
431 
432  return feedback;
433 }
434 
435 std::vector<double> JSONQuadFeedbackControl::getCableState(const tgSpringCableActuator& cable)
436 {
437  // For each string, scale value from -1 to 1 based on initial length or max tension of motor
438 
439  std::vector<double> state;
440 
441  // Scale length by starting length
442  const double startLength = cable.getStartLength();
443  state.push_back((cable.getCurrentLength() - startLength) / startLength);
444 
445  const double maxTension = cable.getConfig().maxTens;
446  state.push_back((cable.getTension() - maxTension / 2.0) / maxTension);
447 
448  return state;
449 }
450 
451 std::vector<double> JSONQuadFeedbackControl::transformFeedbackActions(std::vector< std::vector<double> >& actions)
452 {
453  // Placeholder
454  std::vector<double> feedback;
455 
456  // Leave in place for generalization later
457  const std::size_t numControllers = 1;
458  const std::size_t numActions = m_config.numActions;
459 
460  assert( actions.size() == numControllers);
461  assert( actions[0].size() == numActions);
462 
463  // Scale values back to -1 to +1
464  for( std::size_t i = 0; i < numControllers; i++)
465  {
466  for( std::size_t j = 0; j < numActions; j++)
467  {
468  feedback.push_back(actions[i][j] * 2.0 - 1.0);
469  }
470  }
471 
472  return feedback;
473 }
474 
Contains the definition of class ImpedanceControl. $Id$.
virtual array_4D scaleEdgeActions(Json::Value edgeParam)
void update(std::vector< double > &descCom, double dt)
virtual void onTeardown(BaseSpineModelLearning &subject)
virtual const double getTension() const
virtual const double getStartLength() const
void setConnectivity(const std::vector< tgCPGActuatorControl * > &allStrings, array_4D edgeParams)
Definition of the tgCPGStringControl observer class.
A template base class for a tensegrity spine.
A class to read a learning configuration from a .ini file.
virtual void onStep(BaseSpineModelLearning &subject, double dt)
Contains the definition of abstract base class tgSpringCableActuator. Assumes that the string is line...
A series of functions to assist with file input/output.
JSONQuadFeedbackControl(JSONQuadFeedbackControl::Config config, std::string args, std::string resourcePath="")
Contains the definition of class AnnealEvolution. Adapting NeuroEvolution to do Simulated Annealing...
virtual void onSetup(BaseSpineModelLearning &subject)
Contains the definition of class tgBasicActuator.
const Config & getConfig() const
std::vector< T * > find(const tgTagSearch &tagSearch)
Definition: tgModel.h:128
virtual void onStep(tgModel &model, double dt)
Definition of class CPGEquationsFB.
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, double hffMin=0.0, double hffMax=0.0)
virtual void onSetup(tgModel &model)
virtual const double getCurrentLength() const
void notifyStep(double dt)
void assignNodeNumberFB(CPGEquationsFB &CPGSys, array_2D nodeParams)