view src/sc_grammar.cpp @ 0:add35537fdbb tip

Initial import
author irh <ian.r.hobson@gmail.com>
date Thu, 25 Aug 2011 11:05:55 +0100
parents
children
line wrap: on
line source
//  Copyright 2011, Ian Hobson.
//
//  This file is part of gpsynth.
//
//  gpsynth is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  gpsynth is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with gpsynth in the file COPYING. 
//  If not, see http://www.gnu.org/licenses/.

#include "sc_grammar.hpp"

#include "boost_ex.hpp"
#include "graph_helpers.hpp"
#include "logger.hpp"
#include "statistics.hpp"
#include "std_ex.hpp"

#include "json/json.h"

#include "boost/graph/random.hpp"
#include "boost/graph/topological_sort.hpp"

#include <iostream>
#include <iterator>
#include <fstream>
#include <set>
#include <sstream>

const int g_maximum_mixer_channels = 3;

const double g_probability_command_node_command = 0.85;
const double g_probability_command_node_special = 0.15;

const double g_probability_mod_node_command = 0.25;
const double g_probability_mod_node_special = 0.15;
const double g_probability_mod_node_constant = 0.4;

const double g_probability_argument_affected = 1.0;

const double g_probability_input_mutation = 1.0;
const double g_probability_connection_mutation = 1.0;

const double g_minimum_connection_weight = 0.001;

namespace {
  
  double MapCoefficientToRange(double c, const stdx::Range<double>& range) {
    return range.minimum_ + (range.Size() * c);
  }
  
  // Returns argument connection weight and offset, scaled to argument input
  void GenerateWeightAndOffset(const sc::Argument& argument,
                               sg::CommandRate rate,
                               sg::Connection* connection) {
    const stdx::Range<double>& scale_range = (rate == sg::kControlRate) 
                                             ? argument.range_control_
                                             : argument.range_;
    if (argument.fixed_range_) {
      connection->weight_ = scale_range.Size();
      connection->offset_ = scale_range.Minimum();
    } else if (argument.name_ == "in") {
      // input connections don't have an offset
      double weight = stdx::RandomCoefficient();
      if (rate == sg::kAudioRate) {
        // square audio weightings
        connection->weight_ = weight * weight;
      }
    } else {
      // generate random weight and offset
      double weight;
      do {
        weight = stdx::RandomCoefficient();
      } while (weight < g_minimum_connection_weight);
      double offset = stdx::RandomRange(0.0, 1.0 - weight);
      switch (argument.scaling_mode_) {
        case sc::Argument::kScalingLinear:
          connection->weight_ = MapCoefficientToRange(weight, scale_range);
          connection->offset_ = MapCoefficientToRange(offset, scale_range);
          break;

        case sc::Argument::kScalingLog:
          connection->weight_ = MapCoefficientToRange(weight * weight * weight,
                                                      scale_range);
          connection->offset_ = MapCoefficientToRange(offset * offset * offset,
                                                      scale_range);
          break;

        case sc::Argument::kConstant:
          throw std::runtime_error("ApplyRandomWeightAndOffset - preset argument provided");
      }
    }
  }

sc::Argument ParseArgument(const Json::Value& json) {
  sc::Argument arg;
  arg.name_ = json.get("name", "").asString();
  if (json.isMember("constant")) {
    arg.scaling_mode_ = sc::Argument::kConstant;
    arg.constant_value_ = json["constant"].asDouble();
    return arg;
  }
  if (json.isMember("range")) {
    const Json::Value range = json["range"];
    arg.range_.SetMinimum(range[0].asDouble());
    arg.range_.SetMaximum(range[1].asDouble());
  }
  if (json.isMember("range_control")) {
    const Json::Value range = json["range_control"];
    arg.range_control_.SetMinimum(range[0].asDouble());
    arg.range_control_.SetMaximum(range[1].asDouble());
  } else {
    arg.range_control_ = arg.range_;
  }
  if (json.isMember("fixed_range")) {
    arg.fixed_range_ = json["fixed_range"].asBool();
  }
  std::string scaling = json.get("scaling", "linear").asString();
  if (scaling == "linear") {
    arg.scaling_mode_ = sc::Argument::kScalingLinear;
  } else if (scaling == "log") {
    arg.scaling_mode_ = sc::Argument::kScalingLog;
  }
  return arg;
}

sc::Command ParseCommand(const Json::Value& json,
                         const std::map<std::string, sc::Argument>& types,
                         sc::Command::CommandMode command_mode) {
  if (json.type() == Json::stringValue) {
    return sc::Command(json.asString(), command_mode);
  }
  // name
  std::string name = json["name"].asString();
  // output type
  sc::Command::OutputType output_type = sc::Command::kAll;
  if (json.isMember("output")) {
    std::string output = json["output"].asString();
    if (output == "control") {
      output_type = sc::Command::kControl;
    } else if (output == "audio") {
      output_type = sc::Command::kAudio;
    }
  }
  // arguments
  std::vector<sc::Argument> args;
  // [in] argument for modifiers
  if (command_mode == sc::Command::kModifier) {
    args.push_back(sc::Argument("in"));
  }
  Json::Value json_args = json["args"];
  Json::ValueType arg_type = json_args.type();
  if (arg_type == Json::stringValue) {
    // single argument as string
    args.push_back(sc::Argument(json_args.asString()));
  } else if (arg_type == Json::objectValue) {
    // single argument object
    if (json_args.isMember("type")) {
      args.push_back(stdx::GetFromMap(types, json_args["type"].asString()));
      args.back().name_ = json_args["name"].asString();
    } else {
      args.push_back(ParseArgument(json_args));
    }      
  } else {
    // array of arguments
    for (int i = 0; i < json_args.size(); i++) {
      Json::Value arg = json_args[i];
      if (arg.type() == Json::stringValue) {
        args.push_back(sc::Argument(arg.asString()));
      } else {
        if (arg.isMember("type")) {
          args.push_back(stdx::GetFromMap(types, arg["type"].asString()));
          args.back().name_ = arg["name"].asString();
        } else {
          args.push_back(ParseArgument(arg));
        }
      }
    }
  }
  // success
  return sc::Command(name, command_mode, output_type, args);
}


sc::Command ParseSource(const Json::Value& json,
                        const std::map<std::string, sc::Argument>& types) {
  return ParseCommand(json, types, sc::Command::kSource);
}

sc::Command ParseModifier(const Json::Value& json,
                          const std::map<std::string, sc::Argument>& types) {
  return ParseCommand(json, types, sc::Command::kModifier);
}

void MutateValue(double* value, bool* mutation_occurred,
                 double range_min = 0.0, double range_max = 1.0) {
  // add gaussian noise to values, low variance 
  static bx::GaussianGenerator generator = bx::MakeGaussianGenerator(0, 0.125);
  double new_value = stdx::Clamp(*value + generator(), range_min, range_max);
  if (*value != new_value) {
    *value = new_value;
    *mutation_occurred = true;
  }
}
  
} // namespace



namespace sc {
  
Grammar::Grammar(const std::string& json_data)
: max_depth_(3)
{ 
  ParseJSON(json_data); 
}

void Grammar::ParseJSON(const std::string& json_data) {
  commands_.clear();
  Json::Reader reader;
  Json::Value root;
  if (!reader.parse(json_data, root)) {
    std::stringstream message;
    message << "Grammar::Parse: Failed to parse data\n"
            << reader.getFormattedErrorMessages();
    throw std::runtime_error(message.str());
  }
  try {
    // argument types
    // stores predefined argument types while parsing grammar
    std::map<std::string, Argument> argument_types;
    const Json::Value& args = root["arg-types"];
    for (int i = 0; i < args.size(); i++) {
      const Json::Value& arg = args[i];
      argument_types[arg["type"].asString()] = ParseArgument(arg);
    }
    // sound sources
    const Json::Value& sources = root["sources"];
    for (int i = 0; i < sources.size(); i++) {
      commands_.push_back(ParseSource(sources[i], argument_types)); 
    }
    // modifiers
    const Json::Value& modifiers = root["modifiers"];
    for (int i = 0; i < modifiers.size(); i++) {
      commands_.push_back(ParseModifier(modifiers[i], argument_types));
    }
  } catch (const std::exception& e) {
    std::stringstream message;
    message << "Grammar::Parse: " << e.what();
    throw std::runtime_error(message.str());
  }
}
  
int Grammar::RandomCommand(sg::CommandRate rate,
                           int tree_depth,
                           bool must_be_modifier /* = false */) const {
  Command::OutputType output;
  Command::CommandMode mode;
  CommandID command = -1;
  CommandID number_of_commands = commands_.size();
  // if we're at or one away from maximum depth then the command needs to 
  // be a source, headroom of 1 for some parameter control
  bool must_be_source = (tree_depth >= (max_depth_ - 1));
  do {
    command = stdx::Random(number_of_commands);
    output = commands_[command].Output();
    if (rate == sg::kAudioRate) {
      do {
        command = stdx::Random(number_of_commands);
        output = commands_[command].Output();
      } while (!(output == Command::kAll || output == Command::kAudio));
    } else {
      do {
        command = stdx::Random(number_of_commands);
        output = commands_[command].Output();
      } while (!(output == Command::kAll || output == Command::kControl));
    }
    mode = commands_[command].Mode();
  } while ((must_be_source && (mode != Command::kSource))
           || (must_be_modifier && (mode != Command::kModifier)));
  return static_cast<int>(command);
}
  
sg::Vertex Grammar::CreateCommand(sg::Graph &graph,
                                  sg::CommandRate rate,
                                  int depth,
                                  bool must_be_modifier /* = false */,
                                  bool make_modifier_input /* = true */) const {
  // select a random command for the node
  int command_id = RandomCommand(rate, depth, must_be_modifier);
  // create the node and add it to the graph
  sg::Node command_info(command_id, sg::kCommand, rate);
  sg::Vertex vertex = boost::add_vertex(command_info, graph);
  // a modifier command has to have further nodes added to its main input
  const Command& command = commands_[command_id];
  for (int i = 0; i < command.Arguments().size(); i++) {
    const Argument& argument = command.GetArgument(i);
    if (argument.name_ == "in") {
      if (make_modifier_input) {
        // modifier input must be given a command tree
        sg::Vertex tree = RandomTree(graph, rate, kTreeMode_Command, depth + 1);
        sg::Connection connection(i);
        GenerateWeightAndOffset(argument, rate, &connection);
        boost::add_edge(tree, vertex, connection, graph);
      }
    } else if (argument.scaling_mode_ == sc::Argument::kConstant) {
      // don't attach inputs to predefined arguments
      continue;
    } else if (stdx::Chance(g_probability_argument_affected)) {
      // randomly create input for command arguments
      sg::Vertex tree = RandomTree(graph,
                                   sg::kControlRate,
                                   kTreeMode_Mod,
                                   depth + 1);
      sg::Connection connection(i);
      GenerateWeightAndOffset(argument, rate, &connection);
      boost::add_edge(tree, vertex, connection, graph);  
    }
  }
  return vertex;
}
  
sg::Vertex Grammar::RandomTree(sg::Graph &graph,
                               sg::CommandRate rate,
                               TreeMode tree_mode,
                               int depth) const {
  enum NodeType {
    kCommand,
    kSpecial,
    kConstant,
    kParameter
  };
  static stats::ProbabilitySelector node_type_selector;
  if (!node_type_selector.Initialized()) {
    std::vector<double> probabilities(3);
    probabilities[kCommand] = g_probability_mod_node_command;
    probabilities[kSpecial] = g_probability_mod_node_special;
    probabilities[kConstant] = g_probability_mod_node_constant;
    node_type_selector.SetProbabilities(probabilities);
  }
  // establish the type of node to create
  int node_type;
  if (depth >= max_depth_) {
    if (tree_mode == kTreeMode_Command) {
      // if we're at maximum depth in command mode then create a command
      node_type = kCommand;
    } else {
      // in mod mode at maximum depth pick either a constant or parameter
      if (stdx::Chance(g_probability_mod_node_constant)) {
        node_type = kConstant;
      } else {
        node_type = kParameter;
      }
    }
  } else {
    if (tree_mode == kTreeMode_Command) {
      if (stdx::Chance(g_probability_command_node_command)) {
        node_type = kCommand;
      } else {
        node_type = kSpecial;
      }
    } else {
      // mod mode below max depth, any node type is ok
      node_type = static_cast<NodeType>(node_type_selector());
    }   
  }
  if (node_type == kCommand) {
    return CreateCommand(graph, rate, depth);
  } else if (node_type == kSpecial) {
    int command = stdx::Random(Command::kNumberOfSpecialCommands);
    return SpecialCommand(command, graph, rate, tree_mode, depth + 1);   
  } else if (node_type == kConstant) {
    return boost::add_vertex(sg::Node(stdx::RandomCoefficient()), graph);
  } else if (node_type == kParameter) {
    return boost::add_vertex(sg::Node(RandomParameter(graph), sg::kParameter),
                             graph);
  }
  throw std::runtime_error("Grammar::RandomTree - unknown node type"); 
}

int Grammar::RandomParameter(sg::Graph& graph) const {
  // as a simple rule, randomly pick an existing parameter, or if chosen 
  // parameter is greater than # of parameters, then add a new one
  std::vector<double>& parameters = graph[boost::graph_bundle].parameters_;
  int number_of_parameters = static_cast<int>(parameters.size());
  // parameter ids start at 1
  int parameter = stdx::RandomRangeInt(1, number_of_parameters + 1);
  // bump parameter count if we've got a new parameter
  if (parameter > number_of_parameters) {
    // store a random value for the new parameter
    parameters.push_back(stdx::RandomCoefficient());
  }
  return parameter;
}

void Grammar::AddTreeToSpecialCommand(sg::Vertex command_node,
                                      sg::Graph& graph,
                                      int input_id,
                                      TreeMode tree_mode,
                                      sg::CommandRate command_rate,
                                      bool* constant_added,
                                      std::set<int>* parameters,
                                      int depth) const {
  bool success = false;
  while (!success) {
    sg::Vertex tree;
    // first channel or audio command should enforce a command source
    if (input_id == 0) {
      tree = RandomTree(graph, command_rate, kTreeMode_Command, depth + 1);
    } else {
      tree = RandomTree(graph, command_rate, tree_mode, depth + 1);
    }
    sg::NodeType tree_type = graph[tree].type_;
    if (tree_type == sg::kConstant) {
      // we only need one constant per mixer
      if (*constant_added) {
        RemoveSubTree(tree, graph);
        continue;
      }
      *constant_added = true;
    } else if (tree_type == sg::kParameter) {
      // we don't want duplicated parameters for special command inputs
      if (parameters->find(graph[tree].id_) != parameters->end()) {
        RemoveSubTree(tree, graph);
        continue;
      }
      // store the parameter id
      parameters->insert(graph[tree].id_);
    }
    // apply weighting
    double weight = stdx::RandomCoefficient();
    if (command_rate == sg::kAudioRate) {
      // scale audio to curve
      weight *= weight;
    }
    // make the connection to the generated tree
    boost::add_edge(tree, command_node, sg::Connection(weight), graph);
    success = true;
  }
}
  
sg::Vertex Grammar::SpecialCommand(int command_id,
                                   sg::Graph& graph,
                                   sg::CommandRate command_rate,
                                   TreeMode tree_mode,
                                   int depth /* = 0 */,
                                   int minimum_channels /* = 2 */) const {
  // create the special command node
  sg::Node command_info(command_id, sg::kSpecial, command_rate);
  sg::Vertex mixer = boost::add_vertex(command_info, graph);
  // pick a random number of channels to create for this command
  int channels = stdx::RandomRangeInt(minimum_channels,
                                      g_maximum_mixer_channels);
  bool constant_added = false;
  std::set<int> parameters;
  // add a tree for each channel
  for (int i = 0; i < channels; i++) {
    AddTreeToSpecialCommand(mixer,
                            graph,
                            i,
                            tree_mode,
                            command_rate,
                            &constant_added,
                            &parameters,
                            depth);
  }
  return mixer;
}

void Grammar::RandomGraph(sg::Graph& graph) const {
  graph.clear();
  // root output node
  SpecialCommand(sc::Command::kMixer, graph, sg::kAudioRate, kTreeMode_Command);
}
  
void Grammar::PickCrossoverNodes(const sg::Graph& parent_1, 
                                 const sg::Graph& parent_2,
                                 sg::Vertex* crossover_node_1,
                                 sg::Vertex* crossover_node_2) const {
  sg::Vertex source_node_1;
  sg::Vertex source_node_2;
  bool nodes_found = false;
  do {
    // pick random edges and their nodes
    source_node_1 = boost::random_vertex(parent_1, bx::RandomEngine());
    source_node_2 = boost::random_vertex(parent_2, bx::RandomEngine());
    // avoid the root output node
    if ((source_node_1 == 0) || (source_node_2 == 0)) {
      continue;
    }
    // check it's ok to swap these nodes
    const sg::Node& info_1 = parent_1[source_node_1];
    const sg::Node& info_2 = parent_2[source_node_2];
    // command rates need to match
    if (info_1.rate_ != info_2.rate_) {
      continue;
    }
    // we don't want to swap two constants
    if (info_1.type_ == sg::kConstant && info_2.type_ == sg::kConstant) {
      continue;
    }
    // we don't want to swap parameter inputs with same parameter id
    if ((info_1.type_ == sg::kParameter)
        && (info_2.type_ == sg::kParameter)
        && (info_1.id_ == info_2.id_)) {
      continue;
    }
    // check that we're not replacing a control modifer's input with non-command
    sg::Edge connection_1 = *(boost::out_edges(source_node_1, parent_1).first);
    sg::Edge connection_2 = *(boost::out_edges(source_node_2, parent_2).first);
    sg::Vertex target_node_1 = boost::target(connection_1, parent_1);
    sg::Vertex target_node_2 = boost::target(connection_2, parent_2);
    const sg::Node& info_target_1 = parent_1[target_node_1];
    const sg::Node& info_target_2 = parent_2[target_node_2];
    if ((info_target_1.type_ == sg::kCommand)
        || (info_target_1.type_ == sg::kSpecial)) {
      const sc::Command& target_command_1 = commands_[info_target_1.id_];
      const std::vector<Argument>& arguments_1 = target_command_1.Arguments();
      const sg::Connection& info_connection_1 = parent_1[connection_1];
      if (arguments_1[info_connection_1.input_].name_ == "in") {
        // target input is a modifier's 'in' argument, only allow commands to
        // be swapped in
        if (!(info_2.type_ == sg::kCommand || info_2.type_ == sg::kSpecial)) {
          continue;
        }
      }
    }
    if ((info_target_2.type_ == sg::kCommand)
        || (info_target_2.type_ == sg::kSpecial)) {
      const sc::Command& target_command_2 = commands_[info_target_2.id_];
      const std::vector<Argument>& arguments_2 = target_command_2.Arguments();
      const sg::Connection& info_connection_2 = parent_1[connection_2];
      if (arguments_2[info_connection_2.input_].name_ == "in") {
        // target input is a modifier's 'in' argument, only allow commands to
        // be swapped in
        if (!(info_1.type_ == sg::kCommand || info_1.type_ == sg::kSpecial)) {
          continue;
        }
      }
    }
    nodes_found = true;
  } while (!nodes_found);
  *crossover_node_1 = source_node_1;
  *crossover_node_2 = source_node_2;
}
  
bool Grammar::MutationReplaceSubTree(sg::Graph& graph) const {
  // sanity check
  if (boost::num_vertices(graph) <= 1) {
    throw std::runtime_error("sc::Grammar::MutationReplaceSubTree - graph has too few nodes");
  }
  // pick node to replace
  sg::Vertex node_to_replace;
  do {
    node_to_replace = boost::random_vertex(graph, bx::RandomEngine());
  } while (node_to_replace == 0);
  sg::CommandRate new_tree_rate = graph[node_to_replace].rate_;
  sg::Edge connection = *(boost::out_edges(node_to_replace, graph).first);
  sg::Vertex connection_target = boost::target(connection, graph);
  // store the connection info contained in the connection edge
  sg::Connection connection_info = graph[connection];
  const sg::Node& target_info = graph[connection_target];
  // determine new tree mode
  TreeMode new_tree_mode = kTreeMode_Mod;
  if (new_tree_rate == sg::kAudioRate) {
    // audio rate nodes always require command tree replacements
    new_tree_mode = kTreeMode_Command;
  } else if (target_info.type_ == sg::kCommand) {
    // if the connection target is a modifier 
    // and the connection input is 0 then a command tree is needed
    if (commands_[target_info.id_].Mode() == Command::kModifier) {
      if (connection_info.input_ == 0) {
        new_tree_mode = kTreeMode_Command;
      }
    }
  } else if (target_info.type_ == sg::kSpecial) {
    // first input to special commands is a command tree
    if (connection_info.input_ == 0) {
      new_tree_mode = kTreeMode_Command;
    }
  }
  // remove the sub tree
  RemoveSubTree(connection, graph);
  // generate new tree
  sg::Vertex new_tree = RandomTree(graph, new_tree_rate, new_tree_mode, 0);
  // connect tree to graph
  boost::add_edge(new_tree, connection_target, connection_info, graph);
  return true;
}
  
  
  
bool Grammar::MutationAddSubTree(sg::Graph& graph) const {
  // sanity check
  if (graph[0].type_ != sg::kSpecial) {
    throw std::runtime_error("sc::Grammar::MutationAddSubTree - missing root mixer node");
  }
  bool mutation_occurred = false;
  while (!mutation_occurred) {
    // pick a command or special node to add a tree input to
    sg::Vertex node;
    sg::NodeType node_type;
    do {
      node = boost::random_vertex(graph, bx::RandomEngine());
      node_type = graph[node].type_;
    } while (!(node_type == sg::kCommand || node_type == sg::kSpecial));
    if (node_type == sg::kCommand) {
      int node_id = graph[node].id_;
      const std::vector<Argument>& arguments = commands_[node_id].Arguments();
      std::size_t number_of_arguments = arguments.size();
      sg::InEdgeIterator edge;
      sg::InEdgeIterator edge_end;
      std::vector<bool> used_arguments(arguments.size(), false);
      for (boost::tie(edge, edge_end) = boost::in_edges(node, graph);
           edge != edge_end;
           ++edge) {
        used_arguments[graph[*edge].input_] = true;
      }
      // check that there is an unused argument available for this command
      bool argument_available = false;
      for (std::size_t i = 0; i < number_of_arguments; i++) {
        if (used_arguments[i] == false) {
          // we don't want to attach a tree to a preset argument
          if (arguments[i].scaling_mode_ != Argument::kConstant) {
            argument_available = true;
            break;
          }
        }
      }
      if (argument_available) {
        // find a random argument to insert a tree into
        std::size_t argument;
        do {
          argument = stdx::Random(arguments.size());
        } while (used_arguments[argument] == true
                 || arguments[argument].scaling_mode_ == Argument::kConstant);
        // add a tree and connect to the unused argument
        sg::Vertex tree = RandomTree(graph,
                                     sg::kControlRate,
                                     kTreeMode_Mod,
                                     0);
        sg::Connection connection(static_cast<int>(argument));
        GenerateWeightAndOffset(arguments[argument],
                                sg::kControlRate,
                                &connection);
        boost::add_edge(tree, node, connection, graph);
        mutation_occurred = true;
      }
    } else if (node_type == sg::kSpecial) {
      int node_inputs = static_cast<int>(boost::in_degree(node, graph));
      // we need to check the special command's inputs to avoid adding redundant
      // constants or parameters
      bool constant_added = false;
      std::set<int> parameters;
      sg::InEdgeIterator edge;
      sg::InEdgeIterator edge_end;
      for (boost::tie(edge, edge_end) = boost::in_edges(node, graph);
           edge != edge_end;
           ++edge) {
        const sg::Node& source = graph[boost::source(*edge, graph)];
        if (source.type_ == sg::kConstant) {
          constant_added = true;
        }
        if (source.type_ == sg::kParameter) {
          parameters.insert(source.id_);
        }
      }
      // select tree mode based on command rate
      TreeMode tree_mode;
      if (graph[node].rate_ == sg::kAudioRate) {
        tree_mode = kTreeMode_Command;
      } else {
        tree_mode = kTreeMode_Mod;
      }
      // add the tree
      AddTreeToSpecialCommand(node,
                              graph,
                              node_inputs,
                              tree_mode,
                              graph[node].rate_,
                              &constant_added,
                              &parameters,
                              0);
      mutation_occurred = true;
    }  
  }
  return true;
}

bool Grammar::MutationModifyInputs(sg::Graph& graph) const {
  // sanity check
  if (graph[boost::graph_bundle].parameters_.size() == 0) {
    sg::VertexIterator vertex;
    sg::VertexIterator vertex_end;
    bool constant_found = false;
    for (boost::tie(vertex, vertex_end) = boost::vertices(graph);
         vertex != vertex_end;
         ++vertex) {
      sg::Node& node = graph[*vertex];
      if (node.type_ == sg::kConstant) {
        constant_found = true;
        break;
      }
    }
    if (!constant_found) {
      // no parameters, no constants, nothing to do..
      return false;
    }
  }
  // make a random generator with a narrow guassian distribution 
  bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125);
  bool mutation_occurred = false;
  while (!mutation_occurred) {
    // mutate parameters
    foreach (double& parameter, graph[boost::graph_bundle].parameters_) {
      if (stdx::Chance(g_probability_input_mutation)) {
        MutateValue(&parameter, &mutation_occurred);
      }
    }
    // mutate constants
    sg::VertexIterator vertex;
    sg::VertexIterator vertex_end;
    for (boost::tie(vertex, vertex_end) = boost::vertices(graph);
         vertex != vertex_end;
         ++vertex) {
      sg::Node& node = graph[*vertex];
      if (node.type_ == sg::kConstant) {
        // constant value
        if (stdx::Chance(g_probability_input_mutation)) {
          MutateValue(&node.constant_value_, &mutation_occurred);
        }
      }
    }
  }
  return true;
}

bool Grammar::MutationModifyConnections(sg::Graph& graph) const {
  // sanity check
  if (boost::num_edges(graph) < 1) {
    throw std::runtime_error("sc::Grammar::MutationModifyConnections - graph has no connections");
  }
  bx::GaussianGenerator gaussian = bx::MakeGaussianGenerator(0, 0.125);
  bool mutation_occurred = false;
  while (!mutation_occurred) {
    sg::EdgeIterator edge;
    sg::EdgeIterator edge_end;
    for (boost::tie(edge, edge_end) = boost::edges(graph);
         edge != edge_end;
         ++edge) {
      if (stdx::Chance(g_probability_connection_mutation)) {
        sg::Connection& connection = graph[*edge];
        const sg::Node& target = graph[boost::target(*edge, graph)];
        const sc::Argument& argument = GetCommandArgument(target.id_,
                                                          connection.input_);
        if (!argument.fixed_range_) {
          // mutate weight
          MutateValue(&connection.weight_,
                      &mutation_occurred,
                      g_minimum_connection_weight);
          // special command inputs and modifier command input arguments 
          // have offsets set to zero
          if ((target.type_ != sg::kSpecial) && (argument.name_ != "in")) {
            MutateValue(&connection.offset_, &mutation_occurred,
                        0.0, 1.0 - connection.weight_);
          }
        }
      }
    }    
  }
  return true;
}
  
const std::vector<sc::Argument>& Grammar::CommandArguments(int command) const {
  return commands_[command].Arguments();
}

bool Grammar::MutationReplaceCommand(sg::Graph& graph) const {
  // sanity check
  if (boost::num_vertices(graph) <= 1) {
    throw std::runtime_error("sc::Grammar::MutationReplaceCommand - graph has too few nodes");
  }
  sg::Vertex node_to_replace;
  sg::Node* node_info;
  // find command to replace
  do {
    node_to_replace = boost::random_vertex(graph, bx::RandomEngine());
    node_info = &graph[node_to_replace];
  } while (node_info->type_ != sg::kCommand);
  int old_id = node_info->id_;
  // generate new command id
  int new_id;
  int number_of_commands = static_cast<int>(commands_.size());
  do {
    // new command must match old output and mode
    new_id = stdx::Random(number_of_commands);
  } while (new_id != old_id
           && commands_[new_id].Output() != commands_[old_id].Output()
           && commands_[new_id].Mode() != commands_[old_id].Mode());
  // new command info
  const std::vector<sc::Argument>& arguments = CommandArguments(new_id);
  std::size_t number_of_arguments = commands_[new_id].Arguments().size();
  // remove invalid parameters
  // removing an in edge invalidates the iterators, so we need to restart the
  // search each time an edge is removed
  sg::InEdgeIterator edge;
  sg::InEdgeIterator edge_end;
  bool do_it_again;
  do {
    do_it_again = false;
    for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph);
         edge != edge_end;
         ++edge) {
      int input = graph[*edge].input_;
      // remove input tree if it's for an extraneous parameter
      // or if the argument is a constant
      if (input >= number_of_arguments
          || arguments[input].scaling_mode_ == sc::Argument::kConstant) {
        RemoveSubTree(*edge, graph);
        do_it_again = true;
        // break out of for loop
        break;
      }
    }
  } while (do_it_again);
  // now invalid parameters have been removed, validate the rest
  for (boost::tie(edge, edge_end) = boost::in_edges(node_to_replace, graph);
       edge != edge_end;
       ++edge) {
    // if the new argument is fixed range then correct the connection weight
    int input = graph[*edge].input_;
    if (arguments[input].fixed_range_) {
      GenerateWeightAndOffset(arguments[input],
                              node_info->rate_, 
                              &graph[*edge]);
    }
  }
  // apply new command id
  node_info->id_ = new_id;
  return true;
}
  
int Grammar::GetCommandInputID(int command) const {
  const std::vector<sc::Argument>& arguments = commands_[command].Arguments();
  for (std::size_t i = 0, size = arguments.size(); i < size; i++) {
    if (arguments[i].name_ == "in") {
      return static_cast<int>(i);
    }
  }
  return -1;
}
  
const Argument& Grammar::GetCommandArgument(int command_id,
                                            int argument_id) const {
  return commands_[command_id].Arguments()[argument_id];
}

bool Grammar::MutationInsertCommand(sg::Graph& graph) const {
  // sanity check
  if (boost::num_vertices(graph) == 0) {
    throw std::runtime_error("sc::Grammar::MutationInsertCommand - graph is empty");
  }
  // pick a random edge as insertion point
  sg::Edge edge_to_replace;
  sg::Vertex source_command;
  sg::NodeType source_type;
  do {
    edge_to_replace = boost::random_edge(graph, bx::RandomEngine());
    source_command = boost::source(edge_to_replace, graph);
    source_type = graph[source_command].type_;
  } while (source_type != sg::kCommand);
  // store target connection info
  sg::Vertex target_command = boost::target(edge_to_replace, graph);
  sg::Connection target_connection = graph[edge_to_replace];
  // make the new command
  const sg::Node& source_node = graph[source_command];
  sg::CommandRate rate = source_node.rate_;
  sg::Vertex new_command = CreateCommand(graph, rate, 0, true, false);
  // remove the existing connection
  boost::remove_edge(source_command, target_command, graph);
  // connect the new command to the source
  sg::Connection source_connection(GetCommandInputID(graph[new_command].id_));
  // generate weighting for new connection
  double weight = stdx::RandomCoefficient();
  if (rate == sg::kAudioRate) {
    weight *= weight;
  }
  source_connection.weight_ = weight;
  boost::add_edge(source_command, new_command, source_connection, graph);
  // connect new command to the target, use old connection weighting
  boost::add_edge(new_command, target_command, target_connection, graph);
  // success!
  return true;
}

} // sc namespace